2016-04-18 18 views
4

Я хочу, чтобы иметь возможность захватить datapoint, нарисованный в диаграмме, и переместить его и изменить его положение, перетащив его поверх элемента управления диаграммы.Как перетащить DataPoint и переместить его в элемент управления диаграммой

Как я могу ..

  1. ..grab конкретной точки серии (название серии = "Моя серия")
  2. При отпускании точки серии должны изменить свою позицию/значения

Это похоже на то, чтобы сделать точки серии подвижными с событием перетаскивания.

Здесь цвет точек (точки) должны быть в состоянии двигаться:

enter image description here

Есть некоторые карты, как DevExpress диаграммы, которые выполняют эту задачу, но я хочу сделать это в обычной схеме MS.

+0

Вы действительно хотите переместить datapoint (т. е. изменить его x- и/или y-значение) или вы хотите переместить (т. прокрутить) серию? – TaW

+0

@TaW: У меня есть серия (точки типа) с различными точками на ней, которая является графикой пользователя, используя определенный ввод диалога. Поэтому я хочу переместить datapoint так, чтобы позиция другой точки не изменялась, только позиция конкретной точки, которая перетаскивается, должна измениться. Также после перетаскивания точка должна зафиксироваться в новом положении. Спасибо за ответ. Мне действительно нужно это сделать. – LittleThunder

+0

Хорошо, вижу. Таким образом, перетаскивание должно содержать как x, так и y-значения. Интересная идея! Первая проблема заключается в преобразовании в пикселы и значения данных и из них. Можете ли вы рассказать мне, что такое значения данных? Тип данных? - Вторая интерактивность. Я буду играть с ним позже сегодня ;-) – TaW

ответ

7

Перемещение DataPoint не является встроенной функцией управления Chart. Мы должны кодироваться ..

Проблема с взаимодействующими с диаграммой с помощью мыши, что есть не один, а три системы на работе координат в Chart:

  • штурманской элементы, как a Legend или Annotation измеряются в процентах от соответствующих контейнеров. Эти данные составляют ElementPosition и обычно идут от 0-100%.

  • Координаты мыши и все графики, выполненные в одном из трех событий Paint, все работают в пикселях; они идут от 0-Chart.ClientSize.Width/Height.

  • DataPoints имеют значение x и один (или более) y-значения (-ы). Это двойники, и они могут идти от и до любого места, где вы их устанавливаете.

Для нашей задачи нам нужно конвертировать между пикселей мыши и значения данных.

Осмотрите Update ниже!

enter image description hereenter image description here

Есть несколько способов сделать это, но я думаю, что это чистейшая:

Сначала мы создаем несколько переменных уровня класса, которые держат ссылки на цели:

// 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; 

Для функции InnerPlotPositionClientRectanglesee 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; 
     } 
} 
+0

: Спасибо, я попробовал ваш метод, и это сработало для меня :) – LittleThunder

+0

Если вы довольны ответом, рассмотрите [принимаю] (http://stackoverflow.com/help/accepted-answer) это ..! - Я вижу, что вы никогда этого не делали. Пойдите (невидимую) галочку в левом верхнем углу, под голосами ответа и нажмите на нее! Он становится зеленым и обретает нам небольшую репутацию. – TaW

+0

Хорошо, спасибо, что я новичок в переполнении стека, и я не знал, что сделаю это сейчас. Любопытно, можем ли мы сделать то же самое решение, используя событие перетаскивания, например dragEnter, dragOver и dragLeave. Ваше решение работает нормально, просто любопытно посмотреть, возможно ли это. :) – LittleThunder