如何拖动DataPoint并在Chart控件中移动它
我希望能够抓取图表中绘制的数据点并移动它并通过将其拖动到图表控件上来更改其位置。
我怎么能够 ..
- ..抓住具体的系列点(系列名称=“我的系列”)
- 释放时,系列点应改变其位置/值
这就像使用拖动事件使系列点可移动一样。
这里的颜色点(点)应该能够移动:
有些图表如devExpress图表执行此任务但我想在正常的MS图表中执行此操作。
移动DataPoint
不是Chart
控件的内置function。 我们需要编码..
通过鼠标与图表交互的问题是图表中没有一个坐标系统可以使用, 但是
-
图表元素(如
Legend
或Annotation
以相应容器的百分比度量。 这些数据构成一个ElementPosition
,通常为0-100%
。 -
鼠标坐标和在三个
Paint
事件之一中绘制的所有图形都以像素为单位; 它们来自0-Chart.ClientSize.Width/Height
。 -
DataPoints
具有x值和一个(或多个)y值。 这些是双打的,他们可以去往你设置的任何地方。
对于我们的任务,我们需要在鼠标像素和数据值之间进行转换。
请看下面的更新!
有几种方法可以做到这一点,但我认为这是最干净的:
首先,我们创建一些类级变量来保存对目标的引用:
// variables holding moveable parts: ChartArea ca_ = null; Series s_ = null; DataPoint dp_ = null; bool synched = false;
当我们设置图表时,我们填写其中一些:
ca_ = chart1.ChartAreas[0]; s_ = chart1.Series[0];
接下来我们需要两个辅助函数。 他们在像素和数据值之间进行第一次转换:
// two helper functions: void SyncAllPoints(ChartArea ca, Series s) { foreach (DataPoint dp in s.Points) SyncAPoint(ca, s, dp); synched = true; } void SyncAPoint(ChartArea ca, Series s, DataPoint dp) { float mh = dp.MarkerSize / 2f; float px = (float)ca.AxisX.ValueToPixelPosition(dp.XValue); float py = (float)ca.AxisY.ValueToPixelPosition(dp.YValues[0]); dp.Tag = (new RectangleF(px - mh, py - mh, dp.MarkerSize, dp.MarkerSize)); }
请注意,我选择使用每个DataPoints
的Tag
来保存一个RectangleF
,它具有DataPoint
标记的clientRectangle。
每当图表resize或布局中的其他更改(如图例等的大小调整等)发生时,这些矩形都会发生变化 ,因此我们需要每次都重新同步它们! 当然,每当添加DataPoint
时,您都需要初始设置它们!
这是Resize
事件:
private void chart1_Resize(object sender, EventArgs e) { synched = false; }
从PrePaint
事件触发实际的矩形刷新:
private void chart1_PrePaint(object sender, ChartPaintEventArgs e) { if ( !synched) SyncAllPoints(ca_, s_); }
请注意,调用ValueToPixelPosition
并不总是有效 ! 如果你在错误的时间调用它,它将返回null ..我们从PrePaint
事件中调用它,这很好。 这面旗帜将有助于提高效率。
现在实际移动一个点 :像往常一样,我们需要编写三个鼠标事件的代码:
在MouseDown
我们遍历Points
集合,直到我们找到一个带有包含鼠标位置的Tag
。 然后我们存储它并改变它的颜色..:
private void chart1_MouseDown(object sender, MouseEventArgs e) { foreach (DataPoint dp in s_.Points) if (((RectangleF)dp.Tag).Contains(e.Location)) { dp.Color = Color.Orange; dp_ = dp; break; } }
在MouseMove
我们进行反向计算并设置我们的点的值; 请注意,我们还会同步其新位置并触发Chart
以刷新显示:
private void chart1_MouseMove(object sender, MouseEventArgs e) { if (e.Button.HasFlag(MouseButtons.Left) && dp_ != null) { float mh = dp_.MarkerSize / 2f; double vx = ca_.AxisX.PixelPositionToValue(e.Location.X); double vy = ca_.AxisY.PixelPositionToValue(e.Location.Y); dp_.SetValueXY(vx, vy); SyncAPoint(ca_, s_, dp_); chart1.Invalidate(); } else { Cursor = Cursors.Default; foreach (DataPoint dp in s_.Points) if (((RectangleF)dp.Tag).Contains(e.Location)) { Cursor = Cursors.Hand; break; } } }
最后我们在MouseUp
事件中清理:
private void chart1_MouseUp(object sender, MouseEventArgs e) { if (dp_ != null) { dp_.Color = s_.Color; dp_ = null; } }
以下是我设置图表的方法:
Series S1 = chart1.Series[0]; ChartArea CA = chart1.ChartAreas[0]; S1.ChartType = SeriesChartType.Point; S1.MarkerSize = 8; S1.Points.AddXY(1, 1); S1.Points.AddXY(2, 7); S1.Points.AddXY(3, 2); S1.Points.AddXY(4, 9); S1.Points.AddXY(5, 19); S1.Points.AddXY(6, 9); S1.ToolTip = "(#VALX{0.##} / #VALY{0.##})"; S1.Color = Color.SeaGreen; CA.AxisX.Minimum = S1.Points.Select(x => x.XValue).Min(); CA.AxisX.Maximum = S1.Points.Select(x => x.XValue).Max() + 1; CA.AxisY.Minimum = S1.Points.Select(x => x.YValues[0]).Min(); CA.AxisY.Maximum = S1.Points.Select(x => x.YValues[0]).Max() + 1; CA.AxisX.Interval = 1; CA.AxisY.Interval = 1; ca_ = chart1.ChartAreas[0]; s_ = chart1.Series[0];
请注意,我已设置Minima
和Maxima
以及两个Axes
的Intervals
。 这会使Chart
自动显示Labels
, GridLines
, TickMarks
等,从而阻止Chart
运行。
另请注意,这适用于X和Y值的任何DataType
。 只需要修改Tooltip
格式..
最后注意事项:为了防止用户从ChartArea
移出DataPoint
,您可以将此检查添加到MouseMove
事件的if-clause
中:
RectangleF ippRect = InnerPlotPositionClientRectangle(chart1, ca_); if (!ippRect.Contains(e.Location) ) return;
对于InnerPlotPositionClientRectangle
函数, 请看这里!
更新:
在重新访问代码时,我想知道为什么我没有选择更简单的方法:
DataPoint curPoint = null; private void chart1_MouseUp(object sender, MouseEventArgs e) { curPoint = null; } private void chart1_MouseMove(object sender, MouseEventArgs e) { if (e.Button.HasFlag(MouseButtons.Left)) { ChartArea ca = chart1.ChartAreas[0]; Axis ax = ca.AxisX; Axis ay = ca.AxisY; HitTestResult hit = chart1.HitTest(eX, eY); if (hit.PointIndex >= 0) curPoint = hit.Series.Points[hit.PointIndex]; if (curPoint != null) { Series s = hit.Series; double dx = ax.PixelPositionToValue(eX); double dy = ay.PixelPositionToValue(eY); curPoint.XValue = dx; curPoint.YValues[0] = dy; } }
下载Microsoft图表控件的示例环境
https://code.msdn.microsoft.com/Samples-Environments-for-b01e9c61
检查一下:
图表function – >交互式图表 – >选择 – >通过拖动更改值