Files
ntpa/NPlot/Windows.PlotSurface2D.cs
T

2618 lines
97 KiB
C#
Executable File

/*
* NPlot - A charting library for .NET
*
* Windows.PlotSurface2d.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.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Printing;
using System.Text;
using System.Windows.Forms;
namespace NPlot.Windows
{
/// <summary>
/// A Windows.Forms PlotSurface2D control.
/// </summary>
/// <remarks>
/// Unfortunately it's not possible to derive from both Control and NPlot.PlotSurface2D.
/// </remarks>
[ToolboxBitmap(typeof (PlotSurface2D), "PlotSurface2D.ico")]
public class PlotSurface2D : Control, IPlotSurface2D, ISurface
{
/// <summary>
/// This is the signature of the function used for InteractionOccurred events.
/// TODO: expand this to include information about the event.
/// </summary>
/// <param name="sender"></param>
public delegate void InteractionHandler(object sender);
/// <summary>
/// This is the signature of the function used for PreRefresh events.
/// </summary>
/// <param name="sender"></param>
public delegate void PreRefreshHandler(object sender);
private readonly ArrayList interactions_ = new ArrayList();
private readonly NPlot.PlotSurface2D ps_;
private IContainer components;
private ToolTip coordinates_;
private KeyEventArgs lastKeyEventArgs_;
private PlotContextMenu rightMenu_;
//private ArrayList selectedObjects_;
private Axis xAxis1ZoomCache_;
private Axis xAxis2ZoomCache_;
private Axis yAxis1ZoomCache_;
private Axis yAxis2ZoomCache_;
/// <summary>
/// Default constructor.
/// </summary>
public PlotSurface2D()
{
// This call is required by the Windows.Forms Form Designer.
InitializeComponent();
// double buffer, and update when resize.
base.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
base.SetStyle(ControlStyles.DoubleBuffer, true);
base.SetStyle(ControlStyles.UserPaint, true);
base.ResizeRedraw = true;
ps_ = new NPlot.PlotSurface2D();
InteractionOccured += OnInteractionOccured;
PreRefresh += OnPreRefresh;
}
/// <summary>
/// Flag to display a coordinates in a tooltip.
/// </summary>
[
Category("PlotSurface2D"),
Description("Whether or not to show coordinates in a tool tip when the mouse hovers above the plot area."),
Browsable(true),
Bindable(true)
]
public bool ShowCoordinates
{
get { return coordinates_.Active; }
set { coordinates_.Active = value; }
}
/// <summary>
/// The physical XAxis1 that was last drawn.
/// </summary>
[
Browsable(false)
]
public PhysicalAxis PhysicalXAxis1Cache
{
get { return ps_.PhysicalXAxis1Cache; }
}
/// <summary>
/// The physical YAxis1 that was last drawn.
/// </summary>
[
Browsable(false)
]
public PhysicalAxis PhysicalYAxis1Cache
{
get { return ps_.PhysicalYAxis1Cache; }
}
/// <summary>
/// The physical XAxis2 that was last drawn.
/// </summary>
[
Browsable(false)
]
public PhysicalAxis PhysicalXAxis2Cache
{
get { return ps_.PhysicalXAxis2Cache; }
}
/// <summary>
/// The physical YAxis2 that was last drawn.
/// </summary>
[
Browsable(false)
]
public PhysicalAxis PhysicalYAxis2Cache
{
get { return ps_.PhysicalYAxis2Cache; }
}
/// <summary>
/// When true, tool tip will display x value as a DateTime. Quick hack - this will probably be
/// changed at some point.
/// </summary>
[Bindable(true), Browsable(true), Category("PlotSurface2D"),
Description("When true, tool tip will display x value as a DateTime. Quick hack - this will probably be changed at some point.")]
public bool DateTimeToolTip { get; set; }
/// <summary>
/// Sets the right context menu. Custom menus can be designed by overriding
/// NPlot.Windows.PlotSurface2D.ContextMenu.
/// </summary>
[
Browsable(false),
Bindable(false)
]
public PlotContextMenu RightMenu
{
get { return rightMenu_; }
set
{
rightMenu_ = value;
if (rightMenu_ != null)
{
rightMenu_.PlotSurface2D = this;
}
}
}
/// <summary>
/// Gets an instance of a NPlot.Windows.PlotSurface2D.ContextMenu that
/// is useful in typical situations.
/// </summary>
public static PlotContextMenu DefaultContextMenu
{
get { return new PlotContextMenu(); }
}
/// <summary>
/// Allows access to the PlotSurface2D.
/// </summary>
[
Browsable(false),
Bindable(false)
]
public NPlot.PlotSurface2D Inner
{
get { return ps_; }
}
/// <summary>
/// Clears the plot and resets to default values.
/// </summary>
public void Clear()
{
xAxis1ZoomCache_ = null;
yAxis1ZoomCache_ = null;
xAxis2ZoomCache_ = null;
yAxis2ZoomCache_ = null;
ps_.Clear();
interactions_.Clear();
}
/// <summary>
/// Adds a drawable object to the plot surface. If the object is an IPlot,
/// the PlotSurface2D axes will also be updated.
/// </summary>
/// <param name="p">The IDrawable object to add to the plot surface.</param>
public void Add(IDrawable p)
{
ps_.Add(p);
}
/// <summary>
/// Adds a drawable object to the plot surface against the specified axes. If
/// the object is an IPlot, the PlotSurface2D axes will also be updated.
/// </summary>
/// <param name="p">the IDrawable object to add to the plot surface</param>
/// <param name="xp">the x-axis to add the plot against.</param>
/// <param name="yp">the y-axis to add the plot against.</param>
public void Add(IDrawable p, NPlot.PlotSurface2D.XAxisPosition xp, NPlot.PlotSurface2D.YAxisPosition yp)
{
ps_.Add(p, xp, yp);
}
/// <summary>
/// Adds a drawable object to the plot surface. If the object is an IPlot,
/// the PlotSurface2D axes will also be updated.
/// </summary>
/// <param name="p">The IDrawable object to add to the plot surface.</param>
/// <param name="zOrder">The z-ordering when drawing (objects with lower numbers are drawn first)</param>
public void Add(IDrawable p, int zOrder)
{
ps_.Add(p, zOrder);
}
/// <summary>
/// Adds a drawable object to the plot surface against the specified axes. If
/// the object is an IPlot, the PlotSurface2D axes will also be updated.
/// </summary>
/// <param name="p">the IDrawable object to add to the plot surface</param>
/// <param name="xp">the x-axis to add the plot against.</param>
/// <param name="yp">the y-axis to add the plot against.</param>
/// <param name="zOrder">The z-ordering when drawing (objects with lower numbers are drawn first)</param>
public void Add(IDrawable p, NPlot.PlotSurface2D.XAxisPosition xp,
NPlot.PlotSurface2D.YAxisPosition yp, int zOrder)
{
ps_.Add(p, xp, yp, zOrder);
}
/// <summary>
/// Gets or Sets the legend to use with this plot surface.
/// </summary>
[
Browsable(false),
Bindable(false)
]
public Legend Legend
{
get { return ps_.Legend; }
set { ps_.Legend = value; }
}
/// <summary>
/// Gets or Sets the legend z-order.
/// </summary>
[
Browsable(true),
Bindable(true),
Category("PlotSurface2D"),
Description("Determines the order with respect to other IDrawables on the plot surface in which the legend is drawn. " +
"The higher this value, the higher the position in the draw order.")
]
public int LegendZOrder
{
get { return ps_.LegendZOrder; }
set { ps_.LegendZOrder = value; }
}
/// <summary>
/// Whether or not the title will be scaled according to size of the plot
/// surface.
/// </summary>
[
Browsable(true),
Bindable(true),
Description("Whether or not the title will be scaled according to size of the plot surface."),
Category("PlotSurface2D")
]
public bool AutoScaleTitle
{
get { return ps_.AutoScaleTitle; }
set { ps_.AutoScaleTitle = value; }
}
/// <summary>
/// When plots are added to the plot surface, the axes they are attached to
/// are immediately modified to reflect data of the plot. If
/// AutoScaleAutoGeneratedAxes is true when a plot is added, the axes will
/// be turned in to auto scaling ones if they are not already [tick marks,
/// tick text and label size scaled to size of plot surface]. If false,
/// axes will not be autoscaling.
/// </summary>
[
Browsable(true),
Bindable(true),
Description("When plots are added to the plot surface, the axes they are attached to are immediately modified " +
"to reflect data of the plot. If AutoScaleAutoGeneratedAxes is true when a plot is added, the axes will be " +
"turned in to auto scaling ones if they are not already [tick marks, tick text and label size scaled to size " +
"of plot surface]. If false, axes will not be autoscaling."),
Category("PlotSurface2D")
]
public bool AutoScaleAutoGeneratedAxes
{
get { return ps_.AutoScaleAutoGeneratedAxes; }
set { ps_.AutoScaleAutoGeneratedAxes = value; }
}
/// <summary>
/// The plot surface title.
/// </summary>
[
Category("PlotSurface2D"),
Description("The plot surface title"),
Browsable(true),
Bindable(true)
]
public string Title
{
get { return ps_.Title; }
set
{
ps_.Title = value;
//helpful in design view. But crap in applications!
//this.Refresh();
}
}
/// <summary>
/// The font used to draw the title.
/// </summary>
[
Category("PlotSurface2D"),
Description("The font used to draw the title."),
Browsable(true),
Bindable(false)
]
public Font TitleFont
{
get { return ps_.TitleFont; }
set { ps_.TitleFont = value; }
}
/// <summary>
/// Padding of this width will be left between what is drawn and the control border.
/// </summary>
[
Category("PlotSurface2D"),
Description("Padding of this width will be left between what is drawn and the control border."),
Browsable(true),
Bindable(true)
]
public new int Padding
{
get { return ps_.Padding; }
set { ps_.Padding = value; }
}
/// <summary>
/// The first abscissa axis.
/// </summary>
[
Browsable(false)
]
public Axis XAxis1
{
get { return ps_.XAxis1; }
set { ps_.XAxis1 = value; }
}
/// <summary>
/// The first ordinate axis.
/// </summary>
[
Browsable(false)
]
public Axis YAxis1
{
get { return ps_.YAxis1; }
set { ps_.YAxis1 = value; }
}
/// <summary>
/// The second abscissa axis.
/// </summary>
[
Browsable(false)
]
public Axis XAxis2
{
get { return ps_.XAxis2; }
set { ps_.XAxis2 = value; }
}
/// <summary>
/// The second ordinate axis.
/// </summary>
[
Browsable(false)
]
public Axis YAxis2
{
get { return ps_.YAxis2; }
set { ps_.YAxis2 = value; }
}
/// <summary>
/// A color used to paint the plot background. Mutually exclusive with PlotBackImage and PlotBackBrush
/// </summary>
/// <remarks>not browsable or bindable because only set method.</remarks>
[
Category("PlotSurface2D"),
Description("Set the plot background color."),
Browsable(true),
Bindable(false)
]
public Color PlotBackColor
{
set { ps_.PlotBackColor = value; }
}
/// <summary>
/// An imaged used to paint the plot background. Mutually exclusive with PlotBackColor and PlotBackBrush
/// </summary>
/// <remarks>not browsable or bindable because only set method.</remarks>
[
Browsable(false),
Bindable(false)
]
public System.Drawing.Bitmap PlotBackImage
{
set { ps_.PlotBackImage = value; }
}
/// <summary>
/// A Rectangle brush used to paint the plot background. Mutually exclusive with PlotBackColor and PlotBackBrush
/// </summary>
/// <remarks>not browsable or bindable because only set method.</remarks>
[
Browsable(false),
Bindable(false)
]
public IRectangleBrush PlotBackBrush
{
set { ps_.PlotBackBrush = value; }
}
/// <summary>
/// Sets the title to be drawn using a solid brush of this color.
/// </summary>
/// <remarks>not browsable or bindable because only set method.</remarks>
[
Browsable(false),
Bindable(false)
]
public Color TitleColor
{
set { ps_.TitleColor = value; }
}
/// <summary>
/// The brush used for drawing the title.
/// </summary>
[
Browsable(true),
Bindable(true),
Description("The brush used for drawing the title."),
Category("PlotSurface2D")
]
public Brush TitleBrush
{
get { return ps_.TitleBrush; }
set { ps_.TitleBrush = value; }
}
/// <summary>
/// Set smoothing mode for drawing plot objects.
/// </summary>
[
Category("PlotSurface2D"),
Description("Set smoothing mode for drawing plot objects."),
Browsable(true),
Bindable(true)
]
public SmoothingMode SmoothingMode
{
get { return ps_.SmoothingMode; }
set { ps_.SmoothingMode = value; }
}
/// <summary>
/// Add an axis constraint to the plot surface. Axis constraints can
/// specify relative world-pixel scalings, absolute axis positions etc.
/// </summary>
/// <param name="c">The axis constraint to add.</param>
public void AddAxesConstraint(AxesConstraint c)
{
ps_.AddAxesConstraint(c);
}
/// <summary>
/// Remove a drawable object from the plot surface.
/// </summary>
/// <param name="p">the drawable to remove</param>
/// <param name="updateAxes">whether or not to update the axes after removing the idrawable.</param>
public void Remove(IDrawable p, bool updateAxes)
{
ps_.Remove(p, updateAxes);
}
/// <summary>
/// Gets an array list containing all drawables currently added to the PlotSurface2D.
/// </summary>
[
Browsable(false),
Bindable(false)
]
public ArrayList Drawables
{
get { return ps_.Drawables; }
}
/// <summary>
/// All functionality of the OnPaint method is provided by this function.
/// This allows use of the all encompasing PlotSurface.
/// </summary>
/// <param name="pe">the PaintEventArgs from paint event.</param>
/// <param name="width">width of the control</param>
/// <param name="height">height of the control</param>
public void DoPaint(PaintEventArgs pe, int width, int height)
{
PreRefresh(this);
foreach (Interactions.Interaction i in interactions_)
{
i.DoPaint(pe, width, height);
}
/*
// make sure don't redraw after a refresh.
this.horizontalBarPos_ = -1;
this.verticalBarPos_ = -1;
*/
Graphics g = pe.Graphics;
Rectangle border = new Rectangle(0, 0, width, height);
if (g == null)
{
throw (new NPlotException("null graphics context!"));
}
if (ps_ == null)
{
throw (new NPlotException("null NPlot.PlotSurface2D"));
}
if (border == Rectangle.Empty)
{
throw (new NPlotException("null border context"));
}
Draw(g, border);
}
/// <summary>
/// All functionality of the OnMouseDown function is contained here.
/// This allows use of the all encompasing PlotSurface.
/// </summary>
/// <param name="e">The mouse event args from the window we are drawing to.</param>
public void DoMouseDown(MouseEventArgs e)
{
bool dirty = false;
foreach (Interactions.Interaction i in interactions_)
{
i.DoMouseDown(e, this);
dirty |= i.DoMouseDown(e, this);
}
if (dirty)
{
Refresh();
}
}
/// <summary>
/// All functionality of the OnMouseMove function is contained here.
/// This allows use of the all encompasing PlotSurface.
/// </summary>
/// <param name="e">The mouse event args from the window we are drawing to.</param>
/// <param name="ctr">The control that the mouse event happened in.</param>
public void DoMouseMove(MouseEventArgs e, Control ctr)
{
bool dirty = false;
foreach (Interactions.Interaction i in interactions_)
{
i.DoMouseMove(e, ctr, lastKeyEventArgs_);
dirty |= i.DoMouseMove(e, ctr, lastKeyEventArgs_);
}
if (dirty)
{
Refresh();
}
// Update coordinates if necessary.
if (coordinates_.Active)
{
// we are here
Point here = new Point(e.X, e.Y);
if (ps_.PlotAreaBoundingBoxCache.Contains(here))
{
coordinates_.ShowAlways = true;
// according to Mns Erlandson, this can sometimes be the case.
if (PhysicalXAxis1Cache == null)
return;
if (PhysicalYAxis1Cache == null)
return;
double x = PhysicalXAxis1Cache.PhysicalToWorld(here, true);
double y = PhysicalYAxis1Cache.PhysicalToWorld(here, true);
string s = "";
if (!DateTimeToolTip)
{
s = "(" + x.ToString("g4") + "," + y.ToString("g4") + ")";
}
else
{
DateTime dateTime = new DateTime((long) x);
s = dateTime.ToShortDateString() + " " + dateTime.ToLongTimeString() + Environment.NewLine + y.ToString("f4");
}
coordinates_.SetToolTip(this, s);
}
else
{
coordinates_.ShowAlways = false;
}
}
}
/// <summary>
/// All functionality of the OnMouseUp function is contained here.
/// This allows use of the all encompasing PlotSurface.
/// </summary>
/// <param name="e">The mouse event args from the window we are drawing to.</param>
/// <param name="ctr">The control that the mouse event happened in.</param>
public void DoMouseUp(MouseEventArgs e, Control ctr)
{
bool dirty = false;
ArrayList local_interactions = (ArrayList) interactions_.Clone();
foreach (Interactions.Interaction i in local_interactions)
{
dirty |= i.DoMouseUp(e, ctr);
}
if (dirty)
{
Refresh();
}
if (e.Button == MouseButtons.Right)
{
Point here = new Point(e.X, e.Y);
//selectedObjects_ = ps_.HitTest(here);
if (rightMenu_ != null)
rightMenu_.Menu.Show(ctr, here);
}
}
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
/// <remarks>Modified! :-)</remarks>
private void InitializeComponent()
{
components = new Container();
coordinates_ = new ToolTip(components);
//
// PlotSurface2D
//
BackColor = SystemColors.ControlLightLight;
Size = new Size(328, 272);
}
/// <summary>
/// the key down callback
/// </summary>
/// <param name="e">information pertaining to the event</param>
protected override void OnKeyDown(KeyEventArgs e)
{
lastKeyEventArgs_ = e;
}
/// <summary>
/// The key up callback.
/// </summary>
/// <param name="e">information pertaining to the event</param>
protected override void OnKeyUp(KeyEventArgs e)
{
lastKeyEventArgs_ = e;
}
/// <summary>
/// the paint event callback.
/// </summary>
/// <param name="pe">the PaintEventArgs</param>
protected override void OnPaint(PaintEventArgs pe)
{
DoPaint(pe, Width, Height);
base.OnPaint(pe);
}
/// <summary>
/// Draws the plot surface on the supplied graphics surface [not the control surface].
/// </summary>
/// <param name="g">The graphics surface on which to draw</param>
/// <param name="bounds">
/// A bounding box on this surface that denotes the area on the
/// surface to confine drawing to.
/// </param>
public void Draw(Graphics g, Rectangle bounds)
{
// If we are not in design mode then draw as normal.
if (LicenseManager.UsageMode == LicenseUsageMode.Designtime)
{
drawDesignMode(g, bounds);
}
ps_.Draw(g, bounds);
}
/// <summary>
/// Draw a lightweight representation of us for design mode.
/// </summary>
private void drawDesignMode(Graphics g, Rectangle bounds)
{
g.DrawRectangle(new Pen(Color.Black), bounds.X + 2, bounds.Y + 2, bounds.Width - 4, bounds.Height - 4);
g.DrawString("PlotSurface2D: " + Title, TitleFont, TitleBrush, bounds.X + bounds.Width/2.0f, bounds.Y + bounds.Height/2.0f);
}
/// <summary>
/// Mouse down event handler.
/// </summary>
/// <param name="e">the event args.</param>
protected override void OnMouseDown(MouseEventArgs e)
{
DoMouseDown(e);
base.OnMouseDown(e);
}
/// <summary>
/// Mouse Wheel event handler.
/// </summary>
/// <param name="e">the event args</param>
protected override void OnMouseWheel(MouseEventArgs e)
{
DoMouseWheel(e);
base.OnMouseWheel(e);
}
/// <summary>
/// All functionality of the OnMouseWheel function is containd here.
/// This allows use of the all encompasing PlotSurface.
/// </summary>
/// <param name="e">the event args.</param>
public void DoMouseWheel(MouseEventArgs e)
{
bool dirty = false;
foreach (Interactions.Interaction i in interactions_)
{
i.DoMouseWheel(e, this);
dirty |= i.DoMouseWheel(e, this);
}
if (dirty)
{
Refresh();
}
}
/// <summary>
/// MouseMove event handler.
/// </summary>
/// <param name="e">The event arguments.</param>
protected override void OnMouseMove(MouseEventArgs e)
{
DoMouseMove(e, this);
base.OnMouseMove(e);
}
/// <summary>
/// MouseLeave event handler. It has to invalidate the control to get rid of
/// any remnant of vertical and horizontal guides.
/// </summary>
/// <param name="e">The event arguments.</param>
protected override void OnMouseLeave(EventArgs e)
{
DoMouseLeave(e, this);
base.OnMouseLeave(e);
}
/// <summary>
/// </summary>
/// <param name="e"></param>
/// <param name="ctr"></param>
public void DoMouseLeave(EventArgs e, Control ctr)
{
bool dirty = false;
foreach (Interactions.Interaction i in interactions_)
{
dirty = i.DoMouseLeave(e, this) || dirty;
}
if (dirty)
Refresh();
}
/// <summary>
/// mouse up event handler.
/// </summary>
/// <param name="e">The event arguments.</param>
protected override void OnMouseUp(MouseEventArgs e)
{
DoMouseUp(e, this);
base.OnMouseUp(e);
}
/// <summary>
/// sets axes to be those saved in the cache.
/// </summary>
public void OriginalDimensions()
{
if (xAxis1ZoomCache_ != null)
{
XAxis1 = xAxis1ZoomCache_;
XAxis2 = xAxis2ZoomCache_;
YAxis1 = yAxis1ZoomCache_;
YAxis2 = yAxis2ZoomCache_;
xAxis1ZoomCache_ = null;
xAxis2ZoomCache_ = null;
yAxis1ZoomCache_ = null;
yAxis2ZoomCache_ = null;
}
Refresh();
}
private void DrawHorizontalSelection(Point start, Point end, UserControl ctr)
{
// the clipping rectangle in screen coordinates
Rectangle clip = ctr.RectangleToScreen(
new Rectangle(
ps_.PlotAreaBoundingBoxCache.X,
ps_.PlotAreaBoundingBoxCache.Y,
ps_.PlotAreaBoundingBoxCache.Width,
ps_.PlotAreaBoundingBoxCache.Height));
start = ctr.PointToScreen(start);
end = ctr.PointToScreen(end);
ControlPaint.FillReversibleRectangle(
new Rectangle(Math.Min(start.X, end.X), clip.Y, Math.Abs(end.X - start.X), clip.Height),
Color.White);
}
/// <summary>
/// Print the chart as currently shown by the control
/// </summary>
/// <param name="preview">If true, show print preview window.</param>
public void Print(bool preview)
{
PrintDocument printDocument = new PrintDocument();
printDocument.PrintPage += NPlot_PrintPage;
printDocument.DefaultPageSettings.Landscape = true;
DialogResult result;
if (!preview)
{
PrintDialog dlg = new PrintDialog();
dlg.Document = printDocument;
result = dlg.ShowDialog();
}
else
{
PrintPreviewDialog dlg = new PrintPreviewDialog();
dlg.Document = printDocument;
result = dlg.ShowDialog();
}
if (result == DialogResult.OK)
{
try
{
printDocument.Print();
}
catch
{
Console.WriteLine("caught\n");
}
}
}
private void NPlot_PrintPage(object sender, PrintPageEventArgs ev)
{
Rectangle r = ev.MarginBounds;
Draw(ev.Graphics, r);
ev.HasMorePages = false;
}
/// <summary>
/// Coppies the chart currently shown in the control to the clipboard as an image.
/// </summary>
public void CopyToClipboard()
{
System.Drawing.Bitmap b = new System.Drawing.Bitmap(Width, Height);
Graphics g = Graphics.FromImage(b);
g.Clear(Color.White);
Draw(g, new Rectangle(0, 0, b.Width - 1, b.Height - 1));
Clipboard.SetDataObject(b, true);
}
/// <summary>
/// Coppies data in the current plot surface view window to the clipboard
/// as text.
/// </summary>
public void CopyDataToClipboard()
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < ps_.Drawables.Count; ++i)
{
IPlot plot = ps_.Drawables[i] as IPlot;
if (plot != null)
{
Axis xAxis = ps_.WhichXAxis(plot);
Axis yAxis = ps_.WhichYAxis(plot);
RectangleD region = new RectangleD(
xAxis.WorldMin,
yAxis.WorldMin,
xAxis.WorldMax - xAxis.WorldMin,
yAxis.WorldMax - yAxis.WorldMin);
plot.WriteData(sb, region, true);
}
}
Clipboard.SetDataObject(sb.ToString(), true);
}
/// <summary>
/// Remembers the current axes - useful in interactions.
/// </summary>
public void CacheAxes()
{
if (xAxis1ZoomCache_ == null && xAxis2ZoomCache_ == null &&
yAxis1ZoomCache_ == null && yAxis2ZoomCache_ == null)
{
if (XAxis1 != null)
{
xAxis1ZoomCache_ = (Axis) XAxis1.Clone();
}
if (XAxis2 != null)
{
xAxis2ZoomCache_ = (Axis) XAxis2.Clone();
}
if (YAxis1 != null)
{
yAxis1ZoomCache_ = (Axis) YAxis1.Clone();
}
if (YAxis2 != null)
{
yAxis2ZoomCache_ = (Axis) YAxis2.Clone();
}
}
}
/// <summary>
/// Adds and interaction to the plotsurface that adds functionality that responds
/// to a set of mouse / keyboard events.
/// </summary>
/// <param name="i">the interaction to add.</param>
public void AddInteraction(Interactions.Interaction i)
{
interactions_.Add(i);
}
/// <summary>
/// Remove a previously added interaction
/// </summary>
/// <param name="i">interaction to remove</param>
public void RemoveInteraction(Interactions.Interaction i)
{
interactions_.Remove(i);
}
/// <summary>
/// Event is fired when an interaction happens with the plot that causes it to be modified.
/// </summary>
public event InteractionHandler InteractionOccured;
/// <summary>
/// Default function called when plotsurface modifying interaction occured.
/// Override this, or add method to InteractionOccured event.
/// </summary>
/// <param name="sender"></param>
protected void OnInteractionOccured(object sender)
{
// do nothing.
}
/// <summary>
/// Event fired when we are about to paint.
/// </summary>
public event PreRefreshHandler PreRefresh;
/// <summary>
/// Default function called just before a refresh happens.
/// </summary>
/// <param name="sender"></param>
protected void OnPreRefresh(object sender)
{
// do nothing.
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (components != null)
components.Dispose();
}
base.Dispose(disposing);
}
#region class PlotContextMenu
/// <summary>
/// Summary description for ContextMenu.
/// </summary>
public class PlotContextMenu
{
#region IPlotMenuItem
/// <summary>
/// elements of the MenuItems array list must implement this interface.
/// </summary>
public interface IPlotMenuItem
{
/// <summary>
/// Gets the Windows.Forms.MenuItem associated with the PlotMenuItem
/// </summary>
MenuItem MenuItem { get; }
/// <summary>
/// This method is called for each menu item before the menu is
/// displayed. It is useful for implementing check marks, disabling
/// etc.
/// </summary>
/// <param name="plotContextMenu"></param>
void OnPopup(PlotContextMenu plotContextMenu);
}
#endregion
#region PlotMenuSeparator
/// <summary>
/// A plot menu item for separators.
/// </summary>
public class PlotMenuSeparator : IPlotMenuItem
{
private readonly int index_;
private readonly MenuItem menuItem_;
/// <summary>
/// Constructor
/// </summary>
/// <param name="index"></param>
public PlotMenuSeparator(int index)
{
menuItem_ = new MenuItem();
index_ = index;
menuItem_.Index = index_;
menuItem_.Text = "-";
}
/// <summary>
/// Index of this menu item in the menu.
/// </summary>
public int Index
{
get { return index_; }
}
/// <summary>
/// The Windows.Forms.MenuItem associated with this IPlotMenuItem
/// </summary>
public MenuItem MenuItem
{
get { return menuItem_; }
}
/// <summary>
/// </summary>
/// <param name="plotContextMenu"></param>
public void OnPopup(PlotContextMenu plotContextMenu)
{
// do nothing.
}
}
#endregion
#region PlotMenuItem
/// <summary>
/// A Plot menu item suitable for specifying basic menu items
/// </summary>
public class PlotMenuItem : IPlotMenuItem
{
private readonly EventHandler callback_;
private readonly int index_;
private readonly MenuItem menuItem_;
private readonly string text_;
/// <summary>
/// Constructor
/// </summary>
/// <param name="text">Menu item text</param>
/// <param name="index">Index in the manu</param>
/// <param name="callback">EventHandler to call if menu selected.</param>
public PlotMenuItem(string text, int index, EventHandler callback)
{
text_ = text;
index_ = index;
callback_ = callback;
menuItem_ = new MenuItem();
menuItem_.Index = index;
menuItem_.Text = text;
menuItem_.Click += callback;
}
/// <summary>
/// The text to put in the menu for this menu item.
/// </summary>
public string Text
{
get { return text_; }
}
/// <summary>
/// Index of this menu item in the menu.
/// </summary>
public int Index
{
get { return index_; }
}
/// <summary>
/// EventHandler to call if menu selected.
/// </summary>
public EventHandler Callback
{
get { return callback_; }
}
/// <summary>
/// The Windows.Forms.MenuItem associated with this IPlotMenuItem
/// </summary>
public MenuItem MenuItem
{
get { return menuItem_; }
}
/// <summary>
/// Called before menu drawn.
/// </summary>
/// <param name="plotContextMenu">The plot menu this item is a member of.</param>
public virtual void OnPopup(PlotContextMenu plotContextMenu)
{
// do nothing.
}
}
#endregion
#region PlotZoomBackMenuItem
/// <summary>
/// A Plot Menu Item that provides necessary functionality for the
/// zoom back menu item (graying out if zoomed right out in addition
/// to basic functionality).
/// </summary>
public class PlotZoomBackMenuItem : PlotMenuItem
{
/// <summary>
/// Constructor
/// </summary>
/// <param name="text">Text associated with this item in the menu.</param>
/// <param name="index">Index of this item in the menu.</param>
/// <param name="callback">EventHandler to call when menu item is selected.</param>
public PlotZoomBackMenuItem(string text, int index, EventHandler callback)
: base(text, index, callback)
{
}
/// <summary>
/// Called before menu drawn.
/// </summary>
/// <param name="plotContextMenu">The plot menu this item is a member of.</param>
public override void OnPopup(PlotContextMenu plotContextMenu)
{
MenuItem.Enabled = plotContextMenu.plotSurface2D_.xAxis1ZoomCache_ != null;
}
}
#endregion
#region PlotShowCoordinatesMenuItem
/// <summary>
/// A Plot Menu Item that provides necessary functionality for the
/// show coordinates menu item (tick mark toggle in addition to basic
/// functionality).
/// </summary>
public class PlotShowCoordinatesMenuItem : PlotMenuItem
{
/// <summary>
/// Constructor
/// </summary>
/// <param name="text">Text associated with this item in the menu.</param>
/// <param name="index">Index of this item in the menu.</param>
/// <param name="callback">EventHandler to call when menu item is selected.</param>
public PlotShowCoordinatesMenuItem(string text, int index, EventHandler callback)
: base(text, index, callback)
{
}
/// <summary>
/// Called before menu drawn.
/// </summary>
/// <param name="plotContextMenu">The plot menu this item is a member of.</param>
public override void OnPopup(PlotContextMenu plotContextMenu)
{
MenuItem.Checked = plotContextMenu.plotSurface2D_.ShowCoordinates;
}
}
#endregion
private ArrayList menuItems_;
/// <summary>
/// The PlotSurface2D associated with the context menu. Classes inherited
/// from PlotContextMenu will likely use this to implement their functionality.
/// </summary>
protected PlotSurface2D plotSurface2D_;
private ContextMenu rightMenu_;
/// <summary>
/// Constructor creates
/// </summary>
public PlotContextMenu()
{
ArrayList menuItems = new ArrayList();
menuItems = new ArrayList();
menuItems.Add(new PlotZoomBackMenuItem("Original Dimensions", 0, mnuOriginalDimensions_Click));
menuItems.Add(new PlotShowCoordinatesMenuItem("Show World Coordinates", 1, mnuDisplayCoordinates_Click));
menuItems.Add(new PlotMenuSeparator(2));
menuItems.Add(new PlotMenuItem("Print", 3, mnuPrint_Click));
menuItems.Add(new PlotMenuItem("Print Preview", 4, mnuPrintPreview_Click));
menuItems.Add(new PlotMenuItem("Copy To Clipboard", 5, mnuCopyToClipboard_Click));
menuItems.Add(new PlotMenuItem("Copy Data To Clipboard", 6, mnuCopyDataToClipboard_Click));
SetMenuItems(menuItems);
}
/// <summary>
/// Gets an arraylist of all PlotMenuItems that comprise the
/// menu. If this list is changed, this class must be told to
/// update using the Update method.
/// </summary>
public ArrayList MenuItems
{
get { return menuItems_; }
}
/// <summary>
/// The PlotSurface2D associated with the context menu. Generally, the user
/// should not set this. It is used internally by PlotSurface2D.
/// </summary>
public PlotSurface2D PlotSurface2D
{
set { plotSurface2D_ = value; }
}
/// <summary>
/// Gets the Windows.Forms context menu managed by this object.
/// </summary>
public ContextMenu Menu
{
get { return rightMenu_; }
}
/// <summary>
/// Sets the context menu according to the IPlotMenuItem's in the provided
/// ArrayList. The current menu items can be obtained using the MenuItems
/// property and extended if desired.
/// </summary>
/// <param name="menuItems"></param>
public void SetMenuItems(ArrayList menuItems)
{
menuItems_ = menuItems;
rightMenu_ = new ContextMenu();
foreach (IPlotMenuItem item in menuItems_)
{
rightMenu_.MenuItems.Add(item.MenuItem);
}
rightMenu_.Popup += rightMenu__Popup;
}
private void mnuOriginalDimensions_Click(object sender, EventArgs e)
{
plotSurface2D_.OriginalDimensions();
}
private void mnuCopyToClipboard_Click(object sender, EventArgs e)
{
plotSurface2D_.CopyToClipboard();
}
private void mnuCopyDataToClipboard_Click(object sender, EventArgs e)
{
plotSurface2D_.CopyDataToClipboard();
}
private void mnuPrint_Click(object sender, EventArgs e)
{
plotSurface2D_.Print(false);
}
private void mnuPrintPreview_Click(object sender, EventArgs e)
{
plotSurface2D_.Print(true);
}
private void mnuDisplayCoordinates_Click(object sender, EventArgs e)
{
plotSurface2D_.ShowCoordinates = !plotSurface2D_.ShowCoordinates;
}
private void rightMenu__Popup(object sender, EventArgs e)
{
foreach (IPlotMenuItem item in menuItems_)
{
item.OnPopup(this);
}
}
}
#endregion
/// <summary>
/// Encapsulates a number of separate "Interactions". An interaction is basically
/// a set of handlers for mouse and keyboard events that work together in a
/// specific way.
/// </summary>
public abstract class Interactions
{
/// <summary>
/// Base class for an interaction. All methods are virtual. Not abstract as not all interactions
/// need to use all methods. Default functionality for each method is to do nothing.
/// </summary>
public class Interaction
{
/// <summary>
/// Handler for this interaction if a mouse down event is received.
/// </summary>
/// <param name="e">event args</param>
/// <param name="ctr">reference to the control</param>
/// <returns>true if plot surface needs refreshing.</returns>
public virtual bool DoMouseDown(MouseEventArgs e, Control ctr)
{
return false;
}
/// <summary>
/// Handler for this interaction if a mouse up event is received.
/// </summary>
/// <param name="e">event args</param>
/// <param name="ctr">reference to the control</param>
/// <returns>true if plot surface needs refreshing.</returns>
public virtual bool DoMouseUp(MouseEventArgs e, Control ctr)
{
return false;
}
/// <summary>
/// Handler for this interaction if a mouse move event is received.
/// </summary>
/// <param name="e">event args</param>
/// <param name="ctr">reference to the control</param>
/// <param name="lastKeyEventArgs"></param>
/// <returns>true if plot surface needs refreshing.</returns>
public virtual bool DoMouseMove(MouseEventArgs e, Control ctr, KeyEventArgs lastKeyEventArgs)
{
return false;
}
/// <summary>
/// Handler for this interaction if a mouse move event is received.
/// </summary>
/// <param name="e">event args</param>
/// <param name="ctr">reference to the control</param>
/// <returns>true if plot surface needs refreshing.</returns>
public virtual bool DoMouseWheel(MouseEventArgs e, Control ctr)
{
return false;
}
/// <summary>
/// Handler for this interaction if a mouse Leave event is received.
/// </summary>
/// <param name="e">event args</param>
/// <param name="ctr">reference to the control</param>
/// <returns>true if the plot surface needs refreshing.</returns>
public virtual bool DoMouseLeave(EventArgs e, Control ctr)
{
return false;
}
/// <summary>
/// Handler for this interaction if a paint event is received.
/// </summary>
/// <param name="pe">paint event args</param>
/// <param name="width"></param>
/// <param name="height"></param>
public virtual void DoPaint(PaintEventArgs pe, int width, int height)
{
}
}
#region RubberBandSelection
/// <summary>
/// </summary>
public class RubberBandSelection : Interaction
{
private readonly Point unset_ = new Point(-1, -1);
private Point endPoint_ = new Point(-1, -1);
private bool selectionInitiated_;
private Point startPoint_ = new Point(-1, -1);
/// <summary>
/// </summary>
/// <param name="e"></param>
/// <param name="ctr"></param>
public override bool DoMouseDown(MouseEventArgs e, Control ctr)
{
// keep track of the start point and flag that select initiated.
selectionInitiated_ = true;
startPoint_.X = e.X;
startPoint_.Y = e.Y;
// invalidate the end point
endPoint_ = unset_;
return false;
}
/// <summary>
/// </summary>
/// <param name="e"></param>
/// <param name="ctr"></param>
/// <param name="lastKeyEventArgs"></param>
public override bool DoMouseMove(MouseEventArgs e, Control ctr, KeyEventArgs lastKeyEventArgs)
{
if ((e.Button == MouseButtons.Left) && selectionInitiated_)
{
// we are here
Point here = new Point(e.X, e.Y);
// delete the previous box
if (endPoint_ != unset_)
{
DrawRubberBand(startPoint_, endPoint_, ctr);
}
endPoint_ = here;
// and redraw the last one
DrawRubberBand(startPoint_, endPoint_, ctr);
}
return false;
}
/// <summary>
/// </summary>
/// <param name="e"></param>
/// <param name="ctr"></param>
public override bool DoMouseUp(MouseEventArgs e, Control ctr)
{
NPlot.PlotSurface2D ps = ((PlotSurface2D) ctr).Inner;
// handle left button (selecting region).
if ((e.Button == MouseButtons.Left) && selectionInitiated_)
{
endPoint_.X = e.X;
endPoint_.Y = e.Y;
// flag stopped selecting.
selectionInitiated_ = false;
if (endPoint_ != unset_)
{
DrawRubberBand(startPoint_, endPoint_, ctr);
}
Point minPoint = new Point(0, 0);
minPoint.X = Math.Min(startPoint_.X, endPoint_.X);
minPoint.Y = Math.Min(startPoint_.Y, endPoint_.Y);
Point maxPoint = new Point(0, 0);
maxPoint.X = Math.Max(startPoint_.X, endPoint_.X);
maxPoint.Y = Math.Max(startPoint_.Y, endPoint_.Y);
Rectangle r = ps.PlotAreaBoundingBoxCache;
if (minPoint != maxPoint && (r.Contains(minPoint) || r.Contains(maxPoint)))
{
((PlotSurface2D) ctr).CacheAxes();
((PlotSurface2D) ctr).PhysicalXAxis1Cache.SetWorldLimitsFromPhysical(minPoint, maxPoint);
((PlotSurface2D) ctr).PhysicalXAxis2Cache.SetWorldLimitsFromPhysical(minPoint, maxPoint);
((PlotSurface2D) ctr).PhysicalYAxis1Cache.SetWorldLimitsFromPhysical(maxPoint, minPoint);
((PlotSurface2D) ctr).PhysicalYAxis2Cache.SetWorldLimitsFromPhysical(maxPoint, minPoint);
// reset the start/end points
startPoint_ = unset_;
endPoint_ = unset_;
((PlotSurface2D) ctr).InteractionOccured(this);
return true;
}
}
return false;
}
/// <summary>
/// Draws a rectangle representing selection area.
/// </summary>
/// <param name="start">a corner of the rectangle.</param>
/// <param name="end">a corner of the rectangle diagonally opposite the first.</param>
/// <param name="ctr">
/// The control to draw to - this may not be us, if we have
/// been contained by a PlotSurface.
/// </param>
private void DrawRubberBand(Point start, Point end, Control ctr)
{
NPlot.PlotSurface2D ps = ((PlotSurface2D) ctr).Inner;
Rectangle rect = new Rectangle();
// the clipping rectangle in screen coordinates
Rectangle clip = ctr.RectangleToScreen(
new Rectangle(
ps.PlotAreaBoundingBoxCache.X,
ps.PlotAreaBoundingBoxCache.Y,
ps.PlotAreaBoundingBoxCache.Width,
ps.PlotAreaBoundingBoxCache.Height));
// convert to screen coords
start = ctr.PointToScreen(start);
end = ctr.PointToScreen(end);
// now, "normalize" the rectangle
if (start.X < end.X)
{
rect.X = start.X;
rect.Width = end.X - start.X;
}
else
{
rect.X = end.X;
rect.Width = start.X - end.X;
}
if (start.Y < end.Y)
{
rect.Y = start.Y;
rect.Height = end.Y - start.Y;
}
else
{
rect.Y = end.Y;
rect.Height = start.Y - end.Y;
}
rect = Rectangle.Intersect(rect, clip);
ControlPaint.DrawReversibleFrame(
new Rectangle(rect.X, rect.Y, rect.Width, rect.Height),
Color.White, FrameStyle.Dashed);
}
}
#endregion
#region HorizontalGuideline
/// <summary>
/// Horizontal line interaction
/// </summary>
public class HorizontalGuideline : Interaction
{
private readonly Color color_;
private int barPos_;
/// <summary>
/// Constructor
/// </summary>
public HorizontalGuideline()
{
color_ = Color.Black;
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="lineColor"></param>
public HorizontalGuideline(Color lineColor)
{
color_ = lineColor;
}
/// <summary>
/// </summary>
/// <param name="pe"></param>
/// <param name="width"></param>
/// <param name="height"></param>
public override void DoPaint(PaintEventArgs pe, int width, int height)
{
barPos_ = -1;
}
/// <summary>
/// </summary>
/// <param name="e"></param>
/// <param name="ctr"></param>
/// <param name="lastKeyEventArgs"></param>
public override bool DoMouseMove(MouseEventArgs e, Control ctr, KeyEventArgs lastKeyEventArgs)
{
NPlot.PlotSurface2D ps = ((PlotSurface2D) ctr).Inner;
// if mouse isn't in plot region, then don't draw horizontal line
if (e.X > ps.PlotAreaBoundingBoxCache.Left && e.X < ps.PlotAreaBoundingBoxCache.Right &&
e.Y > ps.PlotAreaBoundingBoxCache.Top && e.Y < (ps.PlotAreaBoundingBoxCache.Bottom - 1))
{
if (ps.PhysicalXAxis1Cache != null)
{
// the clipping rectangle in screen coordinates
Rectangle clip = ctr.RectangleToScreen(
new Rectangle(
ps.PlotAreaBoundingBoxCache.X,
ps.PlotAreaBoundingBoxCache.Y,
ps.PlotAreaBoundingBoxCache.Width,
ps.PlotAreaBoundingBoxCache.Height));
Point p = ctr.PointToScreen(new Point(e.X, e.Y));
if (barPos_ != -1)
{
ControlPaint.DrawReversibleLine(
new Point(clip.Left, barPos_),
new Point(clip.Right, barPos_), color_);
}
if (p.Y < clip.Bottom && p.Y > clip.Top)
{
ControlPaint.DrawReversibleLine(
new Point(clip.Left, p.Y),
new Point(clip.Right, p.Y), color_);
barPos_ = p.Y;
}
else
{
barPos_ = -1;
}
}
}
else
{
if (barPos_ != -1)
{
Rectangle clip = ctr.RectangleToScreen(
new Rectangle(
ps.PlotAreaBoundingBoxCache.X,
ps.PlotAreaBoundingBoxCache.Y,
ps.PlotAreaBoundingBoxCache.Width,
ps.PlotAreaBoundingBoxCache.Height));
ControlPaint.DrawReversibleLine(
new Point(clip.Left, barPos_),
new Point(clip.Right, barPos_), color_);
barPos_ = -1;
}
}
return false;
}
/// <summary>
/// </summary>
/// <param name="e"></param>
/// <param name="ctr"></param>
/// <returns></returns>
public override bool DoMouseLeave(EventArgs e, Control ctr)
{
if (barPos_ != -1)
{
NPlot.PlotSurface2D ps = ((PlotSurface2D) ctr).Inner;
Rectangle clip = ctr.RectangleToScreen(
new Rectangle(
ps.PlotAreaBoundingBoxCache.X,
ps.PlotAreaBoundingBoxCache.Y,
ps.PlotAreaBoundingBoxCache.Width,
ps.PlotAreaBoundingBoxCache.Height));
ControlPaint.DrawReversibleLine(
new Point(clip.Left, barPos_),
new Point(clip.Right, barPos_), color_);
barPos_ = -1;
}
return false;
}
}
#endregion
#region VerticalGuideline
/// <summary>
/// </summary>
public class VerticalGuideline : Interaction
{
private readonly Color color_;
private int barPos_;
/// <summary>
/// </summary>
public VerticalGuideline()
{
color_ = Color.Black;
}
/// <summary>
/// </summary>
/// <param name="lineColor"></param>
public VerticalGuideline(Color lineColor)
{
color_ = lineColor;
}
/// <summary>
/// </summary>
/// <param name="pe"></param>
/// <param name="width"></param>
/// <param name="height"></param>
public override void DoPaint(PaintEventArgs pe, int width, int height)
{
barPos_ = -1;
}
/// <summary>
/// </summary>
/// <param name="e"></param>
/// <param name="ctr"></param>
/// <param name="lastKeyEventArgs"></param>
public override bool DoMouseMove(MouseEventArgs e, Control ctr, KeyEventArgs lastKeyEventArgs)
{
NPlot.PlotSurface2D ps = ((PlotSurface2D) ctr).Inner;
// if mouse isn't in plot region, then don't draw horizontal line
if (e.X > ps.PlotAreaBoundingBoxCache.Left && e.X < (ps.PlotAreaBoundingBoxCache.Right - 1) &&
e.Y > ps.PlotAreaBoundingBoxCache.Top && e.Y < ps.PlotAreaBoundingBoxCache.Bottom)
{
if (ps.PhysicalXAxis1Cache != null)
{
// the clipping rectangle in screen coordinates
Rectangle clip = ctr.RectangleToScreen(
new Rectangle(
ps.PlotAreaBoundingBoxCache.X,
ps.PlotAreaBoundingBoxCache.Y,
ps.PlotAreaBoundingBoxCache.Width,
ps.PlotAreaBoundingBoxCache.Height));
Point p = ctr.PointToScreen(new Point(e.X, e.Y));
if (barPos_ != -1)
{
ControlPaint.DrawReversibleLine(
new Point(barPos_, clip.Top),
new Point(barPos_, clip.Bottom), color_);
}
if (p.X < clip.Right && p.X > clip.Left)
{
ControlPaint.DrawReversibleLine(
new Point(p.X, clip.Top),
new Point(p.X, clip.Bottom), color_);
barPos_ = p.X;
}
else
{
barPos_ = -1;
}
}
}
else
{
if (barPos_ != -1)
{
Rectangle clip = ctr.RectangleToScreen(
new Rectangle(
ps.PlotAreaBoundingBoxCache.X,
ps.PlotAreaBoundingBoxCache.Y,
ps.PlotAreaBoundingBoxCache.Width,
ps.PlotAreaBoundingBoxCache.Height)
);
ControlPaint.DrawReversibleLine(
new Point(barPos_, clip.Top),
new Point(barPos_, clip.Bottom), color_);
barPos_ = -1;
}
}
return false;
}
/// <summary>
/// Handler for mouse leave event
/// </summary>
/// <param name="e">event args</param>
/// <param name="ctr"></param>
/// <returns></returns>
public override bool DoMouseLeave(EventArgs e, Control ctr)
{
if (barPos_ != -1)
{
NPlot.PlotSurface2D ps = ((PlotSurface2D) ctr).Inner;
Rectangle clip = ctr.RectangleToScreen(
new Rectangle(
ps.PlotAreaBoundingBoxCache.X,
ps.PlotAreaBoundingBoxCache.Y,
ps.PlotAreaBoundingBoxCache.Width,
ps.PlotAreaBoundingBoxCache.Height));
ControlPaint.DrawReversibleLine(
new Point(barPos_, clip.Top),
new Point(barPos_, clip.Bottom), color_);
barPos_ = -1;
}
return false;
}
}
#endregion
#region HorizontalDrag
/// <summary>
/// </summary>
public class HorizontalDrag : Interaction
{
private readonly Point unset_ = new Point(-1, -1);
private bool dragInitiated_;
private Point lastPoint_ = new Point(-1, -1);
/// <summary>
/// </summary>
/// <param name="e"></param>
/// <param name="ctr"></param>
public override bool DoMouseDown(MouseEventArgs e, Control ctr)
{
NPlot.PlotSurface2D ps = ((PlotSurface2D) ctr).Inner;
if (e.X > ps.PlotAreaBoundingBoxCache.Left && e.X < (ps.PlotAreaBoundingBoxCache.Right) &&
e.Y > ps.PlotAreaBoundingBoxCache.Top && e.Y < ps.PlotAreaBoundingBoxCache.Bottom)
{
dragInitiated_ = true;
lastPoint_.X = e.X;
lastPoint_.Y = e.Y;
}
return false;
}
/// <summary>
/// </summary>
/// <param name="e"></param>
/// <param name="ctr"></param>
/// <param name="lastKeyEventArgs"></param>
public override bool DoMouseMove(MouseEventArgs e, Control ctr, KeyEventArgs lastKeyEventArgs)
{
NPlot.PlotSurface2D ps = ((PlotSurface2D) ctr).Inner;
if ((e.Button == MouseButtons.Left) && dragInitiated_)
{
int diffX = e.X - lastPoint_.X;
((PlotSurface2D) ctr).CacheAxes();
// original code was using PixelWorldLength of the physical axis
// but it was not working for non-linear axes - the code below works
// in all cases
if (ps.XAxis1 != null)
{
Axis axis = ps.XAxis1;
PointF pMin = ps.PhysicalXAxis1Cache.PhysicalMin;
PointF pMax = ps.PhysicalXAxis1Cache.PhysicalMax;
PointF physicalWorldMin = pMin;
PointF physicalWorldMax = pMax;
physicalWorldMin.X -= diffX;
physicalWorldMax.X -= diffX;
double newWorldMin = axis.PhysicalToWorld(physicalWorldMin, pMin, pMax, false);
double newWorldMax = axis.PhysicalToWorld(physicalWorldMax, pMin, pMax, false);
axis.WorldMin = newWorldMin;
axis.WorldMax = newWorldMax;
}
if (ps.XAxis2 != null)
{
Axis axis = ps.XAxis2;
PointF pMin = ps.PhysicalXAxis2Cache.PhysicalMin;
PointF pMax = ps.PhysicalXAxis2Cache.PhysicalMax;
PointF physicalWorldMin = pMin;
PointF physicalWorldMax = pMax;
physicalWorldMin.X -= diffX;
physicalWorldMax.X -= diffX;
double newWorldMin = axis.PhysicalToWorld(physicalWorldMin, pMin, pMax, false);
double newWorldMax = axis.PhysicalToWorld(physicalWorldMax, pMin, pMax, false);
axis.WorldMin = newWorldMin;
axis.WorldMax = newWorldMax;
}
lastPoint_ = new Point(e.X, e.Y);
((PlotSurface2D) ctr).InteractionOccured(this);
return true;
}
return false;
}
/// <summary>
/// </summary>
/// <param name="e"></param>
/// <param name="ctr"></param>
public override bool DoMouseUp(MouseEventArgs e, Control ctr)
{
if ((e.Button == MouseButtons.Left) && dragInitiated_)
{
lastPoint_ = unset_;
dragInitiated_ = false;
}
return false;
}
}
#endregion
#region VerticalDrag
/// <summary>
/// </summary>
public class VerticalDrag : Interaction
{
private readonly Point unset_ = new Point(-1, -1);
private bool dragInitiated_;
private Point lastPoint_ = new Point(-1, -1);
/// <summary>
/// </summary>
/// <param name="e"></param>
/// <param name="ctr"></param>
public override bool DoMouseDown(MouseEventArgs e, Control ctr)
{
NPlot.PlotSurface2D ps = ((PlotSurface2D) ctr).Inner;
if (e.X > ps.PlotAreaBoundingBoxCache.Left && e.X < (ps.PlotAreaBoundingBoxCache.Right) &&
e.Y > ps.PlotAreaBoundingBoxCache.Top && e.Y < ps.PlotAreaBoundingBoxCache.Bottom)
{
dragInitiated_ = true;
lastPoint_.X = e.X;
lastPoint_.Y = e.Y;
}
return false;
}
/// <summary>
/// </summary>
/// <param name="e"></param>
/// <param name="ctr"></param>
/// <param name="lastKeyEventArgs"></param>
public override bool DoMouseMove(MouseEventArgs e, Control ctr, KeyEventArgs lastKeyEventArgs)
{
NPlot.PlotSurface2D ps = ((PlotSurface2D) ctr).Inner;
if ((e.Button == MouseButtons.Left) && dragInitiated_)
{
int diffY = e.Y - lastPoint_.Y;
((PlotSurface2D) ctr).CacheAxes();
if (ps.YAxis1 != null)
{
Axis axis = ps.YAxis1;
PointF pMin = ps.PhysicalYAxis1Cache.PhysicalMin;
PointF pMax = ps.PhysicalYAxis1Cache.PhysicalMax;
PointF physicalWorldMin = pMin;
PointF physicalWorldMax = pMax;
physicalWorldMin.Y -= diffY;
physicalWorldMax.Y -= diffY;
double newWorldMin = axis.PhysicalToWorld(physicalWorldMin, pMin, pMax, false);
double newWorldMax = axis.PhysicalToWorld(physicalWorldMax, pMin, pMax, false);
axis.WorldMin = newWorldMin;
axis.WorldMax = newWorldMax;
}
if (ps.YAxis2 != null)
{
Axis axis = ps.YAxis2;
PointF pMin = ps.PhysicalYAxis2Cache.PhysicalMin;
PointF pMax = ps.PhysicalYAxis2Cache.PhysicalMax;
PointF physicalWorldMin = pMin;
PointF physicalWorldMax = pMax;
physicalWorldMin.Y -= diffY;
physicalWorldMax.Y -= diffY;
double newWorldMin = axis.PhysicalToWorld(physicalWorldMin, pMin, pMax, false);
double newWorldMax = axis.PhysicalToWorld(physicalWorldMax, pMin, pMax, false);
axis.WorldMin = newWorldMin;
axis.WorldMax = newWorldMax;
}
lastPoint_ = new Point(e.X, e.Y);
((PlotSurface2D) ctr).InteractionOccured(this);
return true;
}
return false;
}
/// <summary>
/// </summary>
/// <param name="e"></param>
/// <param name="ctr"></param>
public override bool DoMouseUp(MouseEventArgs e, Control ctr)
{
if ((e.Button == MouseButtons.Left) && dragInitiated_)
{
lastPoint_ = unset_;
dragInitiated_ = false;
}
return false;
}
}
#endregion
#region HorizontalRangeSelection
/// <summary>
/// This plot intraction allows the user to select horizontal regions.
/// </summary>
public class HorizontalRangeSelection : Interaction
{
private readonly Point unset_ = new Point(-1, -1);
private Point endPoint_ = new Point(-1, -1);
private int minimumPixelDistanceForSelect_ = 5;
private Point previousPoint_ = new Point(-1, -1);
private bool selectionInitiated_;
private double smallestAllowedRange_ = double.Epsilon*100.0;
private Point startPoint_ = new Point(-1, -1);
/// <summary>
/// Default constructor
/// </summary>
public HorizontalRangeSelection()
{
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="smallestAllowedRange">the smallest distance between the selected xmin and xmax for the selection to be performed.</param>
public HorizontalRangeSelection(double smallestAllowedRange)
{
smallestAllowedRange_ = smallestAllowedRange;
}
/// <summary>
/// The minimum width of the selected region (in pixels) for the interaction to zoom.
/// </summary>
public int MinimumPixelDistanceForSelect
{
get { return minimumPixelDistanceForSelect_; }
set { minimumPixelDistanceForSelect_ = value; }
}
/// <summary>
/// The smallest range (distance between world min and world max) selectable.
/// If a smaller region is selected, the selection will do nothing.
/// </summary>
public double SmallestAllowedRange
{
get { return smallestAllowedRange_; }
set { smallestAllowedRange_ = value; }
}
/// <summary>
/// Handler for mouse down event for this interaction
/// </summary>
/// <param name="e">the mouse event args</param>
/// <param name="ctr">the plot surface this event applies to</param>
public override bool DoMouseDown(MouseEventArgs e, Control ctr)
{
NPlot.PlotSurface2D ps = ((PlotSurface2D) ctr).Inner;
if (e.X > ps.PlotAreaBoundingBoxCache.Left && e.X < ps.PlotAreaBoundingBoxCache.Right &&
e.Y > ps.PlotAreaBoundingBoxCache.Top && e.Y < ps.PlotAreaBoundingBoxCache.Bottom)
{
// keep track of the start point and flag that select initiated.
selectionInitiated_ = true;
startPoint_.X = e.X;
startPoint_.Y = e.Y;
previousPoint_.X = e.X;
previousPoint_.Y = e.Y;
// invalidate the end point
endPoint_ = unset_;
return false;
}
selectionInitiated_ = false;
endPoint_ = unset_;
startPoint_ = unset_;
return false;
}
/// <summary>
/// Handler for mouse move event for this interaction
/// </summary>
/// <param name="e">the mouse event args</param>
/// <param name="ctr">the plot surface this event applies to</param>
/// <param name="lastKeyEventArgs"></param>
public override bool DoMouseMove(MouseEventArgs e, Control ctr, KeyEventArgs lastKeyEventArgs)
{
NPlot.PlotSurface2D ps = ((PlotSurface2D) ctr).Inner;
// if dragging on axis to zoom.
if ((e.Button == MouseButtons.Left) && selectionInitiated_)
{
Point endPoint_ = previousPoint_;
if (e.X > ps.PlotAreaBoundingBoxCache.Left && e.X < ps.PlotAreaBoundingBoxCache.Right &&
e.Y > ps.PlotAreaBoundingBoxCache.Top && e.Y < ps.PlotAreaBoundingBoxCache.Bottom)
{
endPoint_ = new Point(e.X, e.Y);
DrawHorizontalSelection(previousPoint_, endPoint_, ctr);
previousPoint_ = endPoint_;
}
else
{
endPoint_ = new Point(e.X, e.Y);
if (e.X < ps.PlotAreaBoundingBoxCache.Left) endPoint_.X = ps.PlotAreaBoundingBoxCache.Left + 1;
if (e.X > ps.PlotAreaBoundingBoxCache.Right) endPoint_.X = ps.PlotAreaBoundingBoxCache.Right - 1;
DrawHorizontalSelection(previousPoint_, endPoint_, ctr);
previousPoint_ = endPoint_;
}
}
return false;
}
/// <summary>
/// Handler for mouse up event for this interaction
/// </summary>
/// <param name="e">the mouse event args</param>
/// <param name="ctr">the plot surface this event applies to</param>
public override bool DoMouseUp(MouseEventArgs e, Control ctr)
{
NPlot.PlotSurface2D ps = ((PlotSurface2D) ctr).Inner;
if ((e.Button == MouseButtons.Left) && selectionInitiated_)
{
endPoint_.X = e.X;
endPoint_.Y = e.Y;
if (e.X < ps.PlotAreaBoundingBoxCache.Left) endPoint_.X = ps.PlotAreaBoundingBoxCache.Left + 1;
if (e.X > ps.PlotAreaBoundingBoxCache.Right) endPoint_.X = ps.PlotAreaBoundingBoxCache.Right - 1;
// flag stopped selecting.
selectionInitiated_ = false;
if (endPoint_ != unset_)
{
DrawHorizontalSelection(startPoint_, endPoint_, ctr);
}
// ignore very small selections
if (Math.Abs(endPoint_.X - startPoint_.X) < minimumPixelDistanceForSelect_)
{
return false;
}
((PlotSurface2D) ctr).CacheAxes();
// determine the new x axis 1 world limits (and check to see if they are far enough appart).
double xAxis1Min = double.NaN;
double xAxis1Max = double.NaN;
if (ps.XAxis1 != null)
{
int x1 = Math.Min(endPoint_.X, startPoint_.X);
int x2 = Math.Max(endPoint_.X, startPoint_.X);
int y = ps.PhysicalXAxis1Cache.PhysicalMax.Y;
xAxis1Min = ps.PhysicalXAxis1Cache.PhysicalToWorld(new Point(x1, y), true);
xAxis1Max = ps.PhysicalXAxis1Cache.PhysicalToWorld(new Point(x2, y), true);
if (xAxis1Max - xAxis1Min < smallestAllowedRange_)
{
return false;
}
}
// determine the new x axis 2 world limits (and check to see if they are far enough appart).
double xAxis2Min = double.NaN;
double xAxis2Max = double.NaN;
if (ps.XAxis2 != null)
{
int x1 = Math.Min(endPoint_.X, startPoint_.X);
int x2 = Math.Max(endPoint_.X, startPoint_.X);
int y = ps.PhysicalXAxis2Cache.PhysicalMax.Y;
xAxis2Min = ps.PhysicalXAxis2Cache.PhysicalToWorld(new Point(x1, y), true);
xAxis2Max = ps.PhysicalXAxis2Cache.PhysicalToWorld(new Point(x2, y), true);
if (xAxis2Max - xAxis2Min < smallestAllowedRange_)
{
return false;
}
}
// now actually update the world limits.
if (ps.XAxis1 != null)
{
ps.XAxis1.WorldMax = xAxis1Max;
ps.XAxis1.WorldMin = xAxis1Min;
}
if (ps.XAxis2 != null)
{
ps.XAxis2.WorldMax = xAxis2Max;
ps.XAxis2.WorldMin = xAxis2Min;
}
((PlotSurface2D) ctr).InteractionOccured(this);
return true;
}
return false;
}
private void DrawHorizontalSelection(Point start, Point end, Control ctr)
{
NPlot.PlotSurface2D ps = ((PlotSurface2D) ctr).Inner;
// the clipping rectangle in screen coordinates
Rectangle clip = ctr.RectangleToScreen(
new Rectangle(
ps.PlotAreaBoundingBoxCache.X,
ps.PlotAreaBoundingBoxCache.Y,
ps.PlotAreaBoundingBoxCache.Width,
ps.PlotAreaBoundingBoxCache.Height));
start = ctr.PointToScreen(start);
end = ctr.PointToScreen(end);
ControlPaint.FillReversibleRectangle(
new Rectangle(Math.Min(start.X, end.X), clip.Y, Math.Abs(end.X - start.X), clip.Height),
Color.White);
}
}
#endregion
#region AxisDrag
/// <summary>
/// </summary>
public class AxisDrag : Interaction
{
private readonly bool enableDragWithCtr_;
private Axis axis_;
private bool doing_;
private Point lastPoint_;
private PhysicalAxis physicalAxis_;
private float sensitivity_ = 200.0f;
private Point startPoint_;
/// <summary>
/// </summary>
/// <param name="enableDragWithCtr"></param>
public AxisDrag(bool enableDragWithCtr)
{
enableDragWithCtr_ = enableDragWithCtr;
}
/// <summary>
/// </summary>
/// <value></value>
public float Sensitivity
{
get { return sensitivity_; }
set { sensitivity_ = value; }
}
/// <summary>
/// </summary>
/// <param name="e"></param>
/// <param name="ctr"></param>
public override bool DoMouseDown(MouseEventArgs e, Control ctr)
{
// if the mouse is inside the plot area [the tick marks are here and part of the
// axis], then don't invoke drag.
NPlot.PlotSurface2D ps = ((PlotSurface2D) ctr).Inner;
if (e.X > ps.PlotAreaBoundingBoxCache.Left && e.X < ps.PlotAreaBoundingBoxCache.Right &&
e.Y > ps.PlotAreaBoundingBoxCache.Top && e.Y < ps.PlotAreaBoundingBoxCache.Bottom)
{
return false;
}
if ((e.Button == MouseButtons.Left))
{
// see if hit with axis.
ArrayList objects = ps.HitTest(new Point(e.X, e.Y));
foreach (object o in objects)
{
if (o is Axis)
{
doing_ = true;
axis_ = (Axis) o;
//PhysicalAxis[] physicalAxisList = new[]
// {ps.PhysicalXAxis1Cache, ps.PhysicalXAxis2Cache, ps.PhysicalYAxis1Cache, ps.PhysicalYAxis2Cache};
if (ps.PhysicalXAxis1Cache.Axis == axis_)
physicalAxis_ = ps.PhysicalXAxis1Cache;
else if (ps.PhysicalXAxis2Cache.Axis == axis_)
physicalAxis_ = ps.PhysicalXAxis2Cache;
else if (ps.PhysicalYAxis1Cache.Axis == axis_)
physicalAxis_ = ps.PhysicalYAxis1Cache;
else if (ps.PhysicalYAxis2Cache.Axis == axis_)
physicalAxis_ = ps.PhysicalYAxis2Cache;
lastPoint_ = startPoint_ = new Point(e.X, e.Y);
return false;
}
}
}
return false;
}
/// <summary>
/// </summary>
/// <param name="e"></param>
/// <param name="ctr"></param>
/// <param name="lastKeyEventArgs"></param>
public override bool DoMouseMove(MouseEventArgs e, Control ctr, KeyEventArgs lastKeyEventArgs)
{
NPlot.PlotSurface2D ps = ((PlotSurface2D) ctr).Inner;
// if dragging on axis to zoom.
if ((e.Button == MouseButtons.Left) && doing_ && physicalAxis_ != null)
{
if (enableDragWithCtr_ && lastKeyEventArgs != null && lastKeyEventArgs.Control)
{
}
else
{
float dist =
(e.X - lastPoint_.X) +
(-e.Y + lastPoint_.Y);
lastPoint_ = new Point(e.X, e.Y);
if (dist > sensitivity_/3.0f)
{
dist = sensitivity_/3.0f;
}
PointF pMin = physicalAxis_.PhysicalMin;
PointF pMax = physicalAxis_.PhysicalMax;
double physicalWorldLength = Math.Sqrt((pMax.X - pMin.X)*(pMax.X - pMin.X) + (pMax.Y - pMin.Y)*(pMax.Y - pMin.Y));
float prop = (float) (physicalWorldLength*dist/sensitivity_);
prop *= 2;
((PlotSurface2D) ctr).CacheAxes();
float relativePosX = (startPoint_.X - pMin.X)/(pMax.X - pMin.X);
float relativePosY = (startPoint_.Y - pMin.Y)/(pMax.Y - pMin.Y);
if (float.IsInfinity(relativePosX) || float.IsNaN(relativePosX)) relativePosX = 0.0f;
if (float.IsInfinity(relativePosY) || float.IsNaN(relativePosY)) relativePosY = 0.0f;
PointF physicalWorldMin = pMin;
PointF physicalWorldMax = pMax;
physicalWorldMin.X += relativePosX*prop;
physicalWorldMax.X -= (1 - relativePosX)*prop;
physicalWorldMin.Y -= relativePosY*prop;
physicalWorldMax.Y += (1 - relativePosY)*prop;
double newWorldMin = axis_.PhysicalToWorld(physicalWorldMin, pMin, pMax, false);
double newWorldMax = axis_.PhysicalToWorld(physicalWorldMax, pMin, pMax, false);
axis_.WorldMin = newWorldMin;
axis_.WorldMax = newWorldMax;
((PlotSurface2D) ctr).InteractionOccured(this);
return true;
}
}
return false;
}
/// <summary>
/// </summary>
/// <param name="e"></param>
/// <param name="ctr"></param>
public override bool DoMouseUp(MouseEventArgs e, Control ctr)
{
if (doing_)
{
doing_ = false;
axis_ = null;
physicalAxis_ = null;
lastPoint_ = new Point();
}
return false;
}
}
#endregion
#region MouseWheelZoom
/// <summary>
/// </summary>
public class MouseWheelZoom : Interaction
{
//private Point point_ = new Point(-1, -1);
private float sensitivity_ = 60.0f;
/// <summary>
/// Number of screen pixels equivalent to one wheel step.
/// </summary>
public float Sensitivity
{
get { return sensitivity_; }
set { sensitivity_ = value; }
}
//private bool mouseDown_ = false;
/// <summary>
/// </summary>
/// <param name="e"></param>
/// <param name="ctr"></param>
public override bool DoMouseUp(MouseEventArgs e, Control ctr)
{
//mouseDown_ = false;
return false;
}
/// <summary>
/// </summary>
/// <param name="e"></param>
/// <param name="ctr"></param>
public override bool DoMouseDown(MouseEventArgs e, Control ctr)
{
//NPlot.PlotSurface2D ps = ((Windows.PlotSurface2D)ctr).Inner;
//if (e.X > ps.PlotAreaBoundingBoxCache.Left && e.X < ps.PlotAreaBoundingBoxCache.Right &&
// e.Y > ps.PlotAreaBoundingBoxCache.Top && e.Y < ps.PlotAreaBoundingBoxCache.Bottom)
//{
// point_.X = e.X;
// point_.Y = e.Y;
// mouseDown_ = true;
//}
return false;
}
/// <summary>
/// </summary>
/// <param name="e"></param>
/// <param name="ctr"></param>
public override bool DoMouseWheel(MouseEventArgs e, Control ctr)
{
NPlot.PlotSurface2D ps = ((PlotSurface2D) ctr).Inner;
((PlotSurface2D) ctr).CacheAxes();
float delta = e.Delta/(float) e.Delta;
delta *= sensitivity_;
Axis axis = null;
PointF pMin = PointF.Empty;
PointF pMax = PointF.Empty;
KeyEventArgs keyArgs = ((PlotSurface2D) ctr).lastKeyEventArgs_;
bool zoom = (keyArgs != null && keyArgs.Control);
if (keyArgs != null && keyArgs.Shift)
{
axis = ps.YAxis1;
pMin = ps.PhysicalYAxis1Cache.PhysicalMin;
pMax = ps.PhysicalYAxis1Cache.PhysicalMax;
}
else
{
axis = ps.XAxis1;
pMin = ps.PhysicalXAxis1Cache.PhysicalMin;
pMax = ps.PhysicalXAxis1Cache.PhysicalMax;
}
if (axis == null) return false;
PointF physicalWorldMin = pMin;
PointF physicalWorldMax = pMax;
physicalWorldMin.X -= delta;
physicalWorldMax.X -= (zoom) ? -delta : delta;
physicalWorldMin.Y += delta;
physicalWorldMax.Y += (zoom) ? -delta : delta;
double newWorldMin = axis.PhysicalToWorld(physicalWorldMin, pMin, pMax, false);
double newWorldMax = axis.PhysicalToWorld(physicalWorldMax, pMin, pMax, false);
axis.WorldMin = newWorldMin;
axis.WorldMax = newWorldMax;
((PlotSurface2D) ctr).InteractionOccured(this);
return true;
}
}
#endregion
}
}
}