/* * NPlot - A charting library for .NET * * StepPlot.cs * Copyright (C) 2003-2006 Matt Howlett and others. * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * 3. Neither the name of NPlot nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Drawing; namespace NPlot { /// /// Encapsulates functionality for plotting data as a stepped line. /// public class StepPlot : BaseSequencePlot, IPlot, ISequencePlot { private bool center_; private bool hideHorizontalSegments_; private bool hideVerticalSegments_; private Pen pen_ = new Pen(Color.Black); private float scale_ = 1.0f; /// /// Constructor. /// public StepPlot() { Center = false; } /// /// Gets or sets whether or not steps should be centered. If true, steps will be centered on the /// X abscissa values. If false, the step corresponding to a given x-value will be drawn between /// this x-value and the next x-value at the current y-height. /// public bool Center { set { center_ = value; } get { return center_; } } /// /// The pen used to draw the plot /// public Pen Pen { get { return pen_; } set { pen_ = value; } } /// /// The color of the pen used to draw lines in this plot. /// public Color Color { set { if (pen_ != null) { pen_.Color = value; } else { pen_ = new Pen(value); } } get { return pen_.Color; } } /// /// If true, then vertical lines are hidden. /// public bool HideVerticalSegments { get { return hideVerticalSegments_; } set { hideVerticalSegments_ = value; } } /// /// If true, then vertical lines are hidden. /// public bool HideHorizontalSegments { get { return hideHorizontalSegments_; } set { hideHorizontalSegments_ = value; } } /// /// The horizontal line length is multiplied by this amount. Default /// corresponds to a value of 1.0. /// public float WidthScale { get { return scale_; } set { scale_ = value; } } /// /// Draws the step plot on a GDI+ surface against the provided x and y axes. /// /// The GDI+ surface on which to draw. /// The X-Axis to draw against. /// The Y-Axis to draw against. public virtual void Draw(Graphics g, PhysicalAxis xAxis, PhysicalAxis yAxis) { SequenceAdapter data = new SequenceAdapter(DataSource, DataMember, OrdinateData, AbscissaData); double leftCutoff = xAxis.PhysicalToWorld(xAxis.PhysicalMin, false); double rightCutoff = xAxis.PhysicalToWorld(xAxis.PhysicalMax, false); for (int i = 0; i < data.Count; ++i) { PointD p1 = data[i]; if (Double.IsNaN(p1.X) || Double.IsNaN(p1.Y)) { continue; } PointD p2; PointD p3; if (i + 1 != data.Count) { p2 = data[i + 1]; if (Double.IsNaN(p2.X) || Double.IsNaN(p2.Y)) { continue; } p2.Y = p1.Y; p3 = data[i + 1]; } else { // Check that we are not dealing with a DataSource of 1 point. // This check is done here so it is only checked on the end // condition and not for every point in the DataSource. if (data.Count > 1) { p2 = data[i - 1]; } else { // TODO: Once log4net is set up post a message to the user that a step-plot of 1 really does not make any sense. p2 = p1; } double offset = p1.X - p2.X; p2.X = p1.X + offset; p2.Y = p1.Y; p3 = p2; } if (center_) { double offset = (p2.X - p1.X)/2.0f; p1.X -= offset; p2.X -= offset; p3.X -= offset; } PointF xPos1 = xAxis.WorldToPhysical(p1.X, false); PointF yPos1 = yAxis.WorldToPhysical(p1.Y, false); PointF xPos2 = xAxis.WorldToPhysical(p2.X, false); PointF yPos2 = yAxis.WorldToPhysical(p2.Y, false); PointF xPos3 = xAxis.WorldToPhysical(p3.X, false); PointF yPos3 = yAxis.WorldToPhysical(p3.Y, false); // do horizontal clipping here, to speed up if ((p1.X < leftCutoff || p1.X > rightCutoff) && (p2.X < leftCutoff || p2.X > rightCutoff) && (p3.X < leftCutoff || p3.X > rightCutoff)) { continue; } if (!hideHorizontalSegments_) { if (scale_ != 1.0f) { float middle = (xPos2.X + xPos1.X)/2.0f; float width = xPos2.X - xPos1.X; width *= scale_; g.DrawLine(Pen, (int) (middle - width/2.0f), yPos1.Y, (int) (middle + width/2.0f), yPos2.Y); } else { g.DrawLine(Pen, xPos1.X, yPos1.Y, xPos2.X, yPos2.Y); } } if (!hideVerticalSegments_) { g.DrawLine(Pen, xPos2.X, yPos2.Y, xPos3.X, yPos3.Y); } } } /// /// Returns an X-axis suitable for use by this plot. The axis will be one that is just long /// enough to show all data. /// /// X-axis suitable for use by this plot. public Axis SuggestXAxis() { SequenceAdapter data = new SequenceAdapter(DataSource, DataMember, OrdinateData, AbscissaData); if (data.Count < 2) { return data.SuggestXAxis(); } // else Axis a = data.SuggestXAxis(); PointD p1 = data[0]; PointD p2 = data[1]; PointD p3 = data[data.Count - 2]; PointD p4 = data[data.Count - 1]; double offset1; double offset2; if (!center_) { offset1 = 0.0f; offset2 = p4.X - p3.X; } else { offset1 = (p2.X - p1.X)/2.0f; offset2 = (p4.X - p3.X)/2.0f; } a.WorldMin -= offset1; a.WorldMax += offset2; return a; } /// /// Returns an Y-axis suitable for use by this plot. The axis will be one that is just long /// enough to show all data. /// /// Y-axis suitable for use by this plot. public Axis SuggestYAxis() { SequenceAdapter data = new SequenceAdapter(DataSource, DataMember, OrdinateData, AbscissaData); return data.SuggestYAxis(); } /// /// Draws a representation of this plot in the legend. /// /// The graphics surface on which to draw. /// A rectangle specifying the bounds of the area in the legend set aside for drawing. public virtual void DrawInLegend(Graphics g, Rectangle startEnd) { g.DrawLine(pen_, startEnd.Left, (startEnd.Top + startEnd.Bottom)/2, startEnd.Right, (startEnd.Top + startEnd.Bottom)/2); } } }