predict-idlab / plotly-resampler

Visualize large time series data with plotly.py
https://predict-idlab.github.io/plotly-resampler/latest
MIT License
990 stars 67 forks source link

line plot ends cutoff after last visible point #257

Open cubano21 opened 10 months ago

cubano21 commented 10 months ago

Hi, First of all, thanks for this great package! I recently started using plotly but was running into serious slowdowns trying to load simulated waveforms with large numbers of points. This is perfect for quickly showing the plots, but still being able to dive down and check low level details.

I noticed something while using it, however, which is that when zooming in on line plots the lines will cutoff after the last visible data point: image

This behavior makes sense when you consider that the resampled data effectively ends there, but in my opinion it would look nicer and feel more natural if the line ends didn't disappear and reappear depending on the exact zoom coordinates. In my case the waveform data are generated by a simulator with a dynamic time step, making the points somewhat sparse when nothing is happening, which can lead to large "missing ends" when zooming.

I think this can be improved by adding the 2 points just before and after the zoom window before resampling, which shouldn't add much overhead. I tested the idea with a simple dynamic patch that tweaks the start and end indexes before they get sent to the sampler and it seems to work well in my limited testing, but I haven't gotten to know your package well enough to know if there is a more appropriate way to do this or other unintended consequences.

Here's the code for my little hack (which probably won't handle any special cases):

from plotly_resampler.aggregation.plotly_aggregator_parser import PlotlyAggregatorParser

class patched_parser(PlotlyAggregatorParser):
    @staticmethod
    def get_start_end_indices(hf_trace_data, axis_type, start, end):
        start_idx, end_idx = PlotlyAggregatorParser.get_start_end_indices(hf_trace_data, axis_type, start, end)
        start_idx = max(0, start_idx-1)
        end_idx = min(len(hf_trace_data["x"]), end_idx+1)
        return start_idx, end_idx

from plotly_resampler.figure_resampler import figure_resampler_interface
figure_resampler_interface.PlotlyAggregatorParser = patched_parser

and the same waveform as before: image

jonasvdd commented 8 months ago

Hi @cubano21,

Thank you for submitting this feature request. I can certainly see the added value of your proposed method, and will therefore look into incorporating this. Class inheritance, as proposed b you, certainly seem like a good start! :)

Out of interest, how do you style your plotly figures like that, they look very neat! :)

Kind regards, Jonas

cubano21 commented 8 months ago

Thanks!

I setup a plotly template that is mostly mimicking the defaults for matplotlib which I've gotten pretty used to, with the added gridlines. To apply it to all plots automatically I just updated the default template to add my changes on top of the standard plotly template.

template = go.layout.Template()
_font = dict(family='sans-serif', size=18)
_axis_layout = {
    'showline': True,
    'linecolor': 'black',
    'ticks': 'outside',
    'mirror': True,
    'gridcolor': 'rgba(0,0,0,0.3)',
    'griddash': 'dot',
    'zeroline': False,
}
template.update({
    'layout': {
        'plot_bgcolor': 'white',
        'margin': dict(r=50, l=50, t=50, b=50),
        'font': _font,
        'xaxis': _axis_layout,
        'yaxis': _axis_layout,
        'legend': dict(yanchor="top", y=0.99, xanchor="left", x=0.01, title=''),
    }
})
pio.templates['myTemplate'] = template
pio.templates.default = 'plotly+myTemplate'