plotly.js icon indicating copy to clipboard operation
plotly.js copied to clipboard

Add tooltip annotation functionality

Open kb- opened this issue 1 year ago • 4 comments

This PR addresses https://github.com/plotly/plotly.js/issues/7054

The tooltip function can be activated by a new modebar button.
It allows to add an annotation to every clicked point. By default, tooltips contain x, y and when available z, or ohlc data (open, high, low, close).

Tooltips can be customized with an optional tooltiptemplate (leverages existing hovertemplate code) and tooltip annotation options (leverages existing annotations code)
331887266-f7258b47-6eb2-4c3c-a3ce-f23899fe57e1
I added examples for various plot types in \test\image\mocks

Additional interactivity:

When a plot is created with editable: true, the tooltips can be dragged around for placement or deleted interactively. Their text can also be edited. To delete a tooltip, click on its text and delete it.

Compatibility:

(active for plots with hasCartesian or hasGL2D)

  • ✓ bar
  • x barpolar (not active)
  • ~ box (missing information in data.points[0], the tooltip is not very useful)
    image
  • ✓ candlestick
  • x carpet (not active, same for hover and spike)
  • x choropleth (not active)
  • x choroplethmap (not active)
  • x choroplethmapbox (not active)
  • x cone (not active)
  • ✓ contour
  • x contourcarpet (not active, same for hover and spike)
  • x densitymap (not active)
  • x densitymapbox (not active)
  • ~ funnel (wrong placement) Should disable? not very useful for this graph
  • x funnelarea (not active)
  • ✓ heatmap
  • ~~✓ heatmapgl~~(trace type no longer exists)
  • ✓ histogram
  • ✓ histogram2d
  • ✓ histogram2dcontour
  • x icicle (not active)
  • x image (not active)
  • x indicator (not active)
  • x isosurface (not active)
  • x mesh3d (not active)
  • ✓ ohlc
  • x parcats (not active)
  • x parcoords (not active)
  • x pie (not active)
  • ~~✓ pointcloud~~(trace type no longer exists)
  • x sankey (not active)
  • ✓ scatter
  • x scatter3d (not active)
  • x scattercarpet:
    • wrong placement, a and b missing, wrong y.
    • note: implemented hover has a wrong y too (more obvious in scattercarpet-text)
  • x scattergeo (not active)
  • ✓ scattergl
  • x scattermap (not active)
  • x scattermapbox (not active)
  • x scatterpolar (not active)
  • x scatterpolargl (not active)
  • x scattersmith (not active)
  • x scatterternary (not active)
  • ✓ splom
  • x streamtube (not active)
  • x sunburst (not active)
  • x surface (not active)
  • x table (not active, not useful)
  • x treemap (not useful)
  • x violin (missing information in data.points[0])
  • x volume (not active)
  • ✓ waterfall

Usage example:

// Generate date and time values
function generateDateTime(start, count) {
    var dates = [];
    var current = new Date(start);
    for (var i = 0; i < count; i++) {
        dates.push(new Date(current));
        current.setHours(current.getHours() + 7);
    }
    return dates;
}

// Generate random y-values
function generateRandomYValues(count) {
    return Array.from({ length: count }, function() {
        return Math.random() * 20;
    });
}

// Generate data points
var xValues = generateDateTime('2024-04-01T12:00:00Z', 10);

var trace1 = {
    name: 'Trace 1',
    x: generateDateTime('2024-04-01T12:00:00Z', 5),
    y: generateRandomYValues(5),
    type: 'scatter',
    // Tooltip Template with: 
    // trace name, 
    // hour-minute-second, 
    // y value in exponential notation with 2 digits
    tooltiptemplate: "%{fullData.name}<br>%{x|%H:%M:%S}<br>y: %{y:.2e}",
    
    // Tooltip annotation custom format:
    tooltip: {
        align: 'left',
        arrowcolor: 'blue',
        arrowhead: 2, // Arrow type
        arrowsize: 1.2,
        arrowwidth: 1,
        font: {
            color: 'red',
            family: 'Arial',
            size: 16
        },
        showarrow: true,
        xanchor: 'left'
    }
};

var trace2 = {
    name: 'Trace 2',
    x: generateDateTime('2024-04-01T12:00:00Z', 5),
    y: generateRandomYValues(5),
    type: 'scatter',
    // Tooltip uses default template and annotation format
};

var data = [trace1, trace2];

var layout = {
    title: 'Custom Tooltip Example'
};

var config = {
    editable: true, // editable allows Tooltip drag-positioning and deletion
    modeBarButtonsToAdd: ['tooltip'] // Add the tooltip button
};

Plotly.newPlot('plot', data, layout, config);

kb- avatar Aug 22 '24 05:08 kb-

This is my first contribution to a large project like this.

Most CI tests pass, but I need some help for the mock-validation test. test\image\mocks\tooltip_heatmap.json It looks like I have to add the tooltip and tooltiptemplate attributes to each trace type attributes.js.

I've been able to add tooltiptemplate (string) to the heatmap trace attributes. src\traces\heatmap\attributes.js But I don't know how to add tooltip (object; it should accept the same attributes than annotations)

node tasks/test_mock.js tooltip_heatmap

validating tooltip_heatmap Expected false to be true file: tooltip_heatmap [ { "code": "unused", "container": "data", "trace": 0, "path": [ "tooltip" ], "astr": "tooltip", "msg": "In data trace 0, container tooltip did not get coerced" } ]

C:\Users\olivi\Documents\code\plotly.js\tasks\test_mock.js:44 throw error; ^ Failed at { "mocks": [ "tooltip_heatmap" ] } (Use node --trace-uncaught ... to show where the exception was thrown)

kb- avatar Aug 28 '24 10:08 kb-

@kb- I'll try to get someone to have a look next week - thanks.

gvwilson avatar Aug 28 '24 12:08 gvwilson

@emilykl Can you help me fix the remaining issues?

kb- avatar Oct 02 '24 15:10 kb-

@emilykl Can you help me fix the remaining issues?

I figured it out. These tests don't pass on master node tasks/test_mock.mjs candlestick_double-y-axis ohlc_first, so I removed the tests I had based on them (tooltip_tooltiptemplate_candlestick_double-y-axis ce08efbbd66a2a0a0ea8e561d20828b62a03df17 and tooltip_tooltiptemplate_ohlc baf913ed17ff0f389c28f1641a94c7f99101b897)

kb- avatar Oct 13 '24 02:10 kb-

Apologies @kb- - we are trying to get the next Dash and Plotly.py releases out the door, so we're short on cycles for reviewing PRs (particularly ones that touch this many files). I'll try to prioritize it in the next work cycle, but realistically it's going to be at least a couple more weeks :-( Thanks for your patience - @gvwilson

gvwilson avatar Mar 13 '25 13:03 gvwilson