Skip to content

Commit c07de25

Browse files
authored
Merge branch 'main' into update-external-renderer-default
2 parents 4a94f1b + 6056b20 commit c07de25

19 files changed

+601
-1294
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22
All notable changes to this project will be documented in this file.
33
This project adheres to [Semantic Versioning](http://semver.org/).
44

5+
## Unreleased
6+
7+
### Updated
8+
- Updated Plotly.js from version 3.0.1 to version 3.0.3. See the [plotly.js CHANGELOG](https://github.com/plotly/plotly.js/blob/master/CHANGELOG.md#303----2025-07-23) for more information.
9+
10+
### Added
11+
- Exposed `plotly.io.get_chrome()` as a function which can be called from within a Python script. [[#5282](https://github.com/plotly/plotly.py/pull/5282)]
12+
513
## [6.2.0] - 2025-06-26
614

715
### Added

commands.py

Lines changed: 23 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -244,27 +244,10 @@ def update_plotlyjs(plotly_js_version, outdir):
244244

245245

246246
# FIXME: switch to argparse
247-
def update_schema_bundle_from_master():
247+
def update_schema_bundle_from_master(args):
248248
"""Update the plotly.js schema and bundle from master."""
249-
250-
if "--devrepo" in sys.argv:
251-
devrepo = sys.argv[sys.argv.index("--devrepo") + 1]
252-
else:
253-
devrepo = "plotly/plotly.js"
254-
255-
if "--devbranch" in sys.argv:
256-
devbranch = sys.argv[sys.argv.index("--devbranch") + 1]
257-
else:
258-
devbranch = "master"
259-
260-
if "--local" in sys.argv:
261-
local = sys.argv[sys.argv.index("--local") + 1]
262-
else:
263-
local = None
264-
265-
if local is None:
266-
build_info = get_latest_publish_build_info(devrepo, devbranch)
267-
249+
if args.local is None:
250+
build_info = get_latest_publish_build_info(args.devrepo, args.devbranch)
268251
archive_url, bundle_url, schema_url = get_bundle_schema_urls(
269252
build_info["build_num"]
270253
)
@@ -275,14 +258,14 @@ def update_schema_bundle_from_master():
275258
# Update schema in package data
276259
overwrite_schema(schema_url)
277260
else:
278-
# this info could be more informative but
279-
# it doesn't seem as useful in a local context
280-
# and requires dependencies and programming.
261+
# this info could be more informative but it doesn't seem as
262+
# useful in a local context and requires dependencies and
263+
# programming.
281264
build_info = {"vcs_revision": "local", "committer_date": str(time.time())}
282-
devrepo = local
283-
devbranch = ""
265+
args.devrepo = args.local
266+
args.devbranch = ""
284267

285-
archive_uri, bundle_uri, schema_uri = get_bundle_schema_local(local)
268+
archive_uri, bundle_uri, schema_uri = get_bundle_schema_local(args.local)
286269
overwrite_bundle_local(bundle_uri)
287270
overwrite_schema_local(schema_uri)
288271

@@ -293,23 +276,23 @@ def update_schema_bundle_from_master():
293276

294277
# Replace version with bundle url
295278
package_json["dependencies"]["plotly.js"] = (
296-
archive_url if local is None else archive_uri
279+
archive_url if args.local is None else archive_uri
297280
)
298281
with open(package_json_path, "w") as f:
299282
json.dump(package_json, f, indent=2)
300283

301284
# update plotly.js version in _plotlyjs_version
302285
rev = build_info["vcs_revision"]
303286
date = str(build_info["committer_date"])
304-
version = "_".join([devrepo, devbranch, date[:10], rev[:8]])
287+
version = "_".join([args.devrepo, args.devbranch, date[:10], rev[:8]])
305288
overwrite_plotlyjs_version_file(version)
306-
install_js_deps(local)
289+
install_js_deps(args.local)
307290

308291

309-
def update_plotlyjs_dev(outdir):
292+
def update_plotlyjs_dev(args, outdir):
310293
"""Update project to a new development version of plotly.js."""
311294

312-
update_schema_bundle_from_master()
295+
update_schema_bundle_from_master(args)
313296
perform_codegen(outdir)
314297

315298

@@ -328,7 +311,14 @@ def parse_args():
328311

329312
subparsers.add_parser("format", help="reformat code")
330313

331-
subparsers.add_parser("updateplotlyjsdev", help="update plotly.js for development")
314+
p_update_dev = subparsers.add_parser(
315+
"updateplotlyjsdev", help="update plotly.js for development"
316+
)
317+
p_update_dev.add_argument(
318+
"--devrepo", default="plotly/plotly.js", help="repository"
319+
)
320+
p_update_dev.add_argument("--devbranch", default="master", help="branch")
321+
p_update_dev.add_argument("--local", default=None, help="local path")
332322

333323
subparsers.add_parser("updateplotlyjs", help="update plotly.js")
334324

@@ -353,7 +343,7 @@ def main():
353343
lint_code(outdir)
354344

355345
elif args.cmd == "updateplotlyjsdev":
356-
update_plotlyjs_dev(outdir)
346+
update_plotlyjs_dev(args, outdir)
357347

358348
elif args.cmd == "updateplotlyjs":
359349
version = plotly_js_version()

doc/python/figurewidget.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jupyter:
2222
pygments_lexer: ipython3
2323
version: 3.6.5
2424
plotly:
25-
description: Introduction to the new Plotly FigureWidget
25+
description: Introduction to the Plotly FigureWidget
2626
display_as: chart_events
2727
language: python
2828
layout: base
@@ -34,6 +34,12 @@ jupyter:
3434
redirect_from: /python/ipython-widgets/
3535
---
3636

37+
The Plotly FigureWidget allows you to add Plotly charts as interactive widgets in Jupyter and other compatible notebooks. To use the FigureWidget, you'll need to install `anywidget`:
38+
39+
```bash
40+
pip install anywidget
41+
```
42+
3743
#### Create a Simple FigureWidget
3844
Create an empty FigureWidget and then view it.
3945

doc/python/line-and-scatter.md

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,144 @@ fig.update_traces(textposition="bottom right")
284284
fig.show()
285285
```
286286

287+
### Swarm (or Beeswarm) Plots
288+
289+
Swarm plots show the distribution of values in a column by giving each entry one dot and adjusting the y-value so that dots do not overlap and appear symmetrically around the y=0 line. They complement [histograms](https://plotly.com/python/histograms/), [box plots](https://plotly.com/python/box-plots/), and [violin plots](https://plotly.com/python/violin/). This example could be generalized to implement a swarm plot for multiple categories by adjusting the y-coordinate for each category.
290+
291+
```python
292+
import pandas as pd
293+
import plotly.express as px
294+
import collections
295+
296+
297+
def negative_1_if_count_is_odd(count):
298+
# if this is an odd numbered entry in its bin, make its y coordinate negative
299+
# the y coordinate of the first entry is 0, so entries 3, 5, and 7 get
300+
# negative y coordinates
301+
if count % 2 == 1:
302+
return -1
303+
else:
304+
return 1
305+
306+
307+
def swarm(
308+
X_series,
309+
fig_title,
310+
point_size=16,
311+
fig_width=800,
312+
gap_multiplier=1.2,
313+
bin_fraction=0.95, # slightly undersizes the bins to avoid collisions
314+
):
315+
# sorting will align columns in attractive c-shaped arcs rather than having
316+
# columns that vary unpredictably in the x-dimension.
317+
# We also exploit the fact that sorting means we see bins sequentially when
318+
# we add collision prevention offsets.
319+
X_series = X_series.copy().sort_values()
320+
321+
# we need to reason in terms of the marker size that is measured in px
322+
# so we need to think about each x-coordinate as being a fraction of the way from the
323+
# minimum X value to the maximum X value
324+
min_x = min(X_series)
325+
max_x = max(X_series)
326+
327+
list_of_rows = []
328+
# we will count the number of points in each "bin" / vertical strip of the graph
329+
# to be able to assign a y-coordinate that avoids overlapping
330+
bin_counter = collections.Counter()
331+
332+
for x_val in X_series:
333+
# assign this x_value to bin number
334+
# each bin is a vertical strip slightly narrower than one marker
335+
bin = (((fig_width*bin_fraction*(x_val-min_x))/(max_x-min_x)) // point_size)
336+
337+
# update the count of dots in that strip
338+
bin_counter.update([bin])
339+
340+
# remember the "y-slot" which tells us the number of points in this bin and is sufficient to compute the y coordinate unless there's a collision with the point to its left
341+
list_of_rows.append(
342+
{"x": x_val, "y_slot": bin_counter[bin], "bin": bin})
343+
344+
# iterate through the points and "offset" any that are colliding with a
345+
# point to their left apply the offsets to all subsequent points in the same bin.
346+
# this arranges points in an attractive swarm c-curve where the points
347+
# toward the edges are (weakly) further right.
348+
bin = 0
349+
offset = 0
350+
for row in list_of_rows:
351+
if bin != row["bin"]:
352+
# we have moved to a new bin, so we need to reset the offset
353+
bin = row["bin"]
354+
offset = 0
355+
# see if we need to "look left" to avoid a possible collision
356+
for other_row in list_of_rows:
357+
if (other_row["bin"] == bin-1):
358+
# "bubble" the entry up until we find a slot that avoids a collision
359+
while ((other_row["y_slot"] == row["y_slot"]+offset)
360+
and (((fig_width*(row["x"]-other_row["x"]))/(max_x-min_x)
361+
// point_size) < 1)):
362+
offset += 1
363+
# update the bin count so we know whether the number of
364+
# *used* slots is even or odd
365+
bin_counter.update([bin])
366+
367+
row["y_slot"] += offset
368+
# The collision free y coordinate gives the items in a vertical bin
369+
# y-coordinates to evenly spread their locations above and below the
370+
# y-axis (we'll make a correction below to deal with even numbers of
371+
# entries). For now, we'll assign 0, 1, -1, 2, -2, 3, -3 ... and so on.
372+
# We scale this by the point_size*gap_multiplier to get a y coordinate
373+
# in px.
374+
row["y"] = (row["y_slot"]//2) * \
375+
negative_1_if_count_is_odd(row["y_slot"])*point_size*gap_multiplier
376+
377+
# if the number of points is even, move y-coordinates down to put an equal
378+
# number of entries above and below the axis
379+
for row in list_of_rows:
380+
if bin_counter[row["bin"]] % 2 == 0:
381+
row["y"] -= point_size*gap_multiplier/2
382+
383+
df = pd.DataFrame(list_of_rows)
384+
# One way to make this code more flexible to e.g. handle multiple categories
385+
# would be to return a list of "swarmified" y coordinates here and then plot
386+
# outside the function.
387+
# That generalization would let you "swarmify" y coordinates for each
388+
# category and add category specific offsets to put the each category in its
389+
# own row
390+
391+
fig = px.scatter(
392+
df,
393+
x="x",
394+
y="y",
395+
title=fig_title,
396+
)
397+
# we want to suppress the y coordinate in the hover value because the
398+
# y-coordinate is irrelevant/misleading
399+
fig.update_traces(
400+
marker_size=point_size,
401+
# suppress the y coordinate because the y-coordinate is irrelevant
402+
hovertemplate="<b>value</b>: %{x}",
403+
)
404+
# we have to set the width and height because we aim to avoid icon collisions
405+
# and we specify the icon size in the same units as the width and height
406+
fig.update_layout(width=fig_width, height=(
407+
point_size*max(bin_counter.values())+200))
408+
fig.update_yaxes(
409+
showticklabels=False, # Turn off y-axis labels
410+
ticks='', # Remove the ticks
411+
title=""
412+
)
413+
return fig
414+
415+
416+
df = px.data.iris() # iris is a pandas DataFrame
417+
fig = swarm(df["sepal_length"], "Sepal length distribution from 150 iris samples")
418+
# The iris data set entries are rounded so there are no collisions.
419+
# a more interesting test case for collision avoidance is:
420+
# fig = swarm(pd.Series([1, 1.5, 1.78, 1.79, 1.85, 2,
421+
# 2, 2, 2, 3, 3, 2.05, 2.1, 2.2, 2.5, 12]))
422+
fig.show()
423+
```
424+
287425
## Scatter and line plots with go.Scatter
288426

289427
If Plotly Express does not provide a good starting point, it is possible to use [the more generic `go.Scatter` class from `plotly.graph_objects`](/python/graph-objects/). Whereas `plotly.express` has two functions `scatter` and `line`, `go.Scatter` can be used both for plotting points (makers) or lines, depending on the value of `mode`. The different options of `go.Scatter` are documented in its [reference page](https://plotly.com/python/reference/scatter/).

doc/python/static-image-export.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,12 @@ Plotly also provides a CLI for installing Chrome from the command line.
6464

6565
Run `plotly_get_chrome` to install Chrome.
6666

67-
You can also install Chrome from within Python using `plotly.io.install_chrome()`
67+
You can also install Chrome from Python using `plotly.io.get_chrome()`
6868

6969
```python
7070
import plotly.io as pio
7171

72-
pio.install_chrome()
72+
pio.get_chrome()
7373
```
7474

7575
See the **Additional Information on Browsers with Kaleido** section below for more details on browser compatibility for Kaleido.
@@ -273,6 +273,8 @@ The following settings are available.
273273

274274
`mathjax`: Location of the MathJax bundle needed to render LaTeX characters. Defaults to a CDN location. If fully offline export is required, set this to a local MathJax bundle.
275275

276+
`plotlyjs`: Location of the Plotly.js bundle to use. Can be a local file path or URL. By default, Kaleido uses the Plotly.js bundle included with Plotly.py.
277+
276278
`topojson`: Location of the topojson files needed to render choropleth traces. Defaults to a CDN location. If fully offline export is required, set this to a local directory containing the Plotly.js topojson files.
277279

278280
`mapbox_access_token`: The default Mapbox access token (Kaleido v0 only). Mapbox traces are deprecated. See the [MapLibre Migration](https://plotly.com/python/mapbox-to-maplibre/) page for more details.

doc/python/text-and-annotations.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,11 @@ This example shows how to add a note about the data source or interpretation at
789789
```python
790790
import plotly.express as px
791791
df = px.data.iris()
792+
793+
fig = px.scatter(df, x="sepal_width", y="sepal_length", color="species",
794+
size='petal_length', hover_data=['petal_width'])
795+
796+
792797
fig.update_layout(
793798
title=dict(text="Note: this is the Plotly title element.",
794799
# keeping this title string short avoids getting the text cut off in small windows

doc/python/tile-county-choropleth.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jupyter:
2222
pygments_lexer: ipython3
2323
version: 3.10.0
2424
plotly:
25-
description: How to make a choropleth map of US counties in Python with Plotly.
25+
description: How to make tile choropleth maps in Python with Plotly.
2626
display_as: maps
2727
language: python
2828
layout: base
@@ -254,4 +254,4 @@ fig.show()
254254

255255
See [function reference for `px.choropleth_map`](https://plotly.com/python-api-reference/generated/plotly.express.choropleth_map) or https://plotly.com/python/reference/choroplethmap/ for more information about the attributes available.
256256

257-
For Mapbox-based tile maps, see [function reference for `px.choropleth_mapbox`](https://plotly.com/python-api-reference/generated/plotly.express.choropleth_mapbox) or https://plotly.com/python/reference/choroplethmapbox/.
257+
For (deprecated) Mapbox-based tile maps, see [function reference for `px.choropleth_mapbox`](https://plotly.com/python-api-reference/generated/plotly.express.choropleth_mapbox) or https://plotly.com/python/reference/choroplethmapbox/.

js/install.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
{
22
"schemaVersion": 1,
3-
"name": "jupyterlab-plotly",
3+
"packageName": "jupyterlab-plotly",
44
"version": "6.0.1",
5+
"packageManager": "python",
56
"jupyterlab": {
67
"mimeExtension": "lib/mimeExtension.js"
78
}
8-
}
9-
9+
}

js/lib/mimeExtension.js

Lines changed: 99 additions & 99 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)