Sync: Adding shamap and documentation to enable easier sync next time OHM is updated.
1 // --------------------------------------------------------------------------------------------------------------------
2 // <copyright file="Plot.cs" company="OxyPlot">
3 // The MIT License (MIT)
5 // Copyright (c) 2012 Oystein Bjorke
7 // Permission is hereby granted, free of charge, to any person obtaining a
8 // copy of this software and associated documentation files (the
9 // "Software"), to deal in the Software without restriction, including
10 // without limitation the rights to use, copy, modify, merge, publish,
11 // distribute, sublicense, and/or sell copies of the Software, and to
12 // permit persons to whom the Software is furnished to do so, subject to
13 // the following conditions:
15 // The above copyright notice and this permission notice shall be included
16 // in all copies or substantial portions of the Software.
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21 // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22 // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
23 // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
24 // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 // Represents a control that displays a plot.
29 // --------------------------------------------------------------------------------------------------------------------
30 namespace OxyPlot.WindowsForms
33 using System.ComponentModel;
34 using System.Diagnostics;
36 using System.Runtime.InteropServices;
37 using System.Windows.Forms;
43 /// Represents a control that displays a plot.
46 public class Plot : Control, IPlotControl
49 /// The category for the properties of this control.
51 private const string OxyPlotCategory = "OxyPlot";
54 /// The invalidate lock.
56 private readonly object invalidateLock = new object();
61 private readonly object modelLock = new object();
64 /// The rendering lock.
66 private readonly object renderingLock = new object();
69 /// The current model (holding a reference to this plot control).
72 private PlotModel currentModel;
75 /// The is model invalidated.
77 private bool isModelInvalidated;
82 private PlotModel model;
85 /// The mouse manipulator.
88 private ManipulatorBase mouseManipulator;
91 /// The update data flag.
93 private bool updateDataFlag = true;
96 /// The zoom rectangle.
98 private Rectangle zoomRectangle;
101 /// The render context.
103 private GraphicsRenderContext renderContext;
106 /// Initializes a new instance of the <see cref="Plot"/> class.
110 this.renderContext = new GraphicsRenderContext();
112 // ReSharper disable DoNotCallOverridableMethodsInConstructor
113 this.DoubleBuffered = true;
114 // ReSharper restore DoNotCallOverridableMethodsInConstructor
115 this.KeyboardPanHorizontalStep = 0.1;
116 this.KeyboardPanVerticalStep = 0.1;
117 this.PanCursor = Cursors.Hand;
118 this.ZoomRectangleCursor = Cursors.SizeNWSE;
119 this.ZoomHorizontalCursor = Cursors.SizeWE;
120 this.ZoomVerticalCursor = Cursors.SizeNS;
124 /// Gets the actual model.
126 /// <value> The actual model. </value>
127 public PlotModel ActualModel
136 /// Gets or sets the keyboard pan horizontal step.
138 /// <value> The keyboard pan horizontal step. </value>
139 [Category(OxyPlotCategory)]
140 public double KeyboardPanHorizontalStep { get; set; }
143 /// Gets or sets the keyboard pan vertical step.
145 /// <value> The keyboard pan vertical step. </value>
146 [Category(OxyPlotCategory)]
147 public double KeyboardPanVerticalStep { get; set; }
150 /// Gets or sets the model.
154 [Category(OxyPlotCategory)]
155 public PlotModel Model
164 if (this.model != value)
167 this.OnModelChanged();
173 /// Gets or sets the pan cursor.
175 [Category(OxyPlotCategory)]
176 public Cursor PanCursor { get; set; }
179 /// Gets or sets the horizontal zoom cursor.
181 [Category(OxyPlotCategory)]
182 public Cursor ZoomHorizontalCursor { get; set; }
185 /// Gets or sets the rectangle zoom cursor.
187 [Category(OxyPlotCategory)]
188 public Cursor ZoomRectangleCursor { get; set; }
191 /// Gets or sets vertical zoom cursor.
193 [Category(OxyPlotCategory)]
194 public Cursor ZoomVerticalCursor { get; set; }
197 /// Get the axes from a point.
199 /// <param name="pt">
202 /// <param name="xaxis">
205 /// <param name="yaxis">
208 public void GetAxesFromPoint(ScreenPoint pt, out Axis xaxis, out Axis yaxis)
210 if (this.Model == null)
217 this.Model.GetAxesFromPoint(pt, out xaxis, out yaxis);
221 /// Get the series from a point.
223 /// <param name="pt">
224 /// The point (screen coordinates).
226 /// <param name="limit">
232 public Series GetSeriesFromPoint(ScreenPoint pt, double limit)
234 if (this.Model == null)
239 return this.Model.GetSeriesFromPoint(pt, limit);
243 /// The hide tracker.
245 public void HideTracker()
250 /// The hide zoom rectangle.
252 public void HideZoomRectangle()
254 this.zoomRectangle = Rectangle.Empty;
259 /// The invalidate plot.
261 /// <param name="updateData">
264 public void InvalidatePlot(bool updateData)
266 lock (this.invalidateLock)
268 this.isModelInvalidated = true;
269 this.updateDataFlag = this.updateDataFlag || updateData;
276 /// Called when the Model property has been changed.
278 public void OnModelChanged()
280 lock (this.modelLock)
282 if (this.currentModel != null)
284 this.currentModel.AttachPlotControl(null);
287 if (this.Model != null)
289 if (this.Model.PlotControl != null)
291 throw new InvalidOperationException(
292 "This PlotModel is already in use by some other plot control.");
295 this.Model.AttachPlotControl(this);
296 this.currentModel = this.Model;
300 this.InvalidatePlot(true);
306 /// <param name="axis">
309 /// <param name="x0">
312 /// <param name="x1">
315 public void Pan(Axis axis, ScreenPoint x0, ScreenPoint x1)
318 this.InvalidatePlot(false);
324 /// <param name="deltax">
325 /// The horizontal delta.
327 /// <param name="deltay">
328 /// The vertical delta.
330 public void PanAll(double deltax, double deltay)
332 foreach (var a in this.ActualModel.Axes)
334 a.Pan(a.IsHorizontal() ? deltax : deltay);
337 this.InvalidatePlot(false);
341 /// The refresh plot.
343 /// <param name="updateData">
346 public void RefreshPlot(bool updateData)
348 lock (this.invalidateLock)
350 this.isModelInvalidated = true;
351 this.updateDataFlag = this.updateDataFlag || updateData;
360 /// <param name="axis">
363 public void Reset(Axis axis)
366 this.InvalidatePlot(false);
370 /// Sets the cursor type.
372 /// <param name="cursorType">
375 public void SetCursorType(CursorType cursorType)
380 this.Cursor = this.PanCursor;
382 case CursorType.ZoomRectangle:
383 this.Cursor = this.ZoomRectangleCursor;
385 case CursorType.ZoomHorizontal:
386 this.Cursor = this.ZoomHorizontalCursor;
388 case CursorType.ZoomVertical:
389 this.Cursor = this.ZoomVerticalCursor;
392 this.Cursor = Cursors.Arrow;
398 /// The show tracker.
400 /// <param name="data">
403 public void ShowTracker(TrackerHitResult data)
405 // not implemented for WindowsForms
409 /// The show zoom rectangle.
414 public void ShowZoomRectangle(OxyRect r)
416 this.zoomRectangle = new Rectangle((int)r.Left, (int)r.Top, (int)r.Width, (int)r.Height);
423 /// <param name="axis">
426 /// <param name="p1">
429 /// <param name="p2">
432 public void Zoom(Axis axis, double p1, double p2)
435 this.InvalidatePlot(false);
441 public void ZoomAll()
443 foreach (var a in this.Model.Axes)
448 this.InvalidatePlot(false);
454 /// <param name="delta">
457 public void ZoomAllAxes(double delta)
459 foreach (var a in this.ActualModel.Axes)
461 this.ZoomAt(a, delta);
464 this.RefreshPlot(false);
470 /// <param name="axis">
473 /// <param name="factor">
479 public void ZoomAt(Axis axis, double factor, double x = double.NaN)
483 double sx = (axis.Transform(axis.ActualMaximum) + axis.Transform(axis.ActualMinimum)) * 0.5;
484 x = axis.InverseTransform(sx);
487 axis.ZoomAt(factor, x);
488 this.InvalidatePlot(false);
492 /// The on mouse down.
497 protected override void OnMouseDown(MouseEventArgs e)
501 if (this.mouseManipulator != null)
509 if (this.ActualModel != null)
511 var args = this.CreateMouseEventArgs(e);
512 this.ActualModel.HandleMouseDown(this, args);
519 this.mouseManipulator = this.GetManipulator(e);
521 if (this.mouseManipulator != null)
523 this.mouseManipulator.Started(this.CreateManipulationEventArgs(e));
528 /// The on mouse move.
533 protected override void OnMouseMove(MouseEventArgs e)
537 if (this.ActualModel != null)
539 var args = this.CreateMouseEventArgs(e);
540 this.ActualModel.HandleMouseMove(this, args);
547 if (this.mouseManipulator != null)
549 this.mouseManipulator.Delta(this.CreateManipulationEventArgs(e));
554 /// Raises the <see cref="E:System.Windows.Forms.Control.MouseUp"/> event.
557 /// A <see cref="T:System.Windows.Forms.MouseEventArgs"/> that contains the event data.
559 protected override void OnMouseUp(MouseEventArgs e)
562 this.Capture = false;
564 if (this.ActualModel != null)
566 var args = this.CreateMouseEventArgs(e);
567 this.ActualModel.HandleMouseUp(this, args);
574 if (this.mouseManipulator != null)
576 this.mouseManipulator.Completed(this.CreateManipulationEventArgs(e));
579 this.mouseManipulator = null;
583 /// Raises the <see cref="E:System.Windows.Forms.Control.MouseWheel"/> event.
586 /// A <see cref="T:System.Windows.Forms.MouseEventArgs"/> that contains the event data.
588 protected override void OnMouseWheel(MouseEventArgs e)
590 base.OnMouseWheel(e);
591 bool isControlDown = ModifierKeys == Keys.Control;
592 var m = new ZoomStepManipulator(this, e.Delta * 0.001, isControlDown);
593 m.Started(new ManipulationEventArgs(e.Location.ToScreenPoint()));
597 /// Raises the <see cref="E:System.Windows.Forms.Control.Paint"/> event.
600 /// A <see cref="T:System.Windows.Forms.PaintEventArgs"/> that contains the event data.
602 protected override void OnPaint(PaintEventArgs e)
607 lock (this.invalidateLock)
609 if (this.isModelInvalidated)
611 if (this.model != null)
613 this.model.Update(this.updateDataFlag);
614 this.updateDataFlag = false;
617 this.isModelInvalidated = false;
621 lock (this.renderingLock)
623 this.renderContext.SetGraphicsTarget(e.Graphics);
624 if (this.model != null)
626 this.model.Render(this.renderContext, this.Width, this.Height);
629 if (this.zoomRectangle != Rectangle.Empty)
631 using (var zoomBrush = new SolidBrush(Color.FromArgb(0x40, 0xFF, 0xFF, 0x00)))
632 using (var zoomPen = new Pen(Color.Black))
634 zoomPen.DashPattern = new float[] { 3, 1 };
635 e.Graphics.FillRectangle(zoomBrush, this.zoomRectangle);
636 e.Graphics.DrawRectangle(zoomPen, this.zoomRectangle);
641 catch (Exception paintException)
643 var trace = new StackTrace(paintException);
644 Debug.WriteLine(paintException);
645 Debug.WriteLine(trace);
646 using (var font = new Font("Arial", 10))
648 e.Graphics.DrawString(
649 "OxyPlot paint exception: " + paintException.Message, font, Brushes.Red, 10, 10);
655 /// Raises the <see cref="E:System.Windows.Forms.Control.PreviewKeyDown"/> event.
658 /// A <see cref="T:System.Windows.Forms.PreviewKeyDownEventArgs"/> that contains the event data.
660 protected override void OnPreviewKeyDown(PreviewKeyDownEventArgs e)
662 base.OnPreviewKeyDown(e);
663 if (e.KeyCode == Keys.A)
668 bool control = (e.Modifiers & Keys.Control) == Keys.Control;
669 bool alt = (e.Modifiers & Keys.Alt) == Keys.Alt;
700 if ((deltax * deltax) + (deltay * deltay) > 0)
702 deltax = deltax * this.ActualModel.PlotArea.Width * this.KeyboardPanHorizontalStep;
703 deltay = deltay * this.ActualModel.PlotArea.Height * this.KeyboardPanVerticalStep;
705 // small steps if the user is pressing control
712 this.PanAll(deltax, deltay);
717 if (Math.Abs(zoom) > 1e-8)
724 this.ZoomAllAxes(1 + (zoom * 0.12));
729 if (control && alt && this.ActualModel != null)
734 this.SetClipboardText(this.ActualModel.CreateTextReport());
737 this.SetClipboardText(this.ActualModel.ToCode());
741 // this.SetClipboardText(this.ActualModel.ToXml());
748 /// Raises the <see cref="E:System.Windows.Forms.Control.Resize"/> event.
751 /// An <see cref="T:System.EventArgs"/> that contains the event data.
753 protected override void OnResize(EventArgs e)
756 this.InvalidatePlot(false);
760 /// Converts the changed button.
763 /// The <see cref="System.Windows.Forms.MouseEventArgs"/> instance containing the event data.
766 /// The mouse button.
768 private static OxyMouseButton ConvertChangedButton(MouseEventArgs e)
772 case MouseButtons.Left:
773 return OxyMouseButton.Left;
774 case MouseButtons.Middle:
775 return OxyMouseButton.Middle;
776 case MouseButtons.Right:
777 return OxyMouseButton.Right;
778 case MouseButtons.XButton1:
779 return OxyMouseButton.XButton1;
780 case MouseButtons.XButton2:
781 return OxyMouseButton.XButton2;
784 return OxyMouseButton.Left;
788 /// Creates the mouse event arguments.
791 /// The <see cref="System.Windows.Forms.MouseEventArgs"/> instance containing the event data.
794 /// Mouse event arguments.
796 private OxyMouseEventArgs CreateMouseEventArgs(MouseEventArgs e)
798 return new OxyMouseEventArgs
800 ChangedButton = ConvertChangedButton(e),
801 Position = new ScreenPoint(e.Location.X, e.Location.Y),
802 IsShiftDown = (ModifierKeys & Keys.Shift) == Keys.Shift,
803 IsControlDown = (ModifierKeys & Keys.Control) == Keys.Control,
804 IsAltDown = (ModifierKeys & Keys.Alt) == Keys.Alt,
809 /// Creates the manipulation event args.
812 /// The MouseEventArgs instance containing the event data.
815 /// A manipulation event args object.
817 private ManipulationEventArgs CreateManipulationEventArgs(MouseEventArgs e)
819 return new ManipulationEventArgs(e.Location.ToScreenPoint());
823 /// Gets the manipulator for the current mouse button and modifier keys.
829 /// A manipulator or null if no gesture was recognized.
831 private ManipulatorBase GetManipulator(MouseEventArgs e)
833 bool control = (ModifierKeys & Keys.Control) == Keys.Control;
834 bool shift = (ModifierKeys & Keys.Shift) == Keys.Shift;
835 bool alt = (ModifierKeys & Keys.Alt) == Keys.Alt;
837 bool lmb = e.Button == MouseButtons.Left;
838 bool rmb = e.Button == MouseButtons.Right;
839 bool mmb = e.Button == MouseButtons.Middle;
840 bool xb1 = e.Button == MouseButtons.XButton1;
841 bool xb2 = e.Button == MouseButtons.XButton2;
843 // MMB / control RMB / control+alt LMB
844 if (mmb || (control && lmb) || (control && alt && rmb))
846 return new ZoomRectangleManipulator(this);
849 // Right mouse button / alt+left mouse button
850 if (lmb || (rmb && alt))
854 return new ResetManipulator(this);
856 return new PanManipulator(this);
862 return new TrackerManipulator(this) { Snap = !control, PointsOnly = shift };
865 // XButtons are zoom-stepping
868 double d = xb1 ? 0.05 : -0.05;
869 return new ZoomStepManipulator(this, d, control);
876 /// The set clipboard text.
878 /// <param name="text">
881 private void SetClipboardText(string text)
885 // todo: can't get the following solution to work
886 // http://stackoverflow.com/questions/5707990/requested-clipboard-operation-did-not-succeed
887 Clipboard.SetText(text);
889 catch (ExternalException ee)
891 // Requested Clipboard operation did not succeed.
892 MessageBox.Show(this, ee.Message, "OxyPlot");