Advanced
Advanced topics¶
Taipy exposes advanced features that the Plotly library provides. This section demonstrates some of those features, when they are needed and how to use them in your Taipy application.
Multiple traces with different dataset sizes¶
The Adding a trace example explained how to create multiple
traces on the same chart.
However, a strong limitation was that all series have to have the same size.
This limitation is due to the technical nature of a Pandas DataFrame, which must
have all its columns holding the same number of items.
To allow for plotting series of different sizes, we set the data
property of the chart control to an array of dictionaries (converted to Pandas
DataFrames internally): each dictionary that holds different series that must all
be the same size to be used in the chart control, but different dictionaries in the
data array may have different sizes.
The data column names must then be prefixed by their index in the data array (starting
at index 0) followed by a slash: "1/value" indicates the key (or the column) called "value"
in the second element of the array set to the data property of the
chart control.
Here is an example that demonstrates this. We want to plot two different data sets that represent the same function (x squared) on two different intervals.
- The first trace is very coarse, with a data point at every other x unit between -10 and 10.
- The second trace is far more detailed, with ten data points between each x unit, on the interval [-4, 4].
Here is the code that does just that:
# The first data set uses the x interval [-10..10],
# with one point at every other unit
x1_range = [x*2 for x in range(-5, 6)]
# The second data set uses the x interval [-4..4],
# with ten point between every unit
x2_range = [x/10 for x in range(-40, 41)]
# Definition of the two data sets
data = [
# Coarse data set
{
"x": x1_range,
"Coarse": [x*x for x in x1_range]
},
# Fine data set
{
"x": x2_range,
"Fine": [x*x for x in x2_range]
}
]
As you can see, the array data has two elements:
- At index 0: A dictionary that has two properties: "x" is set to the array
[-10, -8, ..., 8, 10], which is the x range for the coarse plot, and "y"
is the series of all these values, squared.
Both these arrays hold 11 elements. - At index 1: A dictionary that has two properties: "x" is set to the array
[-4.0, -3.9, ..., 3.9, 4.0], which is the x range for the fine plot, and
"y" is the series of all these values, squared, just like in the first
element.
Both these arrays hold 81 elements.
To use these series in the chart control, the x and y properties must indicate which trace they refer to and be set with the indexed data set syntax as shown below:
Definition
<|{data}|chart|x[1]=0/x|y[1]=0/Coarse|x[2]=1/x|y[2]=1/Fine|>
<taipy:chart x[1]="0/x" y[1]="0/Coarse" x[2]="1/x" y[2]="1/Fine">{data}</taipy:chart>
import taipy.gui.builder as tgb
...
tgb.chart("{data}", x__1="0/x", y__1="0/Coarse", x__2="1/x", y__2="1/Fine")
Trace number 1 (the Coarse trace) is defined by x[1] and y[1], and uses
the series data[0]["x"] and data[0]["Coarse"];
Trace number 2 (the Fine trace) is defined by x[2] and y[2], and uses
the series data[1]["x"] and data[1]["Fine"].
Here is what the resulting plot looks like:


Large datasets¶
When binding a chart component to a large dataset, performance becomes a critical consideration.
Large datasets can consume significant system resources, resulting in slower rendering times,
reduced responsiveness, and overall degraded performance, especially in interactive
applications.
These issues can negatively impact the user experience, particularly when users need to interact
with or manipulate the chart.
Consider a scenario where an application needs to visualize large datasets, allowing users to
trigger calculations and make decisions based on the data.
Below is a basic example of a large dataset represented in Python:
x_values = ...
Y_values = ...
data = pd.DataFrame({"X": x_values, "Y": y_values})
In this example, x_values could be a sequence of 50,000 integers, while y_values is generated from a noisy log-sine function, also containing 50,000 samples.
To represent this data using Taipy GUI, you can define a chart
control as follows:
Definition
<|{data}|chart|type=markers|x=X|y=Y|>
<taipy:chart type="markers" x="X" y="Y">{data}</taipy:chart>
import taipy.gui.builder as tgb
...
tgb.chart("{data}", type="markers", x="X", y="Y")
The initial chart display with 50,000 data points will look like this:


As seen in the chart, with 50,000 data points, it becomes difficult to interpret the information due to over-plotting. Furthermore, rendering such a large dataset takes significant time, and the entire dataset must be transmitted to the frontend, further affecting performance.
To improve both application performance and chart readability, it is necessary to reduce the number of data points rendered. This can be achieved through techniques such as downsampling, aggregation, or filtering, which can limit the volume of data without losing critical insights
Solution 1: Linear interpolation
One approach to handle large datasets is to apply linear interpolation. This technique reduces the number of data points by approximating the values between points along a straight line, effectively downsampling the dataset.
Using Python and the NumPy package, this solution can be implemented easily and efficiently:
1 2 3 |
|
- line 1: The original x_values array is reduced by selecting one value for every 100 points.
- line 2: The y_values array is reshaped, grouping every 100 consecutive data points.
- line 3: The mean of each group of 100 points is calculated, resulting in a smaller dataset.
This results in a dataset with 100 times fewer points, while still preserving the overall shape of the original data.
Here is the updated chart using the downsampled (interpolated) dataset:


While linear interpolation significantly reduces the dataset size and improves chart performance,
it has some limitations. Specifically, it smooths out the data, which can obscure important details,
such as sharp changes or high-frequency fluctuations.
With this technique, the noise present in the original sine function has been smoothed away, which
might not be desirable for data analysts or scientists who need to observe finer data
characteristics.
Solution 2: Sub-sampling
Sub-sampling is a simple technique where a representative subset of the original data is selected, for example, by picking every 100th data point. This directly reduces the number of points, enhancing performance.
With NumPy and Python's slicing syntax, sub-sampling is straightforward:
x_values = x_values[::100]
y_values = y_values[::100]
As a result, the dataset data is reduced to 1/100th of its original size, while still retaining the overall shape of the data.
Here is the chart with the sub-sampled dataset:


However, sub-sampling has its limitations. It may skip over significant trends or abrupt changes in
the data, especially if key points are not selected. While it's a quick and efficient solution, it
may result in the loss of critical details in datasets with high-frequency variations or sudden
transitions.
As you can see, only a very few number of noisy data points remain after the sampling, which is
expected.
Solution 3: Decimation
Decimation is a more refined approach to reducing dataset size while preserving essential information and trends. By selectively removing data points based on specific criteria, such as frequency content or statistical significance, decimation balances performance with data integrity.
The chart
control has a decimator property that accepts an instance
of a subclass of Decimator
. This class transforms the dataset declared in the
data property, reducing the number of points to be displayed
while ensuring that key data features are retained.
To use decimation, you must instantiate a decimator:
decimator = MinMaxDecimator(200)
In this case, the decimator limits the displayed points to 200, which is an extreme reduction, but it highlights the effect. For practical usage, typical values range from 1000 to 2000, depending on the horizontal resolution of the screen: it's often unnecessary to render more points than a monitor can display.
Several decimator types are available, including MinMaxDecimator
, LTTB
, RDP
, and
ScatterDecimator
. Each of these implements different algorithms, better suited for specific
shapes of data.
To apply the decimator to the chart, set the decimator variable to the decimator property:
Definition
<|{data}|chart|type=markers|x=X|y=Y|decimator={decimator}|>
<taipy:chart type="markers" x="X" y="Y" decimator="{decimator}">{data}</taipy:chart>
import taipy.gui.builder as tgb
...
tgb.chart("{data}", type="markers", x="X", y="Y", decimator="{decimator}")
Here is the resulting chart after applying decimation:


The chart retains more of the dataset's characteristics compared to simpler methods like
sub-sampling or linear interpolation. The global sine-like shape is preserved, and the noise remains
visible.
At the same time, performance is greatly improved: only 200 points are rendered instead of 50,000,
a reduction by a factor of 250.
Note that decimation does not alter nor duplicate the original dataset.
You can zoom in and out using the chart's built-in zoom tool to reveal or hide more details
dynamically.
For example, the following image shows the chart zoomed in on a specific area:


And here is the result after zooming in:


As you zoom in, more details are revealed, while still adhering to the 200-point limit, ensuring smooth performance and responsive interactions.
Adding annotations¶
You can add text annotations on top of a chart using the annotations property of
the dictionary set to the layout property of the chart
control.
These annotations can be connected to an arrow that points to an exact location in
the chart coordinates system, and have many customization capabilities. You may
look at the documentation on the
annotations documentation
page on Plotly's website.
Here is an example demonstrating how you can add annotations to your chart controls:
# Function to plot: x^3/3 - x
def f(x):
return x*x*x/3-x
# x values: [-2.2, ..., 2.2]
x = [(x-10)/4.5 for x in range(0, 21)]
data = {
"x": x,
# y: [f(-2.2), ..., f(2.2)]
"y": [f(x) for x in x]
}
layout = {
# Chart title
"title": "Local extrema",
"annotations": [
# Annotation for local maximum (x = -1)
{
"text": "Local <b>max</b>",
"font": {
"size": 20
},
"x": -1,
"y": f(-1)
},
# Annotation for local minimum (x = 1)
{
"text": "Local <b>min</b>",
"font": {
"size": 20
},
"x": 1,
"y": f(1),
"xanchor": "left"
}
]
}
The important part is the definition of the layout object, the rest of the code being
very standard.
You can see how we create a dictionary for every annotation we want to add, including the
text information, location, font setting, etc.
Adding the annotations to the chart control is a matter of setting the layout property of the chart control to that object:
Definition
<|{data}|chart|layout={layout}|>
<taipy:chart layout="{layout}">{data}</taipy:chart>
import taipy.gui.builder as tgb
...
tgb.chart("{data}", layout="{layout}")
Here is how the annotated chart control shows on the page:


Adding shapes¶
Shapes can be rendered on top of a chart at defined locations.
To create shapes, you must add shape descriptors to the shapes property of
the dictionary set to the layout property of the chart
control.
The documentation for shape descriptors can be found in the
shapes documentation
page on Plotly's website.
Here is some code that creates shapes on top of a chart control:
# Function to plot: x^3/3-x
def f(x):
return x*x*x/3-x
# x values: [-2.2, ..., 2.2]
x = [(x-10)/4.5 for x in range(0, 21)]
data = {
"x": x,
# y: [f(-2.2), ..., f(2.2)]
"y": [f(x) for x in x]
}
shape_size = 0.1
layout = {
"shapes": [
# Shape for local maximum (x = -1)
{
"x0": -1-shape_size,
"y0": f(-1)-2*shape_size,
"x1": -1+shape_size,
"y1": f(-1)+2*shape_size,
"fillcolor": "green",
"opacity": 0.5
},
# Shape for local minimum (x = 1)
{
"x0": 1-shape_size,
"y0": f(1)-2*shape_size,
"x1": 1+shape_size,
"y1": f(1)+2*shape_size,
"fillcolor": "red",
"opacity": 0.5
}
]
}
The part you should be looking at is the setting of the property shapes in
the layout dictionary: each shape is defined as a dictionary that holds all
the parameters that configure how and where to render it.
Note how we indicate the size of the shape, using the shape_size variable.
Here is the definition of the chart control:
Definition
<|{data}|chart|layout={layout}|>
<taipy:chart layout="{layout}">{data}</taipy:chart>
import taipy.gui.builder as tgb
...
tgb.chart("{data}", layout="{layout}")
And here is what the resulting chart looks like:


Tracking selection¶
The chart control has the selected property
that can be used to track data point selection in the chart control.
This property is dynamic and indexed; the bound variable holds an array of point
indexes. It is updated when the user selects points on the chart.
Here is some code that calculates the mean of the values of the selected points of a chart control:
# x = [0..20]
x = list(range(0, 21))
data = {
"x": x,
# A list of random values within [1, 10]
"y": [random.uniform(1, 10) for _ in x]
}
layout = {
# Force the Box select tool
"dragmode": "select",
# Remove all margins around the plot
"margin": {"l":0,"r":0,"b":0,"t":0}
}
config = {
# Hide Plotly's mode bar
"displayModeBar": False
}
selected_indices = []
mean_value = 0.
def on_change(state, var, val):
if var == "selected_indices":
state.mean_value = numpy.mean([data["y"][idx] for idx in val]) if len(val) else 0
The part you should be looking at is the use of the default callback property on_change: it detects the changes in selected_indices and calculates the mean, which updates the text in the title.
We also create two objects (layout and config) used to remove the mode bar above the chart and force the Box select tool.
Here is the definition of the chart and text controls:
Definition
## Mean of <|{len(selected_indices)}|raw|> selected points: <|{mean_value}|format=%.2f|raw|>
<|{data}|chart|selected={selected_indices}|layout={layout}|plot_config={config}|>
<h2>Mean of
<taipy:text raw="true">{len(selected_indices)}</taipy:text>
selected points:
<taipy:text format="%.2f" raw="true">{mean_value}</taipy:text>
</h2>
<taipy:chart selected="{selected_indices}" layout="{layout}"
plot_config="{config}">{data}</taipy:chart>
import taipy.gui.builder as tgb
...
tgb.chart("{data}", selected="{selected_indices}", layout="{layout}", plot_config="{config}")
And here is what the resulting page looks like:


Using the Ploty Python library¶
The chart control of Taipy GUI lets you create graphs that can be more or less complex using the control's properties. There are situations, though, when it would be more straightforward to code the chart using the Plotly Open Source Graphing Library for Python or even the high-level Ploty Express API.
An obvious use case where you would want to do that is when you were able to find a code sample
based on Plotly Python that looks very similar to what you want to achieve with Taipy GUI, and
you don't want to rewrite the sample using the chart's properties. It would be really easier
if a copy-paste of the sample code suffice.
The figure property is available to do precisely that: create the figure
(an instance of
plotly.graph_objects.Figure
)
and set it as the figure property value. The resulting chart will be exactly what you expect.
Note that thanks for figure being a dynamic property, you can use Python to generate a new graph and have your page update automatically.
Here is an example of injecting a figure defined using the Plotly Python API in a Taipy GUI
page.
We want to create three (violin plots)[https://plotly.com/python/violin/] representing three
different probability distributions available in the (Numpy)[https://numpy.org/] package. A single
figure will be created, to which we will add three traces: one for each probability distribution we
want to represent.
Here is the code that does just that:
import numpy as np
import plotly.graph_objects as go
from taipy.gui import Gui
# Create the Plotly figure object
figure = go.Figure()
# Add trace for Normal Distribution
figure.add_trace(go.Violin(name="Normal",
y=np.random.normal(loc=0, scale=1, size=1000),
box_visible=True, meanline_visible=True))
# Add trace for Exponential Distribution
figure.add_trace(go.Violin(name="Exponential",
y=np.random.exponential(scale=1, size=1000),
box_visible=True, meanline_visible=True))
# Add trace for Uniform Distribution
figure.add_trace(go.Violin(name="Uniform",
y=np.random.uniform(low=0, high=1, size=1000),
box_visible=True, meanline_visible=True))
# Updating layout for better visualization
figure.update_layout(title="Different Probability Distributions")
To have the chart control directly use this Plotly figure, all you must do is set the figure property to the object you have just created:
Definition
<|chart|figure={figure}|>
<taipy:chart figure="{figure}"/>
import taipy.gui.builder as tgb
...
tgb.chart(figure="{figure}")
When the page is displayed, the Plotly magic operates:

