-
-
Notifications
You must be signed in to change notification settings - Fork 64
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
Log support #207
Log support #207
Conversation
Code ⬇️ implementation which projects LTTB its buckets to equidistant log buckets. import numpy as np
from plotly_resampler.aggregation.aggregation_interface import DataPointSelector
from typing import Union
class LogLTTB(DataPointSelector):
@staticmethod
def _argmax_area(prev_x, prev_y, avg_next_x, avg_next_y, x_bucket, y_bucket) -> int:
"""Vectorized triangular area argmax computation.
Parameters
----------
prev_x : float
The previous selected point is x value.
prev_y : float
The previous selected point its y value.
avg_next_x : float
The x mean of the next bucket
avg_next_y : float
The y mean of the next bucket
x_bucket : np.ndarray
All x values in the bucket
y_bucket : np.ndarray
All y values in the bucket
Returns
-------
int
The index of the point with the largest triangular area.
"""
return np.abs(
x_bucket * (prev_y - avg_next_y)
+ y_bucket * (avg_next_x - prev_x)
+ (prev_x * avg_next_y - avg_next_x * prev_y)
).argmax()
def _arg_downsample(
self, x: Union[np.ndarray, None], y: np.ndarray, n_out: int, **kwargs
) -> np.ndarray:
"""TODO complete docs"""
# We need a valid x array to determing the x-range
assert x is not None, "x cannot be None for this downsampler"
# the log function to use
lf = np.log1p
offset = np.unique(
np.searchsorted(
x, np.exp(np.linspace(lf(x[0]), lf(x[-1]), n_out + 1)).astype(np.int64)
)
)
# Construct the output array
sampled_x = np.empty(len(offset) + 1, dtype="int64")
sampled_x[0] = 0
sampled_x[-1] = x.shape[0] - 1
# Convert x & y to int if it is boolean
if x.dtype == np.bool_:
x = x.astype(np.int8)
if y.dtype == np.bool_:
y = y.astype(np.int8)
a = 0
for i in range(len(offset) - 2):
a = (
self._argmax_area(
prev_x=x[a],
prev_y=y[a],
avg_next_x=np.mean(x[offset[i + 1] : offset[i + 2]]),
avg_next_y=y[offset[i + 1] : offset[i + 2]].mean(),
x_bucket=x[offset[i] : offset[i + 1]],
y_bucket=y[offset[i] : offset[i + 1]],
)
+ offset[i]
)
sampled_x[i + 1] = a
# ------------ EDGE CASE ------------
# next-average of last bucket = last point
sampled_x[-2] = (
self._argmax_area(
prev_x=x[a],
prev_y=y[a],
avg_next_x=x[-1], # last point
avg_next_y=y[-1],
x_bucket=x[offset[-2] : offset[-1]],
y_bucket=y[offset[-2] : offset[-1]],
)
+ offset[-2]
)
return sampled_x Code ⬇️ example which creates a log plot: from plotly_resampler.aggregation import NoGapHandler
n = 100_000
y = np.sin(np.arange(n) / 2_000) + np.random.randn(n) / 10
fr = FigureResampler(False)
fr.add_trace(
go.Scattergl(mode="lines+markers", marker_color=np.abs(y) / np.max(np.abs(y))),
hf_x=np.arange(n),
hf_y=y,
downsampler=LogLTTB(),
gap_handler=NoGapHandler(),
)
fr.update_xaxes(type="log")
fr.update_layout(template='plotly_white', title='log axis demo') |
Codecov Report
❗ Your organization is not using the GitHub App Integration. As a result you may experience degraded service beginning May 15th. Please install the Github App Integration for your organization. Read more. @@ Coverage Diff @@
## main #207 +/- ##
==========================================
+ Coverage 97.15% 97.16% +0.01%
==========================================
Files 13 13
Lines 985 989 +4
==========================================
+ Hits 957 961 +4
Misses 28 28
📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more |
plotly_resampler/figure_resampler/figure_resampler_interface.py
Outdated
Show resolved
Hide resolved
LGTM! |
Partially addresses #206
Closes #190
Specifically, log-axis their zoom functionality will be supported.
TODO: