Shape That Scales With Zoom
Posted: Tue Mar 02, 2010 3:11 am
I'm working on displaying a Parabolic SAR indicator on a candlestick chart - a series of dots, one above or below each candle. Originally, I just created a small Ellipse2D circle and set it into the XYLineAndShapeRenderer and turned shape display on, line display off. This displayed the dots well enough. However, I found no satisfactory way to choose an appropriate dot size that worked well for all charts at all zoom levels and I could not find a renderer that would scale arbitrary item shapes, like my little circle. (If anyone is aware of such a renderer, or a better way to do this, please tell me and I'll happily convert to the officially supported way!) So, after much scouring of source code, I worked out a simple hack for scalable dot renderer. It's not a general-purpose shape-scaling renderer, but probably could form the basis for one with some tinkering (i.e., with an appropriate affine transform.) As it is, maybe this will be of use for someone else, or maybe provide an idea for solving a similar problem:
Code: Select all
/**
* ScalableDotRenderer is an XYLineAndShapeRenderer extension that produces a single circular dot per item whose size scales depending on the
* zoom factor for the plot axes. (This version also sets the base default so that only the shapes are visible, no lines.)
*/
class ScalableDotRenderer extends XYLineAndShapeRenderer {
/** Default factor by which to multiply bar width to obtain dot size */
protected static final double DEFAULT_WIDTH_FACTOR = 0.5;
/** Factor by which to multiply bar width to obtain dot size */
protected double barwidthfactor = DEFAULT_WIDTH_FACTOR;
/** The dot to use for the current pass */
protected Shape thedot = null;
/**
* Constructor that sets the dot size as a proportion of the bar width
*
* @param factor the factor by which to multiply bar width to obtain dot size
*/
public ScalableDotRenderer(double factor) {
this();
this.barwidthfactor = factor;
}
/**
* Default constructor, leave bar width factor set to default;
*/
private ScalableDotRenderer() {
super();
setBaseLinesVisible(false);
setBaseShapesVisible(true);
}
/**
* Override XYLineAndShapeRenderer.initialize() to ensure that thedot is recreated on next drawItem().
*/
@Override
public XYItemRendererState initialise(Graphics2D g2,
Rectangle2D dataArea,
XYPlot plot,
XYDataset data,
PlotRenderingInfo info) {
thedot = null;
return super.initialise(g2, dataArea, plot, data, info);
}
/**
* Override the XYLineAndShapeRenderer.drawItem() to create and cache the scaled dot.
*/
@Override
public void drawItem(Graphics2D g2, XYItemRendererState state,
Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot,
ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset,
int series, int item, CrosshairState crosshairState, int pass) {
if (thedot == null) {
double width = 0.0;
RectangleEdge domainEdge = plot.getDomainAxisEdge();
double widthms = source.getWidth();
double left = dataset.getXValue(series, item) - widthms;
for (int j = 0; j < 2; j++) { // check at least two bars to ensure an accurate width
double right = left + widthms;
double lpos = domainAxis.valueToJava2D(left, dataArea, domainEdge);
double rpos = domainAxis.valueToJava2D(right, dataArea, domainEdge);
width = Math.max(width, Math.abs(rpos - lpos));
left += widthms;
}
width *= barwidthfactor;
thedot = new Ellipse2D.Double(-width * 0.5, -width * 0.5, width, width);
}
super.drawItem(g2, state, dataArea, info, plot, domainAxis, rangeAxis, dataset, series, item, crosshairState, pass);
}
/**
* Override XYLineAndShapeRenderer.getItemShape() to return the properly scaled dot.
*/
@Override
public Shape getItemShape(int series, int item) {
return thedot;
}
}