Reputation: 149
I want to create a zoom picturebox like in PictureBox Zoom for mschart. When I hover the mouse on the chart, the picturebox should show zoom view, so that I can select an appropriate point. Can this happen in winforms C#.net?
Upvotes: 0
Views: 304
Reputation: 149
Another solution is using chart control as a zoom view. You can view the data point marked in red when the mouse moves over the original chart. As shown below:
Here is the code :
private void chart1_MouseMove(object sender, MouseEventArgs e)
{
Point mousePoint = new Point(e.X, e.Y);
double mouse_Xvalue = chart1.ChartAreas[0].AxisX.PixelPositionToValue(e.X);
double mouse_Yvalue = chart1.ChartAreas[0].AxisY.PixelPositionToValue(e.Y);
DataPoint Prev_DataPoint = chart1.Series[0].Points.Select(x => x)
.Where(x => x.XValue >= mouse_Xvalue)
.DefaultIfEmpty(chart1.Series[0].Points.First()).First();
DataPoint Next_DataPoint = chart1.Series[0].Points.Select(x => x)
.Where(x => x.XValue <= mouse_Xvalue)
.DefaultIfEmpty(chart1.Series[0].Points.Last()).Last();
double diff_prev = Math.Abs(Prev_DataPoint.XValue - mouse_Xvalue);
double diff_next = Math.Abs(Next_DataPoint.XValue - mouse_Xvalue);
int zoffset = 15;
int setindexX = diff_prev < diff_next ?
chart1.Series[0].Points.IndexOf(Prev_DataPoint)
: chart1.Series[0].Points.IndexOf(Next_DataPoint);
int setXmin = (setindexX - zoffset) >= 0 ? (setindexX - zoffset)
: 0;
int setXmax = (setindexX + zoffset) < chart1.Series[0].Points.Count
? (setindexX + zoffset)
: chart1.Series[0].Points.Count - 1;
if (zoomchart.Series.Count > 0)
zoomchart.Series.Clear();
Series series = new Series();
Series series2 = new Series();
series.Points.Clear();
series2.Points.Clear();
for (int i = setXmin; i <= setXmax; i++)
series.Points.AddXY(chart1.Series[0].Points[i].XValue,
chart1.Series[0].Points[i].YValues[0]);
series.Color = chart1.Series[0].Color;
series.ChartType = SeriesChartType.Line;
series2.Points.AddXY(chart1.Series[0].Points[setindexX].XValue,
chart1.Series[0].Points[setindexX].YValues[0]);
series2.Color = Color.Red;
series2.ChartType = SeriesChartType.Point;
series2.Points[0].Label = series2.Points[0].XValue.ToString("F2") + ", "
+ series2.Points[0].YValues[0].ToString("F2");
zoomchart.Series.Add(series);
zoomchart.Series.Add(series2);
zoomchart.Invalidate();
zoomchart.ChartAreas[0].AxisX.Minimum = series.Points[0].XValue;
zoomchart.ChartAreas[0].AxisX.Maximum = series.Points.FindMaxByValue("X").XValue;
zoomchart.ChartAreas[0].AxisY.Minimum = series.Points.FindMinByValue().YValues[0];
zoomchart.ChartAreas[0].AxisY.Maximum = series.Points.FindMaxByValue().YValues[0];
}
Upvotes: 1
Reputation: 54433
Here is a solution that does what op asked for: Create a PictureBox
that shows a zoomed portion of a Chart
which will move as one moves across the chart.
It looks fine but one will still have to move across those tiny unzoomed pixels..
Here is how is done and set up:
Whenever necessary the PictureBox zoomPBox
must be set up again; setting it up involves taking a few measurements and creating a screenshot of the Chart
. For this the chart is enlarged temporarily and then reset to the original size.
Note: Whenever the chart get resized or changed in any other way the setup routine must be called again.
The PictureBox zoomPBox
is set to SizeMode Normal
and is nested in a Panel
. In the setup we enlarge the zoomPBox
to hold the whole Bitmap
. The Panel zoomPanel
has AutoScroll = false
to avoid scrollbars.
One complication is the automatic sizing the Chart controls does. When enlarging it the content is enlarged but e.g. none of the fonts are. This leads to different aspect ratios between the normal and the zoomed plot area. To keep movement in synch we can't have this. Therefore we not only want to cut out the actual inner plot area without Legend
, Title
or Axes
from the zoomed screenshot but also stretch it to the same aspect ratio as the unzoomed plot area..
Here is the result:
The code for the MouseMove
is not so involved..:
private void chart_MouseMove(object sender, MouseEventArgs e)
{
if (zoomPBox.Image == null) return;
Rectangle ri = Rectangle.Round(
InnerPlotPositionClientRectangle(chart, chart.ChartAreas[0]));
Size szi = zoomPBox.Image.Size;
Size szp = zoomPanel.ClientSize;
Point cp = new Point( e.X - ri.X , e.Y - ri.Y );
float zx = 1f * szi.Width / ri.Width;
float zy = 1f * szi.Height / ri.Height; // should be the same
int x = round( szp.Width / 2 - cp.X * zx );
int y = round( szp.Height / 2 - cp.Y * zy );
zoomPBox.Location = new Point(x, y); // now we move the pBox into position
zoomPBox.Invalidate();
}
As you can see I Invalidate
the PictureBox
; that is to allow it to draw crosshair lines onto itself for better control; here is the Paint
event:
private void zoomPBox_Paint(object sender, PaintEventArgs e)
{
Size sz = zoomPanel.ClientSize;
int x = sz.Width / 2 - zoomPBox.Left;
int y = sz.Height / 2 - zoomPBox.Top;
e.Graphics.DrawLine(Pens.LightGray, 0, y, zoomPBox.Width, y);
e.Graphics.DrawLine(Pens.LightGray, x, 0, x, zoomPBox.Height);
}
Now for the setup routine:
void setupZoomBox(Chart chart, PictureBox pbox, float zoom)
{
ChartArea ca = chart.ChartAreas[0];
Size sz = chart.ClientSize;
Size szi = new Size(round(sz.Width * zoom), round(sz.Height * zoom));
Bitmap bmp2 = null;
chart.Refresh();
// original plot area
Rectangle pao = Rectangle.Round(InnerPlotPositionClientRectangle(chart, ca));
float ro = 1f * (pao.Width+2) / (pao.Height+2); // original aspect ratio
chart.ClientSize = szi;
chart.Refresh(); // enforce immediate layout
// zoomed plot area
Rectangle paz = Rectangle.Round(InnerPlotPositionClientRectangle(chart, ca));
float rz = 1f * paz.Width / paz.Height; // zoomed aspect ratio
// target rectangle, same aspect ratio as unzoomed area
int th = paz.Height;
int tw = round(paz.Height * ro );
// if (ro > rz)
//tw = round(th * ro); //else th = round(tw / ro);
Rectangle tgtR = new Rectangle(0, 0, tw, th);
// bitmap to hold only the zoomed inner plot area
bmp2 = new Bitmap(tgtR.Width, tgtR.Height);
// source area: Only the inner plot area plus 1 line of axis pixels:
Rectangle srcR = Rectangle.Round(
new RectangleF(paz.X - 1, paz.Y - 1, paz.Width + 2, paz.Height + 2));
// bitmap to hold the whole zoomed chart:
using (Bitmap bmp = new Bitmap(szi.Width, szi.Height))
{
Rectangle drawR = new Rectangle(0, 0, szi.Width, szi.Height);
chart.DrawToBitmap(bmp, drawR); // screenshot
using (Graphics g = Graphics.FromImage(bmp2)) // crop stretched
g.DrawImage(bmp, tgtR, srcR, GraphicsUnit.Pixel);
}
chart.ClientSize = sz; // reset chart
// you should dispose of the old Image if there is one before setting the new one!!
pbox.Image = bmp2;
pbox.ClientSize = bmp2.Size;
}
At a few spots I need to get the pixel size of the so called InnerPlotPosition
; (ElementPosition
in a MSChart
includes Location
and Size
in percentages of the respective container area.) I use functions I have posted before, e.g. here.
Upvotes: 1