public static GraphBounds Join(GraphBounds left, GraphBounds right)
 {
     return new GraphBounds(
         Math.Min(left.MinX, right.MinX),
         Math.Max(left.MaxX, right.MaxX),
         Math.Min(left.MinY, right.MinY),
         Math.Max(left.MaxY, right.MaxY));
 }
        protected override void GetRawDataBounds(out GraphBounds nonTransformedBounds, out GraphBounds transformedBounds)
        {
            if (_Graphs.Count == 0)
            {
                base.GetRawDataBounds(out nonTransformedBounds, out transformedBounds);
                return;
            }
            nonTransformedBounds = GraphBounds.CreateInitial();
            transformedBounds = GraphBounds.CreateInitial();
            foreach (DisplayedGraph gr in _Graphs)
            {
                foreach (KeyValuePair<double, double> kv in gr.Graph.SortedPoints)
                {
                    double x = kv.Key;
                    nonTransformedBounds.MinX = Math.Min(nonTransformedBounds.MinX, x);
                    nonTransformedBounds.MaxX = Math.Max(nonTransformedBounds.MaxX, x);
                    DoTransformX(ref x);
                    if (!double.IsInfinity(x) && !double.IsNaN(x))
                    {
                        transformedBounds.MinX = Math.Min(transformedBounds.MinX, x);
                        transformedBounds.MaxX = Math.Max(transformedBounds.MaxX, x);
                    }
                    double y = kv.Value;
                    nonTransformedBounds.MinY = Math.Min(nonTransformedBounds.MinY, y);
                    nonTransformedBounds.MaxY = Math.Max(nonTransformedBounds.MaxY, y);
                    DoTransformY(ref y);
                    if (!double.IsInfinity(y) && !double.IsNaN(y))
                    {
                        transformedBounds.MinY = Math.Min(transformedBounds.MinY, y);
                        transformedBounds.MaxY = Math.Max(transformedBounds.MaxY, y);
                    }
                }

            }
        }
        public virtual void UpdateScaling()
        {
            GraphBounds nonTransformedBounds;
            GetRawDataBounds(out nonTransformedBounds, out _TransformedBounds);
            if (_AlwaysShowZeroX)
            {
                _TransformedBounds.MinX = Math.Min(0, _TransformedBounds.MinX);
                _TransformedBounds.MaxX = Math.Max(0, _TransformedBounds.MaxX);
            }
            if (_AlwaysShowZeroY)
            {
                _TransformedBounds.MinY = Math.Min(0, _TransformedBounds.MinY);
                _TransformedBounds.MaxY = Math.Max(0, _TransformedBounds.MaxY);
            }
            if (_CenterX)
            {
                double max = Math.Max(Math.Abs(_TransformedBounds.MinX), Math.Abs(_TransformedBounds.MaxX));
                _TransformedBounds.MinX = -max;
                _TransformedBounds.MaxX = max;
            }
            if (_CenterY)
            {
                double max = Math.Max(Math.Abs(_TransformedBounds.MinY), Math.Abs(_TransformedBounds.MaxY));
                _TransformedBounds.MinY = -max;
                _TransformedBounds.MaxY = max;
            }

            GraphBounds transformedBounds = _TransformedBounds;

            if (_ForceCustomBounds)
            {
                transformedBounds = TransformedBounds;
                double minX = transformedBounds.MinX, minY = transformedBounds.MinY, maxX = transformedBounds.MaxX, maxY = transformedBounds.MaxY;
                if (TransformX != null)
                {
                    TransformX(this, false, ref minX);
                    TransformX(this, false, ref maxX);
                }
                if (TransformY != null)
                {
                    TransformY(this, false, ref minY);
                    TransformY(this, false, ref maxY);
                }
                nonTransformedBounds = new GraphBounds(minX, maxX, minY, maxY);
            }

            _DataRectangle = new Rectangle(_AdditionalPadding.Left, _AdditionalPadding.Top, Width - _AdditionalPadding.Horizontal - 1, Height - _AdditionalPadding.Vertical - 1);

            Graphics gr = Graphics.FromHwnd(Handle);

            //Create list of grid points, screen coordinates will be assigned later
            _YGridObj = _YGrid.CreateDimensionObjectTemplate(transformedBounds.MinY, transformedBounds.MaxY, nonTransformedBounds.MinY, nonTransformedBounds.MaxY, _DataRectangle.Height);

            /* Grid/labels computation algorithm:
             *  1. Compute fixed Y padding (font height)
             *  2. Determine Y labels, place them, compute X padding (max. Y label width)
             *  3. Determine X labels, place them
             *  4. Compute label visibility
             */

            int maxLabelWidth = 0;
            int yPadding = Size.Ceiling(gr.MeasureString("M", Font)).Height + BigRulerDash + RulerTextSpacing;
            if (!_XGrid.ShowLabels)
                yPadding = 5;

            //Reflect Y padding only. Used by MapY()
            _DataRectangle = new Rectangle(_AdditionalPadding.Left, _AdditionalPadding.Top, Width - _AdditionalPadding.Horizontal - 1, Height - _AdditionalPadding.Vertical - 1 - yPadding);

            for (int i = 0; i < _YGridObj.Data.Length; i++)
            {
                GridLine line = _YGridObj.Data[i];
                double rawVal = line.RawValue;
                if (_YGrid.TransformLabelValues && !_YGrid.ProportionalToTransformedScale)
                    DoTransformY(ref rawVal);
                string val = string.Format(_DefaultYFormat, rawVal);
                if (FormatYValue != null)
                    FormatYValue(this, line.RawValue, ref val);
                line.Label = val;
                Size labelSize = Size.Ceiling(gr.MeasureString(val, Font));
                line.LabelSize = labelSize.Height;
                maxLabelWidth = Math.Max(maxLabelWidth, labelSize.Width);

                line.ScreenCoordinate = MapY(line.RawValue, !_YGridObj.Transformed);
                if (i == 0)
                    line.TextCoordinate = Math.Min(line.ScreenCoordinate - line.LabelSize / 2, _DataRectangle.Bottom - line.LabelSize);
                else if (i == (_YGridObj.Data.Length - 1))
                    line.TextCoordinate = Math.Max(line.ScreenCoordinate - line.LabelSize / 2, _DataRectangle.Top);
                else
                    line.TextCoordinate = line.ScreenCoordinate - line.LabelSize / 2;
            }

            int xPadding = maxLabelWidth + BigRulerDash + RulerTextSpacing;
            if (!_YGrid.ShowLabels)
                xPadding = 0;

            //Reflect both X and Y padding
            _DataRectangle = new Rectangle(_AdditionalPadding.Left + xPadding, _AdditionalPadding.Top, Width - _AdditionalPadding.Horizontal - 1 - xPadding, Height - _AdditionalPadding.Vertical - 1 - yPadding);

            _XGridObj = _XGrid.CreateDimensionObjectTemplate(transformedBounds.MinX, transformedBounds.MaxX, nonTransformedBounds.MinX, nonTransformedBounds.MaxX, _DataRectangle.Width);

            for (int i = 0; i < _XGridObj.Data.Length; i++)
            {
                GridLine line = _XGridObj.Data[i];
                double rawVal = line.RawValue;
                if (_XGrid.TransformLabelValues && !_XGrid.ProportionalToTransformedScale)
                    DoTransformX(ref rawVal);

                string val = string.Format(_DefaultXFormat, rawVal);
                if (FormatXValue != null)
                    FormatXValue(this, line.RawValue, ref val);
                line.Label = val;
                line.LabelSize = Size.Ceiling(gr.MeasureString(val, Font)).Width;
                line.LabelVisible = true;

                line.ScreenCoordinate = MapX(line.RawValue, !_YGridObj.Transformed);
                if (i == 0)
                    line.TextCoordinate = Math.Max(line.ScreenCoordinate - line.LabelSize / 2, _DataRectangle.Left);
                else if (i == (_XGridObj.Data.Length - 1))
                    line.TextCoordinate = Math.Min(line.ScreenCoordinate - line.LabelSize / 2, _DataRectangle.Right - line.LabelSize);
                else
                    line.TextCoordinate = line.ScreenCoordinate - line.LabelSize / 2;
            }

            _YGridObj.ComputeLabelVisibility(_YGrid.DistributeLabelsEvenly && _YGridObj.Transformed, true);
            _XGridObj.ComputeLabelVisibility(_XGrid.DistributeLabelsEvenly && _XGridObj.Transformed, false);
            Invalidate();
        }
 protected virtual void GetRawDataBounds(out GraphBounds nonTransformedBounds, out GraphBounds transformedBounds)
 {
     nonTransformedBounds = new GraphBounds();
     transformedBounds = new GraphBounds();
 }
        public void ForceNewBounds(double minX, double maxX, double minY, double maxY)
        {
            if (TransformX != null)
            {
                TransformX(this, true, ref minX);
                TransformX(this, true, ref maxX);
            }
            if (TransformY != null)
            {
                TransformY(this, true, ref minY);
                TransformY(this, true, ref maxY);
            }

            _ForcedBounds = new GraphBounds(minX, maxX, minY, maxY);
            ForceCustomBounds = true;
        }