Showing posts with label Magnifier. Show all posts
Showing posts with label Magnifier. Show all posts

Tuesday, January 13, 2009

JFreeChart and JXLayer Adjustable Magnifier

JXLayer is cool. I wrote about Dave Gilbert's demo applet that uses JXLayer and Piet Blok's work to show a magnifying glass over a JFreeChart. Since then Piet has updated his MagnifierUI code and I extended it into a more configurable magnifying glass. In the applet below, you should be able to use your mouse wheel to adjust the magnification factor of the magnifying glass. Also, while holding down the Control and Shift keys, the mouse wheel should adjust the size of the magnifying glass.

Note: Windows users may need to right-click on the chart to enable the mouse wheel adjustments. For some reason, the popup menu that appears after a right-click causes the applet to start listening to mouse wheel events. This is most noticeable when dragging the applet out of the browser and then closing the dragged-out applet and returning to the browser window. This problem does not occur on Linux.

The code is pretty straightforward. I created a subclass of MagnifierUI called AdjustableMagnifierUI. In it I override the processMouseWheelEvent method. If the Control and Shift keys are pressed on a mouse wheel event, the setMagnifyingFactor method in MagnifierUI is called. If no modifier keys are pressed, the setRadius method is called.

The Windows hack is in the applet class. I override setVisible as follows:
public void setVisible(boolean visible) {
  super.setVisible(visible);
  if (isShowing()) {
    // simulate right-mouse click to bring-up popup menu
    chartPanel.mousePressed(new MouseEvent(chartPanel,
        0, 0,
        java.awt.event.InputEvent.BUTTON3_DOWN_MASK,
        0, 0, 1, true));
    // immediately hide the popup menu
    chartPanel.getPopupMenu().setVisible(false);
  }
}
If the applet is showing, I simulate a right-mouse click to bring-up JFreeChart's popup menu. Then the popup is immediately hidden. This enables the mouse wheel events to be handled correctly in Windows XP SP3. This hack only works when setVisible is called. If the user has Java SE 6 Update 10 or later and he drags the applet out of the browser and then closes the dragged-out window, the applet returns to its original spot in the browser window WITHOUT calling setVisible. In this case, mouse wheel events will not cause any magnifier adjustments UNTIL the user right-clicks and shows the popup menu. Dismissing the popup will restore the desired mouse wheel behavior. Does anyone know why this is or have a better solution? This hack is not needed on Ubuntu Linux 8.04.

I ran into one other problem when posting the applet. In order to avoid an AccessControlException, I had to override the isAWTEventListenerEnabled() method to return false in AdjustableMagnifierUI:
@Override
protected boolean isAWTEventListenerEnabled() {
  return false;
}
I didn't notice any problems from overriding this method. Or perhaps this is the cause of the Windows misbehavior? I would appreciate any feedback on this.

To build the applet, you will need these:
  1. My Code: tar.gz or zip
  2. JXLayer library (I use version 3.0)
  3. JFreeChart library (I use version 1.0.12)
  4. JFree JCommon library (I use version 1.0.15)
The first code link above is a modified subset of Piet Blok's generic ZoomUI released on January 11th, 2009.


Monday, December 22, 2008

JXLayer with JFreeChart Demo Application (Not Applet)

I wanted to run Dave Gilbert's JXLayer demo applet that uses JFreeChart. But my 32-bit Intel Mac won't run applets in Java 1.6. I do have Landon Fuller's SoyLatte implementation of Java 1.6 installed. So I converted the applet into an application and can run it from Eclipse. To run the demo in an application, first download the original zip file from Dave Gilbert's blog. (You may also need to download and rename the jxlayer.jar file if it is not in the zip file's lib directory.) You will replace the file JXLayerAppletDemo1.java with the file JXLayerApplicationDemo.java below. Create a new Java project in Eclipse with the updated source file. In the project properties, add these 3 libraries to the external jars:
jcommon-1.0.14.jar
jfreechart-1.0.11.jar
jxlayer.jar


JXLayerApplicationDemo.java:
/* -----------------------
 * JXLayerAppletDemo1.java
 * -----------------------
 * (C) Copyright 2008, by Object Refinery Limited.
 */

package demo.jxlayer;

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.Ellipse2D;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;

//import javax.swing.JApplet;
import javax.swing.JFrame;
import javax.swing.JPanel;

import org.jdesktop.jxlayer.JXLayer;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.annotations.XYDrawableAnnotation;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.DateTickUnit;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.TickUnitSource;
import org.jfree.chart.axis.TickUnits;
import org.jfree.chart.labels.StandardXYToolTipGenerator;
import org.jfree.chart.plot.PiePlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.data.general.DefaultPieDataset;
import org.jfree.data.time.Month;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.data.time.Year;
import org.jfree.data.xy.XYDataset;
import org.jfree.ui.RectangleInsets;

/**
 * An applet that shows a chart with a magnifying glass.
 */
//public class JXLayerAppletDemo1 extends JApplet {
public class JXLayerApplicationDemo extends JPanel {

  /**
   * Instantiate main frame and set visible
   */
  public static void main (String args[]) {
    final JXLayerApplicationDemo app = new JXLayerApplicationDemo();
    JFrame f = new JFrame("JFreeChart Magnifying Glass");
    f.addWindowListener (
        new WindowAdapter() {
          @Override
          public void windowClosing(WindowEvent e) {
            System.exit(0);
          }
        }
    );

    f.add(app, BorderLayout.CENTER);
    f.pack();
    f.setVisible(true);
  }


  /**
   * Constructs the demo application.
   */
  public JXLayerApplicationDemo() {
    super();
    XYDataset dataset = createDataset();
    JFreeChart chart = createChart(dataset);
    ChartPanel chartPanel = new ChartPanel(chart);
    JXLayer layer = new JXLayer(chartPanel);
    MagnifierUI ui1 = new MagnifierUI();
    layer.setUI(ui1);
    chartPanel.setPreferredSize(new java.awt.Dimension(750, 390));
    chartPanel.setPopupMenu(null);
    //setContentPane(layer);
    add(layer);
  }

  /**
   * Creates a sample chart.
   *
   * @param dataset  a dataset for the chart.
   *
   * @return A sample chart.
   */
  private static JFreeChart createChart(XYDataset dataset) {
    JFreeChart chart = ChartFactory.createTimeSeriesChart(
        "JFreeChart and JXLayer",
        null, "$ million", dataset,
        true, true, false);
    XYPlot plot = (XYPlot) chart.getPlot();
    DateAxis xAxis = (DateAxis) plot.getDomainAxis();
    xAxis.setLowerMargin(0.2);
    xAxis.setUpperMargin(0.2);
    xAxis.setStandardTickUnits(createStandardDateTickUnits());

    NumberAxis yAxis = (NumberAxis) plot.getRangeAxis();
    yAxis.setLowerMargin(0.2);
    yAxis.setUpperMargin(0.2);

    XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer();
    renderer.setBaseShapesVisible(true);
    renderer.setBaseLinesVisible(true);
    renderer.setSeriesShape(0, new Ellipse2D.Double(-5.0, -5.0, 10.0, 10.0));
    renderer.setSeriesShape(1, new Ellipse2D.Double(-5.0, -5.0, 10.0, 10.0));
    renderer.setSeriesStroke(0, new BasicStroke(3.0f));
    renderer.setSeriesStroke(1, new BasicStroke(3.0f, BasicStroke.CAP_ROUND,
        BasicStroke.JOIN_ROUND, 5.0f, new float[] {5.0f, 4.0f}, 0.0f));
    renderer.setSeriesFillPaint(0, Color.white);
    renderer.setSeriesFillPaint(1, Color.white);
    renderer.setUseFillPaint(true);

    renderer.setBaseToolTipGenerator(new StandardXYToolTipGenerator());
    renderer.setDefaultEntityRadius(6);

    renderer.addAnnotation(new XYDrawableAnnotation(
        new Month(4, 2005).getFirstMillisecond(), 600, 180, 100, 3.0,
        createPieChart()));
    renderer.addAnnotation(new XYDrawableAnnotation(
        new Month(9, 2007).getFirstMillisecond(), 1250, 120, 100, 2.0,
        createBarChart()));
    renderer.setBaseToolTipGenerator(
        new StandardXYToolTipGenerator("{0} = ({1}, {2})",
            new SimpleDateFormat("yyyy"),
            new DecimalFormat("$#,##0.00")));

    plot.setRenderer(renderer);
    return chart;
  }

  /**
   * Creates a sample dataset.
   *
   * @return A dataset.
   */
  private static XYDataset createDataset() {
    TimeSeries series1 = new TimeSeries("Division A", Year.class);
    series1.add(new Year(2005), 1520);
    series1.add(new Year(2006), 1132);
    series1.add(new Year(2007), 450);
    series1.add(new Year(2008), 620);
    TimeSeries series2 = new TimeSeries("Division B", Year.class);
    series2.add(new Year(2005), 1200);
    series2.add(new Year(2006), 1300);
    series2.add(new Year(2007), 640);
    series2.add(new Year(2008), 520);
    TimeSeriesCollection dataset = new TimeSeriesCollection();
    dataset.addSeries(series1);
    dataset.addSeries(series2);
    return dataset;
  }

  private static JFreeChart createPieChart() {
    DefaultPieDataset dataset = new DefaultPieDataset();
    dataset.setValue("Engineering", 43.2);
    dataset.setValue("Research", 13.2);
    dataset.setValue("Advertising", 20.9);
    PiePlot plot = new PiePlot(dataset);
    plot.setBackgroundPaint(null);
    plot.setOutlinePaint(null);
    plot.setBaseSectionOutlinePaint(Color.white);
    plot.setBaseSectionOutlineStroke(new BasicStroke(2.0f));
    plot.setLabelFont(new Font("Dialog", Font.PLAIN, 18));
    plot.setMaximumLabelWidth(0.25);
    JFreeChart chart = new JFreeChart(plot);
    chart.setBackgroundPaint(null);
    chart.removeLegend();
    chart.setPadding(RectangleInsets.ZERO_INSETS);
    return chart;
  }

  private static JFreeChart createBarChart() {
    DefaultCategoryDataset dataset = new DefaultCategoryDataset();
    dataset.addValue(10.0, "R1", "Q1");
    dataset.addValue(7.0, "R1", "Q2");
    dataset.addValue(8.0, "R1", "Q3");
    dataset.addValue(4.0, "R1", "Q4");
    dataset.addValue(10.6, "R2", "Q1");
    dataset.addValue(6.1, "R2", "Q2");
    dataset.addValue(8.5, "R2", "Q3");
    dataset.addValue(4.3, "R2", "Q4");
    JFreeChart chart = ChartFactory.createBarChart("Sales 2008", null,
        null, dataset, PlotOrientation.VERTICAL, false, false, false);
    chart.setBackgroundPaint(null);
    chart.getPlot().setBackgroundPaint(new Color(200, 200, 255, 60));
    return chart;
  }

  private static TickUnitSource createStandardDateTickUnits() {
    TickUnits units = new TickUnits();
    DateFormat df = new SimpleDateFormat("yyyy");
    units.add(new DateTickUnit(DateTickUnit.YEAR, 1,
        DateTickUnit.YEAR, 1, df));
    units.add(new DateTickUnit(DateTickUnit.YEAR, 2,
        DateTickUnit.YEAR, 1, df));
    units.add(new DateTickUnit(DateTickUnit.YEAR, 5,
        DateTickUnit.YEAR, 5, df));
    return units;
  }

}


Alternatively, the java files can be compiled on the command line. First, navigate to the source directory within the expanded zip archive and type:
javac -classpath .:../lib/jfreechart-1.0.11.jar:../lib/jxlayer.jar:../lib/jcommon-1.0.14.jar demo/jxlayer/*.java

Run the application from the source directory with this command:
java -classpath .:../lib/jfreechart-1.0.11.jar:../lib/jxlayer.jar:../lib/jcommon-1.0.14.jar demo.jxlayer.JXLayerApplicationDemo

The code above gives an example of how to convert an applet to an application. These three steps are necessary:
  • Extend JPanel instead of JApplet.
  • Provide a main method to instantiate the application and place it in a visible JFrame.
  • Call add instead of setContentPane in the constructor.