/* * NPlot - A charting library for .NET * * HistogramPlot.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.Collections; using System.Drawing; namespace NPlot { /// /// Provides ability to draw histogram plots. /// public class HistogramPlot : BaseSequencePlot, IPlot, ISequencePlot { private double baseOffset_; private float baseWidth_ = 1.0f; private bool center_ = true; private bool isStacked_; private Pen pen_ = new Pen(Color.Black); private IRectangleBrush rectangleBrush_ = new RectangleBrushes.Solid(Color.Black); private HistogramPlot stackedTo_; /// /// Set/Get the brush to use if the histogram is filled. /// public IRectangleBrush RectangleBrush { get { return rectangleBrush_; } set { rectangleBrush_ = value; } } /// /// Whether or not the histogram columns will be filled. /// public bool Filled { get; set; } /// /// The width of the histogram bar as a proportion of the data spacing /// (in range 0.0 - 1.0). /// public float BaseWidth { get { return baseWidth_; } set { if (value > 0.0 && value <= 1.0) { baseWidth_ = value; } else { throw new NPlotException("Base width must be between 0.0 and 1.0"); } } } /// /// If true, each histogram column will be centered on the associated abscissa value. /// If false, each histogram colum will be drawn between the associated abscissa value, and the next abscissa value. /// Default value is true. /// public bool Center { set { center_ = value; } get { return center_; } } /// /// If this histogram plot has another stacked on top, this will be true. Else false. /// public bool IsStacked { get { return isStacked_; } } /// /// 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; } } /// /// Horizontal position of histogram columns is offset by this much (in world coordinates). /// public double BaseOffset { set { baseOffset_ = value; } get { return baseOffset_; } } /// /// Renders the histogram. /// /// The Graphics 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) { SequenceAdapter data = new SequenceAdapter(DataSource, DataMember, OrdinateData, AbscissaData); float yoff; for (int i = 0; i < data.Count; ++i) { // (1) determine the top left hand point of the bar (assuming not centered) PointD p1 = data[i]; if (double.IsNaN(p1.X) || double.IsNaN(p1.Y)) continue; // (2) determine the top right hand point of the bar (assuming not centered) PointD p2; if (i + 1 != data.Count) { p2 = data[i + 1]; if (double.IsNaN(p2.X) || double.IsNaN(p2.Y)) continue; p2.Y = p1.Y; } else if (i != 0) { p2 = data[i - 1]; if (double.IsNaN(p2.X) || double.IsNaN(p2.Y)) continue; double offset = p1.X - p2.X; p2.X = p1.X + offset; p2.Y = p1.Y; } else { double offset = 1.0f; p2.X = p1.X + offset; p2.Y = p1.Y; } // (3) now account for plots this may be stacked on top of. HistogramPlot currentPlot = this; yoff = 0.0f; double yval = 0.0f; while (currentPlot.isStacked_) { SequenceAdapter stackedToData = new SequenceAdapter( currentPlot.stackedTo_.DataSource, currentPlot.stackedTo_.DataMember, currentPlot.stackedTo_.OrdinateData, currentPlot.stackedTo_.AbscissaData); yval += stackedToData[i].Y; yoff = yAxis.WorldToPhysical(yval, false).Y; p1.Y += stackedToData[i].Y; p2.Y += stackedToData[i].Y; currentPlot = currentPlot.stackedTo_; } // (4) now account for centering if (center_) { double offset = (p2.X - p1.X)/2.0f; p1.X -= offset; p2.X -= offset; } // (5) now account for BaseOffset (shift of bar sideways). p1.X += baseOffset_; p2.X += baseOffset_; // (6) now get physical coordinates of top two points. 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); if (isStacked_) { currentPlot = this; while (currentPlot.isStacked_) { currentPlot = currentPlot.stackedTo_; } baseWidth_ = currentPlot.baseWidth_; } float width = xPos2.X - xPos1.X; float height; if (isStacked_) { height = -yPos1.Y + yoff; } else { height = -yPos1.Y + yAxis.PhysicalMin.Y; } float xoff = (1.0f - baseWidth_)/2.0f*width; Rectangle r = new Rectangle((int) (xPos1.X + xoff), (int) yPos1.Y, (int) (width - 2*xoff), (int) height); if (Filled) { if (r.Height != 0 && r.Width != 0) { // room for optimization maybe. g.FillRectangle(rectangleBrush_.Get(r), r); } } g.DrawRectangle(Pen, r.X, r.Y, r.Width, r.Height); } } /// /// 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); Axis a = data.SuggestXAxis(); if (data.Count == 0) { return a; } PointD p1; PointD p2; PointD p3; PointD p4; if (data.Count < 2) { p1 = data[0]; p1.X -= 1.0; p2 = data[0]; p3 = p1; p4 = p2; } else { p1 = data[0]; p2 = data[1]; p3 = data[data.Count - 2]; 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 a y-axis that is suitable for drawing this plot. /// /// A suitable y-axis. public Axis SuggestYAxis() { if (isStacked_) { double tmpMax = 0.0f; ArrayList adapterList = new ArrayList(); HistogramPlot currentPlot = this; do { adapterList.Add(new SequenceAdapter( currentPlot.DataSource, currentPlot.DataMember, currentPlot.OrdinateData, currentPlot.AbscissaData) ); } while ((currentPlot = currentPlot.stackedTo_) != null); SequenceAdapter[] adapters = (SequenceAdapter[]) adapterList.ToArray(typeof (SequenceAdapter)); for (int i = 0; i < adapters[0].Count; ++i) { double tmpHeight = 0.0f; for (int j = 0; j < adapters.Length; ++j) { tmpHeight += adapters[j][i].Y; } tmpMax = Math.Max(tmpMax, tmpHeight); } Axis a = new LinearAxis(0.0f, tmpMax); // TODO make 0.08 a parameter. a.IncreaseRange(0.08); return a; } else { 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 void DrawInLegend(Graphics g, Rectangle startEnd) { if (Filled) { g.FillRectangle(rectangleBrush_.Get(startEnd), startEnd); } g.DrawRectangle(Pen, startEnd.X, startEnd.Y, startEnd.Width, startEnd.Height); } /// /// Stack the histogram to another HistogramPlot. /// public void StackedTo(HistogramPlot hp) { SequenceAdapter data = new SequenceAdapter(DataSource, DataMember, OrdinateData, AbscissaData); SequenceAdapter hpData = new SequenceAdapter(hp.DataSource, hp.DataMember, hp.OrdinateData, hp.AbscissaData); if (hp != null) { isStacked_ = true; if (hpData.Count != data.Count) { throw new NPlotException("Can stack HistogramPlot data only with the same number of datapoints."); } for (int i = 0; i < data.Count; ++i) { if (data[i].X != hpData[i].X) { throw new NPlotException("Can stack HistogramPlot data only with the same X coordinates."); } if (hpData[i].Y < 0.0f) { throw new NPlotException("Can stack HistogramPlot data only with positive Y coordinates."); } } } stackedTo_ = hp; } } }