Tooltip Layer Interaction

This dev notebook is intended for manual tooltip testing.

It covers three areas:

  • univariate layers, where tooltip visibility is driven by a single axis position;
  • bivariate layers, where tooltip visibility depends on both coordinates;
  • stacked bar cases, where a single visual column contains multiple tooltip-enabled objects.
In [ ]:
import numpy as np
import pandas as pd
from lets_plot import *

LetsPlot.setup_html()
np.random.seed(42)

Univariate Layers

For these plots, tooltip visibility is expected to be determined by one axis only. This is the main behavior to observe for geom_line, geom_density, geom_bar, geom_histogram, and similar layers.

1. geom_histogram + geom_density

What to test:

  • Hover at the same x position over both layers.
  • Check whether the histogram and density tooltips are chosen consistently when the layers overlap.
  • Check whether switching between layers depends only on horizontal movement.
In [ ]:
u_df = pd.DataFrame({
    'x': np.concatenate([
        np.random.normal(loc=-0.8, scale=0.45, size=250),
        np.random.normal(loc=1.2, scale=0.65, size=250)
    ]),
    'grp': ['A'] * 250 + ['B'] * 250
})

(ggplot(u_df, aes(x='x'))
 + ggtitle('Univariate: histogram + density')
 + geom_histogram(
     aes(fill='grp'),
     bins=18,
     alpha=.45,
     position='identity',
     tooltips=layer_tooltips().line('histogram @grp')
 )
 + geom_density(
     aes(color='grp'),
     size=1.2,
     tooltips=layer_tooltips().line('density @grp')
 ))

2. Two independent geom_line layers

What to test:

  • Hover at the same x coordinate where both lines are present.
  • Check how the two univariate layers compete when they are close vertically but not equal in y.
  • Check whether tooltip selection still follows the univariate rule rather than nearest-point-in-2D behavior.
In [ ]:
line_x = np.arange(0, 10, 0.5)
line_a = pd.DataFrame({
    'x': line_x,
    'y': 2.0 + np.sin(line_x),
    'series': 'A'
})
line_b = pd.DataFrame({
    'x': line_x,
    'y': 2.35 + np.cos(line_x * 0.9),
    'series': 'B'
})

(ggplot()
 + ggtitle('Univariate: two lines')
 + geom_line(
     aes(x='x', y='y'),
     data=line_a,
     color='#1f78b4',
     size=1.3,
     tooltips=layer_tooltips().line('line A')
 )
 + geom_line(
     aes(x='x', y='y'),
     data=line_b,
     color='#e31a1c',
     size=1.3,
     tooltips=layer_tooltips().line('line B')
 ))
In [ ]:
(ggplot()
 + ggtitle('Univariate: two independent lines')
 + geom_line(
     aes(x='x', y='y'),
     data=line_a,
     color='#1f78b4',
     size=1.3,
     tooltips=layer_tooltips().group('a').line('line A')
 )
 + geom_line(
     aes(x='x', y='y'),
     data=line_b,
     color='#e31a1c',
     size=1.3,
     tooltips=layer_tooltips().group('b').line('line B')
 ))

3. geom_line + geom_point

What to test:

  • Hover on a point that lies directly on top of the line.
  • Check whether the point tooltip overrides the line tooltip when both layers are hit.
  • Move slightly left or right and confirm that the line tooltip remains available by x position.
In [ ]:
base_df = pd.DataFrame({
    'x': np.arange(0, 9),
    'y': [1.0, 1.4, 1.3, 2.0, 2.4, 2.1, 2.8, 2.6, 3.0]
})
hl_df = base_df.iloc[[2, 4, 6]].copy()
hl_df['kind'] = ['p1', 'p2', 'p3']

(ggplot(base_df, aes(x='x', y='y'))
 + ggtitle('Univariate: line + point')
 + geom_line(
     color='#33a02c',
     size=1.4,
     tooltips=layer_tooltips().line('line')
 )
 + geom_point(
     data=hl_df,
     size=6,
     color='#ff7f00',
     tooltips=layer_tooltips().line('point @kind')
 ))

Bivariate Layers

For these plots, tooltip visibility is expected to depend on both x and y. This is the behavior to observe for geom_point, geom_polygon, and similar layers.

4. Two independent geom_point layers

What to test:

  • Hover in dense regions where points from different layers are close to each other.
  • Check that tooltip selection follows actual 2D proximity rather than only matching the same x.
  • Check whether nearby points from different layers switch correctly as the cursor moves diagonally.
In [ ]:
point_a = pd.DataFrame({
    'x': [1.0, 1.8, 2.8, 3.3, 4.0],
    'y': [1.0, 2.2, 1.4, 2.8, 1.8],
    'layer': ['A1', 'A2', 'A3', 'A4', 'A5']
})
point_b = pd.DataFrame({
    'x': [1.3, 2.0, 2.9, 3.6, 4.1],
    'y': [1.4, 2.0, 1.8, 2.5, 1.5],
    'layer': ['B1', 'B2', 'B3', 'B4', 'B5']
})

(ggplot()
 + ggtitle('Bivariate: two independent point layers')
 + geom_point(
     aes(x='x', y='y'),
     data=point_a,
     color='#6a3d9a',
     size=6,
     alpha=.8,
     tooltips=layer_tooltips().line('point A @layer')
 )
 + geom_point(
     aes(x='x', y='y'),
     data=point_b,
     color='#b15928',
     size=6,
     alpha=.8,
     tooltips=layer_tooltips().line('point B @layer')
 ))

5. geom_polygon + geom_point

What to test:

  • Hover inside polygon areas away from vertices and points.
  • Hover on points placed inside the polygon and near its border.
  • Check how polygon and point tooltips interact when the cursor is inside the polygon but also close to a point.
In [ ]:
poly_df = pd.DataFrame({
    'x': [0.6, 4.4, 4.0, 1.0],
    'y': [0.7, 1.0, 4.0, 3.6],
    'id': ['region'] * 4,
    'name': ['region'] * 4
})
poly_points = pd.DataFrame({
    'x': [1.2, 2.6, 3.5, 4.1],
    'y': [1.4, 2.6, 1.7, 3.5],
    'name': ['inner-1', 'inner-2', 'inner-3', 'edge-1']
})

(ggplot()
 + ggtitle('Bivariate: polygon + points')
 + geom_polygon(
     aes(x='x', y='y', group='id', fill='name'),
     data=poly_df,
     color='black',
     alpha=.35,
     tooltips=layer_tooltips().line('polygon @name')
 )
 + geom_point(
     aes(x='x', y='y'),
     data=poly_points,
     color='#d95f02',
     size=6,
     tooltips=layer_tooltips().line('point @name')
 )
 + xlim(0, 5)
 + ylim(0, 4.5))

Plot Objects Handled Together

This case is meant to check how multiple tooltip-enabled objects behave when they are rendered as one stacked column.

6. geom_bar(position='stack') with multiple objects in one column

What to test:

  • Hover the same x column at different y positions.
  • Check whether stacked objects remain individually addressable when several objects belong to the same visual column.
  • Pay special attention to duplicated fill values inside one column: visually they merge, but tooltip selection should still expose the underlying object.
In [ ]:
stack_df = pd.DataFrame({
    'x': ['A', 'A', 'A', 'A', 'B', 'B', 'B', 'B'],
    'segment': ['low', 'low', 'high', 'high', 'low', 'low', 'high', 'high'],
    'obj': ['A-low-1', 'A-low-2', 'A-high-1', 'A-high-2', 'B-low-1', 'B-low-2', 'B-high-1', 'B-high-2'],
    'value': [1.0, 1.8, 1.2, 0.9, 1.4, 0.7, 1.6, 1.1]
})

(ggplot(stack_df, aes(x='x', y='value', fill='segment'))
 + ggtitle("Stacked bar: multiple objects in one column")
 + geom_bar(
     stat='identity',
     position='stack',
     color='white',
     size=0.5,
     tooltips=layer_tooltips().line('@obj')
 ))

Additional bar edge cases: negative and zero values

What to test:

  • Hover negative bars below the baseline and check that they remain addressable across their full rendered height.
  • Hover the zero-height bar at the baseline and check whether it still exposes a tooltip object.
  • Compare vertical and horizontal orientations to confirm the same edge cases behave consistently after axis swap.
In [ ]:
bar_edge_df = pd.DataFrame({
    'cat': ['neg', 'zero', 'pos', 'neg2'],
    'value': [-2.5, 0.0, 3.2, -1.4]
})

bar_edge_v = (
    ggplot(bar_edge_df)
    + ggtitle('Bars: negative and zero values, vertical')
    + geom_bar(
        aes(x='cat', y='value', fill='cat'),
        stat='identity',
        orientation='x',
        tooltips=layer_tooltips().line('bar @cat')
    )
)

bar_edge_h = (
    ggplot(bar_edge_df)
    + ggtitle('Bars: negative and zero values, horizontal')
    + geom_bar(
        aes(y='cat', x='value', fill='cat'),
        stat='identity',
        orientation='y',
        tooltips=layer_tooltips().line('bar @cat')
    )
)

gggrid([bar_edge_v, bar_edge_h])

Orientation-Switching Interval Geoms

These are geoms that can be rendered in a vertical form using x + ymin / ymax or in a horizontal form using y + xmin / xmax.

Tooltip testing here should confirm that hit detection follows the rendered interval and the active axis, not a hard-coded vertical-only or horizontal-only rule.

7. geom_errorbar: vertical vs horizontal

What to test:

  • Compare the same interval data rendered in vertical and horizontal orientation.
  • Check whether tooltip visibility follows the full rendered error bar, including whiskers.
  • Verify that switching orientation also switches the axis that primarily controls visibility.
In [ ]:
interval_df = pd.DataFrame({
    'cat': ['A', 'B', 'C', 'D'],
    'mid': [2.2, 3.4, 2.8, 4.0],
    'low': [1.4, 2.7, 1.9, 3.1],
    'high': [3.0, 4.2, 3.7, 4.9]
})

errorbar_v = (
    ggplot(interval_df)
    + ggtitle('geom_errorbar: vertical')
    + geom_errorbar(
        aes(x='cat', ymin='low', ymax='high'),
        width=.3,
        size=1.4,
        color='#1f78b4',
        tooltips=layer_tooltips().line('errorbar @cat')
    )
)

errorbar_h = (
    ggplot(interval_df)
    + ggtitle('geom_errorbar: horizontal')
    + geom_errorbar(
        aes(y='cat', xmin='low', xmax='high'),
        width=.3,
        size=1.4,
        color='#e31a1c',
        tooltips=layer_tooltips().line('errorbar @cat')
    )
)

gggrid([errorbar_v, errorbar_h])

8. geom_crossbar, geom_pointrange, geom_linerange

What to test:

  • Check geoms with and without a center value in both orientations.
  • For geom_crossbar and geom_pointrange, verify that the central mark is connected to the same tooltip object as the interval around it.
  • For geom_linerange, check whether hover behavior is consistent when only the span itself is rendered.
In [ ]:
center_df = pd.DataFrame({
    'cat': ['A', 'B', 'C'],
    'mid': [2.0, 3.3, 2.6],
    'low': [1.2, 2.5, 1.7],
    'high': [2.9, 4.1, 3.5]
})

crossbar_v = (
    ggplot(center_df)
    + ggtitle('crossbar: vertical')
    + geom_crossbar(
        aes(x='cat', y='mid', ymin='low', ymax='high', fill='cat'),
        color='black',
        alpha=.45,
        tooltips=layer_tooltips().line('crossbar @cat')
    )
)

crossbar_h = (
    ggplot(center_df)
    + ggtitle('crossbar: horizontal')
    + geom_crossbar(
        aes(y='cat', x='mid', xmin='low', xmax='high', fill='cat'),
        color='black',
        alpha=.45,
        tooltips=layer_tooltips().line('crossbar @cat')
    )
)

pointrange_v = (
    ggplot(center_df)
    + ggtitle('pointrange: vertical')
    + geom_pointrange(
        aes(x='cat', y='mid', ymin='low', ymax='high'),
        color='#33a02c',
        size=1.1,
        fatten=8,
        tooltips=layer_tooltips().line('pointrange @cat')
    )
)

pointrange_h = (
    ggplot(center_df)
    + ggtitle('pointrange: horizontal')
    + geom_pointrange(
        aes(y='cat', x='mid', xmin='low', xmax='high'),
        color='#ff7f00',
        size=1.1,
        fatten=8,
        tooltips=layer_tooltips().line('pointrange @cat')
    )
)

linerange_v = (
    ggplot(center_df)
    + ggtitle('linerange: vertical')
    + geom_linerange(
        aes(x='cat', ymin='low', ymax='high', color='cat'),
        size=3,
        tooltips=layer_tooltips().line('linerange @cat')
    )
)

linerange_h = (
    ggplot(center_df)
    + ggtitle('linerange: horizontal')
    + geom_linerange(
        aes(y='cat', xmin='low', xmax='high', color='cat'),
        size=3,
        tooltips=layer_tooltips().line('linerange @cat')
    )
)

gggrid([
    crossbar_v, crossbar_h,
    pointrange_v, pointrange_h,
    linerange_v, linerange_h
], ncol=2)

9. geom_ribbon: horizontal vs vertical ribbon

What to test:

  • Check whether a ribbon behaves as univariate along its driving axis after orientation changes.
  • Hover near both interval boundaries and in the filled interior.
  • Verify that tooltip values are consistent for lower and upper bounds in both orientations.
In [ ]:
series = np.arange(1, 7)
ribbon_df = pd.DataFrame({
    'step': series,
    'low': [1.0, 1.4, 1.8, 1.6, 2.0, 2.3],
    'high': [2.2, 2.7, 3.1, 2.9, 3.3, 3.7]
})

ribbon_v = (
    ggplot(ribbon_df)
    + ggtitle('geom_ribbon: vertical')
    + geom_ribbon(
        aes(x='step', ymin='low', ymax='high'),
        fill='#a6cee3',
        color='#1f78b4',
        alpha=.6,
        tooltips=layer_tooltips().line('ribbon @step')
    )
)

ribbon_h = (
    ggplot(ribbon_df)
    + ggtitle('geom_ribbon: horizontal')
    + geom_ribbon(
        aes(y='step', xmin='low', xmax='high'),
        fill='#fdbf6f',
        color='#ff7f00',
        alpha=.6,
        tooltips=layer_tooltips().line('ribbon @step')
    )
)

gggrid([ribbon_v, ribbon_h])

Geoms With Explicit orientation

These geoms expose an orientation parameter in the API. This section is meant to check that tooltip hit testing stays correct when orientation is forced explicitly instead of relying on automatic detection.

10. geom_bar with explicit orientation

What to test:

  • Compare vertical bars with orientation='x' and horizontal bars with orientation='y'.
  • Check whether tooltip visibility follows the axis implied by the forced orientation.
  • Check whether forcing orientation changes only rendering direction, not tooltip content or object identity.
In [ ]:
bar_df = pd.DataFrame({
    'cat': ['A', 'B', 'C', 'D'],
    'value': [3.0, 5.0, 2.5, 4.0]
})

bar_v = (
    ggplot(bar_df)
    + ggtitle("geom_bar: orientation='x'")
    + geom_bar(
        aes(x='cat', y='value', fill='cat'),
        stat='identity',
        orientation='x',
        tooltips=layer_tooltips().line('bar @cat')
    )
)

bar_h = (
    ggplot(bar_df)
    + ggtitle("geom_bar: orientation='y'")
    + geom_bar(
        aes(y='cat', x='value', fill='cat'),
        stat='identity',
        orientation='y',
        tooltips=layer_tooltips().line('bar @cat')
    )
)

gggrid([bar_v, bar_h])

11. geom_density with explicit orientation

What to test:

  • Compare density rendered along x versus along y with the same source values.
  • Check whether tooltip visibility follows the driving axis selected by explicit orientation.
  • Check whether filled interior and boundary line produce consistent tooltip behavior after orientation changes.
In [ ]:
values = np.concatenate([
    np.random.normal(loc=-0.7, scale=0.45, size=250),
    np.random.normal(loc=1.1, scale=0.6, size=250)
])
density_df = pd.DataFrame({'value': values})

density_x = (
    ggplot(density_df)
    + ggtitle("geom_density: orientation='x'")
    + geom_density(
        aes(x='value'),
        orientation='x',
        fill='#a6cee3',
        color='#1f78b4',
        alpha=.55,
        tooltips=layer_tooltips().line('density')
    )
)

density_y = (
    ggplot(density_df)
    + ggtitle("geom_density: orientation='y'")
    + geom_density(
        aes(y='value'),
        orientation='y',
        fill='#fdbf6f',
        color='#ff7f00',
        alpha=.55,
        tooltips=layer_tooltips().line('density')
    )
)

gggrid([density_x, density_y])

12. geom_boxplot with explicit orientation

What to test:

  • Compare vertical and horizontal boxplots produced with explicit orientation.
  • Check tooltip behavior on whiskers, box body, and median line after forcing orientation.
  • Check whether hover selection stays stable near narrow whisker ends where orientation matters most.
In [ ]:
box_a = np.random.normal(loc=1.5, scale=0.35, size=120)
box_b = np.random.normal(loc=2.4, scale=0.5, size=120)
box_c = np.random.normal(loc=3.1, scale=0.4, size=120)
box_df = pd.DataFrame({
    'group': ['A'] * 120 + ['B'] * 120 + ['C'] * 120,
    'value': np.concatenate([box_a, box_b, box_c])
})

boxplot_v = (
    ggplot(box_df)
    + ggtitle("geom_boxplot: orientation='x'")
    + geom_boxplot(
        aes(x='group', y='value', fill='group'),
        orientation='x',
        tooltips=layer_tooltips().line('boxplot @group')
    )
)

boxplot_h = (
    ggplot(box_df)
    + ggtitle("geom_boxplot: orientation='y'")
    + geom_boxplot(
        aes(y='group', x='value', fill='group'),
        orientation='y',
        tooltips=layer_tooltips().line('boxplot @group')
    )
)

gggrid([boxplot_v, boxplot_h])

13. geom_lollipop with explicit orientation

What to test:

  • Check both the stem and the point head in vertical and horizontal orientation.
  • Verify that the tooltip object stays unified across the full lollipop shape after forcing orientation.
  • Pay attention to hover near the intercept side versus the point side, where hit logic may differ.
In [ ]:
lollipop_df = pd.DataFrame({
    'cat': ['A', 'B', 'C', 'D'],
    'value': [1.6, 2.8, 2.1, 3.4]
})

lollipop_x = (
    ggplot(lollipop_df)
    + ggtitle("geom_lollipop: orientation='x'")
    + geom_lollipop(
        aes(x='cat', y='value', color='cat'),
        orientation='x',
        size=1.2,
        tooltips=layer_tooltips().line('lollipop @cat')
    )
)

lollipop_y = (
    ggplot(lollipop_df)
    + ggtitle("geom_lollipop: orientation='y'")
    + geom_lollipop(
        aes(y='cat', x='value', color='cat'),
        orientation='y',
        size=1.2,
        tooltips=layer_tooltips().line('lollipop @cat')
    )
)

gggrid([lollipop_x, lollipop_y])

Common Real-Life Layer Combinations

These are combinations that show up frequently in real plots. They should be tested without anchor first, then with anchored tooltips to exercise crosshair on the same patterns.

geom_smooth without anchor: alone, with univariate, with bivariate, with both

What to test:

  • Check geom_smooth alone as a baseline.
  • Compare geom_smooth with a univariate layer, with a bivariate layer, and with both layer types together.
  • Verify which layer wins selection when smooth competes with line and/or point layers at nearby positions.
In [ ]:
smooth_mix_df = pd.DataFrame({
    'x': np.linspace(0, 8, 80)
})
smooth_mix_df['y'] = 1.3 + np.sin(smooth_mix_df['x']) + 0.18 * np.cos(smooth_mix_df['x'] * 2.6)

line_df = pd.DataFrame({
    'x': np.arange(0, 9),
    'y': [0.9, 1.6, 1.2, 2.0, 1.7, 2.3, 2.0, 2.6, 2.2]
})

point_df = pd.DataFrame({
    'x': [0.8, 2.0, 3.6, 5.2, 6.8],
    'y': [1.9, 1.2, 2.4, 1.8, 2.7],
    'id': ['p1', 'p2', 'p3', 'p4', 'p5']
})

smooth_only = (
    ggplot(smooth_mix_df, aes(x='x', y='y'))
    + ggtitle('smooth alone')
    + geom_smooth(
        method='loess',
        se=False,
        color='#1f78b4',
        size=1.4,
        tooltips=layer_tooltips().line('smooth')
    )
)

smooth_univariate = (
    ggplot(smooth_mix_df, aes(x='x', y='y'))
    + ggtitle('smooth + univariate')
    + geom_line(
        aes(x='x', y='y'),
        data=line_df,
        color='#33a02c',
        size=1.2,
        tooltips=layer_tooltips().line('line')
    )
    + geom_smooth(
        method='loess',
        se=False,
        color='#1f78b4',
        size=1.4,
        tooltips=layer_tooltips().line('smooth')
    )
)

smooth_bivariate = (
    ggplot(smooth_mix_df, aes(x='x', y='y'))
    + ggtitle('smooth + bivariate')
    + geom_point(
        aes(x='x', y='y'),
        data=point_df,
        size=6,
        color='#ff7f00',
        tooltips=layer_tooltips().line('point @id')
    )
    + geom_smooth(
        method='loess',
        se=False,
        color='#1f78b4',
        size=1.4,
        tooltips=layer_tooltips().line('smooth')
    )
)

smooth_both = (
    ggplot(smooth_mix_df, aes(x='x', y='y'))
    + ggtitle('smooth + univariate + bivariate')
    + geom_line(
        aes(x='x', y='y'),
        data=line_df,
        color='#33a02c',
        size=1.2,
        tooltips=layer_tooltips().line('line')
    )
    + geom_point(
        aes(x='x', y='y'),
        data=point_df,
        size=6,
        color='#ff7f00',
        tooltips=layer_tooltips().line('point @id')
    )
    + geom_smooth(
        method='loess',
        se=False,
        color='#1f78b4',
        size=1.4,
        tooltips=layer_tooltips().line('smooth')
    )
)

gggrid([
    smooth_only,
    smooth_univariate,
    smooth_bivariate,
    smooth_both
], ncol=2)

Common real-life combinations without anchor

What to test:

  • geom_bar + geom_errorbar: a common summary chart pattern.
  • geom_boxplot + geom_jitter: distribution summary plus raw points.
  • geom_line + geom_ribbon: line with uncertainty / confidence band.
  • Check which layer gets selected in overlap zones and near boundaries.
In [ ]:
summary_df = pd.DataFrame({
    'cat': ['A', 'B', 'C', 'D'],
    'value': [2.1, 3.4, 2.7, 4.0],
    'low': [1.7, 2.9, 2.2, 3.4],
    'high': [2.5, 3.9, 3.2, 4.6]
})

bar_error_plot = (
    ggplot(summary_df)
    + ggtitle('bar + errorbar')
    + geom_bar(
        aes(x='cat', y='value', fill='cat'),
        stat='identity',
        tooltips=layer_tooltips().line('bar @cat')
    )
    + geom_errorbar(
        aes(x='cat', ymin='low', ymax='high'),
        width=.22,
        size=1.2,
        color='black',
        tooltips=layer_tooltips().line('errorbar @cat')
    )
)

box_jitter_df = pd.DataFrame({
    'group': ['A'] * 36 + ['B'] * 36 + ['C'] * 36,
    'value': np.concatenate([
        np.random.normal(1.4, 0.28, 36),
        np.random.normal(2.2, 0.35, 36),
        np.random.normal(2.8, 0.30, 36)
    ])
})

box_jitter_plot = (
    ggplot(box_jitter_df)
    + ggtitle('boxplot + jitter')
    + geom_boxplot(
        aes(x='group', y='value', fill='group'),
        alpha=.45,
        tooltips=layer_tooltips().line('boxplot @group')
    )
    + geom_jitter(
        aes(x='group', y='value', color='group'),
        width=.12,
        size=3,
        alpha=.75,
        tooltips=layer_tooltips().line('point @group')
    )
)

band_line_df = pd.DataFrame({
    'x': np.arange(1, 9),
    'y': [1.2, 1.7, 1.5, 2.1, 2.0, 2.4, 2.3, 2.7],
    'low': [0.8, 1.3, 1.1, 1.7, 1.6, 2.0, 1.9, 2.3],
    'high': [1.6, 2.1, 1.9, 2.5, 2.4, 2.8, 2.7, 3.1]
})

line_ribbon_plot = (
    ggplot(band_line_df)
    + ggtitle('line + ribbon')
    + geom_ribbon(
        aes(x='x', ymin='low', ymax='high'),
        fill='#a6cee3',
        color='#1f78b4',
        alpha=.45,
        tooltips=layer_tooltips().line('ribbon')
    )
    + geom_line(
        aes(x='x', y='y'),
        color='#e31a1c',
        size=1.3,
        tooltips=layer_tooltips().line('line')
    )
)

gggrid([
    bar_error_plot,
    box_jitter_plot,
    line_ribbon_plot
], ncol=2)

Anchored Variants

These plots repeat the same combination patterns with anchored tooltips. Anchor is used here to exercise crosshair on top of the base interaction tests.

Anchored versions of common combinations

What to test:

  • Check that crosshair appears when tooltips are anchored.
  • Compare the anchored behavior with the non-anchored versions of the same combinations.
  • Verify that crosshair and selected tooltip stay synchronized when layers overlap or compete.
In [ ]:
anch_hist_density = (
    ggplot(u_df, aes(x='x'))
    + ggtitle('anchored: histogram + density')
    + geom_histogram(
        aes(fill='grp'),
        bins=18,
        alpha=.45,
        position='identity',
        tooltips=layer_tooltips().line('histogram @grp').anchor('top_left')
    )
    + geom_density(
        aes(color='grp'),
        size=1.2,
        tooltips=layer_tooltips().line('density @grp').anchor('top_right')
    )
)

anch_line_line = (
    ggplot()
    + ggtitle('anchored: line + line')
    + geom_line(
        aes(x='x', y='y'),
        data=line_a,
        color='#1f78b4',
        size=1.3,
        tooltips=layer_tooltips().line('line A').anchor('top_left')
    )
    + geom_line(
        aes(x='x', y='y'),
        data=line_b,
        color='#e31a1c',
        size=1.3,
        tooltips=layer_tooltips().line('line B').anchor('top_right')
    )
)

anch_line_point = (
    ggplot(base_df, aes(x='x', y='y'))
    + ggtitle('anchored: line + point')
    + geom_line(
        color='#33a02c',
        size=1.4,
        tooltips=layer_tooltips().line('line').anchor('top_left')
    )
    + geom_point(
        data=hl_df,
        size=6,
        color='#ff7f00',
        tooltips=layer_tooltips().line('point @kind').anchor('bottom_right')
    )
)

anch_point_point = (
    ggplot()
    + ggtitle('anchored: point + point')
    + geom_point(
        aes(x='x', y='y'),
        data=point_a,
        color='#6a3d9a',
        size=6,
        alpha=.8,
        tooltips=layer_tooltips().line('point A @layer').anchor('top_left')
    )
    + geom_point(
        aes(x='x', y='y'),
        data=point_b,
        color='#b15928',
        size=6,
        alpha=.8,
        tooltips=layer_tooltips().line('point B @layer').anchor('top_right')
    )
)

anch_polygon_point = (
    ggplot()
    + ggtitle('anchored: polygon + point')
    + geom_polygon(
        aes(x='x', y='y', group='id', fill='name'),
        data=poly_df,
        color='black',
        alpha=.35,
        tooltips=layer_tooltips().line('polygon @name').anchor('top_left')
    )
    + geom_point(
        aes(x='x', y='y'),
        data=poly_points,
        color='#d95f02',
        size=6,
        tooltips=layer_tooltips().line('point @name').anchor('bottom_right')
    )
    + xlim(0, 5)
    + ylim(0, 4.5)
)

anch_stack = (
    ggplot(stack_df, aes(x='x', y='value', fill='segment'))
    + ggtitle('anchored: stacked bar')
    + geom_bar(
        stat='identity',
        position='stack',
        color='white',
        size=0.5,
        tooltips=layer_tooltips().line('@obj').anchor('top_center')
    )
)

anch_bar_error = (
    ggplot(summary_df)
    + ggtitle('anchored: bar + errorbar')
    + geom_bar(
        aes(x='cat', y='value', fill='cat'),
        stat='identity',
        tooltips=layer_tooltips().line('bar @cat').anchor('top_left')
    )
    + geom_errorbar(
        aes(x='cat', ymin='low', ymax='high'),
        width=.22,
        size=1.2,
        color='black',
        tooltips=layer_tooltips().line('errorbar @cat').anchor('top_right')
    )
)

anch_box_jitter = (
    ggplot(box_jitter_df)
    + ggtitle('anchored: boxplot + jitter')
    + geom_boxplot(
        aes(x='group', y='value', fill='group'),
        alpha=.45,
        tooltips=layer_tooltips().line('boxplot @group').anchor('top_left')
    )
    + geom_jitter(
        aes(x='group', y='value', color='group'),
        width=.12,
        size=3,
        alpha=.75,
        tooltips=layer_tooltips().line('point @group').anchor('bottom_right')
    )
)

anch_line_ribbon = (
    ggplot(band_line_df)
    + ggtitle('anchored: line + ribbon')
    + geom_ribbon(
        aes(x='x', ymin='low', ymax='high'),
        fill='#a6cee3',
        color='#1f78b4',
        alpha=.45,
        tooltips=layer_tooltips().line('ribbon').anchor('top_left')
    )
    + geom_line(
        aes(x='x', y='y'),
        color='#e31a1c',
        size=1.3,
        tooltips=layer_tooltips().line('line').anchor('top_right')
    )
)

anch_smooth_only = (
    ggplot(smooth_mix_df, aes(x='x', y='y'))
    + ggtitle('anchored: smooth alone')
    + geom_smooth(
        method='loess',
        se=False,
        color='#1f78b4',
        size=1.4,
        tooltips=layer_tooltips().line('smooth').anchor('top_right')
    )
)

anch_smooth_uni = (
    ggplot(smooth_mix_df, aes(x='x', y='y'))
    + ggtitle('anchored: smooth + univariate')
    + geom_line(
        aes(x='x', y='y'),
        data=line_df,
        color='#33a02c',
        size=1.2,
        tooltips=layer_tooltips().line('line').anchor('top_left')
    )
    + geom_smooth(
        method='loess',
        se=False,
        color='#1f78b4',
        size=1.4,
        tooltips=layer_tooltips().line('smooth').anchor('top_right')
    )
)

anch_smooth_bi = (
    ggplot(smooth_mix_df, aes(x='x', y='y'))
    + ggtitle('anchored: smooth + bivariate')
    + geom_point(
        aes(x='x', y='y'),
        data=point_df,
        size=6,
        color='#ff7f00',
        tooltips=layer_tooltips().line('point @id').anchor('bottom_right')
    )
    + geom_smooth(
        method='loess',
        se=False,
        color='#1f78b4',
        size=1.4,
        tooltips=layer_tooltips().line('smooth').anchor('top_right')
    )
)

anch_smooth_both = (
    ggplot(smooth_mix_df, aes(x='x', y='y'))
    + ggtitle('anchored: smooth + univariate + bivariate')
    + geom_line(
        aes(x='x', y='y'),
        data=line_df,
        color='#33a02c',
        size=1.2,
        tooltips=layer_tooltips().line('line').anchor('top_left')
    )
    + geom_point(
        aes(x='x', y='y'),
        data=point_df,
        size=6,
        color='#ff7f00',
        tooltips=layer_tooltips().line('point @id').anchor('bottom_right')
    )
    + geom_smooth(
        method='loess',
        se=False,
        color='#1f78b4',
        size=1.4,
        tooltips=layer_tooltips().line('smooth').anchor('top_right')
    )
)

gggrid([
    anch_hist_density,
    anch_line_line,
    anch_line_point,
    anch_point_point,
    anch_polygon_point,
    anch_stack,
    anch_bar_error,
    anch_box_jitter,
    anch_line_ribbon,
    anch_smooth_only,
    anch_smooth_uni,
    anch_smooth_bi,
    anch_smooth_both
], ncol=2)
In [ ]: