diff --git a/CHANGELOG.md b/CHANGELOG.md
index c92c3ea6d64..c676479a490 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
## Unreleased
+### Added
+- Add `px.scatter_smith` for plotting complex values (`real` and `imag`) on Smith charts, and support the `"smith"` subplot type in `make_subplots` [[#5824](https://github.com/plotly/plotly.py/pull/5824)], with thanks to @ybai08 for the contribution!
+
### Fixed
- Fix `mpl_to_plotly` tick marker mirroring, axis positioning, and trace visibility for twinned and overlaid subplots (such as `twinx` and `twiny`) [[#5310](https://github.com/plotly/plotly.py/pull/5310)], with thanks to @robertoffmoura for the contribution!
- Fix `mpl_to_plotly` losing matplotlib axis line colors by exporting the color of each axis's displayed spine to the plotly x and y axis `linecolor` [[#5311](https://github.com/plotly/plotly.py/pull/5311)], with thanks to @robertoffmoura for the contribution!
diff --git a/doc/apidoc/plotly.express.rst b/doc/apidoc/plotly.express.rst
index 9a82acf4501..540b3ca3928 100644
--- a/doc/apidoc/plotly.express.rst
+++ b/doc/apidoc/plotly.express.rst
@@ -18,6 +18,7 @@ plotly's high-level API for rapid figure generation. ::
scatter_3d
scatter_polar
scatter_ternary
+ scatter_smith
scatter_map
scatter_geo
line
diff --git a/doc/python/smith-charts.md b/doc/python/smith-charts.md
index 0c6cc461489..61c305b133a 100644
--- a/doc/python/smith-charts.md
+++ b/doc/python/smith-charts.md
@@ -38,6 +38,24 @@ jupyter:
A [Smith Chart](https://en.wikipedia.org/wiki/Smith_chart) is a specialized chart for visualizing [complex numbers](https://en.wikipedia.org/wiki/Complex_number): numbers with both a real and imaginary part.
+### Smith Charts with Plotly Express
+
+[Plotly Express](/python/plotly-express/) is the easy-to-use, high-level interface to Plotly, which [operates on a variety of types of data](/python/px-arguments/) and produces [easy-to-style figures](/python/styling-plotly-express/). With `px.scatter_smith`, each row of the DataFrame is represented as a marker on the Smith chart, with `real` and `imag` giving the real and imaginary parts of its normalized impedance.
+
+```python
+import plotly.express as px
+import pandas as pd
+
+df = pd.DataFrame({
+ "real": [0.2, 0.5, 1.0, 2.0, 0.2, 0.5, 1.0, 2.0],
+ "imag": [0.5, 1.0, 0.5, -1.0, -0.5, -1.0, 2.0, 0.0],
+ "antenna": ["A", "A", "A", "A", "B", "B", "B", "B"],
+})
+
+fig = px.scatter_smith(df, real="real", imag="imag", color="antenna")
+fig.show()
+```
+
### Smith Charts with Plotly Graph Objects
```python
diff --git a/plotly/_subplots.py b/plotly/_subplots.py
index 7983a994a05..8b8fd5c5467 100644
--- a/plotly/_subplots.py
+++ b/plotly/_subplots.py
@@ -8,7 +8,7 @@
# little differently.
import collections
-_single_subplot_types = {"scene", "geo", "polar", "ternary", "map"}
+_single_subplot_types = {"scene", "geo", "polar", "ternary", "map", "smith"}
_subplot_types = set.union(_single_subplot_types, {"xy", "domain"})
# For most subplot types, a trace is associated with a particular subplot
@@ -20,7 +20,7 @@
# the trace property is just named `subplot`. For example setting
# the `scatterpolar.subplot` property to `polar3` associates the scatterpolar
# trace with the third polar subplot in the figure
-_subplot_prop_named_subplot = {"polar", "ternary", "map"}
+_subplot_prop_named_subplot = {"polar", "ternary", "map", "smith"}
# Named tuple to hold an xaxis/yaxis pair that represent a single subplot
@@ -152,6 +152,7 @@ def make_subplots(
- 'polar': Polar subplot for scatterpolar, barpolar, etc.
- 'ternary': Ternary subplot for scatterternary
- 'map': Map subplot for scattermap, choroplethmap and densitymap
+ - 'smith': Smith chart subplot for scattersmith
- 'domain': Subplot type for traces that are individually
positioned. pie, parcoords, parcats, etc.
- trace type: A trace type which will be used to determine
diff --git a/plotly/basedatatypes.py b/plotly/basedatatypes.py
index a7326da8e5a..f099980c7cc 100644
--- a/plotly/basedatatypes.py
+++ b/plotly/basedatatypes.py
@@ -2428,6 +2428,7 @@ def get_subplot(self, row, col, secondary_y=False):
* plotly.graph_objs.layout.Polar: if subplot type is 'polar'
* plotly.graph_objs.layout.Ternary: if subplot type is 'ternary'
* plotly.graph_objs.layout.Map: if subplot type is 'map'
+ * plotly.graph_objs.layout.Smith: if subplot type is 'smith'
* SubplotDomain namedtuple with `x` and `y` fields:
if subplot type is 'domain'.
- x: length 2 list of the subplot start and stop width
diff --git a/plotly/express/__init__.py b/plotly/express/__init__.py
index 9fc66c91ae4..e5e249f5be7 100644
--- a/plotly/express/__init__.py
+++ b/plotly/express/__init__.py
@@ -30,6 +30,7 @@
scatter_3d,
scatter_polar,
scatter_ternary,
+ scatter_smith,
scatter_map,
scatter_geo,
line,
@@ -79,6 +80,7 @@
"scatter_3d",
"scatter_polar",
"scatter_ternary",
+ "scatter_smith",
"scatter_map",
"scatter_geo",
"scatter_matrix",
diff --git a/plotly/express/_chart_types.py b/plotly/express/_chart_types.py
index f78a257cabb..2e48f8a1b05 100644
--- a/plotly/express/_chart_types.py
+++ b/plotly/express/_chart_types.py
@@ -937,6 +937,47 @@ def line_ternary(
line_ternary.__doc__ = make_docstring(line_ternary)
+def scatter_smith(
+ data_frame=None,
+ real=None,
+ imag=None,
+ color=None,
+ symbol=None,
+ size=None,
+ text=None,
+ hover_name=None,
+ hover_data=None,
+ custom_data=None,
+ animation_frame=None,
+ animation_group=None,
+ category_orders=None,
+ labels=None,
+ color_discrete_sequence=None,
+ color_discrete_map=None,
+ color_continuous_scale=None,
+ range_color=None,
+ color_continuous_midpoint=None,
+ symbol_sequence=None,
+ symbol_map=None,
+ opacity=None,
+ size_max=None,
+ title=None,
+ subtitle=None,
+ template=None,
+ width=None,
+ height=None,
+) -> go.Figure:
+ """
+ In a Smith chart scatter plot, each row of `data_frame` is represented by
+ a symbol mark in Smith chart coordinates, positioned by its normalized
+ impedance (`real` + `imag` * j).
+ """
+ return make_figure(args=locals(), constructor=go.Scattersmith)
+
+
+scatter_smith.__doc__ = make_docstring(scatter_smith)
+
+
def scatter_polar(
data_frame=None,
r=None,
diff --git a/plotly/express/_core.py b/plotly/express/_core.py
index 9994720b094..3723613d55a 100644
--- a/plotly/express/_core.py
+++ b/plotly/express/_core.py
@@ -30,10 +30,10 @@
# Declare all supported attributes, across all plot types
direct_attrables = (
- ["base", "x", "y", "z", "a", "b", "c", "r", "theta", "size", "x_start", "x_end"]
- + ["hover_name", "text", "names", "values", "parents", "wide_cross"]
- + ["ids", "error_x", "error_x_minus", "error_y", "error_y_minus", "error_z"]
- + ["error_z_minus", "lat", "lon", "locations", "animation_group"]
+ ["base", "x", "y", "z", "a", "b", "c", "r", "theta", "real", "imag", "size"]
+ + ["x_start", "x_end", "hover_name", "text", "names", "values", "parents"]
+ + ["wide_cross", "ids", "error_x", "error_x_minus", "error_y", "error_y_minus"]
+ + ["error_z", "error_z_minus", "lat", "lon", "locations", "animation_group"]
)
array_attrables = ["dimensions", "custom_data", "hover_data", "path", "wide_variable"]
group_attrables = ["animation_frame", "facet_row", "facet_col", "line_group"]
@@ -2893,7 +2893,7 @@ def init_figure(args, subplot_type, frame_list, nrows, ncols, col_labels, row_la
horizontal_spacing = args.get("facet_col_spacing") or 0.02
else:
# Other subplot types:
- # 'scene', 'geo', 'polar', 'ternary', 'map', 'domain', None
+ # 'scene', 'geo', 'polar', 'ternary', 'map', 'smith', 'domain', None
#
# We can customize subplot spacing per type once we enable faceting
# for all plot types
diff --git a/plotly/express/_doc.py b/plotly/express/_doc.py
index 42cb991df18..fbca4df80f5 100644
--- a/plotly/express/_doc.py
+++ b/plotly/express/_doc.py
@@ -73,6 +73,16 @@
colref_desc,
"Values from this column or array_like are used to position marks along the angular axis in polar coordinates.",
],
+ real=[
+ colref_type,
+ colref_desc,
+ "Values from this column or array_like are used as the real part of the normalized impedance of marks in Smith chart coordinates.",
+ ],
+ imag=[
+ colref_type,
+ colref_desc,
+ "Values from this column or array_like are used as the imaginary part of the normalized impedance of marks in Smith chart coordinates.",
+ ],
values=[
colref_type,
colref_desc,
diff --git a/plotly/subplots.py b/plotly/subplots.py
index 55fac2437ad..4217bee0e50 100644
--- a/plotly/subplots.py
+++ b/plotly/subplots.py
@@ -115,6 +115,7 @@ def make_subplots(
- 'polar': Polar subplot for scatterpolar, barpolar, etc.
- 'ternary': Ternary subplot for scatterternary
- 'map': Map subplot for scattermap
+ - 'smith': Smith chart subplot for scattersmith
- 'domain': Subplot type for traces that are individually
positioned. pie, parcoords, parcats, etc.
- trace type: A trace type which will be used to determine
diff --git a/tests/test_core/test_subplots/test_get_subplot.py b/tests/test_core/test_subplots/test_get_subplot.py
index 7c6d32337e5..99bbdc2be7f 100644
--- a/tests/test_core/test_subplots/test_get_subplot.py
+++ b/tests/test_core/test_subplots/test_get_subplot.py
@@ -159,6 +159,26 @@ def test_get_subplot(self):
domain = fig.data[-1].domain
self.assertEqual(subplot, SubplotDomain(x=domain.x, y=domain.y))
+ def test_get_subplot_smith(self):
+ # "smith" and the "scattersmith" trace type both create smith subplots
+ fig = subplots.make_subplots(
+ rows=1, cols=2, specs=[[{"type": "smith"}, {"type": "scattersmith"}]]
+ )
+ fig.add_trace(go.Scattersmith(real=[0.5, 1], imag=[1, 2]), row=1, col=1)
+ fig.add_trace(go.Scattersmith(real=[2, 3], imag=[-1, 0]), row=1, col=2)
+
+ self.assertEqual(fig.layout.smith.domain.x, (0.0, 0.45))
+ self.assertEqual(fig.layout.smith2.domain.x, (0.55, 1.0))
+ self.assertEqual(fig.data[0].subplot, "smith")
+ self.assertEqual(fig.data[1].subplot, "smith2")
+
+ self.assertEqual(fig.get_subplot(1, 1), fig.layout.smith)
+ self.assertEqual(fig.get_subplot(1, 2), fig.layout.smith2)
+
+ fig.update_traces(marker_color="red", row=1, col=2)
+ self.assertIsNone(fig.data[0].marker.color)
+ self.assertEqual(fig.data[1].marker.color, "red")
+
def test_get_subplot_out_of_bounds(self):
fig = subplots.make_subplots(rows=4, cols=2)
diff --git a/tests/test_optional/test_px/test_px_functions.py b/tests/test_optional/test_px/test_px_functions.py
index 84a451733b4..e1d75309337 100644
--- a/tests/test_optional/test_px/test_px_functions.py
+++ b/tests/test_optional/test_px/test_px_functions.py
@@ -688,3 +688,37 @@ def test_empty_histogram():
fig = px.histogram()
assert len(fig.data) == 1
assert fig.data[0].type == "histogram"
+
+
+def test_scatter_smith_px():
+ real = np.array([0.5, 1.0, 2.0])
+ imag = np.array([1.0, -0.5, 0.0])
+ go_trace = go.Scattersmith(real=real, imag=imag, mode="markers")
+ fig = px.scatter_smith(real=real, imag=imag)
+ _compare_figures(go_trace, fig)
+ assert fig.data[0].subplot == "smith"
+ assert fig.layout.smith.domain.x == (0.0, 1.0)
+
+
+def test_scatter_smith(constructor):
+ data = {
+ "r": [0.5, 1.0, 2.0, 0.2],
+ "x": [1.0, -0.5, 0.0, 3.0],
+ "band": ["low", "low", "high", "high"],
+ "f": [1, 2, 3, 4],
+ }
+ df = constructor(data)
+ fig = px.scatter_smith(
+ df, real="r", imag="x", color="band", text="f", hover_data=["f"]
+ )
+
+ assert [t.type for t in fig.data] == ["scattersmith", "scattersmith"]
+ assert [t.name for t in fig.data] == ["low", "high"]
+ assert_array_equal(fig.data[0].real, [0.5, 1.0])
+ assert_array_equal(fig.data[0].imag, [1.0, -0.5])
+ assert_array_equal(fig.data[1].real, [2.0, 0.2])
+ assert_array_equal(fig.data[1].imag, [0.0, 3.0])
+ assert fig.data[0].mode == "markers+text"
+ assert fig.data[0].hovertemplate == (
+ "band=low
r=%{real}
x=%{imag}
f=%{customdata[0]}"
+ )