简体   繁体   English

如何拖动DataPoint并在Chart控件中移动它

[英]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 .. 我怎样才能 ..

  1. ..grab the specific series point (series name ="My Series") ..抓住具体的系列点(系列名称=“我的系列”)
  2. When released the series point should change its position/ values 释放时,系列点应改变其位置/值

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. 图表元素(如LegendAnnotation以相应容器的百分比度量。 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. 请注意,我选择使用每个DataPointsTag来保存一个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 . 请注意,我已设置MinimaMaxima以及两个AxesIntervals This stops the Chart from running wild with its automatic display of Labels , GridLines , TickMarks etc.. 这会使Chart自动显示LabelsGridLinesTickMarks等,从而阻止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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM