using System;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Windows.Forms;
using Core.Common.Controls.Charting.Customized;
using Core.Common.Controls.Charting.Series;
using Core.Common.Controls.Charting.Properties;
using Core.Common.Utils.Extensions;
using log4net;
using Steema.TeeChart.Styles;
namespace Core.Common.Controls.Charting.Tools
{
///
/// Tools for moving and deleting points (responds to 'DEL'button)
///
public class EditPointTool : ChartViewSeriesToolBase
{
public event EventHandler BeforeDrag;
public event EventHandler MouseHoverPoint;
public event EventHandler AfterPointEdit;
private const double clippingTolerance = 1e-4;
private static readonly ILog log = LogManager.GetLogger(typeof(EditPointTool));
private readonly ToolTip toolTip;
private readonly RingtoetsTChart tChart;
private DragStyle style = DragStyle.Both;
private bool pointHasMoved;
private Color selectedPointerColor = Color.LimeGreen;
private readonly Steema.TeeChart.Styles.PointerStyles selectedPointerStyle = Steema.TeeChart.Styles.PointerStyles.Diamond;
private bool clipXValues = true;
private bool restoreZoom;
///
/// Allows user to interactively change points in a chart
///
///
public EditPointTool(Steema.TeeChart.Chart c) : base(c)
{
toolTip = new ToolTip
{
ShowAlways = false
};
}
///
/// Allows user to interactively change points in a chart
///
///
public EditPointTool(Steema.TeeChart.Styles.Series s) : base(s) {}
///
/// Allows user to interactively change points in a chart
///
public EditPointTool() : this(((Steema.TeeChart.Chart) null)) {}
///
/// Allows user to interactively change points in a chart
/// Constructor for schowing a toolTip displaying the coordinates in drga mode.
///
///
public EditPointTool(RingtoetsTChart c)
: base(c.Chart)
{
tChart = c;
toolTip = new ToolTip();
toolTip.ShowAlways = false;
}
///
/// Color of the point selected in the chart
///
public Color SelectedPointerColor
{
get
{
return selectedPointerColor;
}
set
{
selectedPointerColor = value;
}
}
///
/// True if series line can represent a polygon, otherwise x-coordinate of every point will be limited by x value of it's neighbours.
///
public bool IsPolygon { get; set; }
///
/// Clip means that a point cannot be dragged PASSED another in X direction
///
public bool ClipXValues
{
get
{
return clipXValues;
}
set
{
clipXValues = value;
}
}
///
/// Clip means that a point cannot be dragged PASSED another in Y direction
///
public bool ClipYValues { get; set; }
public DragStyle DragStyles
{
get
{
return style;
}
set
{
style = value;
}
}
public ILineChartSeries Series
{
get
{
return (ILineChartSeries) base.Series;
}
set
{
base.Series = value;
}
}
protected override void KeyEvent(KeyEventArgs e)
{
base.KeyEvent(e);
if (e.KeyCode == Keys.Delete && selectedPointIndex != -1)
{
// try to delete point from datasource so event is fired
DataTable dataTable = LastSelectedSeries.DataSource as DataTable;
if (dataTable != null)
{
dataTable.Rows[selectedPointIndex].Delete();
return;
}
var lineChartSeries = LastSelectedSeries.DataSource as LineChartSeries;
if (lineChartSeries != null)
{
lineChartSeries.series.Delete(selectedPointIndex);
SelectedPointIndex = -1;
return;
}
throw new NotImplementedException(Resources.KeyEvent_Deletion_not_implemented_for_this_type_of_datasource);
}
}
protected override void OnMouseDown(MouseEventArgs e)
{
Point p = new Point(e.X, e.Y);
//why not e.Button?
if (Steema.TeeChart.Utils.GetMouseButton(e) != MouseButtons.Left)
{
return;
}
// in case of mouseclick, select point nearest to mouse
if (LastSelectedSeries != null)
{
var tolerance = Series != null ? Series.PointerSize : 3;
int index = TeeChartHelper.GetNearestPoint(LastSelectedSeries, p, tolerance);
if (-1 != index)
{
if (BeforeDrag != null)
{
var args = new PointEventArgs(GetChartSeriesFromInternalSeries(LastSelectedSeries), index,
LastSelectedSeries.XValues[index],
LastSelectedSeries.YValues[index]);
BeforeDrag(this, args);
if (args.Cancel)
{
return;
}
}
SelectedPointIndex = index;
Chart.CancelMouse = true;
restoreZoom = Chart.Zoom.Active;
Chart.Zoom.Active = false;
Chart.Zoom.Allow = false;
pointHasMoved = false;
Invalidate();
}
}
}
protected override void OnMouseMove(MouseEventArgs e, ref Cursor c)
{
var p = new Point(e.X, e.Y);
Steema.TeeChart.Styles.Series s = ClickedSeries(p);
if (s != null)
{
var tolerance = Series != null ? Series.PointerSize : 3;
int abovePoint = TeeChartHelper.GetNearestPoint(s, p, tolerance);
if (abovePoint > -1)
{
c = GetCursorIcon(style);
Chart.CancelMouse = true;
if (MouseHoverPoint != null)
{
MouseHoverPoint(this, new HoverPointEventArgs(GetChartSeriesFromInternalSeries(s), abovePoint, 0.0, 0.0, true));
}
}
}
else
{
c = Cursors.Default;
if (MouseHoverPoint != null)
{
MouseHoverPoint(this, new HoverPointEventArgs(null, -1, 0.0, 0.0, false));
}
}
if (Steema.TeeChart.Utils.GetMouseButton(e) != MouseButtons.Left)
{
return;
}
if (SelectedPointIndex > -1)
{
if ((style == DragStyle.X) || (style == DragStyle.Both))
{
if (LastSelectedSeries != null)
{
LastSelectedSeries.XValues[SelectedPointIndex] = CalculateXValue(p);
}
pointHasMoved = true;
}
if ((style == DragStyle.Y) || (style == DragStyle.Both))
{
if (LastSelectedSeries != null)
{
LastSelectedSeries.YValues[SelectedPointIndex] = CalculateYValue(p);
}
pointHasMoved = true;
}
if (IsPolygon)
{
SynchronizeFirstAndLastPointOfPolygon();
}
if (pointHasMoved)
{
Invalidate();
}
}
}
protected override void OnMouseUp(MouseEventArgs e)
{
if (Steema.TeeChart.Utils.GetMouseButton(e) != MouseButtons.Left)
{
return;
}
if (SelectedPointIndex > -1)
{
//int selectedIndex = SelectedPointIndex;
if (pointHasMoved)
{
if (AfterPointEdit != null)
{
// AfterPointEdit can change underlying and reset the SelectedPointIndex
// MouseMove and MouseUp can run out of sync; return the values set by mousemove = what is visible to the user
var chartSeries = GetChartSeriesFromInternalSeries(LastSelectedSeries);
AfterPointEdit(this, new PointEventArgs(chartSeries, SelectedPointIndex,
LastSelectedSeries.XValues[selectedPointIndex], LastSelectedSeries.YValues[selectedPointIndex]));
if (IsPolygon)
{
if (0 == SelectedPointIndex)
{
if (LastSelectedSeries != null)
{
AfterPointEdit(this, new PointEventArgs(chartSeries, LastSelectedSeries.XValues.Count - 1,
LastSelectedSeries.XValues[selectedPointIndex], LastSelectedSeries.YValues[selectedPointIndex]));
}
}
else if (LastSelectedSeries != null)
{
if ((LastSelectedSeries.XValues.Count - 1) == SelectedPointIndex)
{
AfterPointEdit(this, new PointEventArgs(chartSeries, 0, LastSelectedSeries.XValues[selectedPointIndex], LastSelectedSeries.YValues[selectedPointIndex]));
}
}
}
}
pointHasMoved = false;
}
Chart.Zoom.Active = restoreZoom;
Chart.Zoom.Allow = true;
//hide tooltip
if (tChart != null)
{
toolTip.ShowAlways = false;
toolTip.Hide(tChart);
}
selectedPointIndex = -1;
}
}
protected override void OnCustomSeriesGetPointerStyle(CustomPoint series, GetPointerStyleEventArgs e)
{
if (e.ValueIndex == selectedPointIndex)
{
e.Color = selectedPointerColor;
e.Style = selectedPointerStyle;
}
}
private static Cursor GetCursorIcon(DragStyle dragStyle)
{
switch (dragStyle)
{
case DragStyle.Both:
return Cursors.SizeAll;
case DragStyle.X:
return Cursors.SizeWE;
case DragStyle.Y:
return Cursors.SizeNS;
default:
throw new NotImplementedException(String.Format(Resources.EditPointTool_GetCursorIcon_No_cursor_assigned_for_0_, dragStyle));
}
}
private double CalculateXValue(Point P)
{
double xValue = LastSelectedSeries.XScreenToValue(P.X);
//log.DebugFormat("Clicked at point (x): {0}", xValue);
if (!IsPolygon && ClipXValues)
{
if (SelectedPointIndex > 0)
{
//do not allow to horizontally drag before a neigbouring point.
double lowerLimit = LastSelectedSeries.XValues[SelectedPointIndex - 1];
if (xValue < lowerLimit)
{
log.DebugFormat(Resources.EditPointTool_CalculateXValue_left_limit, xValue, lowerLimit + clippingTolerance);
xValue = lowerLimit + clippingTolerance;
}
}
if (SelectedPointIndex < LastSelectedSeries.Count - 1)
{
//do not allow to horizontally drag past a neigbouring point.
double upperLimit = LastSelectedSeries.XValues[SelectedPointIndex + 1];
if (xValue > upperLimit)
{
log.DebugFormat(Resources.EditPointTool_CalculateXValue_right_limit, xValue, upperLimit - clippingTolerance);
xValue = upperLimit - clippingTolerance;
}
}
}
return xValue;
}
private double CalculateYValue(Point P)
{
double yValue = LastSelectedSeries.YScreenToValue(P.Y);
if (!IsPolygon && ClipYValues)
{
//do not allow to vertical drag beyond a neigbouring point.
double current = LastSelectedSeries.YValues[SelectedPointIndex];
var limits = new List();
if (SelectedPointIndex > 0)
{
limits.Add(LastSelectedSeries.YValues[SelectedPointIndex - 1]);
}
if (SelectedPointIndex < LastSelectedSeries.Count - 1)
{
limits.Add(LastSelectedSeries.YValues[SelectedPointIndex + 1]);
}
return Clip(yValue, current, clippingTolerance, limits);
}
return yValue;
}
private static double Clip(double newValue, double oldValue, double margin, IList limits)
{
foreach (double limit in limits) //note: should these be ordered?
{
if (limit.IsInRange(newValue, oldValue))
{
return oldValue < limit ? limit - margin : limit + margin;
}
}
return newValue;
}
private void SynchronizeFirstAndLastPointOfPolygon()
{
//make sure that the last point is equal to the first. If we moved the first we should move the last and vice-versa
if (0 == SelectedPointIndex)
{
LastSelectedSeries.XValues[LastSelectedSeries.XValues.Count - 1] = LastSelectedSeries.XValues[0];
LastSelectedSeries.YValues[LastSelectedSeries.YValues.Count - 1] = LastSelectedSeries.YValues[0];
}
else if ((LastSelectedSeries.XValues.Count - 1) == SelectedPointIndex)
{
LastSelectedSeries.XValues[0] = LastSelectedSeries.XValues[LastSelectedSeries.XValues.Count - 1];
LastSelectedSeries.YValues[0] = LastSelectedSeries.YValues[LastSelectedSeries.YValues.Count - 1];
}
}
}
public enum DragStyle
{
Both,
X,
Y
}
}