c# charts mousemove datapoint

c# - Cómo arrastrar un DataPoint y moverlo en un control Chart



charts mousemove (2)

Mover un DataPoint no es una función incorporada del control Chart . Necesitamos codificarlo ...

El problema con la interacción con un Gráfico por el mouse es que no hay uno sino tres sistemas de coordenadas en funcionamiento en un Chart :

  • Los elementos del gráfico, como una Legend o una Annotation se miden en porcentajes de los respectivos contenedores. Esos datos constituyen una ElementPosition y generalmente van del 0-100% al 0-100% .

  • Las coordenadas del mouse y todos los gráficos dibujados en uno de los tres eventos Paint , todos funcionan en píxeles; van desde 0-Chart.ClientSize.Width/Height .

  • Los DataPoints tienen un valor xy uno (o más) valores y. Esos son dobles y pueden ir desde y hacia cualquier lugar donde los establezcas.

Para nuestra tarea, necesitamos convertir píxeles de ratón y valores de datos .

¡Mire la actualización a continuación!

Hay varias formas de hacer esto, pero creo que este es el más limpio:

Primero creamos algunas variables de nivel de clase que contienen referencias a los objetivos:

// variables holding moveable parts: ChartArea ca_ = null; Series s_ = null; DataPoint dp_ = null; bool synched = false;

Cuando configuramos el gráfico llenamos algunos de ellos:

ca_ = chart1.ChartAreas[0]; s_ = chart1.Series[0];

Luego necesitamos dos funciones auxiliares. Hacen la primera conversión entre píxeles y valores de datos:

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

Tenga en cuenta que elegí usar la Tag de cada DataPoints para contener un RectangleF que tiene el rectángulo de cliente del marcador de DataPoint .

Estos rectángulos cambiarán cada vez que se cambie el tamaño del gráfico o se hayan producido otros cambios en el diseño, como el tamaño de una leyenda, etc., así que tenemos que volver a sincronizarlos cada vez. ¡Y, por supuesto, debe establecerlos inicialmente cada vez que agrega un DataPoint !

Aquí está el evento de Resize :

private void chart1_Resize(object sender, EventArgs e) { synched = false; }

La actualización real de los rectángulos se desencadena desde el evento PrePaint :

private void chart1_PrePaint(object sender, ChartPaintEventArgs e) { if ( !synched) SyncAllPoints(ca_, s_); }

Tenga en cuenta que llamar a ValueToPixelPosition no siempre es válido . Si lo llama en el momento equivocado, devolverá nulo. Lo estamos llamando desde el evento PrePaint , lo cual está bien. La bandera ayudará a mantener las cosas eficientes.

Ahora para el movimiento real de un punto : como de costumbre, necesitamos codificar los tres eventos del mouse:

En MouseDown recorremos la colección Points hasta que encontremos uno con una Tag que contenga la posición del mouse. Luego lo almacenamos y cambiamos su 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; } }

En MouseMove hacemos el cálculo inverso y establecemos los valores de nuestro punto; tenga en cuenta que también sincronizamos su nueva posición y activamos el Chart para actualizar la pantalla:

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

Finalmente limpiamos en el evento MouseUp :

private void chart1_MouseUp(object sender, MouseEventArgs e) { if (dp_ != null) { dp_.Color = s_.Color; dp_ = null; } }

Así es como he configurado mi gráfico:

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

Tenga en cuenta que he configurado tanto el Minima como el Maxima , así como los Intervals para ambos Axes . Esto evita que el Chart ejecute de manera salvaje con su visualización automática de Labels , TickMarks , TickMarks , etc.

También tenga en cuenta que esto funcionará con cualquier DataType para X- y YValues. Solo el formato Tooltip deberá ser adaptado.

Nota final: para evitar que los usuarios muevan un DataPoint fuera de ChartArea , puede agregar esta verificación en la if-clause MouseMove evento MouseMove :

RectangleF ippRect = InnerPlotPositionClientRectangle(chart1, ca_); if (!ippRect.Contains(e.Location) ) return;

Para la función InnerPlotPositionClientRectangle , ¡ mira aquí!

Actualizar:

Al volver a visitar el código, me pregunto por qué no elegí una forma más simple:

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

Quiero poder tomar un punto de datos dibujado en un gráfico y moverlo y cambiar su posición arrastrándolo sobre el control del gráfico.

Cómo puedo ..

  1. ..grab el punto de la serie específica (nombre de la serie = "Mi serie")
  2. Cuando se libere, el punto de la serie debería cambiar su posición / valores

Es como hacer que los puntos de serie se puedan mover con el evento de arrastre.

Aquí los puntos de color (puntos) deberían poder moverse:

Hay algunos gráficos como el gráfico devExpress que realizan esta tarea, pero quiero hacerlo en un gráfico normal de MS.