Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add tooltip histograms and support for non-visual properties #96

Merged
merged 43 commits into from
Nov 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
fed4348
feat: add histograms to tooltip
flekschas Sep 30, 2023
c51d5c9
feat: add support for non-visual properties in the tooltip
flekschas Oct 4, 2023
ef0e839
Bug fixes and respect contents ordering
flekschas Oct 5, 2023
8c374a6
Hide channel column if no visual channel contents is defined
flekschas Oct 5, 2023
785ac54
docs: describe new tooltip features
flekschas Oct 5, 2023
a02ea0b
Update
flekschas Oct 5, 2023
a93f797
Update docs/axes-legend-tooltip.md
flekschas Oct 8, 2023
c829712
Clarify function name
flekschas Oct 8, 2023
ef89bd0
Merge branch 'flekschas/tooltip-histograms' of github.com:flekschas/j…
flekschas Oct 20, 2023
759636c
Use @manzt rephrasing
flekschas Oct 20, 2023
3bba6d0
Add annotated tooltip image
flekschas Oct 20, 2023
0d49924
Switch to constant objects for better type inference
flekschas Oct 20, 2023
f2b5cce
Fix regression from renaming `showTooltipDb` to `showTooltipDebounced`
flekschas Oct 20, 2023
b717da2
Fix link
flekschas Oct 20, 2023
e3beab9
Extend description
flekschas Oct 20, 2023
82a862f
Update API docs
flekschas Oct 20, 2023
26f0740
Clamp long value text
flekschas Oct 21, 2023
2e7a410
Sort categorical histograms by their dict keys
flekschas Oct 21, 2023
1ede570
Merge branch 'main' into flekschas/tooltip-histograms
flekschas Oct 22, 2023
ed21d7c
Remove unused import
flekschas Oct 24, 2023
97f8eb2
Add support for custom # of histogram bins
flekschas Oct 24, 2023
812c250
docs: add numerical histogram customization
flekschas Oct 24, 2023
ab20493
Merge branch 'flekschas/tooltip-histograms' of github.com:flekschas/j…
flekschas Oct 24, 2023
1dc83ea
feat: add ability to customize the histograms width
flekschas Oct 24, 2023
1a7bcd5
Fix typo
flekschas Oct 25, 2023
25834a9
fix: update histograms prior to the encoding
flekschas Oct 26, 2023
c9079e1
feat: add support for custom histogram ranges
flekschas Oct 27, 2023
45dc0bd
feat: support content-specific num bins
flekschas Oct 27, 2023
3a771e7
Update vitepress
flekschas Oct 30, 2023
f486a2f
Fix histogram ranges and visualize out of range values
flekschas Oct 30, 2023
9642a38
docs: describe tooltip histogram ranges
flekschas Oct 30, 2023
a682f7e
Update and simplify 3rd party dependencies
flekschas Oct 30, 2023
7a52cd5
Switch to treemaps for categorical histograms. Rename `histograms_wid…
flekschas Nov 4, 2023
70ae8ae
Skip highlighting for unknown categorical keys
flekschas Nov 5, 2023
04cf289
Simplify
flekschas Nov 5, 2023
053e3ba
Give more space for longer labels
flekschas Nov 5, 2023
049a3bb
fix: sanitize content to valid HTML class name
flekschas Nov 13, 2023
f97eee1
docs: add example for a treemap histogram
flekschas Nov 14, 2023
6512f49
Rename the tooltip argument `contents` to `properties`
flekschas Nov 23, 2023
3ef2d43
fix: rename first `tooltip()` argument and fix typos
flekschas Nov 25, 2023
2acc343
Merge branch 'main' into flekschas/tooltip-histograms
flekschas Nov 25, 2023
331b425
Merge branch 'flekschas/tooltip-histograms' of github.com:flekschas/j…
flekschas Nov 25, 2023
f5a335c
fix: renaming regression
flekschas Nov 25, 2023
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
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
## v0.15.0

- Allow mixing custom and DataFrame-based data ([#89](https://github.com/flekschas/jupyter-scatter/issues/89))
- Feat: Add support for histograms in the tooltip ([#96](https://github.com/flekschas/jupyter-scatter/pull/96))
- Feat: Add support for non-visualized properties in the toolip ([#96](https://github.com/flekschas/jupyter-scatter/pull/96))
- Fix: Allow mixing custom and DataFrame-based data ([#89](https://github.com/flekschas/jupyter-scatter/issues/89))

## v0.14.3

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@

1. [Install](#install)
2. [Get Started](#get-started)
3. [API docs](docs/api.md)
3. [Docs](https://jupyter-scatter.dev)
4. [Examples](notebooks/examples.ipynb)
5. [Development](#development)

Expand Down
22 changes: 17 additions & 5 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -353,25 +353,37 @@ Set or get the legend settings.
**Example:**

```python
scatter.legend(true, 'top-right', 'small')
scatter.legend(True, 'top-right', 'small')
```


### scatter.tooltip(_enable=Undefined_, _contents=Undefined_, _size=Undefined_) {#scatter.tooltip}
### scatter.tooltip(_enable=Undefined_, _properties=Undefined_, _size=Undefined_, _histograms=Undefined_, _histograms_bins=Undefined_, _histograms_ranges=Undefined_, _histograms_size=Undefined_) {#scatter.tooltip}

Set or get the tooltip settings.

**Arguments:**
- `enable` is a Boolean specifying if the tooltip should be enabled or disabled.
- `contents` is either `"all"` or a set of string specifying for which visual channels the data should be shown in the tooltip. It can be some of `x`, `y`, `color`, `opacity`, and `size`.
- `size` is a string specifying the size of the legend. It must be one of `small`, `medium`, or `large`.
- `properties` is a list of string specifying for which visual or data properties to show in the tooltip. The visual properties can be some of `x`, `y`, `color`, `opacity`, and `size`. Note that visual properties are only shown if they are actually used to data properties. To reference other data properties, specify a column of the bound DataFrame by its name.
- `size` is a string specifying the size of the tooltip. It must be one of `small`, `medium`, or `large`. The default is `"small"`.
- `histograms` is a Boolean specifying if the tooltip should show histograms of the properties
- `histograms_bins` is either an Integer specifying the number of bins of all numerical histograms or a dictionary of property-specific number of bins. The default is `20`.
- `histograms_ranges` is either a tuple of the lower and upper range of all bins or a dictionary of property-specific lower upper bin ranges. The default is `(min(), max())`.
- `histograms_size` is a string specifying the size of the histograms. It must be one of `small`, `medium`, or `large`. The default is `"small"`.

**Returns:** either the legend properties when all arguments are `Undefined` or `self`.

**Example:**

```python
scatter.tooltip(true, 'all', 'small')
scatter.tooltip(
enable=True,
properties=["color", "opacity", "effect_size"],
size="small",
histograms=True,
histograms_bins=12,
histograms_ranges={"effect_size": (0.5, 1.5)},
histograms_width="medium",
)
```


Expand Down
229 changes: 218 additions & 11 deletions docs/axes-legend-tooltip.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ When you encode data properties with the point color, opacity, or size, it's
immensly helpful to know how the encoded data properties relate to the visual
properties by showing a legend.

```py{18-20}
```py{22-24}
import jscatter
import numpy as np
import pandas as pd
Expand All @@ -56,8 +56,12 @@ df = pd.DataFrame({
"mass": np.random.rand(500),
"speed": np.random.rand(500),
"pval": np.random.rand(500),
# Gaussian-distributed floats
"effect_size": np.random.normal(.5, .2, 500),
# Random letters A, B, C, D, E, F, G, H
"cat": np.vectorize(lambda x: chr(65 + round(x * 8)))(np.random.rand(500)),
# Random letters X, Y, Z
"group": np.vectorize(lambda x: chr(88 + round(x * 2)))(np.random.rand(500)),
})

scatter = jscatter.Scatter(
Expand Down Expand Up @@ -102,25 +106,134 @@ scatter.color(labeling={

## Tooltip

Having a legend is necessary to understand the mapping of data to visual
properties but it requires quite a bit of eye movement from the point (you're
currently focusing on) to the legend and back. To make this more efficient,
Jupyter Scatter can show a tooltip explaining a point's encoded properties.
Legends depict how data are mapped to visual properties, yet require repeated
eye movement between individual points and the legend for accurate
interpretation. Jupyter Scatter supports a tooltip to show a point's encoded
properties and related details, alleviating this strain.

```py
scatter.tooltip(True)
```

<div class="img tooltip-1"><div /></div>

By default, the tooltip has an entry for all encoded properties. You can limit
the contents of the tooltip as follows:
Each row in the tooltip corresponds to a property. From left to right, each
property features the:

1. visual property (like `x`, `y`, `color`, `opacity`, or `size`) or data property
2. name as specified by the column name in the bound DataFrame
3. actual data value
4. histogram or treemap of the property distribution

<div class="img tooltip-2"><div /></div>

For numerical properties, the histogram is visualized as a bar chart. For
categorical properties, the histogram is visualized as a
flat [treemap](https://en.wikipedia.org/wiki/Treemapping) where the rectangles
represents the proportion of categories compared to the whole. Treemaps are
useful in scenarios with a lot of categories as shown below.

<div class="img tooltip-treemap"><div /></div>

In both cases, the highlighted bar indicates how the hovered point compares to
the other points.
flekschas marked this conversation as resolved.
Show resolved Hide resolved

By default, the tooltip shows all properties that are visually encoded but you
can limit which properties are shown:

```py
scatter.tooltip(contents=["color", "opacity"])
scatter.tooltip(properties=["color", "opacity"])
```

<div class="img tooltip-2"><div /></div>
<div class="img tooltip-3"><div /></div>

Importantly, you can also show other properties in the tooltip that are not
directly visualized with the scatter plot. Other properties have to be
referenced by their respective column names.

```py{5-6}
scatter.tooltip(
properties=[
"color",
"opacity",
"group",
"effect_size",
]
)
```

<div class="img tooltip-4"><div /></div>

Here, for instance, we're showing the point's `group` and `effect_size`
properties, which are two other DataFrame columns we didn't visualize.

::: tip
The order of `properties` defines the order of the entries.
:::

### Customizing Numerical Histograms

The histograms of numerical data properties consists of `20` bins, by default,
and is covering the entire data range, i.e., it starts at the minumum and ends
at the maximum value. You can adjust both aspects either globally for all
histograms as follows:

```py
scatter.tooltip(histograms_bins=40, histograms_ranges=(0, 1))
```

<div class="img tooltip-5"><div /></div>

To customize the number of bins and the range by property you can do:

```py
scatter.tooltip(
histograms_bins={"color": 10, "effect_size": 30},
histograms_ranges={"color": (0, 1), "effect_size": (0.25, 0.75)}
)
```

<div class="img tooltip-6"><div /></div>

Since an increased number of bins can make it harder to read the histogram, you
can adjust the size as follows:

```py
scatter.tooltip(histograms_size="large")
```

<div class="img tooltip-7"><div /></div>

If you set the histogram range to be smaller than the data extent, some points
might lie outside the histogram. For instance, previously we restricted the
`effect_size` to `[0.25, 0.75]`, meaning we disregarded part of the lower and
upper end of the data.

In this case, hovering a point with an `effect_size` less than `.5` will be
visualized by a red `]` to the left of the histogram to indicate it's value is
smaller than the value represented by the left-most bar.

<div class="img tooltip-8"><div /></div>

Likewise, hovering a point with an `effect_size` larger than `0.75` will be
visualized by a red `[` to the right of the histogram to indicate it's value is
larger than the value represented by the right-most bar.

<div class="img tooltip-9"><div /></div>

Finally, if you want to transform the histogram in some other way, use your
favorite method and save the transformed data before referencing it. For
instance, in the following, we winsorized the `effect_size` to the `[10, 90]`
percentile:

```py
from scipy.stats.mstats import winsorize

df['effect_size_winsorized'] = winsorize(df.effect_size, limits=[0.1, 0.1])
scatter.tooltip(properties=['effect_size_winsorized'])
```

<div class="img tooltip-10"><div /></div>

<style scoped>
.img {
Expand Down Expand Up @@ -201,12 +314,106 @@ scatter.tooltip(contents=["color", "opacity"])
}

.img.tooltip-2 {
width: 596px;
width: 960px;
background-image: url(/images/tooltip-2-light.png)
}
.img.tooltip-2 div { padding-top: 48.489933% }
.img.tooltip-2 div { padding-top: 47.916667% }

:root.dark .img.tooltip-2 {
background-image: url(/images/tooltip-2-dark.png)
}

.img.tooltip-treemap {
width: 1064px;
background-image: url(/images/tooltip-treemap-light.jpg)
}
.img.tooltip-treemap div { padding-top: 40.225564% }

:root.dark .img.tooltip-treemap {
width: 1050px;
background-image: url(/images/tooltip-treemap-dark.jpg)
}
:root.dark .img.tooltip-treemap div { padding-top: 41.333333% }

.img.tooltip-3 {
width: 596px;
background-image: url(/images/tooltip-3-light.png)
}
.img.tooltip-3 div { padding-top: 48.489933% }

:root.dark .img.tooltip-3 {
background-image: url(/images/tooltip-3-dark.png)
}

.img.tooltip-4 {
width: 606px;
background-image: url(/images/tooltip-4-light.png)
}
.img.tooltip-4 div { padding-top: 38.283828% }

:root.dark .img.tooltip-4 {
background-image: url(/images/tooltip-4-dark.png)
}

.img.tooltip-5 {
width: 616px;
background-image: url(/images/tooltip-5-light.png)
}
.img.tooltip-5 div { padding-top: 39.61039% }

:root.dark .img.tooltip-5 {
background-image: url(/images/tooltip-5-dark.png)
}

.img.tooltip-6 {
width: 678px;
background-image: url(/images/tooltip-6-light.png)
}
.img.tooltip-6 div { padding-top: 33.628319% }

:root.dark .img.tooltip-6 {
background-image: url(/images/tooltip-6-dark.png)
}

.img.tooltip-7 {
width: 678px;
background-image: url(/images/tooltip-7-light.png)
}
.img.tooltip-7 div { padding-top: 33.628319% }

:root.dark .img.tooltip-7 {
background-image: url(/images/tooltip-7-dark.png)
}

.img.tooltip-8 {
width: 674px;
background-image: url(/images/tooltip-8-light.png)
}
.img.tooltip-8 div { padding-top: 34.124629% }

:root.dark .img.tooltip-8 {
background-image: url(/images/tooltip-8-dark.png)
}

.img.tooltip-9 {
width: 692px;
background-image: url(/images/tooltip-9-light.png)
}
.img.tooltip-9 div { padding-top: 33.526012% }

:root.dark .img.tooltip-9 {
background-image: url(/images/tooltip-9-dark.png)
}

.img.tooltip-10 {
width: 696px;
background-image: url(/images/tooltip-10-light.png)
}
.img.tooltip-10 div { padding-top: 17.816092% }

:root.dark .img.tooltip-10 {
width: 684px;
background-image: url(/images/tooltip-10-dark.png)
}
:root.dark .img.tooltip-10 div { padding-top: 15.789474% }
</style>
Loading