mirror of https://github.com/mhowlett/nplot.git
288 lines
9.8 KiB
C#
288 lines
9.8 KiB
C#
/*
|
|
* 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.
|
|
*
|
|
* 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
|
|
{
|
|
/// <summary>
|
|
/// Encapsulates functionality for plotting data as a stepped line.
|
|
/// </summary>
|
|
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;
|
|
|
|
/// <summary>
|
|
/// Constructor.
|
|
/// </summary>
|
|
public StepPlot()
|
|
{
|
|
Center = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public bool Center
|
|
{
|
|
set { center_ = value; }
|
|
get { return center_; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// The pen used to draw the plot
|
|
/// </summary>
|
|
public Pen Pen
|
|
{
|
|
get { return pen_; }
|
|
set { pen_ = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// The color of the pen used to draw lines in this plot.
|
|
/// </summary>
|
|
public Color Color
|
|
{
|
|
set
|
|
{
|
|
if (pen_ != null)
|
|
{
|
|
pen_.Color = value;
|
|
}
|
|
else
|
|
{
|
|
pen_ = new Pen(value);
|
|
}
|
|
}
|
|
get { return pen_.Color; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// If true, then vertical lines are hidden.
|
|
/// </summary>
|
|
public bool HideVerticalSegments
|
|
{
|
|
get { return hideVerticalSegments_; }
|
|
set { hideVerticalSegments_ = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// If true, then vertical lines are hidden.
|
|
/// </summary>
|
|
public bool HideHorizontalSegments
|
|
{
|
|
get { return hideHorizontalSegments_; }
|
|
set { hideHorizontalSegments_ = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// The horizontal line length is multiplied by this amount. Default
|
|
/// corresponds to a value of 1.0.
|
|
/// </summary>
|
|
public float WidthScale
|
|
{
|
|
get { return scale_; }
|
|
set { scale_ = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draws the step plot on a GDI+ surface against the provided x and y axes.
|
|
/// </summary>
|
|
/// <param name="g">The GDI+ surface on which to draw.</param>
|
|
/// <param name="xAxis">The X-Axis to draw against.</param>
|
|
/// <param name="yAxis">The Y-Axis to draw against.</param>
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns an X-axis suitable for use by this plot. The axis will be one that is just long
|
|
/// enough to show all data.
|
|
/// </summary>
|
|
/// <returns>X-axis suitable for use by this plot.</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns an Y-axis suitable for use by this plot. The axis will be one that is just long
|
|
/// enough to show all data.
|
|
/// </summary>
|
|
/// <returns>Y-axis suitable for use by this plot.</returns>
|
|
public Axis SuggestYAxis()
|
|
{
|
|
SequenceAdapter data =
|
|
new SequenceAdapter(DataSource, DataMember, OrdinateData, AbscissaData);
|
|
|
|
return data.SuggestYAxis();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draws a representation of this plot in the legend.
|
|
/// </summary>
|
|
/// <param name="g">The graphics surface on which to draw.</param>
|
|
/// <param name="startEnd">A rectangle specifying the bounds of the area in the legend set aside for drawing.</param>
|
|
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);
|
|
}
|
|
}
|
|
} |