How to stop scrolling/compressing at the ends on hover

Hi all,

First of all, many thanks for Plotly and Dash, and for making them open source. They are awesome tools!

I have a chart with a line/scatter trace and overlaid on a bar chart. I used a callback with hoverData so that, when I hover over a bar, a dot appears on the corresponding point of the line. The code below works fine except that when I hover over the bars on the left or right side of the plot, the chart scrolls/compresses leaving an empty gap of several days, like so:

I would like to disable this scrolling/compressing behaviour. Interestingly, when I modify the code such that the bars are highlighted rather than a dot on the line, the scrolling/compressing behaviour does not occur, as shown here:

I looked at the reference docs for the scatter plot to see if there was a setting that might replicate the bar chart behaviour but I couldn’t see it. There may be a difference in the coding under the hood but unfortunately my Javascript skills are limited.

Here’s the minimal reproducible code with some random data. Change example('dot') to example('bar') to see how that works without scrolling/compressing. I am using plotly version 4.8.1 and dash version 1.13.4.

Thanks again

import numpy as np
import pandas as pd
import plotly.graph_objs as go
import dash
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output


def example(highlight_type):

	dates = pd.DataFrame(pd.date_range(start="2020-04-01",end="2020-05-31"), columns=['date'], dtype='str')

	dates['date'] = dates['date'].str[:10]

	sample = {
		'date':dates['date'].values,
		'bar_val':np.random.randint(low=20, high=100, size=len(dates)),
		'line_val':np.random.randint(low=40, high=60, size=len(dates))
	}

	df = pd.DataFrame(sample)

	bars_data = dict(
		type='bar',
		x=df['date'],
		y=df['bar_val'],
	)

	line_data = dict(
		type='scatter',
		x=df['date'],
		y=df['line_val'],
	)

	fig = go.Figure(
		data = [bars_data, line_data],
		layout = dict(
			barmode='overlay',
			xaxis=dict(
				fixedrange=True,
			)
		)
	)


	app = dash.Dash()

	app.layout = html.Div([
		dcc.Graph(id='thegraph', figure=fig)
	])

	@app.callback(
		Output('thegraph', 'figure'),
		[
			Input('thegraph', 'hoverData'),
		]
	)
	def highlight_hover(hoverdata):

		if hoverdata:
			hover = hoverdata['points'][0]
			ptix = hover['pointIndex']

			if highlight_type == 'dot':

				dot_x = fig['data'][1]['x'][ptix]
				dot_y = fig['data'][1]['y'][ptix]

				if len(fig['data']) == 2:

					fig.add_scatter(
						x=[dot_x],
						y=[dot_y],
						name='highlight',
						marker_color='orange',
						hoverinfo='skip',
						marker_size=10,
					)

				else:

					fig.update_traces(
						x=[dot_x],
						y=[dot_y],
						selector=dict(name='highlight')
					)

			elif highlight_type == 'bar':

				bar_x = fig['data'][0]['x'][ptix]
				bar_y = fig['data'][0]['y'][ptix]

				if len(fig['data']) == 2:

					fig.add_bar(
						x=[bar_x],
						y=[bar_y],
						name='highlight',
						marker_color='orange',
						hoverinfo='skip',
						width = 24*60*60*1000,
					)

				else:

					fig.update_traces(
						x=[bar_x],
						y=[bar_y],
						selector=dict(name='highlight')
					)


		return fig


	if __name__ == '__main__':
		app.run_server()

## This scrolls/compresses on hover at the far left/right ends:
example('dot')
## This does not, and is the behaviour I would like for example('dot'):
# example('bar')

Hi @parkerismus thank you for your kind words and for sharing a standalone app! Have you tried setting the xaxis range manually so that it does not change depending on the position of the hover? Another idea is to represent the line trace with `mode=‘lines+markers’ and to set the marker size to zero except for the point hovered at (marker sizes can be defined as an array as in this example). I hope (didn’t test though) that if the trace is already defined then the axis will not jump.

Hi Emmanuelle

Thanks for your tips. I tried out your idea of setting the dot size as an array and included the code below for reference. It (almost) eliminates the dynamic scrolling/compressing, the only issue is that the gaps on either side now appear all the time. That can be addressed by setting range=["2020-03-31","2020-06-01"] under layout and xaxis when defining the figure object.

If you comment that line out and rerun the code below, you’ll see the difference. Note that the start and end dates are each one day removed from those used to define the initial dataframe. Using the same start and end dates would leave the bars for those dates half cut off. Thanks again

import numpy as np
import pandas as pd
import plotly.graph_objs as go
import dash
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output

dates = pd.DataFrame(pd.date_range(start="2020-04-01",end="2020-05-31"), columns=['date'], dtype='str')

dates['date'] = dates['date'].str[:10]

sample = {
	'date':dates['date'].values,
	'bar_val':np.random.randint(low=20, high=100, size=len(dates)),
	'line_val':np.random.randint(low=40, high=60, size=len(dates))
}

df = pd.DataFrame(sample)

bars_data = dict(
	type='bar',
	x=df['date'],
	y=df['bar_val'],
	hoverinfo='none',
	marker_color='green',
	name='bars'
)

line_data = dict(
	type='scatter',
	x=df['date'],
	y=df['line_val'],
	hoverinfo='none',
	mode='lines+markers',
	name='line',
	marker_color='orange',
	# Override default dot size so they don't show on initialization:
	marker_size=[0] * len(dates)
)

fig = go.Figure(
	data = [bars_data, line_data],
	layout = dict(
		barmode='overlay',
		xaxis=dict(
			range=["2020-03-31","2020-06-01"]
		)
	)
)

app = dash.Dash()

app.layout = html.Div([
	dcc.Graph(id='thegraph', figure=fig)
])

@app.callback(
	Output('thegraph', 'figure'),
	[Input('thegraph', 'hoverData')]
)
def highlight_hover(hoverdata):

	if hoverdata:
		hover = hoverdata['points'][0]
		ptix = hover['pointIndex']

		# Reset marker sizes to zero on each new hover:
		marker_sizes = [0] * len(dates)
		# Apply desired marker size at hovered point index:
		marker_sizes[ptix] = 16

		fig.update_traces(
			marker_size=marker_sizes,
			selector=dict(name='line')
		)

	return fig

if __name__ == '__main__':
	app.run_server()
1 Like