3

My data value can vary between 0-100. I would like to display a JFreeChart DialPlot showing the range 0-30, where values larger than 30 are displayed by having the needle fixed at 30 but the true value displayed on the dial.

The image below shows what my example code currently produces:

Current Output

dial example

Here I am displaying the value 50. The dial has wrapped around to point at 14. I would prefer it to be set to the maximum (30), much like with a fuel dial:

Desired Output

enter image description here

Is this possible with JFreeChart? SSCCE code below.

public class DemoChartProblem {

  private final DefaultValueDataset dataset = new DefaultValueDataset(50);
  private final JFrame frame = new JFrame();

  public static void main(String[] args) throws Exception {
    new DemoChartProblem();
  }

  public DemoChartProblem() {
    frame.setPreferredSize(new Dimension(300, 300));
    frame.add(buildDialPlot(0, 30, 5));
    frame.pack();
    frame.setLocationRelativeTo(null);
    frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

    SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {
        frame.setVisible(true);
      }
    });
  }

  private ChartPanel buildDialPlot(int minimumValue, int maximumValue,
      int majorTickGap) {

    DialPlot plot = new DialPlot(dataset);
    plot.setDialFrame(new StandardDialFrame());
    plot.addLayer(new DialValueIndicator(0));
    plot.addLayer(new DialPointer.Pointer());

    StandardDialScale scale = new StandardDialScale(minimumValue, maximumValue,
        -120, -300, majorTickGap, majorTickGap - 1);
    scale.setTickRadius(0.88);
    scale.setTickLabelOffset(0.20);
    plot.addScale(0, scale);

    return new ChartPanel(new JFreeChart(plot));
  }
}
Duncan Jones
  • 67,400
  • 29
  • 193
  • 254

2 Answers2

4

I would be interested to hear if there are better methods.

The disparity between the DialValueIndicator and the maximumValue may be confusing. As an alternative, signify distinct ranges using StandardDialRange:

int redLine = 3 * maximumValue / 5;
plot.addLayer(new StandardDialRange(minimumValue, redLine, Color.blue));
plot.addLayer(new StandardDialRange(redLine, maximumValue, Color.red));

Setting the frame's preferred size is problematic. Instead, override the getPreferredSize() method of ChartPanel:

return new ChartPanel(new JFreeChart(plot)) {
    @Override
    public Dimension getPreferredSize() {
        return new Dimension(300, 300);
    }
};

test image

Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • Thank you for the answer. In my real application, I have included range colours in a similar fashion to highlight the extreme end of the dial. I removed them from the SSCCE because they seemed irrelevant at the time. And you are quite correct about the preffered size comment - I was lazy in my SSCCE :-) – Duncan Jones Apr 24 '13 at 06:32
  • Regarding the disparity, it's true that it may cause confusion. However, I think my options are limited because my real data range could be substantially larger than I suggested in my simplified question. Yet, the standard operating range is as suggested (0-30). It's not an easy situation to present visually to the user. – Duncan Jones Apr 24 '13 at 06:37
  • @DuncanJones: That makes sense. You might look at some other visual indication that it's an intentional feature and not an anomaly. Maybe a different color for the `DialValueIndicator` or one of the `org.jfree.chart.annotations`? – trashgod Apr 24 '13 at 11:19
  • 1
    @trashgod Good idea. I make make the needle red, or similar. – Duncan Jones Apr 24 '13 at 11:39
1

I've just figured out a work-around answer, but I would be interested to hear if there are better methods.

In this solution, I maintain two DefaultValueDataset objects. One contains the real value and one contains a constrained value, no larger than my dial limit. The needle is associated with the constrained set and the dial value is linked to the real value.

public class DemoChartProblem {

  private static final int DISPLAY_MAX = 30;
  private final DefaultValueDataset dataset = new DefaultValueDataset();
  private final DefaultValueDataset displayDataset = new DefaultValueDataset();
  private final JFrame frame = new JFrame();

  public static void main(String[] args) throws Exception {
    new DemoChartProblem();
  }

  public DemoChartProblem() {
    frame.setPreferredSize(new Dimension(300, 300));
    frame.add(buildDialPlot(0, DISPLAY_MAX, 5));
    frame.pack();
    frame.setLocationRelativeTo(null);
    frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

    setValue(50);

    SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {
        frame.setVisible(true);
      }
    });
  }

  private void setValue(int value) {
    dataset.setValue(value);
    displayDataset.setValue(Math.min(DISPLAY_MAX, value));
  }

  private ChartPanel buildDialPlot(int minimumValue, int maximumValue,
      int majorTickGap) {

    DialPlot plot = new DialPlot();
    plot.setDataset(0, dataset);
    plot.setDataset(1, displayDataset);

    plot.setDialFrame(new StandardDialFrame());

    // value indicator uses the real data set
    plot.addLayer(new DialValueIndicator(0));

    // needle uses constrained data set
    plot.addLayer(new DialPointer.Pointer(1));

    StandardDialScale scale = new StandardDialScale(minimumValue, maximumValue,
        -120, -300, majorTickGap, majorTickGap - 1);
    scale.setTickRadius(0.88);
    scale.setTickLabelOffset(0.20);
    plot.addScale(0, scale);

    return new ChartPanel(new JFreeChart(plot));
  }
}
Duncan Jones
  • 67,400
  • 29
  • 193
  • 254
  • As my work-around is currently the only way I've found to achieve the original goal, I've marked this as the answer. – Duncan Jones Apr 25 '13 at 08:14