Skip to content
Merged
20 changes: 20 additions & 0 deletions docs/library/graphing/other-charts/plotly.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,26 @@ def line_chart():
)
```

## Locale Configuration

Use `locale` to localize Plotly number/date formatting and modebar labels:

```python demo exec
df = px.data.gapminder().query("country=='Canada'")
fig = px.line(df, x="year", y="lifeExp", title="Life expectancy in Canada")


def localized_line_chart():
return rx.center(
rx.plotly(
data=fig,
locale="de",
),
)
```

You can still pass `config`; when both are provided, `locale=` is applied as the final locale value.

## 3D graphing example

Let's create a 3D surface plot of Mount Bruno. This is a slightly more complicated example, but it wraps in Reflex using the same method. In fact, you can wrap any figure using the same approach.
Expand Down
1 change: 1 addition & 0 deletions packages/reflex-components-plotly/news/6428.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`rx.plotly` (and its dist variants like `rx.plotly.basic`) now accept a `locale` prop to localize Plotly's number/date formatting and modebar labels. The matching locale data from `plotly.js-locales` is resolved and merged into the chart config at render time, so per-chart locales work without any manual setup.
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ class Plotly(NoSSRComponent):

config: Var[dict] = field(doc="The config of the graph.")

locale: Var[str] = field(
doc="The locale code used for Plotly formatting and modebar labels."
)

use_resize_handler: Var[bool] = field(
default=LiteralVar.create(True),
doc="If true, the graph will resize when the window is resized.",
Expand Down Expand Up @@ -175,24 +179,31 @@ class Plotly(NoSSRComponent):
doc="Fired when a hovered element is no longer hovered."
)

def add_imports(self) -> dict[str, str]:
def add_imports(self) -> ImportDict:
"""Add imports for the plotly component.

Returns:
The imports for the plotly component.
"""
return {
imports: ImportDict = {
# For merging plotly data/layout/templates.
"mergician@v2.0.2": "mergician"
"mergician@v2.0.2": "mergician",
}
if self.locale is not None:
# For locale dictionaries injected into plot config.locales.
imports["plotly.js-locales@3.5.0"] = ImportVar(
Comment thread
masenf marked this conversation as resolved.
tag="plotlyLocales",
is_default=True,
)
return imports

def add_custom_code(self) -> list[str]:
"""Add custom codes for processing the plotly points data.

Returns:
Custom code snippets for the module level.
"""
return [
codes = [
"const removeUndefined = (obj) => {Object.keys(obj).forEach(key => obj[key] === undefined && delete obj[key]); return obj}",
"""
const extractPoints = (points) => {
Expand Down Expand Up @@ -224,6 +235,45 @@ def add_custom_code(self) -> list[str]:
}
""",
]
if self.locale is not None:
codes.append("""
const _rxResolvePlotlyLocaleData = (plotlyLocales, locale) => {
if (locale === undefined || locale === null) return null;
const localeString = String(locale).trim();
if (localeString === "") return null;

const normalizedLocale = localeString.toLowerCase().replace(/_/g, "-");
const localesObject = plotlyLocales?.default ?? plotlyLocales;
if (!localesObject || typeof localesObject !== "object") return null;

return (
localesObject[normalizedLocale] ??
localesObject[normalizedLocale.split("-")[0]] ??
null
);
}

const _rxGetPlotlyLocaleConfig = (config, locale, plotlyLocales) => {
const localeData = _rxResolvePlotlyLocaleData(plotlyLocales, locale);
if (!localeData) {
if (locale === undefined || locale === null || String(locale).trim() === "") {
return config;
}
return { ...config, locale: String(locale) };
}

const localeName = localeData?.name ?? String(locale);
return {
...config,
locale: localeName,
locales: {
...(config?.locales ?? {}),
[localeName]: localeData,
},
};
}
""")
return codes

@classmethod
def create(cls, *children, **props) -> Component:
Expand Down Expand Up @@ -251,7 +301,7 @@ def create(cls, *children, **props) -> Component:

def _exclude_props(self) -> set[str]:
# These props are handled specially in the _render function
return {"data", "layout", "template"}
return {"data", "layout", "template", "locale"}

def _render(self):
tag = super()._render()
Expand Down Expand Up @@ -285,6 +335,16 @@ def _render(self):
Var(_js_expr=str(figure)),
]
)
if self.locale is not None:
config = self.config if self.config is not None else LiteralVar.create({})
tag = tag.set(
props={
**tag.props,
"config": Var(
_js_expr=f"_rxGetPlotlyLocaleConfig({config!s},{self.locale!s},plotlyLocales)"
),
},
)
return tag


Expand Down Expand Up @@ -327,7 +387,7 @@ class PlotlyBasic(Plotly):

lib_dependencies: list[str] = ["plotly.js-basic-dist-min@3.5.1"]

def add_imports(self) -> ImportDict | list[ImportDict]:
def add_imports(self) -> ImportDict:
"""Add imports for the plotly basic component.

Returns:
Expand All @@ -353,7 +413,7 @@ class PlotlyCartesian(Plotly):

lib_dependencies: list[str] = ["plotly.js-cartesian-dist-min@3.5.1"]

def add_imports(self) -> ImportDict | list[ImportDict]:
def add_imports(self) -> ImportDict:
"""Add imports for the plotly cartesian component.

Returns:
Expand All @@ -379,7 +439,7 @@ class PlotlyGeo(Plotly):

lib_dependencies: list[str] = ["plotly.js-geo-dist-min@3.5.1"]

def add_imports(self) -> ImportDict | list[ImportDict]:
def add_imports(self) -> ImportDict:
"""Add imports for the plotly geo component.

Returns:
Expand All @@ -405,7 +465,7 @@ class PlotlyGl3d(Plotly):

lib_dependencies: list[str] = ["plotly.js-gl3d-dist-min@3.5.1"]

def add_imports(self) -> ImportDict | list[ImportDict]:
def add_imports(self) -> ImportDict:
"""Add imports for the plotly 3d component.

Returns:
Expand All @@ -431,7 +491,7 @@ class PlotlyGl2d(Plotly):

lib_dependencies: list[str] = ["plotly.js-gl2d-dist-min@3.5.1"]

def add_imports(self) -> ImportDict | list[ImportDict]:
def add_imports(self) -> ImportDict:
"""Add imports for the plotly 2d component.

Returns:
Expand All @@ -457,7 +517,7 @@ class PlotlyMapbox(Plotly):

lib_dependencies: list[str] = ["plotly.js-mapbox-dist-min@3.5.1"]

def add_imports(self) -> ImportDict | list[ImportDict]:
def add_imports(self) -> ImportDict:
"""Add imports for the plotly mapbox component.

Returns:
Expand All @@ -483,7 +543,7 @@ class PlotlyFinance(Plotly):

lib_dependencies: list[str] = ["plotly.js-finance-dist-min@3.5.1"]

def add_imports(self) -> ImportDict | list[ImportDict]:
def add_imports(self) -> ImportDict:
"""Add imports for the plotly finance component.

Returns:
Expand All @@ -509,7 +569,7 @@ class PlotlyStrict(Plotly):

lib_dependencies: list[str] = ["plotly.js-strict-dist-min@3.5.1"]

def add_imports(self) -> ImportDict | list[ImportDict]:
def add_imports(self) -> ImportDict:
"""Add imports for the plotly strict component.

Returns:
Expand Down
2 changes: 1 addition & 1 deletion pyi_hashes.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"packages/reflex-components-lucide/src/reflex_components_lucide/icon.pyi": "2e1da186a37e2bb8a1d90e16ee9a63b5",
"packages/reflex-components-markdown/src/reflex_components_markdown/markdown.pyi": "79d0a59b1ba12a2f2c4a09fa6b5c776f",
"packages/reflex-components-moment/src/reflex_components_moment/moment.pyi": "eabf233471bc5b94084914f6f35ecd66",
"packages/reflex-components-plotly/src/reflex_components_plotly/plotly.pyi": "6b84ff7659294b20d21b0ef7781531fa",
"packages/reflex-components-plotly/src/reflex_components_plotly/plotly.pyi": "80b36863336e53c050cc61386f1c9271",
"packages/reflex-components-radix/src/reflex_components_radix/__init__.pyi": "a77352f60fb6f4135b5d08a6e56efa6d",
"packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.pyi": "bbd4d1a4fa73275a882c33ba485d0165",
"packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.pyi": "2639b56ce9ccb8b404c6dc12eaace19d",
Expand Down
Loading
Loading