[英]How to drag a DataPoint and move it in a Chart control
I want to be able to grab a datapoint drawn in a chart and to move it and change its position by dragging it over the chart control. 我希望能够抓取图表中绘制的数据点并移动它并通过将其拖动到图表控件上来更改其位置。
How can I .. 我怎样才能 ..
It's like making series points movable with drag event. 这就像使用拖动事件使系列点可移动一样。
Here the color dots (points) should be able to move: 这里的颜色点(点)应该能够移动:
There are some charts like devExpress chart which perform this task but I want to do it in normal MS chart. 有一些图表,如devExpress图表执行此任务,但我想在正常的MS图表中进行。
Moving a DataPoint
is not a built-in feature of the Chart
control. 移动DataPoint
不是Chart
控件的内置功能。 We need to code it.. 我们需要编码..
The problem with interacting with a Chart by mouse is that there are not one but three coordinate systems at work in a Chart
: 用鼠标图表交互的问题是,有不是一个而是三个坐标的工作系统的Chart
:
The chart elements, like a Legend
or an Annotation
are measured in percentages of the respective containers. 图表元素(如Legend
或Annotation
以相应容器的百分比度量。 Those data make up an ElementPosition
and usually go from 0-100%
. 这些数据构成一个ElementPosition
,通常为0-100%
。
The Mouse coordinates and all graphics drawn in one of the three Paint
events, all work in pixels; 鼠标坐标和在三个Paint
事件之一中绘制的所有图形都以像素为单位; they go from 0-Chart.ClientSize.Width/Height
. 它们来自0-Chart.ClientSize.Width/Height
。
The DataPoints
have an x-value and one (or more) y-values(s). DataPoints
具有x值和一个(或多个)y值。 Those are doubles and they can go from and to anywhere you set them to. 这些是双打的,他们可以去往你设置的任何地方。
For our task we need to convert between mouse pixels and data values . 对于我们的任务,我们需要在鼠标像素和数据值之间进行转换。
DO see the Update below! 请看下面的更新!
There are several ways to do this, but I think this is the cleanest: 有几种方法可以做到这一点,但我认为这是最干净的:
First we create a few class level variables that hold references to the targets: 首先,我们创建一些包含目标引用的类级变量:
// variables holding moveable parts:
ChartArea ca_ = null;
Series s_ = null;
DataPoint dp_ = null;
bool synched = false;
When we set up the chart we fill some of them: 当我们设置图表时,我们填写其中一些:
ca_ = chart1.ChartAreas[0];
s_ = chart1.Series[0];
Next we need two helper functions. 接下来我们需要两个辅助函数。 They do the 1st conversion between pixels and data values: 他们在像素和数据值之间进行第一次转换:
// 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));
}
Note that I chose to use the Tag
of each DataPoints
to hold a RectangleF
that has the clientRectangle of the DataPoint
's Marker. 请注意,我选择使用每个DataPoints
的Tag
来保存一个RectangleF
,它具有DataPoint
标记的clientRectangle。
These rectangles will change whenever the chart is resized or other changes in the Layout, like sizing of a Legend etc.. have happend, so we need to re-synch them each time! 每当图表调整大小或布局中的其他更改(如图例等的大小调整等)发生时,这些矩形都会发生变化 ,因此我们需要每次重新同步它们! And, of course you need to initially set them whenever you add a DataPoint
! 当然,每当添加DataPoint
时,您都需要初始设置它们!
Here is the Resize
event: 这是Resize
事件:
private void chart1_Resize(object sender, EventArgs e)
{
synched = false;
}
The actual refreshing of the rectangles is being triggered from the PrePaint
event: 从PrePaint
事件触发实际的矩形刷新:
private void chart1_PrePaint(object sender, ChartPaintEventArgs e)
{
if ( !synched) SyncAllPoints(ca_, s_);
}
Note that calling the ValueToPixelPosition
is not always valid ! 请注意,调用ValueToPixelPosition
并不总是有效 ! If you call it at the wrong time it will return null.. We are calling it from the PrePaint
event, which is fine. 如果你在错误的时间调用它,它将返回null ..我们从PrePaint
事件中调用它,这很好。 The flag will help keeping things efficient. 国旗将有助于保持效率。
Now for the actual moving of a point : As usual we need to code the three mouse events: 现在实际移动一个点 :像往常一样,我们需要编写三个鼠标事件的代码:
In the MouseDown
we loop over the Points
collection until we find one with a Tag
that contains the mouse position. 在MouseDown
我们遍历Points
集合,直到我们找到一个带有包含鼠标位置的Tag
。 Then we store it and change its Color..: 然后我们存储它并改变它的颜色..:
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;
}
}
In the MouseMove
we do the reverse calculation and set the values of our point; 在MouseMove
我们进行反向计算并设置我们的点的值; note that we also synch its new position and trigger the Chart
to refresh the display: 请注意,我们还会同步其新位置并触发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;
}
}
}
Finally we clean up in the MouseUp
event: 最后我们在MouseUp
事件中清理:
private void chart1_MouseUp(object sender, MouseEventArgs e)
{
if (dp_ != null)
{
dp_.Color = s_.Color;
dp_ = null;
}
}
Here is how I have set up my chart: 以下是我设置图表的方法:
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];
Note that I have set both the Minima
and Maxima
as well as the Intervals
for both Axes
. 请注意,我已设置Minima
和Maxima
以及两个Axes
的Intervals
。 This stops the Chart
from running wild with its automatic display of Labels
, GridLines
, TickMarks
etc.. 这会使Chart
自动显示Labels
, GridLines
, TickMarks
等,从而阻止Chart
运行。
Also note that this will work with any DataType
for X- and YValues. 另请注意,这适用于X和Y值的任何DataType
。 Only the Tooltip
formatting will have to be adapted.. 只需要调整Tooltip
格式..
Final note: To prevent the users from moving a DataPoint
off the ChartArea
you can add this check into the if-clause
of the MouseMove
event: 最后注意事项:为了防止用户从ChartArea
移出DataPoint
,您可以将此检查添加到MouseMove
事件的if-clause
中:
RectangleF ippRect = InnerPlotPositionClientRectangle(chart1, ca_);
if (!ippRect.Contains(e.Location) ) return;
For the InnerPlotPositionClientRectangle
function see here! 对于InnerPlotPositionClientRectangle
函数, 请看这里!
Update: 更新:
On revisiting the code I wonder why I didn't choose a simpler way: 在重新访问代码时,我想知道为什么我没有选择更简单的方法:
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(e.X, e.Y);
if (hit.PointIndex >= 0) curPoint = hit.Series.Points[hit.PointIndex];
if (curPoint != null)
{
Series s = hit.Series;
double dx = ax.PixelPositionToValue(e.X);
double dy = ay.PixelPositionToValue(e.Y);
curPoint.XValue = dx;
curPoint.YValues[0] = dy;
}
}
download Samples Environments for Microsoft Chart Controls 下载Microsoft图表控件的示例环境
https://code.msdn.microsoft.com/Samples-Environments-for-b01e9c61 https://code.msdn.microsoft.com/Samples-Environments-for-b01e9c61
Check this: 检查一下:
Chart Features -> Interactive Charting -> Selection -> Changing Values by dragging 图表功能 - >交互式图表 - >选择 - >通过拖动更改值
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.