Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit d717a22

Browse filesBrowse files
committed
Merge branch 'master' of github.com:plotly/plotly.py into remove-requirejs
2 parents 8db8a63 + 8c75004 commit d717a22
Copy full SHA for d717a22

File tree

Expand file treeCollapse file tree

12 files changed

+209
-56
lines changed
Filter options
Expand file treeCollapse file tree

12 files changed

+209
-56
lines changed

‎.circleci/config.yml

Copy file name to clipboardExpand all lines: .circleci/config.yml
-9Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -249,14 +249,6 @@ jobs:
249249
- test_optional:
250250
py: "312_no_numpy"
251251

252-
# Orca
253-
python_38_orca:
254-
docker:
255-
- image: cimg/python:3.8-browsers
256-
steps:
257-
- test_orca:
258-
py: "38"
259-
260252
# Percy
261253
python_39_percy:
262254
docker:
@@ -613,7 +605,6 @@ workflows:
613605
- python_311_optional
614606
- python_312_optional
615607
- python_39_pandas_2_optional
616-
- python_38_orca
617608
- python_39_percy
618609
- python_312_no_numpy
619610
- build-doc

‎CHANGELOG.md

Copy file name to clipboardExpand all lines: CHANGELOG.md
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
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+
### Updated
6+
7+
- Updated plotly.py to use base64 encoding of arrays in plotly JSON to improve performance.
8+
59
## [5.24.1] - 2024-09-12
610

711
### Updated

‎LICENSE.txt

Copy file name to clipboardExpand all lines: LICENSE.txt
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
The MIT License (MIT)
1+
MIT License
22

3-
Copyright (c) 2016-2024 Plotly, Inc
3+
Copyright (c) 2016-2024 Plotly Technologies Inc.
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

‎contributing.md

Copy file name to clipboardExpand all lines: contributing.md
+3-5Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,7 @@ There are many ways to contribute to plotly.py. To contribute effectively, it is
5757

5858
- tests are found in `packages/python/plotly/plotly/tests`. Different
5959
directories correspond to different test jobs (with different dependency sets)
60-
run in continuous integration. These jobs are configured in
61-
`packages/python/plotly/tox.ini`, which itself is used in the Circle CI
62-
configuration file `.circleci/config.yml`. More is explained about tests
60+
run in continuous integration. More is explained about tests
6361
in the following "Technical aspects" section.
6462

6563
- the **documentation** is part of this repository. Its structure and some
@@ -231,10 +229,10 @@ For dev branches, it is also possible to use `updateplotlyjsdev` in two configur
231229

232230
### CircleCI Release
233231

234-
If your devbranch is part of the official plotly.js repository, you can use
232+
If your devbranch is part of the official plotly.js repository, you can use
235233
```bash
236234
python setup.py updateplotlyjsdev --devrepo reponame --devbranch branchname
237-
```
235+
```
238236
to update to development versions of `plotly.js`. This will fetch the `plotly.js` in the CircleCI artifact of the branch `branchname` of the repo `reponame`. If `--devrepo` or `--devbranch` are omitted, `updateplotlyjsdev` defaults using `plotly/plotly.js` and `master` respectively.
239237

240238
### Local Repository

‎doc/requirements.txt

Copy file name to clipboardExpand all lines: doc/requirements.txt
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
plotly==5.24.1
2-
jupytext==1.1.1
2+
jupytext==1.16.4
33
ipywidgets==7.7.2
44
jupyter-client<7
55
jupyter

‎packages/python/chart-studio/chart_studio/tools.py

Copy file name to clipboardExpand all lines: packages/python/chart-studio/chart_studio/tools.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ def _get_embed_url(file_owner_or_url, file_id=None):
287287
"The 'file_id' argument must be a non-negative number."
288288
)
289289

290-
if share_key is "":
290+
if share_key == "":
291291
return "{plotly_rest_url}/~{file_owner}/{file_id}.embed".format(
292292
plotly_rest_url=plotly_rest_url, file_owner=file_owner, file_id=file_id
293293
)

‎packages/python/plotly/_plotly_utils/utils.py

Copy file name to clipboardExpand all lines: packages/python/plotly/_plotly_utils/utils.py
+105-1Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,115 @@
1+
import base64
12
import decimal
23
import json as _json
34
import sys
45
import re
56
from functools import reduce
67

78
from _plotly_utils.optional_imports import get_module
8-
from _plotly_utils.basevalidators import ImageUriValidator
9+
from _plotly_utils.basevalidators import (
10+
ImageUriValidator,
11+
copy_to_readonly_numpy_array,
12+
is_homogeneous_array,
13+
)
14+
15+
16+
int8min = -128
17+
int8max = 127
18+
int16min = -32768
19+
int16max = 32767
20+
int32min = -2147483648
21+
int32max = 2147483647
22+
23+
uint8max = 255
24+
uint16max = 65535
25+
uint32max = 4294967295
26+
27+
plotlyjsShortTypes = {
28+
"int8": "i1",
29+
"uint8": "u1",
30+
"int16": "i2",
31+
"uint16": "u2",
32+
"int32": "i4",
33+
"uint32": "u4",
34+
"float32": "f4",
35+
"float64": "f8",
36+
}
37+
38+
39+
def to_typed_array_spec(v):
40+
"""
41+
Convert numpy array to plotly.js typed array spec
42+
If not possible return the original value
43+
"""
44+
v = copy_to_readonly_numpy_array(v)
45+
46+
np = get_module("numpy", should_load=False)
47+
if not np or not isinstance(v, np.ndarray):
48+
return v
49+
50+
dtype = str(v.dtype)
51+
52+
# convert default Big Ints until we could support them in plotly.js
53+
if dtype == "int64":
54+
max = v.max()
55+
min = v.min()
56+
if max <= int8max and min >= int8min:
57+
v = v.astype("int8")
58+
elif max <= int16max and min >= int16min:
59+
v = v.astype("int16")
60+
elif max <= int32max and min >= int32min:
61+
v = v.astype("int32")
62+
else:
63+
return v
64+
65+
elif dtype == "uint64":
66+
max = v.max()
67+
min = v.min()
68+
if max <= uint8max and min >= 0:
69+
v = v.astype("uint8")
70+
elif max <= uint16max and min >= 0:
71+
v = v.astype("uint16")
72+
elif max <= uint32max and min >= 0:
73+
v = v.astype("uint32")
74+
else:
75+
return v
76+
77+
dtype = str(v.dtype)
78+
79+
if dtype in plotlyjsShortTypes:
80+
arrObj = {
81+
"dtype": plotlyjsShortTypes[dtype],
82+
"bdata": base64.b64encode(v).decode("ascii"),
83+
}
84+
85+
if v.ndim > 1:
86+
arrObj["shape"] = str(v.shape)[1:-1]
87+
88+
return arrObj
89+
90+
return v
91+
92+
93+
def is_skipped_key(key):
94+
"""
95+
Return whether the key is skipped for conversion to the typed array spec
96+
"""
97+
skipped_keys = ["geojson", "layer", "layers", "range"]
98+
return any(skipped_key == key for skipped_key in skipped_keys)
99+
100+
101+
def convert_to_base64(obj):
102+
if isinstance(obj, dict):
103+
for key, value in obj.items():
104+
if is_skipped_key(key):
105+
continue
106+
elif is_homogeneous_array(value):
107+
obj[key] = to_typed_array_spec(value)
108+
else:
109+
convert_to_base64(value)
110+
elif isinstance(obj, list) or isinstance(obj, tuple):
111+
for value in obj:
112+
convert_to_base64(value)
9113

10114

11115
def cumsum(x):

‎packages/python/plotly/plotly/basedatatypes.py

Copy file name to clipboardExpand all lines: packages/python/plotly/plotly/basedatatypes.py
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
display_string_positions,
1616
chomp_empty_strings,
1717
find_closest_string,
18+
convert_to_base64,
1819
)
1920
from _plotly_utils.exceptions import PlotlyKeyError
2021
from .optional_imports import get_module
@@ -3310,6 +3311,9 @@ def to_dict(self):
33103311
if frames:
33113312
res["frames"] = frames
33123313

3314+
# Add base64 conversion before sending to the front-end
3315+
convert_to_base64(res)
3316+
33133317
return res
33143318

33153319
def to_plotly_json(self):

‎packages/python/plotly/plotly/express/_core.py

Copy file name to clipboardExpand all lines: packages/python/plotly/plotly/express/_core.py
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,7 +1236,7 @@ def process_args_into_dataframe(args, wide_mode, var_name, value_name):
12361236
raise ValueError(
12371237
"All arguments should have the same length. "
12381238
"The length of column argument `df[%s]` is %d, whereas the "
1239-
"length of previously-processed arguments %s is %d"
1239+
"length of previously-processed arguments %s is %d"
12401240
% (
12411241
field,
12421242
len(df_input[argument]),
@@ -1274,7 +1274,7 @@ def process_args_into_dataframe(args, wide_mode, var_name, value_name):
12741274
raise ValueError(
12751275
"All arguments should have the same length. "
12761276
"The length of argument `%s` is %d, whereas the "
1277-
"length of previously-processed arguments %s is %d"
1277+
"length of previously-processed arguments %s is %d"
12781278
% (field, len(argument), str(list(df_output.keys())), length)
12791279
)
12801280
df_output[str(col_name)] = to_unindexed_series(argument, str(col_name))
+84Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import json
2+
from unittest import TestCase
3+
import numpy as np
4+
from plotly.tests.test_optional.optional_utils import NumpyTestUtilsMixin
5+
import plotly.graph_objs as go
6+
7+
8+
class TestShouldNotUseBase64InUnsupportedKeys(NumpyTestUtilsMixin, TestCase):
9+
def test_np_geojson(self):
10+
normal_coordinates = [
11+
[
12+
[-87, 35],
13+
[-87, 30],
14+
[-85, 30],
15+
[-85, 35],
16+
]
17+
]
18+
19+
numpy_coordinates = np.array(normal_coordinates)
20+
21+
data = [
22+
{
23+
"type": "choropleth",
24+
"locations": ["AL"],
25+
"featureidkey": "properties.id",
26+
"z": np.array([10]),
27+
"geojson": {
28+
"type": "Feature",
29+
"properties": {"id": "AL"},
30+
"geometry": {"type": "Polygon", "coordinates": numpy_coordinates},
31+
},
32+
}
33+
]
34+
35+
fig = go.Figure(data=data)
36+
37+
assert (
38+
json.loads(fig.to_json())["data"][0]["geojson"]["geometry"]["coordinates"]
39+
== normal_coordinates
40+
)
41+
42+
def test_np_layers(self):
43+
layout = {
44+
"mapbox": {
45+
"layers": [
46+
{
47+
"sourcetype": "geojson",
48+
"type": "line",
49+
"line": {"dash": np.array([2.5, 1])},
50+
"source": {
51+
"type": "FeatureCollection",
52+
"features": [
53+
{
54+
"type": "Feature",
55+
"geometry": {
56+
"type": "LineString",
57+
"coordinates": np.array(
58+
[[0.25, 52], [0.75, 50]]
59+
),
60+
},
61+
}
62+
],
63+
},
64+
},
65+
],
66+
"center": {"lon": 0.5, "lat": 51},
67+
},
68+
}
69+
data = [{"type": "scattermapbox"}]
70+
71+
fig = go.Figure(data=data, layout=layout)
72+
73+
assert (fig.layout["mapbox"]["layers"][0]["line"]["dash"] == (2.5, 1)).all()
74+
75+
assert json.loads(fig.to_json())["layout"]["mapbox"]["layers"][0]["source"][
76+
"features"
77+
][0]["geometry"]["coordinates"] == [[0.25, 52], [0.75, 50]]
78+
79+
def test_np_range(self):
80+
layout = {"xaxis": {"range": np.array([0, 1])}}
81+
82+
fig = go.Figure(data=[{"type": "scatter"}], layout=layout)
83+
84+
assert json.loads(fig.to_json())["layout"]["xaxis"]["range"] == [0, 1]

‎packages/python/plotly/plotly/tests/test_optional/test_px/test_px_functions.py

Copy file name to clipboardExpand all lines: packages/python/plotly/plotly/tests/test_optional/test_px/test_px_functions.py
+3-3Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,15 @@ def _compare_figures(go_trace, px_fig):
2525
def test_pie_like_px():
2626
# Pie
2727
labels = ["Oxygen", "Hydrogen", "Carbon_Dioxide", "Nitrogen"]
28-
values = [4500, 2500, 1053, 500]
28+
values = np.array([4500, 2500, 1053, 500])
2929

3030
fig = px.pie(names=labels, values=values)
3131
trace = go.Pie(labels=labels, values=values)
3232
_compare_figures(trace, fig)
3333

3434
labels = ["Eve", "Cain", "Seth", "Enos", "Noam", "Abel", "Awan", "Enoch", "Azura"]
3535
parents = ["", "Eve", "Eve", "Seth", "Seth", "Eve", "Eve", "Awan", "Eve"]
36-
values = [10, 14, 12, 10, 2, 6, 6, 4, 4]
36+
values = np.array([10, 14, 12, 10, 2, 6, 6, 4, 4])
3737
# Sunburst
3838
fig = px.sunburst(names=labels, parents=parents, values=values)
3939
trace = go.Sunburst(labels=labels, parents=parents, values=values)
@@ -45,7 +45,7 @@ def test_pie_like_px():
4545

4646
# Funnel
4747
x = ["A", "B", "C"]
48-
y = [3, 2, 1]
48+
y = np.array([3, 2, 1])
4949
fig = px.funnel(y=y, x=x)
5050
trace = go.Funnel(y=y, x=x)
5151
_compare_figures(trace, fig)

‎packages/python/plotly/plotly/tests/test_optional/test_utils/test_utils.py

Copy file name to clipboardExpand all lines: packages/python/plotly/plotly/tests/test_optional/test_utils/test_utils.py
-32Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -372,38 +372,6 @@ def test_invalid_encode_exception(self):
372372
with self.assertRaises(TypeError):
373373
_json.dumps({"a": {1}}, cls=utils.PlotlyJSONEncoder)
374374

375-
def test_fast_track_finite_arrays(self):
376-
# if NaN or Infinity is found in the json dump
377-
# of a figure, it is decoded and re-encoded to replace these values
378-
# with null. This test checks that NaN and Infinity values are
379-
# indeed converted to null, and that the encoding of figures
380-
# without inf or nan is faster (because we can avoid decoding
381-
# and reencoding).
382-
z = np.random.randn(100, 100)
383-
x = np.arange(100.0)
384-
fig_1 = go.Figure(go.Heatmap(z=z, x=x))
385-
t1 = time()
386-
json_str_1 = _json.dumps(fig_1, cls=utils.PlotlyJSONEncoder)
387-
t2 = time()
388-
x[0] = np.nan
389-
x[1] = np.inf
390-
fig_2 = go.Figure(go.Heatmap(z=z, x=x))
391-
t3 = time()
392-
json_str_2 = _json.dumps(fig_2, cls=utils.PlotlyJSONEncoder)
393-
t4 = time()
394-
assert t2 - t1 < t4 - t3
395-
assert "null" in json_str_2
396-
assert "NaN" not in json_str_2
397-
assert "Infinity" not in json_str_2
398-
x = np.arange(100.0)
399-
fig_3 = go.Figure(go.Heatmap(z=z, x=x))
400-
fig_3.update_layout(title_text="Infinity")
401-
t5 = time()
402-
json_str_3 = _json.dumps(fig_3, cls=utils.PlotlyJSONEncoder)
403-
t6 = time()
404-
assert t2 - t1 < t6 - t5
405-
assert "Infinity" in json_str_3
406-
407375

408376
class TestNumpyIntegerBaseType(TestCase):
409377
def test_numpy_integer_import(self):

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.