Overlapping symbols in scatter chart

A discussion forum for JFreeChart (a 2D chart library for the Java platform).
Locked
hookumsnivy
Posts: 13
Joined: Mon Aug 14, 2006 9:14 pm

Overlapping symbols in scatter chart

Post by hookumsnivy » Mon Oct 09, 2006 7:40 pm

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.

david.gilbert
JFreeChart Project Leader
Posts: 11734
Joined: Fri Mar 14, 2003 10:29 am
antibot: No, of course not.
Contact:

Post by david.gilbert » Tue Oct 10, 2006 9:25 am

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

:idea: Read my blog
:idea: Support JFree via the Github sponsorship program

hookumsnivy
Posts: 13
Joined: Mon Aug 14, 2006 9:14 pm

Post by hookumsnivy » Wed Oct 11, 2006 5:01 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.

iper
Posts: 9
Joined: Fri Oct 27, 2006 9:07 am

Post by iper » Thu Nov 16, 2006 10:02 pm

any code available?

thanks!

iper
Posts: 9
Joined: Fri Oct 27, 2006 9:07 am

Post by iper » Thu Nov 16, 2006 10:20 pm

my current solution is adding a small random value to the x, y data point, but it is not very flexible since I don't the scale of the original value (thus don't know how small is small).

hookumsnivy
Posts: 13
Joined: Mon Aug 14, 2006 9:14 pm

Post by hookumsnivy » Thu Nov 16, 2006 11:23 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).

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;
	}
}

david.gilbert
JFreeChart Project Leader
Posts: 11734
Joined: Fri Mar 14, 2003 10:29 am
antibot: No, of course not.
Contact:

Post by david.gilbert » Fri Nov 17, 2006 10:34 am

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).
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.

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

:idea: Read my blog
:idea: Support JFree via the Github sponsorship program

Locked