If the range of z-values of the XYZDataset changes, it can be necessary to adjust the range of the ValueAxis of the PaintScaleLegend. The range of the PaintScale might also need to be updated. At present, an eventual DatasetChangeEvent that is triggered if the range of z-values is changing has to be caught and dispatched by custom code outside the JFreeChart library. This piece of custom code needs to permanently keep or at least temporarily get a reference to the XYZDataset, the renderer and the PaintScaleLegend and might also have to be informed if any of these objects is changed or deleted, all in all a non-trivial task.
If we have accessed all the objects that we need to, the range of the ValueAxis of the PaintScaleLegend can then be adjusted, but an adjustement of the range of the PaintScale is not possible since the current PaintScale interface is “read only” and does not allow to set an upper or lower bound. Finally, there is no declared method that would allow to cache the range of z-values of the XYZDataset. In order to find the range, it is required to loop over the entire dataset.
If the PaintScale of the renderer is exchanged with another PaintScale, the changes should be reflected in the PaintScale of the PaintScaleLegend. Ideally, the same object should be used for both the renderer and the legend. At present, it is not ensured that the PaintScaleLegend and the XYBlockRenderer are indeed using the same z-value <-> paint “encoding”. In fact the PaintScaleLegend and the XYBlockRenderer can hold totally different PaintScales.
If the XYZDataset or the XYBlockRenderer is exchanged with a “normal” XYDataset and e. g. an XYLineAndShapeRenderer, the PaintScaleLegend is no longer necessary. At present the PaintScaleLegend must be explicitly removed or set to invisible. There is no way to “intercept” the drawing if the plot doesn´t contain any XYZDataset and XYBlockRenderers.
All in all, ensuring that a PaintScaleLegend is showing information that fit the content of the XYPlot involves much more manual work that keeping a LegendTitle in sync with the XYPlot contents. In fact, the contents of a LegendTitle never need to be updated manually.
Here are a couple of things that I thought would be helpful:
- A new interface RangePaintScale that extends PaintScale and contains setters for the lower and upper bound.
- A mechanism that ensures that, if the z value range of the XYZDataset changes, both the range of the ValueAxis of the PaintScaleLegend and, if a RangePaintScale is used, the range of the PaintScale are adjusted accordingly.
- A mechanism that ensures that both the XYBlockRenderer and the PaintScaleLegend use the same PaintScale.
- More flexibility with regard to how the z-coordinates are visualized. Instead of using a mapping between z-coordinates and paints, one could also map z-coordinates to symbol sizes. The JFreeChart library contains a renderer (XYBubbleRenderer) that maps z-values to circle sizes, and the sizes are dynamically calculated based on the z-coordinate and on the scaling of the axes. One could also map z-coordinates (between a minimum and a maximum z-value) to a symbol size defined in pixels (between a minimum and a maximum symbol size). In order to make this mapping traceable for the viewer, a specialized legend (“SizeScaleLegend”) would be useful. Such an option would make it even more complicated to keep the XYZDataset, the renderer and the legend in sync. If the XYBlockRenderer is exchanged with an “XYSizeRenderer”, the old “PaintScaleLegend” would have to be deleted and a new “SizeScaleLegend” created automatically. It would be desirable if the library would handle these changes automatically and adjust the legend if the type of the renderer or its properties are changing (similar to a normal LegendTitle).
- Finally: why stop with an XYZDataset? Several commercial packages can handle data sets with more than one z range, i.e. each xy-data point can contain several z coordinates that can be used to dynamically define the rendering of the data point (in addition to the xy-coordinates which define the position in the chart), and both the color and the size (and maybe more properties) are dynamically calculated based on additional z-values. It should not be too difficult to implement a dataset of type “XYZ1Z2”, an appropriate renderer that is a combination of an XYBlockRenderer and an XYBubbleRenderer, and ensure that the renderer and the dataset work together. But in order to make the visual information in the chart traceable for the viewer, two separate “ScaleLegends” are required, one that explains the paint encoding, and another one that explains the size encoding, i. e. more than one legend needs to be attached to a single dataset/renderer combination. Such an option involves additional complication. If the numbers of z-coordinates are reduced, i.e. if the “XYZ1Z2Dataset” is exchanged with an XYZDataset or with a normal XYDataset, the corresponding ScaleLegends that are no longer needed should disappear. And if we have a dataset with two z-ranges, use a renderer that renders the first z-range as a size and the second z-range as a paint, and the change the renderer to make it render the first z-range as a paint and the second z-range as a size, the two “ScaleLegends” would have to change places. To track such changes of the dataset and/or renderer and adjust the legends accordingly can be very complex. It would be desirable to make the library handle that automatically.
Here are some classes that aim at handling these issues:
Code: Select all
public interface org.jfree.chart.MultiZRangeSource
public MultiZRangeDataset getMultiZRangeDataset(int index);
public MultiZRangeRenderer getMultiZRangeRenderer(int index);
public int getDatasetCount();
Code: Select all
public interface org.jfree.data.general.MultiZRangeDataset.java
public int getZRangeCount();
public Range getZRange(int index);
public String getZRangeName(int index);
Code: Select all
public interface org.jfree.chart.renderer.MultiZRangeRenderer
public int getZRangeCount();
public Range getZRange(int index);
public void setZRange(int index, Range range);
public void setZRange(int index, Range range, boolean notify);
public void drawZRangeLegendInfo(Graphics2D g2, Rectangle2D area, RectangleEdge legendPosition, ValueAxis axis, RectangleEdge axisEdge, int rangeIndex);
Code: Select all
public class org.jfree.chart.title.ZRangeLegend
The ZRangeLegend contains a reference to a MultiZRangeSource, a datasetIndex and a rangeIndex, all of which are set in the constructor. By this datasetIndex, the ZRangeLegend can access the MultiZRangeDataset and MultiZRangeRenderer located in the MultiZRangeSource at the given datasetIndex.
As explained above, a “MultiZRangeDataset” and a “MultiZRangeRenderer” can contain multiple z ranges, but each ZRangeLegend can only visualize a single range (the ZRangeLegend only contains a single ValueAxis). The rangeIndex indicates the exact range about which the ZRangeLegend should care.
The ZRangeLegend implements PlotChangeListener. If a PlotChangeEvent is received, the ZRangeLegend accesses the MultiZRangeDataset, req uests the value range and the name of the range, and uses the returned range and the returned name as range and label for its axis. It also sets the returned range as range for the MultiZRangeRenderer, which in turn can use that range and forward it to e. g. a PaintScale. Without a ZRangeLegend, the MultiZRangeRenderer will not be notified if the z range of the MultiZRangeDataset has changed!
In the arrange and draw methods of the ZRangeLegend, it is checked whether the MultiZRangeSource indeed contains a MultiZRangeDataset and a MultiZRangeRenderer at the given datasetIndex, and whether they contain a sufficient number of z ranges. If that is not the case, the arrange method returns a new Size2D(0.0, 0.0) and the draw-method returns null, i.e. the ZRangeLegend does not appear on the chart (similar to a LegendTitle without LegendItems).
Code: Select all
public interface org.jfree.data.xy.MultiZRangeXYDataset extends XYZDataset, MultiZRangeDataset
public Number getZN(int series, int item, int range);
public double getZNValue(int series, int item, int range);
Code: Select all
public class org.jfree.data.xy.DefaultMultiZRangeXYDataset extends AbstractXYZDataset implements MultiZRangeDataset.
Code: Select all
public class org.jfree.chart.renderer.xy.XYSizePaintRenderer extends AbstractXYItemRenderer implements MultiZRangeRenderer.
If this renderer is used with default settings (ZRangeRenderType.PAINT) with a dataset that has one additional z range, the resulting plot will look like an XYPlot with an XYZDataset and an XYBlockRenderer. Note that this renderer does not draw Rectangles whose size is dependent on the scaling of the axes but simply uses the shapes returned by getItemShape(series, item).
If the range render type of the renderer is changed to ZRangeRenderType.SIZE, the plot looks a little bit like an XYPlot with an XYBubbleRenderer. However, this renderer does not care about the scaling of the axes and determines the sizes of the circles simply by translating the z value in the given z value range to a pixel value between a predefined minimum and maximum pixel size.
This class is also able to draw the relevant parts of a ZRangeLegend.
Finally, an interface RangePaintScale was defined that extends PaintScale and declares methods for setting the lower and upper bound of the PaintScale. An abstract class AbstractRangePaintScale is also included that simply provides storage capacity for the upper and lower bounds. GrayPaintScale was changed and now extends AbstractRangePaintScale. GrayPaintScale should behave as usual, but now the lower and upper bound can not only be set in the constructor but changed afterwards. In order to get a little bit color in the system, an additional AbstractRangePaintScale implementation is provided that maps z values to a range of colors going from dark green over yellow to dark red.
The uploaded file (patch id 2785069) also contains a demo program.