BubbleChart problem
-
- Posts: 4
- Joined: Wed Sep 23, 2009 2:27 pm
- antibot: No, of course not.
BubbleChart problem
I'm not sure if this is a problem or if I must change something in my code, but the following example draws only big red area and it generates an error.
//////////////////////////////////////
public class BubbleChartDemo1 extends ApplicationFrame
{
public BubbleChartDemo1(String s)
{
super(s);
SampleXYZDataset samplexyzdataset = new SampleXYZDataset();
JFreeChart jfreechart = createChart(samplexyzdataset);
ChartPanel chartpanel = new ChartPanel(jfreechart);
chartpanel.setPreferredSize(new Dimension(500, 270));
setContentPane(chartpanel);
}
private static JFreeChart createChart(XYZDataset xyzdataset)
{
JFreeChart jfreechart = ChartFactory.createBubbleChart("Bubble Chart Demo 1", "X", "Y", xyzdataset, PlotOrientation.VERTICAL, true, true, false);
XYPlot xyplot = jfreechart.getXYPlot();
xyplot.setForegroundAlpha(0.65F);
NumberAxis numberaxis = (NumberAxis)xyplot.getDomainAxis();
numberaxis.setLowerMargin(0.14999999999999999D);
numberaxis.setUpperMargin(0.14999999999999999D);
NumberAxis numberaxis1 = (NumberAxis)xyplot.getRangeAxis();
numberaxis1.setLowerMargin(0.14999999999999999D);
numberaxis1.setUpperMargin(0.14999999999999999D);
return jfreechart;
}
public static JPanel createDemoPanel()
{
JFreeChart jfreechart = createChart(new SampleXYZDataset());
return new ChartPanel(jfreechart);
}
public static String getDemoDescription()
{
return "A bubble chart (this is created using an XYPlot, an XYZDataset, and an XYBubbleRenderer).";
}
public static void main(String args[])
{
BubbleChartDemo1 bubblechartdemo1 = new BubbleChartDemo1("Bubble Chart Demo 1");
bubblechartdemo1.pack();
RefineryUtilities.centerFrameOnScreen(bubblechartdemo1);
bubblechartdemo1.setVisible(true);
}
}
///
public class SampleXYZDataset extends AbstractXYZDataset
implements XYZDataset
{
public SampleXYZDataset()
{
}
public int getSeriesCount()
{
return 1;
}
public String getSeriesName(int i)
{
return "Series 1";
}
public int getItemCount(int i)
{
return xVal.length;
}
public Number getX(int i, int j)
{
return new Double(xVal[j]);
}
public Number getY(int i, int j)
{
return new Double(yVal[j]);
}
public Number getZ(int i, int j)
{
return new Double(zVal[j]);
}
private double xVal[] = {
1359.6959999999997D
};
private double yVal[] = {
99D
};
private double zVal[] = {
8932D
};
public Comparable getSeriesKey(int i) {
return getSeriesName(i);
}
}
/////////////////////////
Here is the stacktrace
sun.dc.pr.PRException: endPath: bad path
at sun.dc.pr.Rasterizer.endPath(Rasterizer.java:537)
at sun.java2d.pipe.DuctusRenderer.createShapeRasterizer(DuctusRenderer.java:553)
at sun.java2d.pipe.DuctusShapeRenderer.renderPath(DuctusShapeRenderer.java:57)
at sun.java2d.pipe.DuctusShapeRenderer.fill(DuctusShapeRenderer.java:49)
at sun.java2d.SunGraphics2D.fill(SunGraphics2D.java:2147)
at org.jfree.chart.renderer.xy.XYBubbleRenderer.drawItem(XYBubbleRenderer.java:242)
at org.jfree.chart.plot.XYPlot.render(XYPlot.java:3738)
at org.jfree.chart.plot.XYPlot.draw(XYPlot.java:3310)
at org.jfree.chart.JFreeChart.draw(JFreeChart.java:1235)
at org.jfree.chart.ChartPanel.paintComponent(ChartPanel.java:1668)
at javax.swing.JComponent.paint(JComponent.java:808)
at javax.swing.JComponent.paintChildren(JComponent.java:647)
at javax.swing.JComponent.paint(JComponent.java:817)
at javax.swing.JLayeredPane.paint(JLayeredPane.java:557)
at javax.swing.JComponent.paintChildren(JComponent.java:647)
at javax.swing.JComponent.paintWithOffscreenBuffer(JComponent.java:4794)
at javax.swing.JComponent.paintDoubleBuffered(JComponent.java:4740)
at javax.swing.JComponent.paint(JComponent.java:798)
at java.awt.GraphicsCallback$PaintCallback.run(GraphicsCallback.java:21)
at sun.awt.SunGraphicsCallback.runOneComponent(SunGraphicsCallback.java:60)
at sun.awt.SunGraphicsCallback.runComponents(SunGraphicsCallback.java:97)
at java.awt.Container.paint(Container.java:1312)
at sun.awt.RepaintArea.paint(RepaintArea.java:177)
at sun.awt.windows.WComponentPeer.handleEvent(WComponentPeer.java:260)
at java.awt.Component.dispatchEventImpl(Component.java:3678)
at java.awt.Container.dispatchEventImpl(Container.java:1627)
at java.awt.Window.dispatchEventImpl(Window.java:1606)
at java.awt.Component.dispatchEvent(Component.java:3477)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:480)
at java.awt.EventDispatchThread.pumpOneEventForHierarchy(EventDispatchThread.java:201)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:151)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:145)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:137)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:100)
sun.dc.pr.PRException: endPath: bad path
at sun.dc.pr.Rasterizer.endPath(Rasterizer.java:537)
at sun.java2d.pipe.DuctusRenderer.createShapeRasterizer(DuctusRenderer.java:553)
at sun.java2d.pipe.DuctusShapeRenderer.renderPath(DuctusShapeRenderer.java:57)
at sun.java2d.pipe.DuctusShapeRenderer.draw(DuctusShapeRenderer.java:45)
at sun.java2d.SunGraphics2D.draw(SunGraphics2D.java:2119)
at org.jfree.chart.renderer.xy.XYBubbleRenderer.drawItem(XYBubbleRenderer.java:245)
at org.jfree.chart.plot.XYPlot.render(XYPlot.java:3738)
at org.jfree.chart.plot.XYPlot.draw(XYPlot.java:3310)
at org.jfree.chart.JFreeChart.draw(JFreeChart.java:1235)
at org.jfree.chart.ChartPanel.paintComponent(ChartPanel.java:1668)
at javax.swing.JComponent.paint(JComponent.java:808)
at javax.swing.JComponent.paintChildren(JComponent.java:647)
at javax.swing.JComponent.paint(JComponent.java:817)
at javax.swing.JLayeredPane.paint(JLayeredPane.java:557)
at javax.swing.JComponent.paintChildren(JComponent.java:647)
at javax.swing.JComponent.paintWithOffscreenBuffer(JComponent.java:4794)
at javax.swing.JComponent.paintDoubleBuffered(JComponent.java:4740)
at javax.swing.JComponent.paint(JComponent.java:798)
at java.awt.GraphicsCallback$PaintCallback.run(GraphicsCallback.java:21)
at sun.awt.SunGraphicsCallback.runOneComponent(SunGraphicsCallback.java:60)
at sun.awt.SunGraphicsCallback.runComponents(SunGraphicsCallback.java:97)
at java.awt.Container.paint(Container.java:1312)
at sun.awt.RepaintArea.paint(RepaintArea.java:177)
at sun.awt.windows.WComponentPeer.handleEvent(WComponentPeer.java:260)
at java.awt.Component.dispatchEventImpl(Component.java:3678)
at java.awt.Container.dispatchEventImpl(Container.java:1627)
at java.awt.Window.dispatchEventImpl(Window.java:1606)
at java.awt.Component.dispatchEvent(Component.java:3477)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:480)
at java.awt.EventDispatchThread.pumpOneEventForHierarchy(EventDispatchThread.java:201)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:151)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:145)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:137)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:100)
//////////////
I'm using JFreeChart 1.0.13 on Windows with JRE 1.4.2_17
Thanks a lot.
Bye
Franco
//////////////////////////////////////
public class BubbleChartDemo1 extends ApplicationFrame
{
public BubbleChartDemo1(String s)
{
super(s);
SampleXYZDataset samplexyzdataset = new SampleXYZDataset();
JFreeChart jfreechart = createChart(samplexyzdataset);
ChartPanel chartpanel = new ChartPanel(jfreechart);
chartpanel.setPreferredSize(new Dimension(500, 270));
setContentPane(chartpanel);
}
private static JFreeChart createChart(XYZDataset xyzdataset)
{
JFreeChart jfreechart = ChartFactory.createBubbleChart("Bubble Chart Demo 1", "X", "Y", xyzdataset, PlotOrientation.VERTICAL, true, true, false);
XYPlot xyplot = jfreechart.getXYPlot();
xyplot.setForegroundAlpha(0.65F);
NumberAxis numberaxis = (NumberAxis)xyplot.getDomainAxis();
numberaxis.setLowerMargin(0.14999999999999999D);
numberaxis.setUpperMargin(0.14999999999999999D);
NumberAxis numberaxis1 = (NumberAxis)xyplot.getRangeAxis();
numberaxis1.setLowerMargin(0.14999999999999999D);
numberaxis1.setUpperMargin(0.14999999999999999D);
return jfreechart;
}
public static JPanel createDemoPanel()
{
JFreeChart jfreechart = createChart(new SampleXYZDataset());
return new ChartPanel(jfreechart);
}
public static String getDemoDescription()
{
return "A bubble chart (this is created using an XYPlot, an XYZDataset, and an XYBubbleRenderer).";
}
public static void main(String args[])
{
BubbleChartDemo1 bubblechartdemo1 = new BubbleChartDemo1("Bubble Chart Demo 1");
bubblechartdemo1.pack();
RefineryUtilities.centerFrameOnScreen(bubblechartdemo1);
bubblechartdemo1.setVisible(true);
}
}
///
public class SampleXYZDataset extends AbstractXYZDataset
implements XYZDataset
{
public SampleXYZDataset()
{
}
public int getSeriesCount()
{
return 1;
}
public String getSeriesName(int i)
{
return "Series 1";
}
public int getItemCount(int i)
{
return xVal.length;
}
public Number getX(int i, int j)
{
return new Double(xVal[j]);
}
public Number getY(int i, int j)
{
return new Double(yVal[j]);
}
public Number getZ(int i, int j)
{
return new Double(zVal[j]);
}
private double xVal[] = {
1359.6959999999997D
};
private double yVal[] = {
99D
};
private double zVal[] = {
8932D
};
public Comparable getSeriesKey(int i) {
return getSeriesName(i);
}
}
/////////////////////////
Here is the stacktrace
sun.dc.pr.PRException: endPath: bad path
at sun.dc.pr.Rasterizer.endPath(Rasterizer.java:537)
at sun.java2d.pipe.DuctusRenderer.createShapeRasterizer(DuctusRenderer.java:553)
at sun.java2d.pipe.DuctusShapeRenderer.renderPath(DuctusShapeRenderer.java:57)
at sun.java2d.pipe.DuctusShapeRenderer.fill(DuctusShapeRenderer.java:49)
at sun.java2d.SunGraphics2D.fill(SunGraphics2D.java:2147)
at org.jfree.chart.renderer.xy.XYBubbleRenderer.drawItem(XYBubbleRenderer.java:242)
at org.jfree.chart.plot.XYPlot.render(XYPlot.java:3738)
at org.jfree.chart.plot.XYPlot.draw(XYPlot.java:3310)
at org.jfree.chart.JFreeChart.draw(JFreeChart.java:1235)
at org.jfree.chart.ChartPanel.paintComponent(ChartPanel.java:1668)
at javax.swing.JComponent.paint(JComponent.java:808)
at javax.swing.JComponent.paintChildren(JComponent.java:647)
at javax.swing.JComponent.paint(JComponent.java:817)
at javax.swing.JLayeredPane.paint(JLayeredPane.java:557)
at javax.swing.JComponent.paintChildren(JComponent.java:647)
at javax.swing.JComponent.paintWithOffscreenBuffer(JComponent.java:4794)
at javax.swing.JComponent.paintDoubleBuffered(JComponent.java:4740)
at javax.swing.JComponent.paint(JComponent.java:798)
at java.awt.GraphicsCallback$PaintCallback.run(GraphicsCallback.java:21)
at sun.awt.SunGraphicsCallback.runOneComponent(SunGraphicsCallback.java:60)
at sun.awt.SunGraphicsCallback.runComponents(SunGraphicsCallback.java:97)
at java.awt.Container.paint(Container.java:1312)
at sun.awt.RepaintArea.paint(RepaintArea.java:177)
at sun.awt.windows.WComponentPeer.handleEvent(WComponentPeer.java:260)
at java.awt.Component.dispatchEventImpl(Component.java:3678)
at java.awt.Container.dispatchEventImpl(Container.java:1627)
at java.awt.Window.dispatchEventImpl(Window.java:1606)
at java.awt.Component.dispatchEvent(Component.java:3477)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:480)
at java.awt.EventDispatchThread.pumpOneEventForHierarchy(EventDispatchThread.java:201)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:151)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:145)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:137)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:100)
sun.dc.pr.PRException: endPath: bad path
at sun.dc.pr.Rasterizer.endPath(Rasterizer.java:537)
at sun.java2d.pipe.DuctusRenderer.createShapeRasterizer(DuctusRenderer.java:553)
at sun.java2d.pipe.DuctusShapeRenderer.renderPath(DuctusShapeRenderer.java:57)
at sun.java2d.pipe.DuctusShapeRenderer.draw(DuctusShapeRenderer.java:45)
at sun.java2d.SunGraphics2D.draw(SunGraphics2D.java:2119)
at org.jfree.chart.renderer.xy.XYBubbleRenderer.drawItem(XYBubbleRenderer.java:245)
at org.jfree.chart.plot.XYPlot.render(XYPlot.java:3738)
at org.jfree.chart.plot.XYPlot.draw(XYPlot.java:3310)
at org.jfree.chart.JFreeChart.draw(JFreeChart.java:1235)
at org.jfree.chart.ChartPanel.paintComponent(ChartPanel.java:1668)
at javax.swing.JComponent.paint(JComponent.java:808)
at javax.swing.JComponent.paintChildren(JComponent.java:647)
at javax.swing.JComponent.paint(JComponent.java:817)
at javax.swing.JLayeredPane.paint(JLayeredPane.java:557)
at javax.swing.JComponent.paintChildren(JComponent.java:647)
at javax.swing.JComponent.paintWithOffscreenBuffer(JComponent.java:4794)
at javax.swing.JComponent.paintDoubleBuffered(JComponent.java:4740)
at javax.swing.JComponent.paint(JComponent.java:798)
at java.awt.GraphicsCallback$PaintCallback.run(GraphicsCallback.java:21)
at sun.awt.SunGraphicsCallback.runOneComponent(SunGraphicsCallback.java:60)
at sun.awt.SunGraphicsCallback.runComponents(SunGraphicsCallback.java:97)
at java.awt.Container.paint(Container.java:1312)
at sun.awt.RepaintArea.paint(RepaintArea.java:177)
at sun.awt.windows.WComponentPeer.handleEvent(WComponentPeer.java:260)
at java.awt.Component.dispatchEventImpl(Component.java:3678)
at java.awt.Container.dispatchEventImpl(Container.java:1627)
at java.awt.Window.dispatchEventImpl(Window.java:1606)
at java.awt.Component.dispatchEvent(Component.java:3477)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:480)
at java.awt.EventDispatchThread.pumpOneEventForHierarchy(EventDispatchThread.java:201)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:151)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:145)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:137)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:100)
//////////////
I'm using JFreeChart 1.0.13 on Windows with JRE 1.4.2_17
Thanks a lot.
Bye
Franco
-
- JFreeChart Project Leader
- Posts: 11734
- Joined: Fri Mar 14, 2003 10:29 am
- antibot: No, of course not.
- Contact:
Re: BubbleChart problem
That PRException thing is something that happens in a lot of different circumstances, but is very common when Java2D tries to draw a shape with very large coordinates. I didn't run your code yet, but I suspect that the large z-value in your dataset, coupled with the fact that there is only one data item in your dataset (so the axis range takes on some default value that is quite small) means that JFreeChart is asking Java2D to draw an extremely large bubble. So that's 2 bugs, one in Java2D and one in JFreeChart for not calculating the bubble size in a sensible way. I will try to fix the latter.
David Gilbert
JFreeChart Project Leader
Read my blog
Support JFree via the Github sponsorship program
JFreeChart Project Leader


-
- Posts: 4
- Joined: Wed Sep 23, 2009 2:27 pm
- antibot: No, of course not.
Re: BubbleChart problem
I solved the problem this way:
1) I wrote a P class that represents a point:
///
public class P {
private final Double x;
private final Double y;
private final Double z;
public P(double x, double y, double z) {
this.x = new Double(x);
this.y = new Double(y);
this.z = new Double(z);
}
public String toString() {
return "X: " + x + ", Y: " + y + ", Z: " + z;
}
public P min(P p2) {
if (p2 == null) {
return this;
}
return new P(Math.min(x.doubleValue(), p2.x.doubleValue()),
Math.min(y.doubleValue(), p2.y.doubleValue()),
Math.min(z.doubleValue(), p2.z.doubleValue()));
}
public P max(P p2) {
if (p2 == null) {
return this;
}
return new P(Math.max(x.doubleValue(), p2.x.doubleValue()),
Math.max(y.doubleValue(), p2.y.doubleValue()),
Math.max(z.doubleValue(), p2.z.doubleValue()));
}
public Double x() {
return x;
}
public Double y() {
return y;
}
public Double z() {
return z;
}
}
///
2) I created an XYZDataset that implements this interface:
///
public interface DatasetInfo {
public P minPoint();
public P maxPoint();
}
///
3) I created a new BubbleRenderer this way:
////
public class BubbleRenderer extends AbstractXYItemRenderer implements
XYItemRenderer,
PublicCloneable {
public BubbleRenderer() {
super();
setBaseLegendShape(new Ellipse2D.Double(-4.0, -4.0, 8.0, 8.0));
}
/**
* Draws the visual representation of a single data item.
*
* @param g2 the graphics device.
* @param state the renderer state.
* @param dataArea the area within which the data is being drawn.
* @param info collects information about the drawing.
* @param plot the plot (can be used to obtain standard color
* information etc).
* @param domainAxis the domain (horizontal) axis.
* @param rangeAxis the range (vertical) axis.
* @param dataset the dataset (an {@link XYZDataset} is expected).
* @param series the series index (zero-based).
* @param item the item index (zero-based).
* @param crosshairState crosshair information for the plot
* (<code>null</code> permitted).
* @param pass the pass index.
*/
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) {
// return straight away if the item is not visible
if (!getItemVisible(series, item)) {
return;
}
PlotOrientation orientation = plot.getOrientation();
// get the data point...
double x = dataset.getXValue(series, item);
double y = dataset.getYValue(series, item);
double z = ((XYZDataset) dataset).getZValue(series, item);
double maxZ = findMaxZ(((XYZDataset) dataset));
RectangleEdge domainAxisLocation = plot.getDomainAxisEdge();
RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge();
double transX = domainAxis.valueToJava2D(x, dataArea, domainAxisLocation);
double transY = rangeAxis.valueToJava2D(y, dataArea, rangeAxisLocation);
Ellipse2D circle = createEllipse2d(transX, transY, z, maxZ,
dataArea, orientation);
g2.setPaint(getItemPaint(series, item));
g2.fill(circle);
g2.setStroke(getItemOutlineStroke(series, item));
g2.setPaint(getItemOutlinePaint(series, item));
g2.draw(circle);
if (isItemLabelVisible(series, item)) {
if (orientation == PlotOrientation.VERTICAL) {
drawItemLabel(g2, orientation, dataset, series, item, transX, transY,
false);
} else if (orientation == PlotOrientation.HORIZONTAL) {
drawItemLabel(g2, orientation, dataset, series, item, transY, transX,
false);
}
}
EntityCollection entities = null;
if (info != null) {
entities = info.getOwner().getEntityCollection();
if (entities != null && circle.intersects(dataArea)) {
addEntity(entities, circle, dataset, series, item, circle.getCenterX(),
circle.getCenterY());
}
}
int domainAxisIndex = plot.getDomainAxisIndex(domainAxis);
int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis);
updateCrosshairValues(crosshairState, x, y, domainAxisIndex,
rangeAxisIndex, transX, transY, orientation);
}
private double findMaxZ(XYZDataset dataset) {
if (dataset instanceof DatasetInfo) {
return ((DatasetInfo)dataset).maxPoint().z().doubleValue();
} else {
throw new UnsupportedOperationException("Not yet implemented
");
}
}
private Ellipse2D createEllipse2d(double transX,
double transY,
double z,
double maxZ,
Rectangle2D dataArea,
PlotOrientation orientation) {
double diameter = calculateBaseDiameter(dataArea) * (z/maxZ);
if (orientation == PlotOrientation.VERTICAL) {
return new Ellipse2D.Double(transX - diameter / 2.0,
transY - diameter / 2.0,
diameter,
diameter);
} else {
return new Ellipse2D.Double(transY - diameter / 2.0,
transX - diameter / 2.0,
diameter,
diameter);
}
}
private double calculateBaseDiameter(Rectangle2D dataArea) {
double percentage = 0.30;
return Math.min(dataArea.getMaxX(), dataArea.getMaxY()) * percentage ;
}
public LegendItem getLegendItem(int datasetIndex, int series) {
LegendItem result = null;
XYPlot plot = getPlot();
if (plot == null) {
return null;
}
XYDataset dataset = plot.getDataset(datasetIndex);
if (dataset != null) {
if (getItemVisible(series, 0)) {
String label =
getLegendItemLabelGenerator().generateLabel(dataset, series);
String description = label;
String toolTipText = null;
if (getLegendItemToolTipGenerator() != null) {
toolTipText =
getLegendItemToolTipGenerator().generateLabel(dataset, series);
}
String urlText = null;
if (getLegendItemURLGenerator() != null) {
urlText = getLegendItemURLGenerator().generateLabel(dataset, series);
}
Shape shape = lookupLegendShape(series);
Paint paint = lookupSeriesPaint(series);
Paint outlinePaint = lookupSeriesOutlinePaint(series);
Stroke outlineStroke = lookupSeriesOutlineStroke(series);
result =
new LegendItem(label, description, toolTipText, urlText, shape,
paint, outlineStroke, outlinePaint);
result.setLabelFont(lookupLegendTextFont(series));
Paint labelPaint = lookupLegendTextPaint(series);
if (labelPaint != null) {
result.setLabelPaint(labelPaint);
}
result.setDataset(dataset);
result.setDatasetIndex(datasetIndex);
result.setSeriesKey(dataset.getSeriesKey(series));
result.setSeriesIndex(series);
}
}
return result;
}
}
/////
Let me know your opinion.
(My username is franco, my domain is francolombardo.net)
Bye
Franco
1) I wrote a P class that represents a point:
///
public class P {
private final Double x;
private final Double y;
private final Double z;
public P(double x, double y, double z) {
this.x = new Double(x);
this.y = new Double(y);
this.z = new Double(z);
}
public String toString() {
return "X: " + x + ", Y: " + y + ", Z: " + z;
}
public P min(P p2) {
if (p2 == null) {
return this;
}
return new P(Math.min(x.doubleValue(), p2.x.doubleValue()),
Math.min(y.doubleValue(), p2.y.doubleValue()),
Math.min(z.doubleValue(), p2.z.doubleValue()));
}
public P max(P p2) {
if (p2 == null) {
return this;
}
return new P(Math.max(x.doubleValue(), p2.x.doubleValue()),
Math.max(y.doubleValue(), p2.y.doubleValue()),
Math.max(z.doubleValue(), p2.z.doubleValue()));
}
public Double x() {
return x;
}
public Double y() {
return y;
}
public Double z() {
return z;
}
}
///
2) I created an XYZDataset that implements this interface:
///
public interface DatasetInfo {
public P minPoint();
public P maxPoint();
}
///
3) I created a new BubbleRenderer this way:
////
public class BubbleRenderer extends AbstractXYItemRenderer implements
XYItemRenderer,
PublicCloneable {
public BubbleRenderer() {
super();
setBaseLegendShape(new Ellipse2D.Double(-4.0, -4.0, 8.0, 8.0));
}
/**
* Draws the visual representation of a single data item.
*
* @param g2 the graphics device.
* @param state the renderer state.
* @param dataArea the area within which the data is being drawn.
* @param info collects information about the drawing.
* @param plot the plot (can be used to obtain standard color
* information etc).
* @param domainAxis the domain (horizontal) axis.
* @param rangeAxis the range (vertical) axis.
* @param dataset the dataset (an {@link XYZDataset} is expected).
* @param series the series index (zero-based).
* @param item the item index (zero-based).
* @param crosshairState crosshair information for the plot
* (<code>null</code> permitted).
* @param pass the pass index.
*/
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) {
// return straight away if the item is not visible
if (!getItemVisible(series, item)) {
return;
}
PlotOrientation orientation = plot.getOrientation();
// get the data point...
double x = dataset.getXValue(series, item);
double y = dataset.getYValue(series, item);
double z = ((XYZDataset) dataset).getZValue(series, item);
double maxZ = findMaxZ(((XYZDataset) dataset));
RectangleEdge domainAxisLocation = plot.getDomainAxisEdge();
RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge();
double transX = domainAxis.valueToJava2D(x, dataArea, domainAxisLocation);
double transY = rangeAxis.valueToJava2D(y, dataArea, rangeAxisLocation);
Ellipse2D circle = createEllipse2d(transX, transY, z, maxZ,
dataArea, orientation);
g2.setPaint(getItemPaint(series, item));
g2.fill(circle);
g2.setStroke(getItemOutlineStroke(series, item));
g2.setPaint(getItemOutlinePaint(series, item));
g2.draw(circle);
if (isItemLabelVisible(series, item)) {
if (orientation == PlotOrientation.VERTICAL) {
drawItemLabel(g2, orientation, dataset, series, item, transX, transY,
false);
} else if (orientation == PlotOrientation.HORIZONTAL) {
drawItemLabel(g2, orientation, dataset, series, item, transY, transX,
false);
}
}
EntityCollection entities = null;
if (info != null) {
entities = info.getOwner().getEntityCollection();
if (entities != null && circle.intersects(dataArea)) {
addEntity(entities, circle, dataset, series, item, circle.getCenterX(),
circle.getCenterY());
}
}
int domainAxisIndex = plot.getDomainAxisIndex(domainAxis);
int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis);
updateCrosshairValues(crosshairState, x, y, domainAxisIndex,
rangeAxisIndex, transX, transY, orientation);
}
private double findMaxZ(XYZDataset dataset) {
if (dataset instanceof DatasetInfo) {
return ((DatasetInfo)dataset).maxPoint().z().doubleValue();
} else {
throw new UnsupportedOperationException("Not yet implemented

}
}
private Ellipse2D createEllipse2d(double transX,
double transY,
double z,
double maxZ,
Rectangle2D dataArea,
PlotOrientation orientation) {
double diameter = calculateBaseDiameter(dataArea) * (z/maxZ);
if (orientation == PlotOrientation.VERTICAL) {
return new Ellipse2D.Double(transX - diameter / 2.0,
transY - diameter / 2.0,
diameter,
diameter);
} else {
return new Ellipse2D.Double(transY - diameter / 2.0,
transX - diameter / 2.0,
diameter,
diameter);
}
}
private double calculateBaseDiameter(Rectangle2D dataArea) {
double percentage = 0.30;
return Math.min(dataArea.getMaxX(), dataArea.getMaxY()) * percentage ;
}
public LegendItem getLegendItem(int datasetIndex, int series) {
LegendItem result = null;
XYPlot plot = getPlot();
if (plot == null) {
return null;
}
XYDataset dataset = plot.getDataset(datasetIndex);
if (dataset != null) {
if (getItemVisible(series, 0)) {
String label =
getLegendItemLabelGenerator().generateLabel(dataset, series);
String description = label;
String toolTipText = null;
if (getLegendItemToolTipGenerator() != null) {
toolTipText =
getLegendItemToolTipGenerator().generateLabel(dataset, series);
}
String urlText = null;
if (getLegendItemURLGenerator() != null) {
urlText = getLegendItemURLGenerator().generateLabel(dataset, series);
}
Shape shape = lookupLegendShape(series);
Paint paint = lookupSeriesPaint(series);
Paint outlinePaint = lookupSeriesOutlinePaint(series);
Stroke outlineStroke = lookupSeriesOutlineStroke(series);
result =
new LegendItem(label, description, toolTipText, urlText, shape,
paint, outlineStroke, outlinePaint);
result.setLabelFont(lookupLegendTextFont(series));
Paint labelPaint = lookupLegendTextPaint(series);
if (labelPaint != null) {
result.setLabelPaint(labelPaint);
}
result.setDataset(dataset);
result.setDatasetIndex(datasetIndex);
result.setSeriesKey(dataset.getSeriesKey(series));
result.setSeriesIndex(series);
}
}
return result;
}
}
/////
Let me know your opinion.
(My username is franco, my domain is francolombardo.net)
Bye
Franco
-
- Posts: 1634
- Joined: Sat Feb 17, 2007 1:51 pm
Re: BubbleChart problem
If I got you right, you want to make sure that the maximum radius, which is calculated from the maximum z value, does not exceed more than 30 % of the data area. In turn, that means that the data item, with the largest z-value will always be rendered to fill 30 % of the data area and might easily hide other data items.
At present, your renderer will only work with an XYZDataset that also implements your DatasetInfo interface, and it doesn´t handle negative z-values.
The minPoint() and maxPoint() methods of your DatasetInfo class allow to access the min/max x and y values of the dataset, but this is never used in your renderer. To get the min/max x and y values, you could also try the various methods in the DatasetUtilities class.
BTW, a "Point" class with methods named x(), y() and z() strongly suggest that we are dealing with a point or data item representation in 3D space. However, it is easily possible that the data item with the highest x-value is not the one with the highest y and the highest z-value, i.e. the max. values for x, y, and z are probably distributed across three data items or "points".
I would suggest the folliwing changes:
- make the percentage (how much of the data atra might be occupied) mutable, and reduce the default to 0.1 (10 %).
- One powerful feature of the rendering concept is that any XYItemRenderer can create a custom XYItemRendererState object that is returned from XYItemRenderer.initialize(...). You could change your renderer to return a custom XYItemRendererState object which caches the min/max values of the z-range. This could be achieved by overwriting the startSeriesPass(XYDataset dataset, int series, int firstItem, int lastItem, int pass, int passCount) method of XYItemRendererState, checking whether the XYDataset is an instance of XYZDataset, and if yes, determine the max and min z values:
store them in the XYItemRendererState object and use them in the drawItem method. That will make your renderer work with any XYZDataset.
A while ago, I have posted a patch which extends JFreeChart possibiities with regard to datasets with additional z-coordinates (see this thread). Two of the classes are similar in purpose to what you have programmed:
That is not so far away from your DatasetInfo interface. Instead of returning two "Points" for the minimum and maximum from which the minimum and maximum z values can be extracted, the interface declares a method to get the range of the z values from which the lower and upper bound can be retrieved. The interface supports several z-ranges to have e. g. one z-range represented by a size and another one by a color.
This renderer was built based on the XYBlockRenderer and XYBubbleRenderer. The translation of a given z-value to a circle size in pixels is calculated by interpolating a given z-value between the minimum and maximum value of the z-range and translation into a pixel size between a given minimum and maximum. In this way, it is possible to handle negative values and to restrict the maximum circle size to a user definable pixel count.
If you want to try the pathc, you are welcome!
At present, your renderer will only work with an XYZDataset that also implements your DatasetInfo interface, and it doesn´t handle negative z-values.
The minPoint() and maxPoint() methods of your DatasetInfo class allow to access the min/max x and y values of the dataset, but this is never used in your renderer. To get the min/max x and y values, you could also try the various methods in the DatasetUtilities class.
BTW, a "Point" class with methods named x(), y() and z() strongly suggest that we are dealing with a point or data item representation in 3D space. However, it is easily possible that the data item with the highest x-value is not the one with the highest y and the highest z-value, i.e. the max. values for x, y, and z are probably distributed across three data items or "points".
I would suggest the folliwing changes:
- make the percentage (how much of the data atra might be occupied) mutable, and reduce the default to 0.1 (10 %).
- One powerful feature of the rendering concept is that any XYItemRenderer can create a custom XYItemRendererState object that is returned from XYItemRenderer.initialize(...). You could change your renderer to return a custom XYItemRendererState object which caches the min/max values of the z-range. This could be achieved by overwriting the startSeriesPass(XYDataset dataset, int series, int firstItem, int lastItem, int pass, int passCount) method of XYItemRendererState, checking whether the XYDataset is an instance of XYZDataset, and if yes, determine the max and min z values:
Code: Select all
double zMax = Double.POSITIVE_INFINITY;
double zMin = Double.NEGATIVE_INFINITY;
for(int i = firstItem; i <= lastItem; i++){
double z = ((XYZDataset)dataset).getZValue(series, i);
zMin = Math.min(zMin, z);
zMax = Math.max(zMax, z);
}
double zRange = zMax - zMin;
A while ago, I have posted a patch which extends JFreeChart possibiities with regard to datasets with additional z-coordinates (see this thread). Two of the classes are similar in purpose to what you have programmed:
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 class org.jfree.chart.renderer.xy.XYSizePaintRenderer extends AbstractXYItemRenderer implements MultiZRangeRenderer.
If you want to try the pathc, you are welcome!
-
- Posts: 4
- Joined: Wed Sep 23, 2009 2:27 pm
- antibot: No, of course not.
Re: BubbleChart problem
First of all, thanks for your reply.
Now, some annotations.
This way the bubbles are someway transparent.
I didn't know the XYItemRendererState: it seems a good idea using it for this pourpouse, I need some time to work on it.
Also your patch sounds very interesting, and I will consider it in my work.
Thanks a lot.
Bye
Franco
Now, some annotations.
To overcome this problem, after reading some threads on this forum, I wrote this code:...the largest z-value will always be rendered to fill 30 % of the data area and might easily hide other data items.
Code: Select all
public class TransparentDrawingSupplier extends DefaultDrawingSupplier {
public Paint getNextPaint() {
return new TransparentColor((Color) super.getNextPaint());
}
}
public class TransparentColor extends Color {
public TransparentColor(Color color) {
super(color.getRed(), color.getGreen(), color.getBlue(), 170);
}
}
//When I create a BubbleChart, but it should work with others....
XYPlot xyplot = jfreechart.getXYPlot();
xyplot.setDrawingSupplier(new TransparentDrawingSupplier());
Oh, I did use the maxPoint method:The minPoint() and maxPoint() methods of your DatasetInfo class allow to access the min/max x and y values of the dataset, but this is never used in your renderer.
Code: Select all
private double findMaxZ(XYZDataset dataset) {
if (dataset instanceof DatasetInfo) {
return ((DatasetInfo)dataset).maxPoint().z().doubleValue();
} else {
throw new UnsupportedOperationException("Not yet implemented");
}
}
Oh, I didn't show you all the code, but the idea is that the maxPoint method gives a point with the max values of x, y, and z, even if it could not belong to the actual series.BTW, a "Point" class with methods named x(), y() and z() strongly suggest that we are dealing with a point or data item representation in 3D space. However, it is easily possible that the data item with the highest x-value is not the one with the highest y and the highest z-value, i.e. the max.
Code: Select all
public class SimpleXYZDataset extends AbstractXYZDataset
implements XYZDataset, DatasetInfo {
private Map series;
private P minPoint;
private P maxPoint;
public SimpleXYZDataset() {
this.series = new HashMap();
}
public int getSeriesCount() {
return this.series.size();
}
public String getSeriesName(int i) {
return series(i).name();
}
public Comparable getSeriesKey(int i) {
return getSeriesName(i);
}
public int getItemCount(int i) {
return iSeriesPoints(i).size();
}
public Number getX(int i, int j) {
return point(i, j).x();
}
public Number getY(int i, int j) {
return point(i, j).y();
}
public Number getZ(int i, int j) {
return point(i, j).z();
}
public void addSeries(Series s) {
this.series.put(new Integer(getSeriesCount()), s);
}
private List iSeriesPoints(int i) {
return series(i).points();
}
private Series series(int i) {
return ((Series)this.series.get(new Integer(i)));
}
private P point(int i, int j) {
return (P) iSeriesPoints(i).get(j);
}
public P minPoint() {
if (minPoint == null) {
for (int i = 0; i < getSeriesCount(); i++) {
minPoint = series(i).minPoint().min(minPoint);
}
if (minPoint == null) {
minPoint = new P(0, 0, 0);
}
}
return minPoint;
}
public P maxPoint() {
if (maxPoint == null) {
for (int i = 0; i < getSeriesCount(); i++) {
maxPoint = series(i).maxPoint().max(maxPoint);
}
if (maxPoint == null) {
maxPoint = new P(0, 0, 0);
}
}
return maxPoint;
}
}
public class Series {
private final String name;
private final List points;
private final P minPoint;
private final P maxPoint;
public Series(String name, List points) {
this.name = name;
this.points = points;
this.minPoint = findPoint(points, PointFunction.MIN);
this.maxPoint = findPoint(points, PointFunction.MAX);
}
private P findPoint(List points, PointFunction function) {
P result = null;
for (Iterator iterator = points.iterator(); iterator.hasNext();) {
result = function.evaluate((P) iterator.next(), result);
}
if (result == null) {
result = new P(0, 0, 0);
}
return result;
}
public String name() {
return name;
}
public List points() {
return points;
}
public String toString() {
return name + ": " + points;
}
public P minPoint() {
return minPoint;
}
public P maxPoint() {
return maxPoint;
}
}
Also your patch sounds very interesting, and I will consider it in my work.
Thanks a lot.
Bye
Franco
-
- Posts: 1634
- Joined: Sat Feb 17, 2007 1:51 pm
Re: BubbleChart problem
I was just pointing out that you are not using the maxPoint().x() and maxPoint().y() methods. And even if you did, the JFreeChart package already contains the interfaces DomainInfo and RangeInfo (both also available with a preceding XY) that can be implemented by XYDatasets to return cached or quickly calculated values for the range of x and y values (or "domain" and "range" values to stick with the JFreeCHart naming convention). This interface is for example used by some methods of the DatasetUtilities class.f_lombardo wrote:Oh, I did use the maxPoint method:The minPoint() and maxPoint() methods of your DatasetInfo class allow to access the min/max x and y values of the dataset, but this is never used in your renderer.Code: Select all
private double findMaxZ(XYZDataset dataset) { if (dataset instanceof DatasetInfo) { return ((DatasetInfo)dataset).maxPoint().z().doubleValue(); } else { throw new UnsupportedOperationException("Not yet implemented"); } }
If you want to provide quick access to the min/max x andf y values, I recommend to implement these interfaces.
Remains the task to get a quick access to the range of z values. For a way to handle that, see the patch.
Regards, Peter
Re: BubbleChart problem
Thanks, this was very useful. I've made some slight tweaks so that maxZ is recalculated if the chart is zoomed / unzoomed, as well as using RendererState to calculate and cache maxZ as suggested in the other posts.
Code: Select all
public class BubbleRenderer extends AbstractXYItemRenderer implements XYItemRenderer {
private double largestBubblePercentage;
public BubbleRenderer() {
this(0.10); // default the largest bubble to 10% of the data area
}
public BubbleRenderer(double percentage) {
this.largestBubblePercentage = percentage;
setBaseLegendShape(new Ellipse2D.Double(-4.0, -4.0, 8.0, 8.0));
}
@Override
public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea, XYPlot plot, XYDataset data, PlotRenderingInfo info) {
if (data instanceof MinMaxDataset){
Range range = plot.getRangeAxis().getRange();
Range domain = plot.getDomainAxis().getRange();
double maxZ = findMaxZ((MinMaxDataset) data, domain, range);
return new XYZItemRendererState(info, domain, range, maxZ);
} else {
throw new UnsupportedOperationException("Not yet implemented");
}
}
/**
* Draws the visual representation of a single data item.
*
* @param g2 the graphics device.
* @param state the renderer state.
* @param dataArea the area within which the data is being drawn.
* @param info collects information about the drawing.
* @param plot the plot (can be used to obtain standard color
* information etc).
* @param domainAxis the domain (horizontal) axis.
* @param rangeAxis the range (vertical) axis.
* @param dataset the dataset (an {@link org.jfree.data.xy.XYZDataset} is expected).
* @param series the series index (zero-based).
* @param item the item index (zero-based).
* @param crosshairState crosshair information for the plot
* (<code>null</code> permitted).
* @param pass the pass index.
*/
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) {
// return straight away if the item is not visible
if (!getItemVisible(series, item)) {
return;
}
XYZItemRendererState xyzState = (XYZItemRendererState) state;
// get the data point...
double x = dataset.getXValue(series, item);
double y = dataset.getYValue(series, item);
// return if the circle is outside the range
if (!xyzState.domain.contains(x) || !xyzState.range.contains(y)){
return;
}
double z = ((XYZDataset) dataset).getZValue(series, item);
double maxZ = xyzState.maxZForRange;
RectangleEdge domainAxisLocation = plot.getDomainAxisEdge();
RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge();
double transX = domainAxis.valueToJava2D(x, dataArea, domainAxisLocation);
double transY = rangeAxis.valueToJava2D(y, dataArea, rangeAxisLocation);
PlotOrientation orientation = plot.getOrientation();
Ellipse2D circle = createEllipse2d(transX, transY, z, maxZ,
dataArea, orientation);
g2.setPaint(getItemPaint(series, item));
g2.fill(circle);
g2.setStroke(getItemOutlineStroke(series, item));
g2.setPaint(getItemOutlinePaint(series, item));
g2.draw(circle);
if (isItemLabelVisible(series, item)) {
if (orientation == PlotOrientation.VERTICAL) {
drawItemLabel(g2, orientation, dataset, series, item, transX, transY,
false);
} else if (orientation == PlotOrientation.HORIZONTAL) {
drawItemLabel(g2, orientation, dataset, series, item, transY, transX,
false);
}
}
EntityCollection entities = null;
if (info != null) {
entities = info.getOwner().getEntityCollection();
if (entities != null && circle.intersects(dataArea)) {
addEntity(entities, circle, dataset, series, item, circle.getCenterX(),
circle.getCenterY());
}
}
int domainAxisIndex = plot.getDomainAxisIndex(domainAxis);
int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis);
updateCrosshairValues(crosshairState, x, y, domainAxisIndex,
rangeAxisIndex, transX, transY, orientation);
}
private double findMaxZ(MinMaxDataset dataset, Range xRange, Range yRange) {
return dataset.maxPoint(xRange, yRange).z();
}
private Ellipse2D createEllipse2d(double transX,
double transY,
double z,
double maxZ,
Rectangle2D dataArea,
PlotOrientation orientation) {
double diameter = calculateBaseDiameter(dataArea) * (z / maxZ);
if (orientation == PlotOrientation.VERTICAL) {
return new Ellipse2D.Double(transX - diameter / 2.0,
transY - diameter / 2.0,
diameter,
diameter);
} else {
return new Ellipse2D.Double(transY - diameter / 2.0,
transX - diameter / 2.0,
diameter,
diameter);
}
}
private double calculateBaseDiameter(Rectangle2D dataArea) {
return Math.min(dataArea.getMaxX(), dataArea.getMaxY()) * largestBubblePercentage;
}
public LegendItem getLegendItem(int datasetIndex, int series) {
LegendItem result = null;
XYPlot plot = getPlot();
if (plot == null) {
return null;
}
XYDataset dataset = plot.getDataset(datasetIndex);
if (dataset != null) {
if (getItemVisible(series, 0)) {
String label =
getLegendItemLabelGenerator().generateLabel(dataset, series);
String description = label;
String toolTipText = null;
if (getLegendItemToolTipGenerator() != null) {
toolTipText =
getLegendItemToolTipGenerator().generateLabel(dataset, series);
}
String urlText = null;
if (getLegendItemURLGenerator() != null) {
urlText = getLegendItemURLGenerator().generateLabel(dataset, series);
}
Shape shape = lookupLegendShape(series);
Paint paint = lookupSeriesPaint(series);
Paint outlinePaint = lookupSeriesOutlinePaint(series);
Stroke outlineStroke = lookupSeriesOutlineStroke(series);
result =
new LegendItem(label, description, toolTipText, urlText, shape,
paint, outlineStroke, outlinePaint);
result.setLabelFont(lookupLegendTextFont(series));
Paint labelPaint = lookupLegendTextPaint(series);
if (labelPaint != null) {
result.setLabelPaint(labelPaint);
}
result.setDataset(dataset);
result.setDatasetIndex(datasetIndex);
result.setSeriesKey(dataset.getSeriesKey(series));
result.setSeriesIndex(series);
}
}
return result;
}
private static class XYZItemRendererState extends XYItemRendererState {
private double maxZForRange = 1;
private Range domain;
private Range range;
private XYZItemRendererState(PlotRenderingInfo info, Range domain, Range range, double maxZ) {
super(info);
this.domain = domain;
this.range = range;
this.maxZForRange = maxZ;
}
}
}