user2587
user2587

Reputation: 149

To create a zoom picturebox for mschart

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

Answers (2)

user2587
user2587

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: Using chart control for zoom

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

TaW
TaW

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:

enter image description here

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

Related Questions