ChartDirector 6.1 (Java Edition)

XY Zooming and Scrolling (Windows)




NOTE: This section describes XY Zooming and Scrolling for Windows applications only. For web applications, please refer to XY Zooming and Scrolling (Web).

This example demonstrates zooming and scrolling in both horizontal and vertical directions. In addition to using mouse click and drag, this example demonstrates using a slider, the mouse wheel and a ViewPortControl to control scrolling and zooming. This example also includes a crosshair track cursor with dynamic labels on the x-axis and y-axis showing the mouse cursor position, and an image map for data point tooltips. A save button is included to save the chart in PNG, JPG, GIF, BMP, SVG or PDF.

The main source code listing of this sample program is included at the end of this section. The code consists of the following main parts:

Source Code Listing

[Java Version] javademo/xyzoomscroll.java
import ChartDirector.*;
import java.util.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.filechooser.*;


public class xyzoomscroll implements DemoModule
{
    //
    // The main method to allow this demo to run as a standalone program.
    //
    public static void main(String args[]) 
    {
        new xyzoomscrollDialog().setVisible(true);
        System.exit(0); 
    } 
    
    //
    // Implementation of the DemoModule interface to allow this demo to run inside the 
    // ChartDirectorDemo browser
    //
    
    // Name of demo program
    public String toString() 
    { 
        return "XY Zooming and Scrolling"; 
    }

    // Number of charts produced in this demo
    public int getNoOfCharts() 
    { 
        // This demo open its own dialog instead of using the right pane of the ChartDirectorDemo 
        // browser for display, so we just display the dialog, and return 0.
        new xyzoomscrollDialog().setVisible(true);
        return 0; 
    }

    // Main code for creating charts
    public void createChart(ChartViewer viewer, int index)
    {
        // do nothing, as the ChartDirectorDemo browser right pane is not used
    }
}


class xyzoomscrollDialog extends JDialog
{
    // XY data points for the chart
    private double[] dataX0;
    private double[] dataY0;
    private double[] dataX1;
    private double[] dataY1;
    private double[] dataX2;
    private double[] dataY2;
   
    //
    // Controls
    //
    private ChartViewer chartViewer1;
    private JButton pointerPB;
    private JButton zoomInPB;
    private JButton zoomOutPB;
    private JSlider zoomBar;
    private JFileChooser saveDialog;
   
    //
    // Constructor
    //       
    xyzoomscrollDialog()
    {    
        // Use DISPOSE_ON_CLOSE to avoid mmeory leak, and set dialog to modal and non-resizable
        setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
        setModal(true);
        setResizable(false);

        // Set title to name of this demo program
        setTitle("XY Zooming and Scrolling");

        // Font to use for user interface elements
        Font uiFont = new Font("Dialog", Font.PLAIN, 11);

        // Top label bar
        JLabel topLabel = new JLabel("Advanced Software Engineering");
        topLabel.setForeground(new java.awt.Color(255, 255, 51));
        topLabel.setBackground(new java.awt.Color(0, 0, 128));
        topLabel.setBorder(new javax.swing.border.EmptyBorder(2, 0, 2, 5));
        topLabel.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT);
        topLabel.setOpaque(true);
        getContentPane().add(topLabel, java.awt.BorderLayout.NORTH);

        // Left panel
        JPanel leftPanel = new JPanel(null);
        leftPanel.setBorder(javax.swing.BorderFactory.createRaisedBevelBorder());

        // Pointer push button
        pointerPB = new JButton("Pointer", loadImageIcon("pointer.gif"));
        pointerPB.setHorizontalAlignment(SwingConstants.LEFT);
        pointerPB.setMargin(new Insets(5, 5, 5, 5));
        pointerPB.addActionListener(new ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                pointerPB_Clicked();
            }});
        leftPanel.add(pointerPB).setBounds(1, 0, 118, 24);
        
        // Zoom In push button
        zoomInPB = new JButton("Zoom In", loadImageIcon("zoomin.gif"));
        zoomInPB.setHorizontalAlignment(SwingConstants.LEFT);
        zoomInPB.setMargin(new Insets(5, 5, 5, 5));
        zoomInPB.addActionListener(new ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                zoomInPB_Clicked();
            }});        
        leftPanel.add(zoomInPB).setBounds(1, 24, 118, 24);

        // Zoom out push button
        zoomOutPB = new JButton("Zoom Out", loadImageIcon("zoomout.gif"));
        zoomOutPB.setHorizontalAlignment(SwingConstants.LEFT);
        zoomOutPB.setMargin(new Insets(5, 5, 5, 5));
        zoomOutPB.addActionListener(new ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                zoomOutPB_Clicked();
            }});        
        leftPanel.add(zoomOutPB).setBounds(1, 48, 118, 24);

        // Save push button
        JButton savePB = new JButton("Save", loadImageIcon("save.gif"));
        savePB.setHorizontalAlignment(SwingConstants.LEFT);
        savePB.setMargin(new Insets(5, 5, 5, 5));
        savePB.addActionListener(new ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                savePB_Clicked();
            }});        
        leftPanel.add(savePB).setBounds(1, 96, 118, 24);

        // Zoom level label
        JLabel zoomLabel = new JLabel("Zoom Level");
        zoomLabel.setHorizontalAlignment(SwingConstants.CENTER);
        leftPanel.add(zoomLabel).setBounds(5, 220, 108, 20);
        
        // Zoom level bar
        zoomBar = new JSlider(1, 100, 50);
        zoomBar.addChangeListener(new javax.swing.event.ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent e) {
                zoomBar_ValueChanged(e);
            }
        });
        leftPanel.add(zoomBar).setBounds(5, 240, 110, 20);        

        // ViewPortControl - use transparent black for region outside the viewport, that is, 
        // to darken the outside region.
        ViewPortControl vpControl = new ViewPortControl();
        vpControl.setViewPortExternalColor(new Color(0, 0, 0, 127));
        vpControl.setViewPortBorderColor(new Color(255, 255, 255, 127));
        vpControl.setSelectionBorderColor(new Color(255, 255, 255, 127)); 
        leftPanel.add(vpControl).setBounds(4, 310, 110, 110);

         // Total expected panel size
        leftPanel.setPreferredSize(new java.awt.Dimension(120, 440));
            
        // Chart Viewer - enabled 2D zooming and scrolling
        chartViewer1 = new ChartViewer();
        chartViewer1.setBackground(new java.awt.Color(0xc0, 0xc0, 0xff));
        chartViewer1.setOpaque(true);
        chartViewer1.setHotSpotCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
        chartViewer1.setScrollDirection(Chart.DirectionHorizontalVertical);
        chartViewer1.setZoomDirection(Chart.DirectionHorizontalVertical);
        chartViewer1.setMouseWheelZoomRatio(1.1);
        chartViewer1.setPreferredSize(new Dimension(500, 480));      
        chartViewer1.addViewPortListener(new ViewPortAdapter() {
            public void viewPortChanged(ViewPortChangedEvent e) {
                chartViewer1_ViewPortChanged(e);
            }
        });
        chartViewer1.addHotSpotListener(new HotSpotAdapter() {
            public void hotSpotClicked(HotSpotEvent e) {
                chartViewer1_HotSpotClicked(e);
        }});
        chartViewer1.addTrackCursorListener(new TrackCursorAdapter() {
            public void mouseMovedPlotArea(MouseEvent e) {
                chartViewer1_MouseMovedPlotArea(e);
            }
        });
         
        // Put the leftPanel and rightPanel on the content pane
        getContentPane().add(leftPanel, java.awt.BorderLayout.WEST);
        getContentPane().add(chartViewer1, java.awt.BorderLayout.CENTER);

        // Set all UI fonts (except labels)
        for (int i = 0; i < leftPanel.getComponentCount(); ++i)
        {
            Component c = leftPanel.getComponent(i);
            if (!(c instanceof JLabel))
                c.setFont(uiFont);
        }

        
        // Layout the window
        pack();
        
        //
        // At this point, the user interface layout has been completed. 
        // Can load data and plot chart now.
        //

        // Initially set the mouse usage to "Pointer" mode (Drag to Scroll mode)
        pointerPB.doClick();
        
        // Load the data
        loadData();
        
        // Trigger a view port update to draw chart.
        chartViewer1.updateViewPort(true, true);

        // Draw and display the full chart in the ViewPortControl
        drawFullChart(vpControl, chartViewer1);

        // Bind the ChartViewer to the ViewPortControl
        vpControl.setViewer(chartViewer1);
    }
   
    //
    // A utility to load an image icon from the Java class path
    //
    private ImageIcon loadImageIcon(String path)
    {
        try { return new ImageIcon(getClass().getClassLoader().getResource(path)); }
        catch (Exception e) { return null; }
    }
    
    //
    // Load the data
    //
    private void loadData()
    {
        //
        // For simplicity, in this demo, we just use hard coded data.
        //
        dataX0 = new double[] {10, 15, 6, -12, 14, -8, 13, -3, 16, 12, 10.5, -7, 3, -10, -5, 2, 5};
        dataY0 = new double[] {130, 150, 80, 110, -110, -105, -130, -15, -170, 125,  125, 60, 25, 
            150, 150, 15, 120};
        dataX1 = new double[] {6, 7, -4, 3.5, 7, 8, -9, -10, -12, 11, 8, -3, -2, 8, 4, -15, 15};
        dataY1 = new double[] {65, -40, -40, 45, -70, -80, 80, 10, -100, 105, 60, 50, 20, 170, -25,
            50, 75};
        dataX2 = new double[] {-10, -12, 11, 8, 6, 12, -4, 3.5, 7, 8, -9, 3, -13, 16, -7.5, -10,
            -15};
        dataY2 = new double[] {65, -80, -40, 45, -70, -80, 80, 90, -100, 105, 60, -75, -150, -40, 
            120, -50, -30};
    }
    
    //
    // The ViewPortChanged event handler. This event occurs if the user scrolls or zooms in
    // or out the chart by dragging or clicking on the chart. It can also be triggered by
    // calling WinChartViewer.updateViewPort.
    //
    private void chartViewer1_ViewPortChanged(ViewPortChangedEvent e)
    {
        // In addition to updating the chart, we may also need to update other controls that
        // changes based on the view port.
        updateControls(chartViewer1);
     
        // Update the chart if necessary
        if (e.needUpdateChart())
            drawChart(chartViewer1);
            
        // Update the image map if necessary
        if (e.needUpdateImageMap())
            updateImageMap(chartViewer1);
    }

    //
    // Update controls when the view port changed
    //
    private void updateControls(ChartViewer viewer)
    {
        // Synchronize the zoom bar value with the view port width/height
        zoomBar.setValue((int)Math.round(Math.min(chartViewer1.getViewPortWidth(), 
            chartViewer1.getViewPortHeight()) * zoomBar.getMaximum()));
    }

    //
    // Draw the chart.
    //
    private void drawChart(ChartViewer viewer)
    {
        // Create an XYChart object 500 x 480 pixels in size, with light blue (c0c0ff) as 
        // background color
        XYChart c = new XYChart(500, 480, 0xc0c0ff);
        
        // Re-cycle the resources of the existing chart, if any. This can improve performance 
        // by reducing the frequency of garbage collections.         
        c.recycle(chartViewer1.getChart());

        // Set the plotarea at (50, 40) and of size 400 x 400 pixels. Use light grey (c0c0c0)
        // horizontal and vertical grid lines. Set 4 quadrant coloring, where the colors of 
        // the quadrants alternate between lighter and deeper grey (dddddd/eeeeee). 
        c.setPlotArea(50, 40, 400, 400, -1, -1, -1, 0xc0c0c0, 0xc0c0c0
            ).set4QBgColor(0xdddddd, 0xeeeeee, 0xdddddd, 0xeeeeee, 0x000000);

        // Enable clipping mode to clip the part of the data that is outside the plot area.
        c.setClipping();

        // Set 4 quadrant mode, with both x and y axes symetrical around the origin
        c.setAxisAtOrigin(Chart.XYAxisAtOrigin, Chart.XAxisSymmetric + Chart.YAxisSymmetric);

        // Add a legend box at (450, 40) (top right corner of the chart) with vertical layout
        // and 8 pts Arial Bold font. Set the background color to semi-transparent grey.
        LegendBox legendBox = c.addLegend(450, 40, true, "arialbd.ttf", 8);
        legendBox.setAlignment(Chart.TopRight);
        legendBox.setBackground(0x40dddddd);

        // Add a titles to axes
        c.xAxis().setTitle("Alpha Index");
        c.yAxis().setTitle("Beta Index");

        // Set axes width to 2 pixels
        c.xAxis().setWidth(2);
        c.yAxis().setWidth(2);

        // The default ChartDirector settings has a denser y-axis grid spacing and less-dense
        // x-axis grid spacing. In this demo, we want the tick spacing to be symmetrical.
        // We use around 50 pixels between major ticks and 25 pixels between minor ticks.
        c.xAxis().setTickDensity(50, 25);
        c.yAxis().setTickDensity(50, 25);

        //
        // In this example, we represent the data by scatter points. If you want to represent
        // the data by somethings else (lines, bars, areas, floating boxes, etc), just modify
        // the code below to use the layer type of your choice. 
        //

        // Add scatter layer, using 11 pixels red (ff33333) X shape symbols
        c.addScatterLayer(dataX0, dataY0, "Group A", Chart.Cross2Shape(), 11, 0xff3333);

        // Add scatter layer, using 11 pixels green (33ff33) circle symbols
        c.addScatterLayer(dataX1, dataY1, "Group B", Chart.CircleShape, 11,    0x33ff33);

        // Add scatter layer, using 11 pixels blue (3333ff) triangle symbols
        c.addScatterLayer(dataX2, dataY2, "Group C", Chart.TriangleSymbol, 11, 0x3333ff);

        //
        // In this example, we have not explicitly configured the full x and y range. In this case, the
        // first time syncLinearAxisWithViewPort is called, ChartDirector will auto-scale the axis and
        // assume the resulting range is the full range. In subsequent calls, ChartDirector will set the
        // axis range based on the view port and the full range.
        //
        viewer.syncLinearAxisWithViewPort("x", c.xAxis());
        viewer.syncLinearAxisWithViewPort("y", c.yAxis());
        
        // We need to update the track line too. If the mouse is moving on the chart (eg. if 
        // the user drags the mouse on the chart to scroll it), the track line will be updated
        // in the MouseMovePlotArea event. Otherwise, we need to update the track line here.
        if ((!viewer.isInMouseMoveEvent()) && viewer.isMouseOnPlotArea())
            crossHair(c, viewer.getPlotAreaMouseX(), viewer.getPlotAreaMouseY());

        // Set the chart image to the ChartViewer
        chartViewer1.setChart(c);
    }

    //
    // Draw the full thumbnail chart and display it in the given ViewPortControl
    //
    private void drawFullChart(ViewPortControl vpc, ChartViewer viewer)
    {
        // Create an XYChart object of the same size as the Viewport Control
        XYChart c = new XYChart(vpc.getWidth(), vpc.getHeight());

        // Set the plotarea to cover the entire chart. Disable grid lines by setting their colors
        // to transparent. Set 4 quadrant coloring, where the colors of the quadrants alternate 
        // between lighter and deeper grey (dddddd/eeeeee). 
        c.setPlotArea(0, 0, c.getWidth() - 1, c.getHeight() - 1, -1, -1, 0xff0000, Chart.Transparent, 
            Chart.Transparent).set4QBgColor(0xdddddd, 0xeeeeee, 0xdddddd, 0xeeeeee, 0x000000);

        // Set 4 quadrant mode, with both x and y axes symetrical around the origin
        c.setAxisAtOrigin(Chart.XYAxisAtOrigin, Chart.XAxisSymmetric + Chart.YAxisSymmetric);

        // The x and y axis scales reflect the full range of the view port
        c.xAxis().setLinearScale(viewer.getValueAtViewPort("x", 0), viewer.getValueAtViewPort("x", 1),
            Chart.NoValue);
        c.yAxis().setLinearScale(viewer.getValueAtViewPort("y", 0), viewer.getValueAtViewPort("y", 1),
            Chart.NoValue);

        // Add scatter layer, using 3 pixels red (ff33333) X shape symbols
        c.addScatterLayer(dataX0, dataY0, "Group A", Chart.Cross2Shape(), 3, 0xff3333, 0xff3333);

        // Add scatter layer, using 3 pixels green (33ff33) circle symbols
        c.addScatterLayer(dataX1, dataY1, "Group B", Chart.CircleShape, 3, 0x33ff33, 0x33ff33);

        // Add scatter layer, using 3 pixels blue (3333ff) triangle symbols
        c.addScatterLayer(dataX2, dataY2, "Group C", Chart.TriangleSymbol, 3, 0x3333ff, 0x3333ff);

        // Set the chart image to the WinChartViewer
        vpc.setChart(c);
    }
    
    //
    // Update the image map used on the chart.
    //
    private void updateImageMap(ChartViewer viewer)
    {
        // Include tool tip for the chart
        if (viewer.getImageMap() == null)
        {
            viewer.setImageMap(viewer.getChart().getHTMLImageMap("clickable", "",
                "title='[{dataSetName}] Alpha = {x}, Beta = {value}'"));
        }
    }
    
    //
    // Handler when a hot spot is clicked. In this demo, we just list out the hot spot parameters
    // in a pop up dialog.
    //
    private void chartViewer1_HotSpotClicked(HotSpotEvent e)
    {
        // We show the pop up dialog only when the mouse action is not in zoom-in or zoom-out mode.
        if ((chartViewer1.getMouseUsage() != Chart.MouseUsageZoomIn) && 
            (chartViewer1.getMouseUsage() != Chart.MouseUsageZoomOut))
            showHotSpotParam(e);
    }
    
    //
    // Utility to list out all hot spot parameters on a pop-up dialog
    //
    private void showHotSpotParam(HotSpotEvent e)
    {
        // Get all parameters and sort them by key
        Hashtable parameters = e.getAttrValues();
        Object[] attributes = parameters.keySet().toArray();
        Arrays.sort(attributes);
    
        // Create a JTable to show the hot spot attribute/value pairs
        Object[][] rows = new Object[parameters.size()][2];
        for (int i = 0; i < attributes.length; ++i)
        {
            rows[i][0] = attributes[i];
            rows[i][1] = parameters.get(attributes[i]);
        }
        JTable table = new JTable(rows, new Object[] {"Parameter", "Value"});

        // Show the table in a dialog
        JDialog d = new JDialog(this, "Hot Spot Parameters", true);
        d.setSize(300, 300);
        Container container = d.getContentPane();

        // Just add some descriptive text to the dialg
        JTextArea t = new JTextArea ("This dialog is for demonstration only." +
            " In this demo, we simply list out all hot spot parameters.");
        t.setLineWrap(true);
        t.setWrapStyleWord(true);
        t.setEditable(false);
        t.setOpaque(false);
        t.setMargin(new Insets(5, 5, 5, 5));
        container.add(t, BorderLayout.NORTH);
    
        // Create the scroll pane on the dialog and add the table to it.
        JScrollPane scrollPane = new JScrollPane(table);
        container.add(scrollPane);

        // Show the dialog on where the mouse click occur    
        Point topLeft = ((ChartViewer)e.getSource()).getLocationOnScreen();
        d.setLocation(topLeft.x + e.getX(), topLeft.y + e.getY());
        d.setVisible(true);
    }
        
    //
    // Pointer (Drag to Scroll) button event handler
    //
    private void pointerPB_Clicked()
    {
        pointerPB.setBackground(new Color(0x80, 0xff, 0x80));
        zoomInPB.setBackground(null);
        zoomOutPB.setBackground(null);
        chartViewer1.setMouseUsage(Chart.MouseUsageScrollOnDrag);
    }

    //
    // Zoom In button event handler
    //
    private void zoomInPB_Clicked()
    {
        pointerPB.setBackground(null);
        zoomInPB.setBackground(new Color(0x80, 0xff, 0x80));
        zoomOutPB.setBackground(null);
        chartViewer1.setMouseUsage(Chart.MouseUsageZoomIn);
    }

    //
    // Zoom Out button event handler
    //
    private void zoomOutPB_Clicked()
    {
        pointerPB.setBackground(null);
        zoomInPB.setBackground(null);
        zoomOutPB.setBackground(new Color(0x80, 0xff, 0x80));
        chartViewer1.setMouseUsage(Chart.MouseUsageZoomOut);
    }
    
    //
    // A utility class to be used with JFileChooser to filter files with certain extensions.
    // This is to maintain compatibility with older versions of Java that does not built-in
    // extension filtering class.
    //
    private static class SimpleExtensionFilter extends FileFilter 
    {
        public String ext;
        public SimpleExtensionFilter(String extension) { this.ext = "." + extension; }
        public String getDescription() { return ext.substring(1);    }
        public boolean accept(java.io.File file) 
        { return file.isDirectory() || file.getName().endsWith(ext); }
    }

    //
    // Save button event handler
    //
    private void savePB_Clicked()
    {
        String[] extensions = { "png", "jpg", "gif", "bmp", "svg", "pdf" };

        // The File Save dialog
        if (null == saveDialog)
        {
            saveDialog = new JFileChooser();
            for (int i = 0; i < extensions.length; ++i)
                saveDialog.addChoosableFileFilter(new SimpleExtensionFilter(extensions[i]));        
            saveDialog.setAcceptAllFileFilterUsed(false);
            saveDialog.setFileFilter(saveDialog.getChoosableFileFilters()[0]);
            saveDialog.setSelectedFile(new java.io.File("chartdirector_demo"));
        }
        
        int status = saveDialog.showSaveDialog(null);
        if ((status == JFileChooser.APPROVE_OPTION) && (null != chartViewer1.getChart()))
        {
            // Add extension if the pathName does not already have one
            String pathName = saveDialog.getSelectedFile().getAbsolutePath();
            boolean hasExtension = false;
            for (int i = 0; i < extensions.length; ++i)
                if (hasExtension = pathName.endsWith("." + extensions[i]))
                    break;
            if ((!hasExtension) && (saveDialog.getFileFilter() instanceof SimpleExtensionFilter))
                pathName += ((SimpleExtensionFilter)saveDialog.getFileFilter()).ext;
            
            // Issue an overwrite confirmation dialog if the file already exists
            if (new java.io.File(pathName).exists())
            {
                if (JOptionPane.YES_OPTION != JOptionPane.showOptionDialog(this, 
                    "File \"" + pathName + "\" already exists, confirm overwrite?", 
                    "Existing File - Confirm Overwrite", 
                    JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, 
                    null, new String[] { "Yes", "No" }, "No"))
                    return;    
            }

            chartViewer1.getChart().makeChart(pathName);                    
        }
    }
    
    //
    // ValueChanged event handler for zoomBar. Zoom in around the center point and try to 
    // maintain the aspect ratio
    //
    private void zoomBar_ValueChanged(javax.swing.event.ChangeEvent e)
    {
        if (!chartViewer1.isInViewPortChangedEvent())
        {
            // Remember the center point
            double centerX = chartViewer1.getViewPortLeft() + chartViewer1.getViewPortWidth() / 2;
            double centerY = chartViewer1.getViewPortTop() + chartViewer1.getViewPortHeight() / 2;

            // Aspect ratio and zoom factor
            double aspectRatio = chartViewer1.getViewPortWidth() / chartViewer1.getViewPortHeight();
            double zoomTo = ((double)zoomBar.getValue()) / zoomBar.getMaximum();

            // Zoom while respecting the aspect ratio
            chartViewer1.setViewPortWidth(zoomTo * Math.max(1, aspectRatio));
            chartViewer1.setViewPortHeight(zoomTo * Math.max(1, 1 / aspectRatio));
            
            // Adjust ViewPortLeft and ViewPortTop to keep center point unchanged
            chartViewer1.setViewPortLeft(centerX - chartViewer1.getViewPortWidth() / 2);
            chartViewer1.setViewPortTop(centerY - chartViewer1.getViewPortHeight() / 2);
                        
            // Update the chart, but no need to update the image map yet, as the chart is still 
            // zooming and is unstable
            chartViewer1.updateViewPort(true, false);
        }
    }
    
    //
    // Draw track cursor when mouse is moving over plotarea
    //
    private void chartViewer1_MouseMovedPlotArea(MouseEvent e)
    {
        ChartViewer viewer = (ChartViewer)e.getSource();

        // Draw crosshair track cursor
        crossHair((XYChart)viewer.getChart(), viewer.getPlotAreaMouseX(), viewer.getPlotAreaMouseY());
        viewer.updateDisplay();

        // Hide the track cursor when the mouse leaves the plot area
        viewer.removeDynamicLayer("MouseExitedPlotArea");
        
        // Update image map if necessary. If the mouse is still dragging, the chart is still 
        // updating and not confirmed, so there is no need to set up the image map.
        if (viewer.isMouseDragging())
            updateImageMap(viewer);
    }

    //
    // Draw cross hair cursor with axis labels
    //
    private void crossHair(XYChart c, int mouseX, int mouseY)
    {
        // Clear the current dynamic layer and get the DrawArea object to draw on it.
        DrawArea d = c.initDynamicLayer();

        // The plot area object
        PlotArea plotArea = c.getPlotArea();

        // Draw a vertical line and a horizontal line as the cross hair
        d.vline(plotArea.getTopY(), plotArea.getBottomY(), mouseX, d.dashLineColor(0x000000, 0x0101));
        d.hline(plotArea.getLeftX(), plotArea.getRightX(), mouseY, d.dashLineColor(0x000000, 0x0101));

        // Draw y-axis label
        String label = "<*block,bgColor=FFFFDD,margin=3,edgeColor=000000*>" + c.formatValue(c.getYValue(
            mouseY, c.yAxis()), "{value|P4}") + "<*/*>";
        TTFText t = d.text(label, "Arial Bold", 8);
        t.draw(plotArea.getLeftX() - 5, mouseY, 0x000000, Chart.Right);

        // Draw x-axis label
        label = "<*block,bgColor=FFFFDD,margin=3,edgeColor=000000*>" + c.formatValue(c.getXValue(mouseX),
            "{value|P4}") + "<*/*>";
        t = d.text(label, "Arial Bold", 8);
        t.draw(mouseX, plotArea.getBottomY() + 5, 0x000000, Chart.Top);
    }
}