Issue #492: DataPointListener for data-point hover/exit/click collision detection#1002
Merged
Conversation
Adds a public collision-detection API so users can react to the mouse interacting with individual rendered data points (bars, markers, bubbles, pie slices, etc.) rather than only seeing a tool tip. - New public DataPointListener interface (default no-op hover/exit/click, each receiving the originating MouseEvent) and immutable ChartDataPoint value object (series name, data-point index, x/y values or label, pixel coordinates, hit shape). - New XChartPanel.addDataPointListener / removeDataPointListener. - Internal DataPointDispatcher mirrors the ToolTips/Cursor pattern and reuses the per-data-point hit shapes already collected for tool tips, so it works for every chart type without enabling tool tips. - Series name + index threaded through the XY, Category, HorizontalBar and Bubble plots via ToolTipData.withSeries(...); other chart types still fire the callbacks with a null series / -1 index. - Right-clicking a listened data point auto-suppresses the built-in Save As.../Print menu so a custom context menu can be shown; right-click elsewhere is unchanged. - TestForIssue492 demo, DataPointListenerTest coverage, and README section. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a new public collision-detection API to XChart’s Swing integration so callers can react to mouse hover/exit/click on individual rendered data points (bars/markers/bubbles/etc.) by reusing the existing per-data-point hit shapes already collected for tooltips.
Changes:
- Introduces
DataPointListenerandChartDataPoint, and wires listener registration intoXChartPanel. - Adds an internal
DataPointDispatcherfed fromPlotInteractionDataafter each repaint to perform hit-testing and dispatch callbacks. - Threads series name + data-point index through selected plot types via
ToolTipData.withSeries(...), plus adds tests and a demo.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| xchart/src/main/java/org/knowm/xchart/XChartPanel.java | Adds listener registration/removal, wires DataPointDispatcher, and suppresses default popup menu over listened data points. |
| xchart/src/main/java/org/knowm/xchart/DataPointListener.java | New public listener interface with hover/exit/click callbacks. |
| xchart/src/main/java/org/knowm/xchart/ChartDataPoint.java | New public value object describing the hit data point and its hit shape. |
| xchart/src/main/java/org/knowm/xchart/internal/chartpart/DataPointDispatcher.java | New internal dispatcher doing hit-testing and callback dispatch. |
| xchart/src/main/java/org/knowm/xchart/internal/chartpart/Chart.java | Plumbs dispatcher into consumeInteractionData(...) so it receives fresh hit-shape data. |
| xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotInteractionData.java | Makes addToolTip(...) return ToolTipData and adds series/index fields + withSeries(...). |
| xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_XY.java | Attaches series name + index to tooltip data for XY plots. |
| xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_Category.java | Attaches series name + index to tooltip data for category bars. |
| xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_HorizontalBar.java | Attaches series name + index to tooltip data for horizontal bars. |
| xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_Bubble.java | Attaches series name + index to tooltip data for bubbles. |
| xchart/src/test/java/org/knowm/xchart/DataPointListenerTest.java | New offscreen-render + synthetic mouse-event test coverage for hover/exit/click behavior and listener wiring. |
| xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue492.java | New demo showcasing hover/click output and custom context menu suppression behavior. |
| README.md | Documents the new “Data Point Listeners” feature and adds it to the feature list. |
Comment on lines
+19
to
+23
| * @Override | ||
| * public void onDataPointClick(ChartDataPoint dataPoint) { | ||
| * System.out.println("Clicked " + dataPoint.getSeriesName() | ||
| * + " point #" + dataPoint.getDataPointIndex()); | ||
| * } |
Comment on lines
+47
to
+52
| /** | ||
| * Called when a data point's shape is clicked. Use {@link MouseEvent#getButton()} /{@link | ||
| * MouseEvent#isPopupTrigger()} to distinguish left-clicks from right-clicks (e.g. to show a | ||
| * context menu). | ||
| * | ||
| * @param dataPoint the clicked data point |
Comment on lines
+79
to
+81
| for (DataPointListener listener : listeners) { | ||
| listener.onDataPointExit(exited, e); | ||
| } |
Comment on lines
+88
to
+90
| for (DataPointListener listener : listeners) { | ||
| listener.onDataPointHover(hit, e); | ||
| } |
Comment on lines
+102
to
+104
| for (DataPointListener listener : listeners) { | ||
| listener.onDataPointClick(hit, e); | ||
| } |
Comment on lines
+92
to
+96
| } | ||
|
|
||
| @Override | ||
| public void mouseClicked(MouseEvent e) { | ||
|
|
Comment on lines
+42
to
+44
| * @param dataPoint the data point the mouse just left | ||
| * @param e the mouse-move event during which the point was left | ||
| */ |
- DataPointDispatcher: iterate over a snapshot of the listeners list in the hover/exit/click dispatch loops so a listener can add/remove listeners from inside a callback (e.g. one-shot handlers) without a ConcurrentModificationException. - DataPointDispatcher: handle mouseExited so leaving the panel while hovering a data point clears the hover state and fires onDataPointExit (previously the listener was left "stuck" and hovered never reset). - DataPointListener javadoc: fix the code example to include the MouseEvent parameter (it wouldn't compile), recommend SwingUtilities.isRightMouseButton over isPopupTrigger inside click handlers, and loosen the onDataPointExit wording to cover the panel-exit case. - Test: cover the mouseExited -> onDataPointExit path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Member
Author
|
Addressed the Copilot review feedback in 3f1c9a1:
Full |
The previous tests constructed an XChartPanel, whose constructor calls Toolkit.getMenuShortcutKeyMaskEx() and throws HeadlessException on the headless CI runner (no X11 DISPLAY). No other test in the repo instantiates XChartPanel -- the convention is headless-safe testing. Refactored to test the collision logic directly against DataPointDispatcher in the internal.chartpart package (fed a PlotInteractionData, driven with synthetic MouseEvents sourced from a plain JPanel), covering hover/exit/ click, mouseExited, listener-mutation-during-callback safety, and isOverDataPoint. Kept the pure ChartDataPoint equality test. Verified with -Djava.awt.headless=true. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #492.
What
Adds a public collision-detection API so users can react to the mouse interacting with an individual rendered data point (a bar, marker, bubble, pie slice, etc.) — for drill-down, custom pop-ups, linked views, or context menus — instead of only seeing a tool tip.
Requested in #492 ("detect mouse collisions on a shape rendered ... fire a mouse listener when the cursor moves over the first blue bar").
API
DataPointListener— all three callbacks aredefaultno-ops, so implementations override only what they need. Each receives the originatingMouseEvent.ChartDataPoint— immutable: series name, data-point index within the series, formatted x/y values (or single label), pixel coordinates, and the hitShape.XChartPanel.addDataPointListener/removeDataPointListener.How it works
DataPointDispatchermirrors the existingToolTips/Cursorpattern and reuses the per-data-point hit shapes already collected for the tool-tip feature, so collision detection works for every chart type and does not require tool tips to be enabled.ToolTipData.withSeries(...); other chart types still fire the callbacks (withnullseries /-1index) since all 11 plot types already populate hit shapes.DataPointListeneris registered and the user right-clicks directly on a data point, the built-in Save As... / Print pop-up is auto-suppressed so a custom context menu can be shown. Right-clicking elsewhere is unchanged.Testing
DataPointListenerTest— renders a bar chart offscreen, drives synthetic mouse events across the plot, and asserts hover/exit/click fire with the correct series name and index for all five bars; plusChartDataPointequality and listener add/remove wiring.xchartsuite: 137/137 pass.TestForIssue492demo (hover/left-click console output + right-click custom context menu on a bar).Notes
SwingUtilities.isRightMouseButton(e)rather thanMouseEvent.isPopupTrigger()(the latter is only meaningful on press/release); the demo and README call this out.🤖 Generated with Claude Code