/* * NPlot - A charting library for .NET * * LinePlot.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 line chart. /// public class LinePlot : BaseSequencePlot, IPlot, ISequencePlot { private Pen pen_ = new Pen(Color.Black); private Color shadowColor_ = Color.FromArgb(100, 100, 100); private Point shadowOffset_ = new Point(1, 1); private bool shadow_; /// /// Default constructor /// public LinePlot() { } /// /// Constructor /// /// The data source to associate with this plot public LinePlot(object dataSource) { DataSource = dataSource; } /// /// Constructor /// /// the ordinate data to associate with this plot. /// the abscissa data to associate with this plot. public LinePlot(object ordinateData, object abscissaData) { OrdinateData = ordinateData; AbscissaData = abscissaData; } /// /// If true, draw a shadow under the line. /// public bool Shadow { get { return shadow_; } set { shadow_ = value; } } /// /// Color of line shadow if drawn. Use Shadow method to turn shadow on and off. /// public Color ShadowColor { get { return shadowColor_; } set { shadowColor_ = value; } } /// /// Offset of shadow line from primary line. /// public Point ShadowOffset { get { return shadowOffset_; } set { shadowOffset_ = value; } } /// /// 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; } } /// /// Draws the line 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 void Draw(Graphics g, PhysicalAxis xAxis, PhysicalAxis yAxis) { if (shadow_) { DrawLineOrShadow(g, xAxis, yAxis, true); } DrawLineOrShadow(g, xAxis, yAxis, false); } /// /// Returns an x-axis that is suitable for drawing this plot. /// /// A suitable x-axis. public Axis SuggestXAxis() { SequenceAdapter data_ = new SequenceAdapter(DataSource, DataMember, OrdinateData, AbscissaData); return data_.SuggestXAxis(); } /// /// Returns a y-axis that is suitable for drawing this plot. /// /// A suitable y-axis. 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); } /// /// Draws the line 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. /// If true draw the shadow for the line. If false, draw line. public void DrawLineOrShadow(Graphics g, PhysicalAxis xAxis, PhysicalAxis yAxis, bool drawShadow) { Pen shadowPen = null; if (drawShadow) { shadowPen = (Pen) Pen.Clone(); shadowPen.Color = ShadowColor; } SequenceAdapter data = new SequenceAdapter(DataSource, DataMember, OrdinateData, AbscissaData); ITransform2D t = Transform2D.GetTransformer(xAxis, yAxis); int numberPoints = data.Count; if (data.Count == 0) { return; } // clipping is now handled assigning a clip region in the // graphic object before this call if (numberPoints == 1) { PointF physical = t.Transform(data[0]); if (drawShadow) { g.DrawLine(shadowPen, physical.X - 0.5f + ShadowOffset.X, physical.Y + ShadowOffset.Y, physical.X + 0.5f + ShadowOffset.X, physical.Y + ShadowOffset.Y); } else { g.DrawLine(Pen, physical.X - 0.5f, physical.Y, physical.X + 0.5f, physical.Y); } } else { // prepare for clipping double leftCutoff = xAxis.PhysicalToWorld(xAxis.PhysicalMin, false); double rightCutoff = xAxis.PhysicalToWorld(xAxis.PhysicalMax, false); if (leftCutoff > rightCutoff) { Utils.Swap(ref leftCutoff, ref rightCutoff); } if (drawShadow) { // correct cut-offs double shadowCorrection = xAxis.PhysicalToWorld(ShadowOffset, false) - xAxis.PhysicalToWorld(new Point(0, 0), false); leftCutoff -= shadowCorrection; rightCutoff -= shadowCorrection; } for (int i = 1; i < numberPoints; ++i) { // check to see if any values null. If so, then continue. double dx1 = data[i - 1].X; double dx2 = data[i].X; double dy1 = data[i - 1].Y; double dy2 = data[i].Y; if (Double.IsNaN(dx1) || Double.IsNaN(dy1) || Double.IsNaN(dx2) || Double.IsNaN(dy2)) { continue; } // do horizontal clipping here, to speed up if ((dx1 < leftCutoff || rightCutoff < dx1) && (dx2 < leftCutoff || rightCutoff < dx2)) { continue; } // else draw line. PointF p1 = t.Transform(data[i - 1]); PointF p2 = t.Transform(data[i]); // when very far zoomed in, points can fall ontop of each other, // and g.DrawLine throws an overflow exception if (p1.Equals(p2)) continue; if (drawShadow) { g.DrawLine(shadowPen, p1.X + ShadowOffset.X, p1.Y + ShadowOffset.Y, p2.X + ShadowOffset.X, p2.Y + ShadowOffset.Y); } else { g.DrawLine(Pen, p1.X, p1.Y, p2.X, p2.Y); } } } } } }