Back to Blog
Python

Python Plotly Interactive Charts: Hover and Custom Data

python plotly interactive charts hover and custom data: Learn to control hover text and embed custom data in Plotly charts using hover_data, customdata, and hovertempl...

PlotlyPythonData VisualizationInteractive ChartsHover TemplatesCustom Data
A Plotly scatter chart with a custom hover tooltip showing extra data fields like product name and margin percentage.

python plotly interactive charts hover and custom data requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

When you build a Plotly chart in Python, the default hover tooltip shows only the x and y values. That is often not enough. You may need to display a product name, a percentage change, a category label, or any other field from your DataFrame. Plotly gives you several ways to control what appears on hover, and the right choice depends on how much control you need and how your data is structured.

This article focuses on the practical mechanics of python plotly interactive charts hover and custom data handling: using hover_data, customdata, and hovertemplate to shape tooltips exactly the way your users need them.

Why Default Hover Text Falls Short

A scatter plot built from two columns shows only those two values in the tooltip. For a financial dashboard, you might want the ticker symbol, the volume, and the percent change alongside the price. For a geographic map, you might want the region name and population density. The default tooltip cannot infer which additional columns are relevant.

Plotly solves this by letting you attach extra fields to each trace. Those fields can be displayed in the hover tooltip, used for click events, or both. The understanding the distinction between data that is only for hover and data that is carried along for other interactions is central to building maintainable charts.

Using hover_data for Simple Column Selection

If you are working with a Plotly Express figure, hover_data is the quickest way to add columns to the tooltip. You pass a list of column names, and Plotly automatically formats them based on the column type.

import plotly.express as px import pandas as pd df = pd.DataFrame({ "city": ["Berlin", "Paris", "Madrid"], "population": [3.6, 2.1, 3.2], "country": ["Germany", "France", "Spain"], "growth": [0.8, 0.3, 0.5] }) fig = px.scatter(df, x="population", y="growth", hover_data=["city", "country"]) fig.show()

Here the hover tooltip shows population, growth, city, and country. Plotly Express uses the column names as labels and applies default number formatting. This works well when you only need to add a few fields and do not care about custom labels or formatting.

One limitation is that hover_data does not let you rename the displayed label. If your column is named pop_2023, the tooltip will show that exact name. To control labels and formatting, you need hovertemplate.

Embedding Arbitrary Data with customdata

For graph objects (plotly.graph_objects), customdata is the mechanism for attaching extra data to each point. It is a 2D array where each row corresponds to a point and each column to an extra field. You can then reference those fields in the hover template using %{customdata[i]}.

import plotly.graph_objects as go fig = go.Figure() fig.add_trace(go.Scatter( x=[1, 2, 3], y=[10, 20, 15], customdata=[["Product A", 0.12], ["Product B", 0.08], ["Product C", 0.15]], hovertemplate="<b>%{customdata[0]}</b><br>" + "Sales: %{y}<br>" + "Margin: %{customdata[1]:.2%}<br>" + "<extra></extra>" )) fig.show()

customdata is not limited to strings and numbers. You can store dates, arrays, or even nested objects, as long as they can be serialized by Plotly. The key advantage is that this data travels with the trace, so it is also available in click and hover events when you add callbacks in a Dash app or Jupyter notebook.

Building Precise Tooltips with hovertemplate

hovertemplate gives you full control over the tooltip content and formatting. It uses a template syntax where %{x}, %{y}, and %{customdata[i]} are replaced with the actual values. You can add HTML tags for styling, control number formatting with :.2f or :.2%, and remove the secondary trace name box with <extra></extra>.

import plotly.express as px import pandas as pd df = pd.DataFrame({ "product": ["Alpha", "Beta", "Gamma"], "revenue": [1200, 950, 1400], "margin": [0.22, 0.18, 0.25] }) fig = px.bar(df, x="product", y="revenue", custom_data=["margin"]) fig.update_traces( hovertemplate="<b>%{x}</b><br>" + "Revenue: $%{y:,.0f}<br>" + "Margin: %{customdata[0]:.1%}<br>" + "<extra></extra>" ) fig.show()

Notice that custom_data in Plotly Express expects a list of column names. The resulting customdata array is indexed starting at 0, so customdata[0] refers to the first column you passed.

When you use hovertemplate, you override the default hover behavior entirely. That means you must include every field you want to display. Omitting %{x} or %{y} will remove them from the tooltip. This is useful when you want to show only derived or custom fields, but it also means you must be explicit.

Mixing hover_data and hovertemplate in Plotly Express

Plotly Express supports both hover_data and hovertemplate together. The hover_data columns are added to the customdata array in the order they appear. You can then reference them in the template. This is a clean way to keep the data binding separate from the presentation logic.

import plotly.express as px df = px.data.gapminder().query("year == 2007") fig = px.scatter( df, x="gdpPercap", y="lifeExp", size="pop", hover_data={"continent": True, "pop": ":,.0f"}, custom_data=["country"] ) fig.update_traces(hovertemplate="<b>%{customdata[0]}</b><br>" + "GDP: %{x:,.0f}<br>" + "Life Exp: %{y:.1f}<br>" + "Continent: %{customdata[1]}<br>" + "Population: %{customdata[2]:,.0f}<br>" + "<extra></extra>") fig.show()

Here hover_data contributes two columns to customdata: continent and pop (with formatting applied). The explicit custom_data parameter adds country as the first column. The order matters: customdata[0] is country, customdata[1] is continent, and customdata[2] is pop. This approach keeps the data selection in the px call and the formatting in the template, which is easier to maintain when you have many traces.

Handling Categorical and Mixed-Type Data

When you attach custom data that includes dates, booleans, or categorical strings, Plotly may try to coerce them into numbers if you use them in certain ways. For example, if you pass a date column as customdata and then reference it in a template, it will appear as a timestamp unless you format it. You can use %{customdata[0]|%Y-%m-%d} to format a date, but the underlying value must be a date object or a string that Plotly can parse.

For categorical data, the safest approach is to keep the values as strings in customdata. Plotly will display them as-is in the tooltip. If you need to perform calculations with those values in a callback, you can convert them in Python after receiving the event data.

A common pitfall is mixing numeric and string columns in customdata without paying attention to the index. Always test the tooltip output with a small sample to confirm that the indices match the columns you intended.

Performance Considerations for Large Datasets

Hover templates and customdata do not significantly slow down rendering for typical chart sizes. The tooltip is generated on demand when the user hovers over a point. However, if you have hundreds of thousands of points, the initial trace creation and the internal data storage can become heavier. customdata adds memory overhead because every point carries the extra fields.

If you are plotting a large scatter plot, consider whether you really need all the extra fields for every point. Sometimes you can downsample the data for display and keep the full detail in a separate lookup table. When a user hovers over a point, you can use the point index to retrieve the full record from the original data source. This keeps the chart lightweight while still providing rich tooltips.

Another performance-related detail is the template string itself. Plotly parses the template for each point when the tooltip is shown. A very long template with many custom fields can add a few milliseconds per hover, which is usually imperceptible. But if you notice lag in a Dash app with many callbacks, simplifying the template or precomputing the display strings in Python can help.

Common Pitfalls and How to Avoid Them

One frequent mistake is using %{customdata} without an index. In a 2D array, %{customdata} alone is not valid; you must specify the column index, like %{customdata[0]}. If you only have one extra field, you still need the [0].

Another issue is forgetting to add <extra></extra> to suppress the secondary tooltip box that shows the trace name. By default, Plotly adds a gray box with the trace name at the top of the tooltip. Including <extra></extra> removes it, giving you a cleaner tooltip.

When using hover_data in Plotly Express, you can pass a dictionary to control formatting. The keys are column names and the values are either True or a format string. For example, hover_data={"pop": ":,.0f"} formats the population with thousands separators. This is a concise way to format without writing a full template, but it does not allow you to change the label.

Finally, be aware that customdata is not automatically included in the hover tooltip. You must explicitly reference it in hovertemplate. If you want to see the custom data in the tooltip without writing a template, you can use hover_data in Plotly Express, which adds the columns to the tooltip automatically. For graph objects, you have to write the template.

Advanced: Using Hover Data in Dash Callbacks

In a Dash application, the hoverData property of a figure contains the point index and the customdata associated with that point. This allows you to build interactive dashboards where hovering over one chart updates another chart or a text panel.

from dash import Dash, dcc, html, Input, Output import plotly.express as px df = px.data.iris() app = Dash(__name__) app.layout = html.Div([ dcc.Graph(id="scatter", figure=px.scatter(df, x="sepal_width", y="sepal_length", custom_data=["species"])), html.Div(id="hover-output") ]) @app.callback( Output("hover-output", "children"), Input("scatter", "hoverData") ) def display_hover(hoverData): if hoverData is None: return "Hover over a point." point = hoverData["points"][0] species = point["customdata"][0] return f"Species: {species}" if __name__ == "__main__": app.run(debug=True)

This pattern is powerful because customdata travels with the point and is available in the callback without needing to look up the original DataFrame. It also keeps the callback logic simple, since you do not have to pass the entire row as a JSON payload.

When using customdata in Dash, be mindful of the data types. JSON serialization converts dates to strings and may lose precision for very large integers. If you need exact numeric values, store them as floats or strings and convert them in the callback.

Choosing the Right Approach for Your Chart

The decision between hover_data, customdata, and hovertemplate depends on your needs. If you are using Plotly Express and only need to add a few columns with default formatting, hover_data is the simplest. If you need custom labels, precise formatting, or HTML styling, hovertemplate is the way to go. If you are working with graph objects or need to access the data in callbacks, customdata is the foundation, and you will almost always pair it with a template.

For a one-off exploratory chart, hover_data saves time. For a polished dashboard that will be used by others, hovertemplate gives you the control to make tooltips clear and professional. Understanding how these mechanisms interact—especially how hover_data populates customdata—lets you combine them effectively without confusion.

python plotly interactive charts hover and custom data: Prac | RYUSLOG DEV