I would like to take special action if 2 symbols overlap.
For example, if the same exact point appears twice, I would like to make the 2nd symbol translucent or the first symbol twice the normal size. Basically I want both symbols to appear even though they have the same coordinates.
How would I go about doing something like that?
Thanks.
Overlapping symbols in scatter chart
-
- JFreeChart Project Leader
- Posts: 11734
- Joined: Fri Mar 14, 2003 10:29 am
- antibot: No, of course not.
- Contact:
You'd need to customise the renderer. When you draw each item (for example, look in the drawItem() method in the XYLineAndShapeRenderer class), you'd need to check if there are any other data items "near" to the current point, and modify the drawing behaviour accordingly. I'm not sure how you'd want to handle items that are close but not equal, since the shapes may still overlap in that case.
David Gilbert
JFreeChart Project Leader
Read my blog
Support JFree via the Github sponsorship program
JFreeChart Project Leader


-
- Posts: 13
- Joined: Mon Aug 14, 2006 9:14 pm
Thanks, I was able to do a basic implementation (when I fill it out a bit more, I'll post some code).
If I keep track of the shapes drawn, then I can easily determine if they overlap (at least if their rectangular bounds overlap). I'm not worried about lines right now (that's for another day), so it's not bad at all.
If I keep track of the shapes drawn, then I can easily determine if they overlap (at least if their rectangular bounds overlap). I'm not worried about lines right now (that's for another day), so it's not bad at all.
-
- Posts: 13
- Joined: Mon Aug 14, 2006 9:14 pm
This is an abstract class and would need a subclass that implements the fixOverlap method. This way you deal with the overlapping shapes any way you want. One thing you can do is make the shape translucent.
Unfortunately, I had to copy the code from XYLineAndShapeRenderer to do what I wanted.
As great a product as JFreeChart is, I think there are some design improvements that could be made in the design to make extensions and adapters more flexible (less code copying).
Unfortunately, I had to copy the code from XYLineAndShapeRenderer to do what I wanted.
As great a product as JFreeChart is, I think there are some design improvements that could be made in the design to make extensions and adapters more flexible (less code copying).
Code: Select all
package extensions;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.entity.EntityCollection;
import org.jfree.chart.plot.CrosshairState;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.PlotRenderingInfo;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYItemRendererState;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.xy.XYDataset;
import org.jfree.ui.RectangleEdge;
import org.jfree.util.ShapeUtilities;
import java.awt.*;
import java.awt.geom.Rectangle2D;
import java.util.LinkedList;
import java.util.Iterator;
/**
* Abstract class intended to deal w/ overlapping shapes.
*
*/
public abstract class OverlappingXYShapeRenderer extends XYLineAndShapeRenderer {
private java.util.List shapes = new LinkedList();
public OverlappingXYShapeRenderer() {
super(false, true);
}
public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea, XYPlot plot, XYDataset data, PlotRenderingInfo info) {
shapes.clear();
return super.initialise(g2, dataArea, plot, data, info);
}
protected abstract Shape fixOverlap(int series, Shape shape, Shape overlappedShape);
/**
* Draws the item shapes and adds chart entities (second pass). This method
* draws the shapes which mark the item positions. If <code>entities</code>
* is not <code>null</code> it will be populated with entity information.
*
* @param g2 the graphics device.
* @param dataArea the area within which the data is being drawn.
* @param plot the plot (can be used to obtain standard color
* information etc).
* @param domainAxis the domain axis.
* @param rangeAxis the range axis.
* @param dataset the dataset.
* @param pass the pass.
* @param series the series index (zero-based).
* @param item the item index (zero-based).
* @param crosshairState the crosshair state.
* @param entities the entity collection.
*/
protected final void drawSecondaryPass(Graphics2D g2, XYPlot plot,
XYDataset dataset,
int pass, int series, int item,
ValueAxis domainAxis,
Rectangle2D dataArea,
ValueAxis rangeAxis,
CrosshairState crosshairState,
EntityCollection entities) {
Shape entityArea = null;
// get the data point...
double x1 = dataset.getXValue(series, item);
double y1 = dataset.getYValue(series, item);
if (Double.isNaN(y1) || Double.isNaN(x1)) {
return;
}
PlotOrientation orientation = plot.getOrientation();
RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);
if (getItemShapeVisible(series, item)) {
Shape shape = getItemShape(series, item);
if (orientation == PlotOrientation.HORIZONTAL) {
shape = ShapeUtilities.createTranslatedShape(shape, transY1,
transX1);
}
else if (orientation == PlotOrientation.VERTICAL) {
shape = ShapeUtilities.createTranslatedShape(shape, transX1,
transY1);
}
Shape overlappedShape = this.getOverlappedShape(shape);
if (overlappedShape != null) {
shape = this.fixOverlap(series, shape, overlappedShape);
}
entityArea = shape;
this.shapes.add(shape);
if (shape.intersects(dataArea)) {
if (getItemShapeFilled(series, item)) {
if (this.getUseFillPaint()) {
g2.setPaint(getItemFillPaint(series, item));
}
else {
g2.setPaint(getItemPaint(series, item));
}
g2.fill(shape);
}
if (this.getDrawOutlines()) {
if (getUseOutlinePaint()) {
g2.setPaint(getItemOutlinePaint(series, item));
}
else {
g2.setPaint(getItemPaint(series, item));
}
g2.setStroke(getItemOutlineStroke(series, item));
g2.draw(shape);
}
}
}
// draw the item label if there is one...
if (isItemLabelVisible(series, item)) {
double xx = transX1;
double yy = transY1;
if (orientation == PlotOrientation.HORIZONTAL) {
xx = transY1;
yy = transX1;
}
drawItemLabel(g2, orientation, dataset, series, item, xx, yy,
(y1 < 0.0));
}
updateCrosshairValues(crosshairState, x1, y1, transX1, transY1,
plot.getOrientation());
// add an entity for the item...
if (entities != null) {
addEntity(entities, entityArea, dataset, series, item, transX1,
transY1);
}
}
private Shape getOverlappedShape(Shape shape) {
for (Iterator iterator = shapes.iterator(); iterator.hasNext();) {
Shape sh = (Shape) iterator.next();
if (shape.intersects(sh.getBounds2D())) {
return sh;
}
}
return null;
}
}
-
- JFreeChart Project Leader
- Posts: 11734
- Joined: Fri Mar 14, 2003 10:29 am
- antibot: No, of course not.
- Contact:
Very true. Originally, I thought the renderers would be small, easily replaced classes (and that people would develop a lot of custom renderers). Over time, more and more functions have been pushed from the plot to the renderers, and they've become relatively complex. I think if I'd foreseen that, the API for the renderer's would be a little different...but at least you *can* copy and paste the source code. Imagine how bad it would be if JFreeChart was secret-source software.hookumsnivy wrote:As great a product as JFreeChart is, I think there are some design improvements that could be made in the design to make extensions and adapters more flexible (less code copying).
That said, I'll try to make improvements where that can be done within the constraint of not modifying the published API.
David Gilbert
JFreeChart Project Leader
Read my blog
Support JFree via the Github sponsorship program
JFreeChart Project Leader

