我希望能够抓取图表中绘制的数据点,并通过将其拖动到图表控件上来移动它并更改其位置。
我怎样才能 ..
- ..抓取特定系列点(系列名称="我的系列")
- 释放时,系列点应更改其位置/值
这就像通过拖动事件使系列点可移动。
这里的颜色点(点)应该能够移动:
有一些图表,如 devExpress 图表可以执行此任务,但我想在普通 MS 图表中执行此任务。
移动 aDataPoint
不是控件的内置功能Chart
。我们需要编码它..
通过鼠标与图表交互的问题在于,在 a 中工作的不是一个而是三个坐标系Chart
:
图表元素(如 aLegend
或 an 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));
}
请注意,我选择使用Tag
eachDataPoints
来保存一个RectangleF
具有 的标记的 clientRectangle DataPoint
。
每当调整图表大小或布局中的其他更改(例如图例的大小等)时,这些矩形都会发生变化,因此我们每次都需要重新同步它们!而且,当然,您需要在添加时初始设置它们!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
包含鼠标位置的 a 。然后我们存储它并更改它的颜色..:
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
Intervals
Axes
Chart
Labels
GridLines
TickMarks
另请注意,这适用DataType
于 X 值和 Y 值。只有Tooltip
格式需要调整..
最后说明:为了防止用户DataPoint
离开,ChartArea
您可以将此检查添加到if-clause
事件MouseMove
中:
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(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;
}
}
下载Microsoft 图表控件的示例环境
https://code.msdn.microsoft.com/Samples-Environments-for-b01e9c61
检查这个:
图表功能 -> 交互式图表 -> 选择 -> 通过拖动更改值