Tooltip on multiseries chart

A discussion forum for JFreeChart (a 2D chart library for the Java platform).
Locked
brimborium
Posts: 23
Joined: Mon Dec 13, 2010 10:23 am
antibot: No, of course not.

Tooltip on multiseries chart

Post by brimborium » Mon Jan 10, 2011 12:07 pm

Hi there,

I am currently working with a multiseries plot. I have a array of plotSeries

Code: Select all

for(int ch = 0; ch < MAXCHANNELS; ch++) plotSeries[ch] = new XYSeries("Series " + ch);
which I add to a chart:

Code: Select all

XYSeriesCollection data = new XYSeriesCollection();
XYLineAndShapeRenderer r = new XYLineAndShapeRenderer();
for(int ch = 0; ch < MAXCHANNELS; ch++) {
	data.addSeries(plotSeries[ch]);
	r.setSeriesStroke(ch, new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
	r.setSeriesShape(ch, new Rectangle(new Dimension(0, 0)));
	r.setSeriesPaint(ch, plotColor[ch]);
	r.setSeriesToolTipGenerator(ch, new StandardXYToolTipGenerator() {
		private static final long serialVersionUID = 1L;
		public String generateToolTip(XYDataset dataset, int series, int item) {
			String toolTipStr = "asdf";
			return toolTipStr;
		}
	});
}

chart = ChartFactory.createXYLineChart("", "", "", data, PlotOrientation.VERTICAL, false, true, false);
XYPlot plot = chart.getXYPlot();
plot.setRenderer(r);
Now this works fine, the renderer works (as the correct colors/strokes are used for the specific series), but It won't show any tooltips for any series. What am I doing wrong?
I don't know if this is important, but I am also changing the content of the plotseries during runtime do display different kind of data. At the initialization, the plotSeries are empty.

Any help would be appreciated.

Cheers, Stefan

EDIT: Oh and I use jfreechart-1.0.13.jar and jcommon-1.0.16.jar

Ok, I created a runalone code example to show you what I mean. The example is a simple chart with 2 series in a dataset displayed. I also apply a renderer to control color/shape of the two curves and I also tried to create a tooltip (as the default one would not show up). But this tooltip does not show as well.
What I want is to create a tooltip which I can control what is written in it and which shows for all series.

Code: Select all

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Rectangle;

import javax.swing.JFrame;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.labels.CustomXYToolTipGenerator;
import org.jfree.chart.labels.XYToolTipGenerator;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;

public class PlotDemo extends JFrame {
	private static final long serialVersionUID = 1L;
	private XYSeries plotSeries1, plotSeries2;

	public PlotDemo() {
		XYSeriesCollection data = new XYSeriesCollection();
		XYLineAndShapeRenderer r = new XYLineAndShapeRenderer();

		plotSeries1 = new XYSeries("demoSeries1");
		plotSeries2 = new XYSeries("demoSeries2");
		for(int i = 0; i < 500; i++) {
			plotSeries1.add(i, Math.sin(i / 30.0));
			plotSeries2.add(i, Math.sin(i / 50.0));
		}

		data.addSeries(plotSeries1);
		data.addSeries(plotSeries2);
		r.setSeriesStroke(0, new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
		r.setSeriesShape(0, new Rectangle(new Dimension(0, 0)));
		r.setSeriesPaint(0, new Color(0.3f, 0.5f, 0.0f));
		r.setSeriesStroke(1, new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
		r.setSeriesShape(1, new Rectangle(new Dimension(0, 0)));
		r.setSeriesPaint(1, new Color(1.0f, 0.1f, 0.0f));

		r.setBaseToolTipGenerator(new CustomXYToolTipGenerator());
		XYToolTipGenerator tt = new XYToolTipGenerator() {
			@Override
			public String generateToolTip(XYDataset arg0, int arg1, int arg2) {
				return String.format("%.2fHz\n%.2fHz", arg0.getXValue(arg1, arg2), arg0.getYValue(arg1, arg2));
			}
		};
		r.setSeriesToolTipGenerator(0, tt);
		r.setSeriesToolTipGenerator(1, tt);

		JFreeChart chart = ChartFactory.createXYLineChart("", "", "",
						data, PlotOrientation.VERTICAL, false, true, false);

		XYPlot plot = chart.getXYPlot();
		plot.setRenderer(r);		

		ValueAxis domain, range;
		domain = new NumberAxis();
		domain.setAutoRange(true);
		range = new NumberAxis();
		range.setAutoRange(true);

		plot.setDomainAxis(domain);
		plot.setRangeAxis(range);

		ChartPanel phnPlotPanel = new ChartPanel(chart);
		add(phnPlotPanel, BorderLayout.CENTER);
	}

	public static void main(String[] args) {
		PlotDemo main = new PlotDemo();
		main.setVisible(true);
		main.setSize(new Dimension(400, 400));
		main.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	}
}
Cheers, Stefan
Last edited by brimborium on Tue Jan 11, 2011 12:56 pm, edited 1 time in total.

brimborium
Posts: 23
Joined: Mon Dec 13, 2010 10:23 am
antibot: No, of course not.

Re: Tooltip on multiseries chart

Post by brimborium » Mon Jan 24, 2011 12:21 pm

Ok, I was able to find a way to get the tooltips to work, but not entirely. Here is a working demo code to show you, what is working and what isn't.

Code: Select all

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.geom.Ellipse2D;

import javax.swing.JFrame;
import javax.swing.UIManager;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.labels.XYToolTipGenerator;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;

public class PlotDemo extends JFrame implements Runnable {
	private static final long serialVersionUID = 1L;
	private XYSeries plotSeries1, plotSeries2;
	private XYPlot plot;

	public PlotDemo() {
		XYSeriesCollection data = new XYSeriesCollection();

		plotSeries1 = new XYSeries("series 1");
		plotSeries2 = new XYSeries("series 2");
		data.addSeries(plotSeries1);
		data.addSeries(plotSeries2);

		JFreeChart chart = ChartFactory.createXYLineChart("", "", "", data, PlotOrientation.VERTICAL, true, true, false);
		plot = chart.getXYPlot();
		refreshRenderer();

		ValueAxis domain = new NumberAxis(), range = new NumberAxis();
		plot.setDomainAxis(domain);
		plot.setRangeAxis(range);

		ChartPanel phnPlotPanel = new ChartPanel(chart);
		add(phnPlotPanel, BorderLayout.CENTER);

		setVisible(true);
		setSize(new Dimension(400, 400));
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	}

	public void refreshRenderer() {
		XYLineAndShapeRenderer r = new XYLineAndShapeRenderer();
		r.setSeriesStroke(0, new BasicStroke(3f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
//		r.setSeriesShape(0, new Ellipse2D.Double( -0.5, -0.5, 1, 1)); // <- tooltips for series 1 disappear if uncommented
		r.setSeriesPaint(0, Color.red);
		r.setSeriesStroke(1, new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
//		r.setSeriesShape(1, new Rectangle(1,1)); // <- tooltips for series 2 disappear if uncommented
		r.setSeriesPaint(1, Color.blue);

		XYToolTipGenerator tt1 = new XYToolTipGenerator() {
			public String generateToolTip(XYDataset dataset, int series, int item) {
				StringBuffer sb = new StringBuffer();
				Number x = dataset.getX(series, item);
				Number y = dataset.getY(series, item);
				sb.append("<html><p style='color:blue;'>HEADER1:</p><br />");
				sb.append(String.format("%.2fHz <br />", x.doubleValue()));
				sb.append(String.format("%.2fdBm</html>", y.doubleValue()));
				return sb.toString();
			}
		};
		XYToolTipGenerator tt2 = new XYToolTipGenerator() {
			public String generateToolTip(XYDataset dataset, int series, int item) {
				StringBuffer sb = new StringBuffer();
				Number x = dataset.getX(series, item);
				Number y = dataset.getY(series, item);
				sb.append("<html><p style='color:blue;'>HEADER2:</p><br />");
				sb.append(String.format("%.2fHz <br />", x.doubleValue()));
				sb.append(String.format("%.2fdBm</html>", y.doubleValue()));
				return sb.toString();
			}
		};
		r.setSeriesToolTipGenerator(0, tt1);
		r.setSeriesToolTipGenerator(1, tt2);
		plot.setRenderer(r);
		
		UIManager.put("ToolTip.background", new Color(0.9f, 0.9f, 0.9f));
		UIManager.put("ToolTip.foreground", new Color(0.2f, 0.2f, 0.2f));
		UIManager.put("ToolTip.font", null);
	}

	public static void main(String[] args) {
		PlotDemo main = new PlotDemo();
		new Thread(main).start();
	}

	@Override
	public void run() {
		for(int i = 0; i < 500; i++)
			plotSeries1.add(i, Math.sin(i / 70.0), false);
		plotSeries1.fireSeriesChanged();
		for(int i = 0; i < 500; i++)
			plotSeries2.add(i, Math.sin(i / 30.0), false);
		plotSeries2.fireSeriesChanged();
		refreshRenderer();
	}
}
The problem now is the following. As soon as I uncomment the commented 2 lines (r.setSeriesShape(...)), the tooltip won't appear anymore. So by setting the shape of the series, the tooltip gets deactivated somehow, although the shape is set before setting the seriesRenderers... Somebody knows how to fix this? Because I definitely want to control the shape of the series...

Appreciate your help!
Cheers,
Stefan

paradoxoff
Posts: 1634
Joined: Sat Feb 17, 2007 1:51 pm

Re: Tooltip on multiseries chart

Post by paradoxoff » Mon Jan 24, 2011 10:44 pm

Hi Stefan,
in order for the tool tip to appear, the mouse must be located within the area defined by the shape around the coordinates of the respective point. Since you use a Rectangle of 1 pixel diameter, that will be very difficult to hit.
Apparently you want to show tool tips on a line chart. This is not possible using the available renderers. In order to achieve that, you will have to make the XYLineAndShapeRenderer to pass through the method drawSecondaryPass() which creates the tool tips by calling addEntity() at the end.
Alternatively you could stick with a small shape and create a new renderer with an overriden addEntity() method. In this new method, you can check the bounds of the shape. If it is too small to be effectively used as a hot spot for a tool tip, call super.addEntity() with a null shape. That will make the renderer to use an Ellipse2D with the default entity radius as hot spot. If the default entiyt radius is too small (it defaults to 3), you can change that by calling setDefaultEntityRadius(int).

brimborium
Posts: 23
Joined: Mon Dec 13, 2010 10:23 am
antibot: No, of course not.

Re: Tooltip on multiseries chart

Post by brimborium » Tue Jan 25, 2011 11:08 am

Hi paradoxoff,

Thank you very much for your answer. You were completely right, the size of the shape was exactly the problem here. I did think that the tooltips are generated based on the distance to the points, not based on actually beeing inside of a shape.
I inherited the XYLineAndShapeRenderer as you suggested and with very little effort I got where I wanted to be. I even can change the shapes size to be zero, which is great (as most of the time I just want the line anyway). Here is the result: Image
And here is the complete code if someone else have the same problem:

Code: Select all

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.util.Locale;

import javax.swing.JFrame;
import javax.swing.UIManager;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.labels.XYToolTipGenerator;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;

public class PlotDemo extends JFrame implements Runnable {
	private static final long serialVersionUID = 1L;
	private XYSeries plotSeries1, plotSeries2;
	private XYPlot plot;

	public PlotDemo() {
		XYSeriesCollection data = new XYSeriesCollection();

		plotSeries1 = new XYSeries("series 1");
		plotSeries2 = new XYSeries("series 2");
		data.addSeries(plotSeries1);
		data.addSeries(plotSeries2);

		JFreeChart chart = ChartFactory.createXYLineChart("", "", "", data, PlotOrientation.VERTICAL, true, true, false);
		plot = chart.getXYPlot();
		refreshRenderer();

		ValueAxis domain = new NumberAxis(), range = new NumberAxis();
		plot.setDomainAxis(domain);
		plot.setRangeAxis(range);

		ChartPanel phnPlotPanel = new ChartPanel(chart);
		add(phnPlotPanel, BorderLayout.CENTER);

		setVisible(true);
		setSize(new Dimension(400, 400));
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	}

	public void refreshRenderer() {
		final Locale loc = new Locale("Deutsch", "Schweiz");

		APXYLineAndShapeRenderer r = new APXYLineAndShapeRenderer();
		r.setSeriesStroke(0, new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
		r.setSeriesShape(0, new Rectangle(0, 0));
		r.setSeriesPaint(0, Color.red);
		r.setSeriesStroke(1, new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
		r.setSeriesShape(1, new Rectangle(0, 0));
		r.setSeriesPaint(1, Color.blue);

		XYToolTipGenerator tt1 = new XYToolTipGenerator() {
			public String generateToolTip(XYDataset dataset, int series, int item) {
				StringBuffer sb = new StringBuffer();
				Number x = dataset.getX(series, item);
				Number y = dataset.getY(series, item);
				String htmlStr = "<html><p style='color:#ff0000;'>Series 1:</p>" +
								String.format(loc, "%.2fHz <br />", x.doubleValue()) +
								String.format(loc, "%.2fdBm</html>", y.doubleValue());
				sb.append(htmlStr);
				return sb.toString();
			}
		};
		XYToolTipGenerator tt2 = new XYToolTipGenerator() {
			public String generateToolTip(XYDataset dataset, int series, int item) {
				StringBuffer sb = new StringBuffer();
				Number x = dataset.getX(series, item);
				Number y = dataset.getY(series, item);
				String htmlStr = "<html><p style='color:#0000ff;'>Series 2:</p>" +
								String.format(loc, "%.2fHz <br />", x.doubleValue()) +
								String.format(loc, "%.2fdBm</html>", y.doubleValue());
				sb.append(htmlStr);
				return sb.toString();
			}
		};
		r.setSeriesToolTipGenerator(0, tt1);
		r.setSeriesToolTipGenerator(1, tt2);
		plot.setRenderer(r);

		UIManager.put("ToolTip.background", new Color(0.9f, 0.9f, 0.9f));
		UIManager.put("ToolTip.font", null);
	}

	public static void main(String[] args) {
		PlotDemo main = new PlotDemo();
		new Thread(main).start();
	}

	@Override
	public void run() {
		for(int i = 0; i < 500; i++)
			plotSeries1.add(i, Math.sin(i / 70.0), false);
		plotSeries1.fireSeriesChanged();
		for(int i = 0; i < 500; i++)
			plotSeries2.add(i, Math.sin(i / 30.0), false);
		plotSeries2.fireSeriesChanged();
		refreshRenderer();
	}
}
And here is the APXYLineAndShapeRenderer I inherited:

Code: Select all

import java.awt.Shape;

import org.jfree.chart.entity.EntityCollection;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.xy.XYDataset;

public class APXYLineAndShapeRenderer extends XYLineAndShapeRenderer {
	private static final long serialVersionUID = 1L; // <- eclipse insists on this and I hate warnings ^^

	@Override
	protected void addEntity(EntityCollection entities, Shape area, XYDataset dataset, int series, int item, double entityX, double entityY) {
		if(area.getBounds().width < 2 || area.getBounds().height < 2) super.addEntity(entities, null, dataset, series, item, entityX, entityY);
		else super.addEntity(entities, area, dataset, series, item, entityX, entityY);
	}
}

Locked