Перемещение DataPoint
не является встроенной функцией управления Chart
. Мы должны кодироваться ..
Проблема с взаимодействующими с диаграммой с помощью мыши, что есть не один, а три системы на работе координат в Chart
:
штурманской элементы, как a Legend
или Annotation
измеряются в процентах от соответствующих контейнеров. Эти данные составляют ElementPosition
и обычно идут от 0-100%
.
Координаты мыши и все графики, выполненные в одном из трех событий Paint
, все работают в пикселях; они идут от 0-Chart.ClientSize.Width/Height
.
DataPoints
имеют значение x и один (или более) y-значения (-ы). Это двойники, и они могут идти от и до любого места, где вы их устанавливаете.
Для нашей задачи нам нужно конвертировать между пикселей мыши и значения данных.
Осмотрите Update ниже!
Есть несколько способов сделать это, но я думаю, что это чистейшая:
Сначала мы создаем несколько переменных уровня класса, которые держат ссылки на цели:
// variables holding moveable parts:
ChartArea ca_ = null;
Series s_ = null;
DataPoint dp_ = null;
bool synched = false;
Когда мы настраиваем график, мы заполняем некоторые из них:
ca_ = chart1.ChartAreas[0];
s_ = chart1.Series[0];
Далее нам понадобятся две вспомогательные функции.Они делают 1-преобразование между пикселями и значениями данных:
// 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
каждого DataPoints
провести RectangleF
, который имеет ClientRectangle из маркеров DataPoint
«s.
Эти прямоугольники будут изменения всякий раз, когда диаграмма изменяется или другие изменения в макете, как проклейки легенды и т.д .. уже случилось, поэтому нам нужно повторно синхронизировать их каждый раз! И, конечно, вам нужно сначала установить их, когда вы добавляете 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
, а также Intervals
для б oth Axes
. Это останавливает Chart
от разрастания с автоматическим отображением Labels
, GridLines
, TickMarks
и т.д ..
отметить также, что это будет работать с любым DataType
для Х-, Y.. Необходимо будет только форматировать Tooltip
..
Конечная нота: Для того, чтобы запретить пользователям перемещение DataPoint
выключение ChartArea
вы можете добавить эту проверку в if-clause
на MouseMove
мероприятия:
RectangleF ippRect = InnerPlotPositionClientRectangle(chart1, ca_);
if (!ippRect.Contains(e.Location)) return;
Для функции InnerPlotPositionClientRectangle
see here!
Обновление:
О пересмотре кода Интересно, почему я не выбрал более простой w ау:
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;
}
}
Вы действительно хотите переместить datapoint (т. е. изменить его x- и/или y-значение) или вы хотите переместить (т. прокрутить) серию? – TaW
@TaW: У меня есть серия (точки типа) с различными точками на ней, которая является графикой пользователя, используя определенный ввод диалога. Поэтому я хочу переместить datapoint так, чтобы позиция другой точки не изменялась, только позиция конкретной точки, которая перетаскивается, должна измениться. Также после перетаскивания точка должна зафиксироваться в новом положении. Спасибо за ответ. Мне действительно нужно это сделать. – LittleThunder
Хорошо, вижу. Таким образом, перетаскивание должно содержать как x, так и y-значения. Интересная идея! Первая проблема заключается в преобразовании в пикселы и значения данных и из них. Можете ли вы рассказать мне, что такое значения данных? Тип данных? - Вторая интерактивность. Я буду играть с ним позже сегодня ;-) – TaW