Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package org.knowm.xchart.standalone.issues;

import org.knowm.xchart.HeatMapChart;
import org.knowm.xchart.HeatMapChartBuilder;
import org.knowm.xchart.SwingWrapper;

/**
* Demonstrates issue #679 — custom tooltips via {@code setToolTipGenerator(...)}, available on
* every series type.
*
* <p>The generator receives a {@code ChartDataPoint} carrying the series name, the data point's
* index within the series data and the default label, and returns the tooltip text to display
* (multi-line via {@code System.lineSeparator()}). Returning {@code null} falls back to the default
* label. Here each heat map cell's tooltip shows extra information looked up from an application
* data structure by the data point index — exactly what the issue asked for.
*/
public class TestForIssue679 {

public static void main(String[] args) {

SwingWrapper<HeatMapChart> wrapper = new SwingWrapper<>(getChart());
wrapper.displayChart();
// tooltips are an XChartPanel feature and are off by default
wrapper.getXChartPanel().setToolTipsEnabled(true);
}

/** Constructs and returns the chart without launching a window (headless-safe). */
public static HeatMapChart getChart() {

HeatMapChart chart =
new HeatMapChartBuilder()
.width(700)
.height(400)
.title("Issue #679 – Custom HeatMap tooltips (hover over a cell)")
.build();
chart.getStyler().setShowValue(true);

int[] xData = {0, 1, 2, 3};
int[] yData = {0, 1, 2};
// heatData[x][y] = cell value; the data point index runs x-major: index = x * yLength + y
int[][] heatData = new int[xData.length][yData.length];
// application-side extra info, indexed the same as the heat data
String[] extraInfo = new String[xData.length * yData.length];
for (int x : xData) {
for (int y : yData) {
heatData[x][y] = (x + 1) * (y + 1);
extraInfo[x * yData.length + y] = "sample count: " + (100 + 10 * x + y);
}
}

chart
.addSeries("heat", xData, yData, heatData)
.setToolTipGenerator(
dataPoint ->
dataPoint.getLabel()
+ System.lineSeparator()
+ extraInfo[dataPoint.getDataPointIndex()]);
return chart;
}
}
18 changes: 18 additions & 0 deletions xchart/src/main/java/org/knowm/xchart/BubbleSeries.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ public BubbleSeries setBubbleSeriesRenderStyle(BubbleSeriesRenderStyle bubbleSer
return this;
}

/**
* @deprecated use {@link #setToolTipGenerator(ToolTipGenerator)} instead; will be removed in
* 4.1.0
*/
@Deprecated
public boolean isCustomToolTips() {

return customToolTips;
Expand All @@ -52,13 +57,22 @@ public boolean isCustomToolTips() {
*
* @param customToolTips true to show the per-data-point strings from {@link
* #setToolTips(String[])}; false to show the default formatted x/y axis values
* @deprecated use {@link #setToolTipGenerator(ToolTipGenerator)} instead, which works on every
* chart type and takes precedence over these strings when both are set; will be removed in
* 4.1.0
*/
@Deprecated
public BubbleSeries setCustomToolTips(boolean customToolTips) {

this.customToolTips = customToolTips;
return this;
}

/**
* @deprecated use {@link #setToolTipGenerator(ToolTipGenerator)} instead; will be removed in
* 4.1.0
*/
@Deprecated
public String[] getToolTips() {

return toolTips;
Expand All @@ -71,7 +85,11 @@ public String[] getToolTips() {
*
* @param toolTips the tooltip strings, one per data point; a null entry (or a null array) falls
* back to the default formatted x/y axis values for that data point
* @deprecated use {@link #setToolTipGenerator(ToolTipGenerator)} instead, which works on every
* chart type and takes precedence over these strings when both are set; will be removed in
* 4.1.0
*/
@Deprecated
public BubbleSeries setToolTips(String[] toolTips) {

this.toolTips = toolTips;
Expand Down
15 changes: 13 additions & 2 deletions xchart/src/main/java/org/knowm/xchart/RadarSeries.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ public class RadarSeries extends MarkerSeries {
private double[] values;
private String[] tooltipOverrides;

// TODO refactor tooltips override
/**
* @param tooltipOverrides Adds custom tooltipOverrides for series. If tooltipOverrides is null,
* they are automatically generated.
* they are automatically generated. Deprecated: pass null and use {@link
* #setToolTipGenerator(ToolTipGenerator)} instead.
*/
public RadarSeries(String name, double[] values, String[] tooltipOverrides) {

Expand All @@ -50,6 +50,11 @@ public RadarSeries setValues(double[] values) {
return this;
}

/**
* @deprecated use {@link #setToolTipGenerator(ToolTipGenerator)} instead; will be removed in
* 4.1.0
*/
@Deprecated
public String[] getTooltipOverrides() {

return tooltipOverrides;
Expand Down Expand Up @@ -154,6 +159,12 @@ public LegendRenderType getLegendRenderType() {
return LegendRenderType.Line;
}

/**
* @deprecated use {@link #setToolTipGenerator(ToolTipGenerator)} instead, which works on every
* chart type and takes precedence over these strings when both are set; will be removed in
* 4.1.0
*/
@Deprecated
public RadarSeries setTooltipOverrides(String[] tooltipOverrides) {

this.tooltipOverrides = tooltipOverrides;
Expand Down
33 changes: 33 additions & 0 deletions xchart/src/main/java/org/knowm/xchart/ToolTipGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.knowm.xchart;

import org.knowm.xchart.internal.series.Series;

/**
* Generates custom tooltip text for a series' data points, replacing the default label built from
* the formatted axis values. Set one on any series via {@link
* Series#setToolTipGenerator(ToolTipGenerator)}:
*
* <pre>
* chart.getHeatMapSeries()
* .setToolTipGenerator(
* dataPoint -&gt; "extra info for cell #" + dataPoint.getDataPointIndex());
* </pre>
*
* <p>The generator is invoked once per rendered data point each time the chart is painted. The
* {@link ChartDataPoint} argument carries the series name, the data point's index within the
* series' data (so raw values can be looked up in the data structures the series was built from),
* the default formatted label or x/y values, and the data point's screen geometry.
*
* <p>Multi-line tooltips are supported: separate lines with {@link System#lineSeparator()}.
*/
@FunctionalInterface
public interface ToolTipGenerator {

/**
* Generate the tooltip text for one data point.
*
* @param dataPoint the rendered data point the tooltip belongs to
* @return the tooltip text, or {@code null} to fall back to the default label
*/
String generateToolTip(ChartDataPoint dataPoint);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import java.awt.Shape;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import java.util.List;
import org.knowm.xchart.ChartDataPoint;
Expand All @@ -17,9 +16,6 @@
*/
public class DataPointDispatcher extends MouseAdapter {

// Matches the default marker hit shape built in ToolTips.ToolTip when no explicit shape is given.
private static final double MARGIN = 5;

private final List<DataPointListener> listeners;
private final List<ChartDataPoint> dataPoints = new ArrayList<>();
private ChartDataPoint hovered = null;
Expand All @@ -35,20 +31,15 @@ public void setData(PlotInteractionData data) {
return;
}
for (PlotInteractionData.ToolTipData td : data.getToolTipDataList()) {
Shape shape = td.shape != null ? td.shape : defaultMarkerShape(td.x, td.y);
Shape shape = td.shape != null ? td.shape : PlotInteractionData.defaultMarkerShape(td.x, td.y);
// listeners see the label that is actually displayed, custom or default
String label = td.getCustomLabel() != null ? td.getCustomLabel() : td.label;
dataPoints.add(
new ChartDataPoint(
td.seriesName, td.dataPointIndex, td.label, td.xValue, td.yValue, td.x, td.y, shape));
td.seriesName, td.dataPointIndex, label, td.xValue, td.yValue, td.x, td.y, shape));
}
}

private static Shape defaultMarkerShape(double x, double y) {

double halfSize = MARGIN * 1.5;
double markerSize = MARGIN * 3;
return new Ellipse2D.Double(x - halfSize, y - halfSize, markerSize, markerSize);
}

private ChartDataPoint hitTest(int x, int y) {

for (ChartDataPoint dataPoint : dataPoints) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ protected void doPaint(Graphics2D g) {
}
// data points
double[] yArr = series.getYData();
for (double yOrig : yArr) {
for (int dataIndex = 0; dataIndex < yArr.length; dataIndex++) {
double yOrig = yArr[dataIndex];
double y;

if (boxPlotStyler.isYAxisLogarithmic()) {
Expand Down Expand Up @@ -121,13 +122,15 @@ protected void doPaint(Graphics2D g) {
g.draw(outPointLine2);

if (toolTipsEnabled) {
interactionData.addToolTip(
xOffset,
yOffset,
series.getName()
+ ":"
+ System.lineSeparator()
+ axesChart.getYAxisFormat().format(yOrig));
interactionData
.addToolTip(
xOffset,
yOffset,
series.getName()
+ ":"
+ System.lineSeparator()
+ axesChart.getYAxisFormat().format(yOrig))
.withSeries(series, dataIndex);
}
} else if (chart.getStyler().getShowWithinAreaPoint()) {

Expand All @@ -136,22 +139,24 @@ protected void doPaint(Graphics2D g) {
series.getMarker().paint(g, xOffset, yOffset, boxPlotStyler.getMarkerSize());

if (toolTipsEnabled) {
interactionData.addToolTip(
xOffset,
yOffset,
series.getName()
+ ":"
+ System.lineSeparator()
+ axesChart.getYAxisFormat().format(yOrig));
interactionData
.addToolTip(
xOffset,
yOffset,
series.getName()
+ ":"
+ System.lineSeparator()
+ axesChart.getYAxisFormat().format(yOrig))
.withSeries(series, dataIndex);
}
}
}

drawBoxPlot(g, series.getName(), boxPlotData);
drawBoxPlot(g, series, boxPlotData);
}
}

private void drawBoxPlot(Graphics2D g, String seriesName, BoxPlotData boxPlotData) {
private void drawBoxPlot(Graphics2D g, S series, BoxPlotData boxPlotData) {

// when all data values are the same yMin == yMax; offsets would be NaN, so skip rendering
if (yMax == yMin) {
Expand Down Expand Up @@ -251,28 +256,31 @@ private void drawBoxPlot(Graphics2D g, String seriesName, BoxPlotData boxPlotDat
area.add(new Area(rect.getBounds()));

if (interactionData != null) {
interactionData.addToolTip(
area,
xOffset,
yOffset,
10,
seriesName
+ ":"
+ System.lineSeparator()
+ "upper: "
+ axesChart.getYAxisFormat().format(boxPlotData.upper)
+ System.lineSeparator()
+ "q3: "
+ axesChart.getYAxisFormat().format(boxPlotData.q3)
+ System.lineSeparator()
+ "median: "
+ axesChart.getYAxisFormat().format(boxPlotData.median)
+ System.lineSeparator()
+ "q1: "
+ axesChart.getYAxisFormat().format(boxPlotData.q1)
+ System.lineSeparator()
+ "lower: "
+ axesChart.getYAxisFormat().format(boxPlotData.lower));
interactionData
.addToolTip(
area,
xOffset,
yOffset,
10,
series.getName()
+ ":"
+ System.lineSeparator()
+ "upper: "
+ axesChart.getYAxisFormat().format(boxPlotData.upper)
+ System.lineSeparator()
+ "q3: "
+ axesChart.getYAxisFormat().format(boxPlotData.q3)
+ System.lineSeparator()
+ "median: "
+ axesChart.getYAxisFormat().format(boxPlotData.median)
+ System.lineSeparator()
+ "q1: "
+ axesChart.getYAxisFormat().format(boxPlotData.q1)
+ System.lineSeparator()
+ "lower: "
+ axesChart.getYAxisFormat().format(boxPlotData.lower))
// the box aggregates the whole series, so there is no single data point index
.withSeries(series, -1);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ public void doPaint(Graphics2D g) {
&& customToolTips[i] != null) {
interactionData
.addToolTip(bubble, xOffset, yOffset, 0, customToolTips[i])
.withSeries(series.getName(), i);
.withSeries(series, i);
} else {
interactionData
.addToolTip(
Expand All @@ -152,7 +152,7 @@ public void doPaint(Graphics2D g) {
0,
axesChart.getXAxisFormat().format(x),
axesChart.getYAxisFormat().format(yOrig))
.withSeries(series.getName(), i);
.withSeries(series, i);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -521,7 +521,7 @@ public void doPaint(Graphics2D g) {
barWidth,
axesChart.getXAxisFormat().format(nextCat),
axesChart.getYAxisFormat().format(yOrig))
.withSeries(series.getName(), dataIndex);
.withSeries(series, dataIndex);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,8 @@ public void doPaint(Graphics2D g) {
label = df.format(value);
}
}
interactionData.addToolTip(path, xOffset, yOffset + 10, 0, label);
// a dial series is a single value, so its only data point is index 0
interactionData.addToolTip(path, xOffset, yOffset + 10, 0, label).withSeries(series, 0);
}
path.moveTo(xCenter, yCenter);

Expand Down
Loading
Loading