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 unaAnnotation
se miden en porcentajes de los respectivos contenedores. Esos datos constituyen unaElementPosition
y generalmente van del0-100%
al0-100%
.Las coordenadas del mouse y todos los gráficos dibujados en uno de los tres eventos
Paint
, todos funcionan en píxeles; van desde0-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 ..
- ..grab el punto de la serie específica (nombre de la serie = "Mi serie")
- 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.
descargar Entornos de muestras para Microsoft Chart Controls
https://code.msdn.microsoft.com/Samples-Environments-for-b01e9c61
Mira esto:
Características del gráfico -> Gráficos interactivos -> Selección -> Cambiar valores arrastrando