Skip to content

Commit 574904e

Browse files
authored
✨ first draft of improved xaxis filtering (#250)
* ✨ first draft of improved xaxis filtering * 🙈 fix bug * 🙏 fix typeError * 🙏 update webdriver-manager * 🙏 fixating chromedriver version * Revert "🙏 fixating chromedriver version" This reverts commit 87bd318. * 🙏 test fixating chrome driver * 🙈 * 🙏 * 💨 remove webdriver manager * ✨ adding docs * 🖊️ review
1 parent 7c3bc80 commit 574904e

File tree

5 files changed

+48
-120
lines changed

5 files changed

+48
-120
lines changed

.github/workflows/test.yml

+4-1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ jobs:
4040
python-version: ${{ matrix.python-version }}
4141

4242
- uses: nanasess/setup-chromedriver@master
43+
with:
44+
# Optional: do not specify to match Chrome's version
45+
chromedriver-version: '114.0.5735.90'
4346

4447
- name: Install Poetry
4548
uses: snok/install-poetry@v1
@@ -73,4 +76,4 @@ jobs:
7376
if: ${{ always() }}
7477

7578
- name: Upload coverage to Codecov
76-
uses: codecov/codecov-action@v1
79+
uses: codecov/codecov-action@v1

plotly_resampler/figure_resampler/figure_resampler_interface.py

+43-64
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
__author__ = "Jonas Van Der Donckt, Jeroen Van Der Donckt, Emiel Deprost"
1010

11+
import itertools
1112
import re
1213
from abc import ABC
1314
from collections import namedtuple
@@ -393,12 +394,37 @@ def _nest_dict_rec(k: str, v: any, out: dict) -> None:
393394

394395
return trace
395396

397+
def _layout_xaxis_to_trace_xaxis_mapping(self) -> Dict[str, List[str]]:
398+
"""Construct a dict which maps the layout xaxis keys to the trace xaxis keys.
399+
400+
Returns
401+
-------
402+
Dict[str, List[str]]
403+
A dict with the layout xaxis values as keys and the trace its corresponding
404+
xaxis anchor value.
405+
406+
"""
407+
# edge case: an empty `go.Figure()` does not yet contain axes keys
408+
if self._grid_ref is None:
409+
return {"xaxis": ["x"]}
410+
411+
mapping_dict = {}
412+
for sub_plot in itertools.chain.from_iterable(self._grid_ref): # flattten
413+
sub_plot = [] if sub_plot is None else sub_plot
414+
for axes in sub_plot: # NOTE: you can have multiple axes in a subplot
415+
layout_xaxes = axes.layout_keys[0]
416+
trace_xaxes = axes.trace_kwargs["xaxis"]
417+
418+
# append the trace xaxis to the layout xaxis key its value list
419+
mapping_dict.setdefault(layout_xaxes, []).append(trace_xaxes)
420+
return mapping_dict
421+
396422
def _check_update_figure_dict(
397423
self,
398424
figure: dict,
399425
start: Optional[Union[float, str]] = None,
400426
stop: Optional[Union[float, str]] = None,
401-
xaxis_filter: str = None,
427+
layout_xaxis_filter: Optional[str] = None,
402428
updated_trace_indices: Optional[List[int]] = None,
403429
) -> List[int]:
404430
"""Check and update the traces within the figure dict.
@@ -421,8 +447,9 @@ def _check_update_figure_dict(
421447
The start time for the new resampled data view, by default None.
422448
stop : Union[float, str], optional
423449
The end time for the new resampled data view, by default None.
424-
xaxis_filter: str, optional
425-
Additional trace-update subplot filter, by default None.
450+
layout_xaxis_filter: str, optional
451+
Additional layout xaxis filter, e.g. the affected x-axis values by the
452+
triggered relayout event (e.g. xaxis), by default None.
426453
updated_trace_indices: List[int], optional
427454
List of trace indices that already have been updated, by default None.
428455
@@ -433,71 +460,23 @@ def _check_update_figure_dict(
433460
modalities which are updated.
434461
435462
"""
436-
xaxis_filter_short = None
437-
if xaxis_filter is not None:
438-
xaxis_filter_short = "x" + xaxis_filter.lstrip("xaxis")
439-
440463
if updated_trace_indices is None:
441464
updated_trace_indices = []
442465

466+
if layout_xaxis_filter is not None:
467+
layout_trace_mapping = self._layout_xaxis_to_trace_xaxis_mapping()
468+
# Retrieve the trace xaxis values that are affected by the relayout event
469+
trace_xaxis_filter: List[str] = layout_trace_mapping[layout_xaxis_filter]
470+
443471
for idx, trace in enumerate(figure["data"]):
444-
# We skip when the trace-idx already has been updated.
445-
if idx in updated_trace_indices:
472+
# We skip when (i) the trace-idx already has been updated or (ii) when
473+
# there is a layout_xaxis_filter and the trace xaxis is not in the filter
474+
if idx in updated_trace_indices or (
475+
layout_xaxis_filter is not None
476+
and trace.get("xaxis", "x") not in trace_xaxis_filter
477+
):
446478
continue
447479

448-
if xaxis_filter is not None:
449-
# the x-anchor of the trace is stored in the layout data
450-
if trace.get("yaxis") is None:
451-
# TODO In versions up until v0.8.2 we made the assumption that yaxis
452-
# = xaxis_filter_short. -> Why did we make this assumption?
453-
y_axis = "y" # + xaxis_filter[1:]
454-
else:
455-
y_axis = "yaxis" + trace.get("yaxis")[1:]
456-
457-
# Also check for overlaying traces - fixes #242
458-
overlaying = figure["layout"].get(y_axis, {}).get("overlaying")
459-
if overlaying:
460-
y_axis = "yaxis" + overlaying[1:]
461-
462-
# Next to the x-anchor, we also fetch the xaxis which matches the
463-
# current trace (i.e. if this value is not None, the axis shares the
464-
# x-axis with one or more traces).
465-
# This is relevant when e.g. fig.update_traces(xaxis='x...') was called.
466-
x_anchor_trace = figure["layout"].get(y_axis, {}).get("anchor")
467-
if x_anchor_trace is not None:
468-
xaxis_matches = (
469-
figure["layout"]
470-
.get("xaxis" + x_anchor_trace.lstrip("x"), {})
471-
.get("matches")
472-
)
473-
else:
474-
xaxis_matches = figure["layout"].get("xaxis", {}).get("matches")
475-
476-
# print(
477-
# f"x_anchor: {x_anchor_trace} - xaxis_filter: {xaxis_filter} ",
478-
# f"- xaxis_matches: {xaxis_matches}"
479-
# )
480-
481-
# We skip when:
482-
# * the change was made on the first row and the trace its anchor is not
483-
# in [None, 'x'] and the matching (a.k.a. shared) xaxis is not equal
484-
# to the xaxis filter argument.
485-
# -> why None: traces without row/col argument and stand on first row
486-
# and do not have the anchor property (hence the DICT.get() method)
487-
# * x_axis_filter_short not in [x_anchor or xaxis matches] for
488-
# NON first rows
489-
if (
490-
xaxis_filter_short == "x"
491-
and (
492-
x_anchor_trace not in (None, "x")
493-
and xaxis_matches != xaxis_filter_short
494-
)
495-
) or (
496-
xaxis_filter_short != "x"
497-
and (xaxis_filter_short not in (x_anchor_trace, xaxis_matches))
498-
):
499-
continue
500-
501480
# If we managed to find and update the trace, it will return the trace
502481
# and thus not None.
503482
updated_trace = self._check_update_trace_data(trace, start=start, end=stop)
@@ -1361,7 +1340,7 @@ def construct_update_data(
13611340
current_graph,
13621341
start=relayout_data[t_start_key],
13631342
stop=relayout_data[t_stop_key],
1364-
xaxis_filter=xaxis,
1343+
layout_xaxis_filter=xaxis,
13651344
updated_trace_indices=updated_trace_indices,
13661345
)
13671346

@@ -1377,7 +1356,7 @@ def construct_update_data(
13771356
xaxis = autorange_key.split(".")[0]
13781357
updated_trace_indices = self._check_update_figure_dict(
13791358
current_graph,
1380-
xaxis_filter=xaxis,
1359+
layout_xaxis_filter=xaxis,
13811360
updated_trace_indices=updated_trace_indices,
13821361
)
13831362
# 2.1. Autorange -> do nothing, the autorange will be applied on the

poetry.lock

+1-52
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ pytest = "^6.2.5"
5959
pytest-cov = "^3.0.0"
6060
selenium = "4.2.0"
6161
pytest-selenium = "^2.0.1"
62-
webdriver-manager = "^3.5.2"
6362
selenium-wire = "^5.0"
6463
pyarrow = "^10.0"
6564
ipywidgets = "^7.7.1"

tests/conftest.py

-2
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ def driver():
5252
from selenium.webdriver.chrome.options import Options
5353
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
5454
from seleniumwire import webdriver
55-
from webdriver_manager.chrome import ChromeDriverManager, ChromeType
5655

5756
time.sleep(1)
5857

@@ -64,7 +63,6 @@ def driver():
6463
options.add_argument("--headless")
6564
# options.add_argument("--no=sandbox")
6665
driver = webdriver.Chrome(
67-
ChromeDriverManager(chrome_type=ChromeType.GOOGLE).install(),
6866
options=options,
6967
desired_capabilities=d,
7068
)

0 commit comments

Comments
 (0)