Example - Chart Selection Callback, Iframe Output, Scrollable

Hi, you might remember me from such threads as:

Simple Example - Candlestick Chart From CSV File


Example - Blank Annotated Figure, Side-by-side Charts, Scrollable Textbox

Well, after driving myself nuts (for no reason) after posting this thread: Plotly Custom Callbacks – Open Plea For Clarification - I’m Still A Newbie

I’m back with a working and documented example of using Box Select on a bar chart to trigger another function that does processing (Minimally, but you get the idea - you can put anything in there) and puts the output into a Scrollable Textbox.

It has a bunch of stuff in there that I researched and tried to add informative comments on, so give it a look:

Remember to change the path to the file and/or the filename if you alter it on your machine. Most certainly you will have to edit the path, since I doubt anyone is duplicating my folder structure. :laughing:


## TallTim's Callback Example -- Based on examples mode here:
## Simple Example - Candlestick Chart From CSV File -- https://community.plotly.com/t/simple-example-candlestick-chart-from-csv-file/76183
## Example - Blnak Annotated FIgure, Side-by-Side Charts, Scrollable Textbox -- https://community.plotly.com/t/example-blank-annotated-figure-side-by-side-charts-scrollable-textbox/77492
## Made this to show how to pass selected data from a chart to a function, and return the result to another component
## I hope this helps newbies, so you don't go astray into the thorny thickets at the base of the knowledge mountain.

# Importing some things
import os
import pandas as pd
import math
# Testing plotting chart results
import plotly.graph_objects as go
from plotly.subplots import make_subplots

# Dash imports to make this a served interactive page, not static
from dash import Dash, html, dash_table, callback, Output, Input, dcc 
import dash_mantine_components as dmc # This is used in the placeholder HTML for the figure
# Dash bootstrap for using color themes
import dash_bootstrap_components as dbc # Used to load a bootstrap theme
# Figure template for themes
from dash_bootstrap_templates import load_figure_template
load_figure_template('CYBORG') # Load dark theme
# Using bootstrap themes
app = Dash(__name__, external_stylesheets = [dbc.themes.CYBORG]) # Refer to the themes to change this if you wish

# Global Variables --------------------------------------------------------------------------------------------------------------------------

myDataPath = r'C:\Users\Tim\PythonEnvironments\NinjaHeatmap' # Change this to reflect where you put the demo csv file
# Box select x values - initialize as NaN for logic checks later
global temp_x_min
global temp_x_max
temp_x_min = float('NaN')
temp_x_max = float('NaN')
resultStatus = list()

# Functions ---------------------------------------------------------------------------------------------------------------------------------

# Utility function to print out to console
def printResults(myInputList):
	tempData = ""
	if myInputList is not None:
		if len(myInputList) > 0:
			for index in myInputList:
				tempData += str(index)

	return tempData

# This function converts a dataframe column name into a series
def convertColToSeries(myRawDataframe, myColumnName):
	tempValList = list()
	tempColumn = pd.DataFrame() # Init empty dataframe
	tempSeries = []
	tempVal = 0
	# Convert pandas column into a series
	tempColumn = myRawDataframe[myColumnName] # Get column values
	tempSeries = pd.Series(tempColumn)
	# Get dimensions for debug output
	temp_series_dim = tempSeries.shape
	tempRows = temp_series_dim[0]
	# Debug
	print("\n" + myColumnName + " Series - Rows: " + str(tempRows))
	# Now do a loop to pack it all into a list
	for tempVal in tempSeries:

	return tempValList

# Simple function to display the timestamp values of the box select, passing the list to the callback
def showIndexes(myInputSeries, min_x, max_x): # Data series, min/max x for box selection indexes
	resultList = list()
	myIndexesToFind = list()
	closeData = ""
	tempData = ""
	outputString = ""
	seperator = "\n"
	# Take min/max x and retrieve the proper values from myInputSeries list
	for idx in range(min_x, (max_x+1), 1): # Range start, stop, step
		# Debug
		print(str(df_Input.loc[idx, ['BarTimestamp']]))
		# Append bar timestamps and closes to results
		tempData = df_Input.loc[idx, ['BarTimestamp']].to_string(index=False) # Supresses index numbers
		closeData = df_Input.loc[idx, ['Close']].to_string(index=False)
		outputString = tempData + " " + closeData + "<br>" # HTML breaks here because Iframe is HTML output, so no 'newline' '\n'

	# If result list is empty, print error msg
	if len(myIndexesToFind) == 0:
		print("\n<Find Seq> No valid indexes to check, aborting...")

	return resultList

## Main Candlestick Chart Drawing Function --------------------------------------------------------------------------------------------------
def myCandleChart(myInputData, myResults): # Adding results to pass final result list...
	# Attempting to use the plotly go objects here... might work?
	myFig = make_subplots(rows=2, row_heights=[0.70,0.30], cols=1, shared_xaxes=True, vertical_spacing=0.02) # Heights must add up to 1

	# Plotly Chart
			row=1, col=1
	# Add FFI subplot with go.Scatter - works, Line is deprecated 
	myFig.add_trace(go.Scatter(x=myInputData['BarTimestamp'], y=myInputData['FancyIndicator'], name="Indicator", line=dict(color='rgb(235,140,52)')), row=2, col=1) # Dark orange
	# Ref - Named colors list -- https://community.plotly.com/t/plotly-colours-list/11730/3
	# Add zeroline to the indicator Subplot
	myFig.add_hline(y=0, line_width=1, line_color="white", row=2, col=1)
	# Remove default rangeslider
	# Background color
	# Axes line color
	myFig.update_xaxes(showline=True, linewidth=1, linecolor='dimgray')
	myFig.update_yaxes(showline=True, linewidth=1, linecolor='dimgray')
	# Tick value display format - yaxis<num> is for subplots
	myFig.update_layout(yaxis={"tickformat" : ','}, yaxis2={"tickformat" : '.2f'}) # Displays thousands,hundreds without scientific 'K' notation
	# Grid line color
	myFig.update_xaxes(showgrid=True, gridwidth=1, gridcolor='dimgray')
	myFig.update_yaxes(showgrid=True, gridwidth=1, gridcolor='dimgray')
	# Adjusting font sizes
			family="Microsoft Sans Serif", # Font needs to be installed on system to be accessible
			size=12, color='rgb(160,166,184)' # Light Gray
	# Chart title
	myFig.update_layout(title="BTCUSD 5min", title_x=0.5, yaxis_title="Price", yaxis2_title="Indicator")
	# Legend Background Transparent Color, 'Paper' background color - plot canvas, sets pan enabled on load
	myFig.update_layout(legend=dict(bgcolor='rgba(0,0,0,0)'), paper_bgcolor='rgb(12,12,12)', dragmode='pan')
	## Ref: https://plotly.com/python/axes/ for range
	# Hiding the legend
	# Adjust initially displayed x-axis range
	myFig.update_xaxes(range=[0, 50]) # Entire set is still there, just shows the index numbers in the range
	myFig.update_yaxes(range=[25600, 26250], row=1) # This sets the range of price in the candle chart for aesthetics
	return myFig

# This is the container that splits up the right panel into a chart/text/input area
def myOutputPanel():
	# Adjusting the row heights helped with the border issue... was 0.30 for second one
	myFig = make_subplots(rows=2, row_heights=[0.70, 0.10], cols=1, vertical_spacing=0.02) # Heights must add up to 1
	# Right pane chart - just a demo placeholder
	myFig.add_trace(go.Scatter(x=[], y=[]), row=1, col=1) # This yields an empty chart
	# Remove default rangeslider
	# Background color
	# Axes line color
	myFig.update_xaxes(showline=True, linewidth=1, linecolor='dimgray')
	myFig.update_yaxes(showline=True, linewidth=1, linecolor='dimgray')
	# Tick value display format - yaxis<num> is for subplots --- I may have the format wrong for the plot, we'll see...
	myFig.update_layout(yaxis={"tickformat" : ','}, yaxis2={"tickformat" : '.2f'}) # Displays thousands,hundreds without scientific 'K' notation
	# Grid line color
	myFig.update_xaxes(showgrid=True, gridwidth=1, gridcolor='dimgray')
	myFig.update_yaxes(showgrid=True, gridwidth=1, gridcolor='dimgray')
	# Adjusting font sizes
			family="Microsoft Sans Serif", # Font needs to be installed on system to be accessible
			size=12, color='rgb(160,166,184)' # Light Gray
		margin=dict(l=1, r=70, b=0, t=90, pad=1) # Margin adjustments
	# Chart title / Lower box title
	myFig.update_layout(title="Another Chart", title_x=0.5)
	# Legend Background Transparent Color, 'Paper' background color - plot canvas, sets pan enabled on load
	myFig.update_layout(legend=dict(bgcolor='rgba(0,0,0,0)'), paper_bgcolor='rgb(12,12,12)')
	# Fixed axes for no zoom

	return myFig

# This function styles the result output so it can be sent to the Iframe with proper text color, etc..
def styleTextOutput(myInputText):
	tempStr = ""
	# Eight zeroes in hex is transparent, also #00FFFFFF
	iframe_text_style = "<span style=\"color:#FFFFFF; background:#00000000; font-family:Arial,Helvectica,sans-serif; font-size: 16px;\">"
	iframe_close_span = "</span>"
	# Concatenates (puts together) string with triple quotes and variables
	tempStr = f"""{iframe_text_style}{myInputText}{iframe_close_span}"""
	# Debug
	#print("\n<Iframe Text Style Func> - Total formatted string is: \n")
	return tempStr

# Init Actions ------------------------------------------------------------------------------------------------------------------------------

# Get directory contents
files = os.listdir(myDataPath)

# Read into dataframe
df_Input = pd.read_csv(myDataPath + '/' + "Input_BTCUSD_5_Minute_Demo.csv") # Edit this if you change the filename of the demo csv file

# Get number of dataframe rows and columns
dataDimensions = df_Input.shape
dataRows = dataDimensions[0]
dataCols = dataDimensions[1]

# Debug
print("\nInput dataframe - Rows: " + str(dataRows) + " Columns: " + str(dataCols))

# Returns series from input dataframe, column name
indicator_Val_List = convertColToSeries(df_Input, 'FancyIndicator')

# We're ready and waiting!
print("\nWaiting for box selection... \n")

# Setting up a dynamic graph container
# Only way to get graph to fill full height was to add -- style={'height':'100vh'} to the main layout Div container
# Config settings go inside the dcc.Graph, instead of fig.show() for setting scroll/zoom
# This was way more of a pain in the ass to find a solution to than it should've been for both of the above...

graph_container = html.Div([
	dcc.Graph(figure = myCandleChart(df_Input, resultStatus), id='chart-placeholder', style={'height':'100vh','width':'50vw'}, config = dict({'scrollZoom' : True}))	# Passing df_Input to chart render function

# Output pane on the right side -- search output and scrollable text box console output
output_container = html.Div(
	dcc.Graph(figure = myOutputPanel(), id='output', style={'height':'70vh','width':'50vw'},
	config={'displayModeBar': False} # Removed toolbar and prevent pan/zoom
	# dcc.Store for retaining output of bar chart index function
				storage_type='session' # local - data kept after browser quits, memory - reset on page refresh, session - data cleared on browser quit 
	html.Iframe(id='iframe-output', srcDoc="", style={'width':'90%','height':220,'scrolling':'auto', 'background-color':'#202020'})
	style={'background-color':'#0C0C0C'} # Sets strip to the right of scrollbox to chart 'paper' color

content = html.Div(

# This creates a stack that places the windows side-by-side
myStack = html.Div(
				content, output_container
			direction="horizontal" # The key setting that enables this
	style={'background-color':'#0C0C0C'} # Sets small strip to the right of Iframe to chart 'paper' color

page_structure = [

# Ditched Grid since it isn't a heatmap
app.layout = dbc.Container(

# Box Select Callback
	Output('iframe-output', 'srcDoc'), # Pushes results to Iframe srcDoc - which is HTML styled
    Input('chart-placeholder', 'selectedData'),	# The input that fires when bars are selected
def display_selected_data(selectedData): # Got a selection? Great, lets do some stuff...
	global resultStatus
	global mySelectedResult
	global myStyledOutput
	if selectedData: # Got somthing in here?
		temp_points = selectedData['points']
		#print("\nJSON Data Type Is: " + str(type(selectedData)))
		if temp_points:
			temp_points_len = len(temp_points)
			temp_points_first = temp_points[0] # First point element
			temp_points_last = temp_points[(temp_points_len - 1)] # Last point element
			temp_x_min = temp_points_first['pointIndex'] # Get first point index
			temp_x_max = temp_points_last['pointIndex'] # Get last point index
			# Debug
			print("\n Points min/max indexes are: " + str(temp_x_min) + ", " + str(temp_x_max))

			if temp_points_len > 0: # Have points been selected?
				# This uses the selected range of points...
				resultStatus = showIndexes(indicator_Val_List, temp_x_min, temp_x_max) # Fire our function to show data at indexes
			# Catch exception for 'NoneType' when results returns nothing...
			if resultStatus is not None: 
				if len(resultStatus) > 0:
					print("\nFinal results: \n")
					# Making string for Iframe
					mySelectedResult = "Final results, Timestamp, Close Price: <br>" + printResults(resultStatus)
					# Stuff data into store...
					dcc.Store(id='select-result', data=resultStatus) # Store data for reference in Iframe
					print("\nFinal results - Nothing selected. \n")
					# Making string for Iframe
					mySelectedResult = "\nFinal results - Nothing selected. \n"
				print("\nFinal results - None Type Error - selection returned nothing... \n")
				mySelectedResult = "<br>Final results - None Type Error - selection returned nothing... <br>"
		# Branch for selected data IF block...		
		else: # No points selected on chart?
			print("\nSorry, no price bars selected.")
			# Making string for Iframe
			mySelectedResult = "<br>Sorry, no price bars selected. <br>"

	mySelectedResult = styleTextOutput(mySelectedResult) # Apply HTML styling for Iframe

	return mySelectedResult # Return HTML styled string to Iframe as specified in the callback Output

# Run the App
if __name__ == '__main__':
	app.run_server(debug=True,host='',port=8060) # Set host argument to allow LAN connections, Port for custom ports

And here’s the csv file I used - you’ll have to save it as the same filename or change the filename in the code for it to work properly:

6/10/2023 8:25:00 PM,-0.50,25789.24,25789.24,25765.28,25779.76
6/10/2023 8:30:00 PM,-0.50,25777.89,25807.54,25761.67,25791.5
6/10/2023 8:35:00 PM,-0.50,25791.15,25827.72,25791.15,25821.8
6/10/2023 8:40:00 PM,-0.50,25822.35,25885.23,25821.76,25847.1
6/10/2023 8:45:00 PM,-0.50,25847.1,25850.69,25833.68,25840.26
6/10/2023 8:50:00 PM,-0.50,25839.71,25851.15,25813.32,25836.01
6/10/2023 8:55:00 PM,-0.50,25834.62,25849.61,25830.74,25841.79
6/10/2023 9:00:00 PM,-0.50,25842.06,25868.25,25838.53,25840.62
6/10/2023 9:05:00 PM,-0.50,25840.6,25846.16,25813.21,25813.22
6/10/2023 9:10:00 PM,-0.50,25813.22,25820.39,25793.97,25806.1
6/10/2023 9:15:00 PM,0.50,25804.85,25809.92,25789.86,25789.9
6/10/2023 9:20:00 PM,0.50,25789.89,25791.37,25768.77,25782.29
6/10/2023 9:25:00 PM,0.50,25782.3,25793.47,25760.65,25778.92
6/10/2023 9:30:00 PM,0.50,25778.92,25786.31,25770.14,25778.27
6/10/2023 9:35:00 PM,0.50,25778.27,25797.74,25777.73,25793.92
6/10/2023 9:40:00 PM,0.50,25794.24,25798.36,25790.62,25795.51
6/10/2023 9:45:00 PM,0.50,25795.5,25805.59,25785.02,25805.59
6/10/2023 9:50:00 PM,0.50,25806.24,25816.44,25784.28,25795.19
6/10/2023 9:55:00 PM,0.50,25795.85,25797.03,25763.07,25771.5
6/10/2023 10:00:00 PM,0.50,25772.25,25787.56,25769.57,25779.99
6/10/2023 10:05:00 PM,-0.50,25779.2,25784.52,25763.24,25769.47
6/10/2023 10:10:00 PM,-0.50,25769.47,25777.18,25751.48,25751.62
6/10/2023 10:15:00 PM,-0.50,25751.62,25764.62,25739.95,25747.35
6/10/2023 10:20:00 PM,-0.50,25747.35,25771.8,25745.35,25754.5
6/10/2023 10:25:00 PM,-0.50,25754.49,25767.23,25738.44,25765.04
6/10/2023 10:30:00 PM,-0.50,25765.04,25768.32,25744.31,25765.99
6/10/2023 10:35:00 PM,-0.50,25765.99,25774.09,25731.69,25746.77
6/10/2023 10:40:00 PM,-0.50,25744.96,25788.51,25744.96,25768.76
6/10/2023 10:45:00 PM,-0.50,25767.99,25777.83,25757.55,25763.91
6/10/2023 10:50:00 PM,-0.50,25763.91,25773.19,25735.07,25742.31
6/10/2023 10:55:00 PM,0.50,25742.3,25758.61,25710.31,25733.57
6/10/2023 11:00:00 PM,0.50,25734.08,25746.73,25716.49,25742.79
6/10/2023 11:05:00 PM,0.50,25742.79,25744.94,25716.4,25729.24
6/10/2023 11:10:00 PM,0.50,25727.33,25733.66,25701.69,25710.49
6/10/2023 11:15:00 PM,0.50,25710.49,25729.47,25708.76,25720.25
6/10/2023 11:20:00 PM,0.50,25718.26,25725.87,25695.58,25700.8
6/10/2023 11:25:00 PM,0.50,25701.26,25710.92,25648,25690.19
6/10/2023 11:30:00 PM,0.50,25690.44,25737.51,25683.44,25702.05
6/10/2023 11:35:00 PM,0.50,25705.86,25717,25680.05,25687.07
6/10/2023 11:40:00 PM,0.50,25687.52,25720.4,25687.52,25712.34
6/10/2023 11:45:00 PM,-0.50,25712.11,25719.96,25698.46,25710.57
6/10/2023 11:50:00 PM,-0.50,25709.28,25767.28,25709.28,25755.48
6/10/2023 11:55:00 PM,-0.50,25754.38,25763.71,25739.31,25745.81
6/11/2023 12:00:00 AM,-0.50,25745.51,25776.13,25739.33,25742.6
6/11/2023 12:05:00 AM,-0.50,25743.63,25761.23,25741.76,25758.57
6/11/2023 12:10:00 AM,-0.50,25758.58,25765.65,25751.61,25757.03
6/11/2023 12:15:00 AM,-0.50,25756.66,25766.91,25746.39,25766.34
6/11/2023 12:20:00 AM,-0.50,25766.34,25767.36,25753.24,25758.45
6/11/2023 12:25:00 AM,-0.50,25758.44,25777.7,25756.04,25777.53
6/11/2023 12:30:00 AM,-0.50,25775.97,25775.97,25757.03,25762.68
6/11/2023 12:35:00 AM,0.50,25761.77,25769.87,25752.15,25768.02
6/11/2023 12:40:00 AM,0.50,25768.02,25819.04,25768.02,25817.54
6/11/2023 12:45:00 AM,0.50,25815.62,25839.14,25775.59,25797.03
6/11/2023 12:50:00 AM,0.50,25797.81,25807.2,25789.87,25801.2
6/11/2023 12:55:00 AM,0.50,25801.07,25827.55,25797.65,25818.71
6/11/2023 1:00:00 AM,0.50,25818.51,25850,25816.73,25834.16
6/11/2023 1:05:00 AM,0.50,25834.17,25841.18,25813.17,25818.91
6/11/2023 1:10:00 AM,0.50,25818.58,25833.9,25805.88,25827.49
6/11/2023 1:15:00 AM,0.50,25826.88,25831.35,25813.78,25816.29
6/11/2023 1:20:00 AM,0.50,25816.99,25830.07,25815.85,25825.28
6/11/2023 1:25:00 AM,-0.50,25826.61,25827.08,25789.1,25800.97
6/11/2023 1:30:00 AM,-0.50,25800.58,25808.22,25781.57,25804.51
6/11/2023 1:35:00 AM,-0.50,25804.5,25807.04,25785.95,25791.55
6/11/2023 1:40:00 AM,-0.50,25791.52,25799.6,25780.78,25785.36
6/11/2023 1:45:00 AM,-0.50,25785.53,25808.02,25784.13,25793.09
6/11/2023 1:50:00 AM,-0.50,25793.4,25802.08,25790.35,25797.41
6/11/2023 1:55:00 AM,-0.50,25797.41,25806.69,25794.3,25802.78
6/11/2023 2:00:00 AM,-0.50,25802.78,25808.44,25797.86,25799.56
6/11/2023 2:05:00 AM,-0.50,25797.25,25816.37,25793.68,25811.55
6/11/2023 2:10:00 AM,-0.50,25811.54,25813.34,25796.12,25799.9
6/11/2023 2:15:00 AM,0.50,25798.85,25803.39,25796.11,25802.4
6/11/2023 2:20:00 AM,0.50,25800.77,25806.74,25786.51,25794.24
6/11/2023 2:25:00 AM,0.50,25793.95,25796.47,25780.16,25782.91
6/11/2023 2:30:00 AM,0.50,25785.2,25800.34,25785.2,25796.69
6/11/2023 2:35:00 AM,0.50,25797.21,25803.06,25786.84,25788
6/11/2023 2:40:00 AM,0.50,25787.99,25788.24,25776.51,25779.54
6/11/2023 2:45:00 AM,0.50,25777.82,25785.71,25760.71,25763.15
6/11/2023 2:50:00 AM,0.50,25763.15,25770.79,25757.06,25767.38
6/11/2023 2:55:00 AM,0.50,25766.73,25772.28,25755.19,25766.11
6/11/2023 3:00:00 AM,0.50,25768.17,25770.19,25750.39,25753.79
6/11/2023 3:05:00 AM,-0.50,25752.22,25764.12,25744.24,25753.59
6/11/2023 3:10:00 AM,-0.50,25751.37,25765.72,25747.74,25765.67
6/11/2023 3:15:00 AM,-0.50,25765.67,25765.67,25753.36,25761.36
6/11/2023 3:20:00 AM,-0.50,25761.36,25783.49,25757.07,25782.18
6/11/2023 3:25:00 AM,-0.50,25782.17,25782.17,25765.89,25767.73
6/11/2023 3:30:00 AM,-0.50,25767.74,25767.74,25746.6,25748.38
6/11/2023 3:35:00 AM,-0.50,25746.92,25750.24,25705.13,25741.17
6/11/2023 3:40:00 AM,-0.50,25740.98,25757.27,25729.62,25752.3
6/11/2023 3:45:00 AM,-0.50,25754.55,25755.83,25726.92,25736.61
6/11/2023 3:50:00 AM,-0.50,25736.61,25746.34,25725.63,25733.15
6/11/2023 3:55:00 AM,0.50,25731.12,25742.77,25731.12,25738.6
6/11/2023 4:00:00 AM,0.50,25738.56,25757.96,25738.56,25753.21
6/11/2023 4:05:00 AM,0.50,25753.21,25755.35,25743.1,25749.68
6/11/2023 4:10:00 AM,0.50,25750.67,25753.65,25735.12,25741.87
6/11/2023 4:15:00 AM,0.50,25742.25,25758.71,25734.88,25737.31
6/11/2023 4:20:00 AM,0.50,25737.54,25752.47,25733.76,25745.01
6/11/2023 4:25:00 AM,0.50,25744.96,25757.94,25744.94,25753.23
6/11/2023 4:30:00 AM,0.50,25753.22,25785.99,25746.65,25774.75
6/11/2023 4:35:00 AM,0.50,25774.4,25779.03,25758.69,25771.34
6/11/2023 4:40:00 AM,0.50,25771.34,25782.54,25761.24,25776.73

And finally, the output of the selection in action, and the corresponding output:

The chart on the right pane doesn’t have anything, but that could be changed easily. Just a placeholder for demo purposes.

I hope this helps someone if you struggled with this stuff like I did. Enjoy!

Hi @TallTim

Glad you are making progress! Here are a couple more tips that will hopefully save you from future frustrations:

This callback won’t update the dcc.Store:

   #  other components


	Output('iframe-output', 'srcDoc'), 
    Input('chart-placeholder', 'selectedData'),
def display_selected_data(selectedData):       
       resultStatus = showIndexes(...)

       # this will not update the dcc.Store
      dcc.Store(id='select-result', data=resultStatus)

      # other code ....

     return mySelectedResults      

This callback will update dcc.Store:

	Output('iframe-output', 'srcDoc'), 
    Output('select-results', 'data'),
    Input('chart-placeholder', 'selectedData'),
def display_selected_data(selectedData):       
     resultStatus = showIndexes(...)      
     mySelectedResults = ....      

     return mySelectedResults, resultStatus  


Ah, I see. Thanks for pointing that out – I was so focused on getting that iFrame scrollbox to work that I didn’t test the dcc.Store fully.

I’ll look at what you suggested and try testing more stuff.

Thank you for the help!

Is there a reason you want to use the iFrame for the results? I think it would be easier to use a html.Div in this case - or you could even use DataTable or Dash AG Grid.

Here’s an example using a Div - Try replacing the Iframe in the layout with:

            html.Div("Selected points, timestamp and close price"),
            html.Div(id='div-output', style={'width': '90%', 'height': 220, 'overflow': 'auto', "whiteSpace": "pre", "border": "solid"})

Then the callback can be:

    Output('div-output', 'children'),  
    Input('chart-placeholder', 'selectedData'),  
def display_selected_data(selectedData):  # Got a selection? Great, lets do some stuff...
    mySelectedResult = []
    if selectedData:
        points = selectedData["points"]
        mySelectedResult = [f"{point['x']}  {point['close']} \n" for point in points]
    return mySelectedResult


Good question, and I did have output in the beginning for debugging going to essentially a ‘empty’ div container for the JSON stuff.

Thing is, I’ll probably have more status/info in that box than what I’d like to scroll down on (and keep the charts visible) so a iFrame with a scrollable text box made more sense.

Appreciate the suggestion, though.

Did you try running the code? The Div scrolls like the Iframe


Ah, I didn’t have the chance just yet - thought it was a plain Div… my mistake – I’ll take a look!

Thank you!

Not that I doubted you, but it sure does!


I’ll have to reconsider, I appreciate the design tip! Also, your quote-cards (Card Group) example caught my eye and I’ve already done some custom CSS styling to it, I’ll probably post a show-and-tell example in a while…

Thanks again for the help and examples, you’re a gem! :gem:


Here’s a teaser for the quoteboard I modified from AnneMarieW’s card example:

Still have to figure out row padding, but its coming along nicely. I grabbed an excerpt of the CoinGecko data to store as a test variable so I don’t ping the site mercilessly doing style changes and color tweaking.

Here’s the working example: Example - Dynamic Resizable Quoteboard With Live/Test Data

Just an update - I’ve reworked things and it totally can retrieve the stored data now, and I’ve even managed to pass that data to a function that will update a graph!

Making some real progress, thanks for the help and suggestions @AnnMarieW !!

1 Like