Skip to content

Commit 6f9f600

Browse files
authored
Merge pull request matplotlib#18732 from QuLogic/nbagg-ff-esr
MNT: Add a ponyfill for ResizeObserver on older browsers.
2 parents 26f8ccc + 1bdbf92 commit 6f9f600

File tree

5 files changed

+230
-2
lines changed

5 files changed

+230
-2
lines changed
+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# CC0 1.0 Universal
2+
3+
## Statement of Purpose
4+
5+
The laws of most jurisdictions throughout the world automatically confer
6+
exclusive Copyright and Related Rights (defined below) upon the creator and
7+
subsequent owner(s) (each and all, an “owner”) of an original work of
8+
authorship and/or a database (each, a “Work”).
9+
10+
Certain owners wish to permanently relinquish those rights to a Work for the
11+
purpose of contributing to a commons of creative, cultural and scientific works
12+
(“Commons”) that the public can reliably and without fear of later claims of
13+
infringement build upon, modify, incorporate in other works, reuse and
14+
redistribute as freely as possible in any form whatsoever and for any purposes,
15+
including without limitation commercial purposes. These owners may contribute
16+
to the Commons to promote the ideal of a free culture and the further
17+
production of creative, cultural and scientific works, or to gain reputation or
18+
greater distribution for their Work in part through the use and efforts of
19+
others.
20+
21+
For these and/or other purposes and motivations, and without any expectation of
22+
additional consideration or compensation, the person associating CC0 with a
23+
Work (the “Affirmer”), to the extent that he or she is an owner of Copyright
24+
and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and
25+
publicly distribute the Work under its terms, with knowledge of his or her
26+
Copyright and Related Rights in the Work and the meaning and intended legal
27+
effect of CC0 on those rights.
28+
29+
1. Copyright and Related Rights. A Work made available under CC0 may be
30+
protected by copyright and related or neighboring rights (“Copyright and
31+
Related Rights”). Copyright and Related Rights include, but are not limited
32+
to, the following:
33+
1. the right to reproduce, adapt, distribute, perform, display, communicate,
34+
and translate a Work;
35+
2. moral rights retained by the original author(s) and/or performer(s);
36+
3. publicity and privacy rights pertaining to a person’s image or likeness
37+
depicted in a Work;
38+
4. rights protecting against unfair competition in regards to a Work,
39+
subject to the limitations in paragraph 4(i), below;
40+
5. rights protecting the extraction, dissemination, use and reuse of data in
41+
a Work;
42+
6. database rights (such as those arising under Directive 96/9/EC of the
43+
European Parliament and of the Council of 11 March 1996 on the legal
44+
protection of databases, and under any national implementation thereof,
45+
including any amended or successor version of such directive); and
46+
7. other similar, equivalent or corresponding rights throughout the world
47+
based on applicable law or treaty, and any national implementations
48+
thereof.
49+
50+
2. Waiver. To the greatest extent permitted by, but not in contravention of,
51+
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
52+
unconditionally waives, abandons, and surrenders all of Affirmer’s Copyright
53+
and Related Rights and associated claims and causes of action, whether now
54+
known or unknown (including existing as well as future claims and causes of
55+
action), in the Work (i) in all territories worldwide, (ii) for the maximum
56+
duration provided by applicable law or treaty (including future time
57+
extensions), (iii) in any current or future medium and for any number of
58+
copies, and (iv) for any purpose whatsoever, including without limitation
59+
commercial, advertising or promotional purposes (the “Waiver”). Affirmer
60+
makes the Waiver for the benefit of each member of the public at large and
61+
to the detriment of Affirmer’s heirs and successors, fully intending that
62+
such Waiver shall not be subject to revocation, rescission, cancellation,
63+
termination, or any other legal or equitable action to disrupt the quiet
64+
enjoyment of the Work by the public as contemplated by Affirmer’s express
65+
Statement of Purpose.
66+
67+
3. Public License Fallback. Should any part of the Waiver for any reason be
68+
judged legally invalid or ineffective under applicable law, then the Waiver
69+
shall be preserved to the maximum extent permitted taking into account
70+
Affirmer’s express Statement of Purpose. In addition, to the extent the
71+
Waiver is so judged Affirmer hereby grants to each affected person a
72+
royalty-free, non transferable, non sublicensable, non exclusive,
73+
irrevocable and unconditional license to exercise Affirmer’s Copyright and
74+
Related Rights in the Work (i) in all territories worldwide, (ii) for the
75+
maximum duration provided by applicable law or treaty (including future time
76+
extensions), (iii) in any current or future medium and for any number of
77+
copies, and (iv) for any purpose whatsoever, including without limitation
78+
commercial, advertising or promotional purposes (the “License”). The License
79+
shall be deemed effective as of the date CC0 was applied by Affirmer to the
80+
Work. Should any part of the License for any reason be judged legally
81+
invalid or ineffective under applicable law, such partial invalidity or
82+
ineffectiveness shall not invalidate the remainder of the License, and in
83+
such case Affirmer hereby affirms that he or she will not (i) exercise any
84+
of his or her remaining Copyright and Related Rights in the Work or (ii)
85+
assert any associated claims and causes of action with respect to the Work,
86+
in either case contrary to Affirmer’s express Statement of Purpose.
87+
88+
4. Limitations and Disclaimers.
89+
1. No trademark or patent rights held by Affirmer are waived, abandoned,
90+
surrendered, licensed or otherwise affected by this document.
91+
2. Affirmer offers the Work as-is and makes no representations or warranties
92+
of any kind concerning the Work, express, implied, statutory or
93+
otherwise, including without limitation warranties of title,
94+
merchantability, fitness for a particular purpose, non infringement, or
95+
the absence of latent or other defects, accuracy, or the present or
96+
absence of errors, whether or not discoverable, all to the greatest
97+
extent permissible under applicable law.
98+
3. Affirmer disclaims responsibility for clearing rights of other persons
99+
that may apply to the Work or any use thereof, including without
100+
limitation any person’s Copyright and Related Rights in the Work.
101+
Further, Affirmer disclaims responsibility for obtaining any necessary
102+
consents, permissions or other rights required for any use of the Work.
103+
4. Affirmer understands and acknowledges that Creative Commons is not a
104+
party to this document and has no duty or obligation with respect to this
105+
CC0 or use of the Work.
106+
107+
For more information, please see
108+
http://creativecommons.org/publicdomain/zero/1.0/.

lib/matplotlib/backends/web_backend/js/mpl.js

+16-2
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,17 @@ mpl.figure.prototype._init_canvas = function () {
169169
'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'
170170
);
171171

172-
var resizeObserver = new ResizeObserver(function (entries) {
172+
// Apply a ponyfill if ResizeObserver is not implemented by browser.
173+
if (this.ResizeObserver === undefined) {
174+
if (window.ResizeObserver !== undefined) {
175+
this.ResizeObserver = window.ResizeObserver;
176+
} else {
177+
var obs = _JSXTOOLS_RESIZE_OBSERVER({});
178+
this.ResizeObserver = obs.ResizeObserver;
179+
}
180+
}
181+
182+
this.resizeObserverInstance = new this.ResizeObserver(function (entries) {
173183
var nentries = entries.length;
174184
for (var i = 0; i < nentries; i++) {
175185
var entry = entries[i];
@@ -222,7 +232,7 @@ mpl.figure.prototype._init_canvas = function () {
222232
}
223233
}
224234
});
225-
resizeObserver.observe(canvas_div);
235+
this.resizeObserverInstance.observe(canvas_div);
226236

227237
function on_mouse_event_closure(name) {
228238
return function (event) {
@@ -676,3 +686,7 @@ mpl.figure.prototype.toolbar_button_onclick = function (name) {
676686
mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {
677687
this.message.textContent = tooltip;
678688
};
689+
690+
///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////
691+
// prettier-ignore
692+
var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError("Constructor requires 'new' operator");i.set(this,e)}function h(){throw new TypeError("Function is not a constructor")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line

lib/matplotlib/backends/web_backend/js/nbagg_mpl.js

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ mpl.figure.prototype.handle_close = function (fig, msg) {
6969
'cleared',
7070
fig._remove_fig_handler
7171
);
72+
fig.resizeObserverInstance.unobserve(fig.canvas_div);
7273

7374
// Update the output cell to use the data from the current canvas.
7475
fig.push_to_output();

lib/matplotlib/backends/web_backend/package.json

+3
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,8 @@
1111
"lint:check": "npm run prettier:check && npm run eslint:check",
1212
"prettier": "prettier --write \"**/*{.ts,.tsx,.js,.jsx,.css,.json}\"",
1313
"prettier:check": "prettier --check \"**/*{.ts,.tsx,.js,.jsx,.css,.json}\""
14+
},
15+
"dependencies": {
16+
"@jsxtools/resize-observer": "^1.0.4"
1417
}
1518
}

tools/embed_js.py

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
"""
2+
Script to embed JavaScript dependencies in mpl.js.
3+
"""
4+
5+
from collections import namedtuple
6+
from pathlib import Path
7+
import re
8+
import shutil
9+
import subprocess
10+
import sys
11+
12+
13+
Package = namedtuple('Package', [
14+
# The package to embed, in some form that `npm install` can use.
15+
'name',
16+
# The path to the source file within the package to embed.
17+
'source',
18+
# The path to the license file within the package to embed.
19+
'license'])
20+
# The list of packages to embed, in some form that `npm install` can use.
21+
JAVASCRIPT_PACKAGES = [
22+
# Polyfill/ponyfill for ResizeObserver.
23+
Package('@jsxtools/resize-observer', 'index.js', 'LICENSE.md'),
24+
]
25+
# This is the magic line that must exist in mpl.js, after which the embedded
26+
# JavaScript will be appended.
27+
MPLJS_MAGIC_HEADER = (
28+
"///////////////// REMAINING CONTENT GENERATED BY embed_js.py "
29+
"/////////////////\n")
30+
31+
32+
def safe_name(name):
33+
"""
34+
Make *name* safe to use as a JavaScript variable name.
35+
"""
36+
return '_'.join(re.split(r'[@/-]', name)).upper()
37+
38+
39+
def prep_package(web_backend_path, pkg):
40+
source = web_backend_path / 'node_modules' / pkg.name / pkg.source
41+
license = web_backend_path / 'node_modules' / pkg.name / pkg.license
42+
if not source.exists():
43+
# Exact version should already be saved in package.json, so we use
44+
# --no-save here.
45+
try:
46+
subprocess.run(['npm', 'install', '--no-save', pkg.name],
47+
cwd=web_backend_path)
48+
except FileNotFoundError as err:
49+
raise ValueError(
50+
f'npm must be installed to fetch {pkg.name}') from err
51+
if not source.exists():
52+
raise ValueError(
53+
f'{pkg.name} package is missing source in {pkg.source}')
54+
elif not license.exists():
55+
raise ValueError(
56+
f'{pkg.name} package is missing license in {pkg.license}')
57+
58+
return source, license
59+
60+
61+
def gen_embedded_lines(pkg, source):
62+
name = safe_name(pkg.name)
63+
print('Embedding', source, 'as', name)
64+
yield '// prettier-ignore\n'
65+
for line in source.read_text().splitlines():
66+
yield (line.replace('module.exports=function', f'var {name}=function')
67+
+ ' // eslint-disable-line\n')
68+
69+
70+
def build_mpljs(web_backend_path, license_path):
71+
mpljs_path = web_backend_path / "js/mpl.js"
72+
mpljs_orig = mpljs_path.read_text().splitlines(keepends=True)
73+
try:
74+
mpljs_orig = mpljs_orig[:mpljs_orig.index(MPLJS_MAGIC_HEADER) + 1]
75+
except IndexError as err:
76+
raise ValueError(
77+
f'The mpl.js file *must* have the exact line: {MPLJS_MAGIC_HEADER}'
78+
) from err
79+
80+
with mpljs_path.open('w') as mpljs:
81+
mpljs.writelines(mpljs_orig)
82+
83+
for pkg in JAVASCRIPT_PACKAGES:
84+
source, license = prep_package(web_backend_path, pkg)
85+
mpljs.writelines(gen_embedded_lines(pkg, source))
86+
87+
shutil.copy(license,
88+
license_path / f'LICENSE{safe_name(pkg.name)}')
89+
90+
91+
if __name__ == '__main__':
92+
# Write the mpl.js file.
93+
if len(sys.argv) > 1:
94+
web_backend_path = Path(sys.argv[1])
95+
else:
96+
web_backend_path = (Path(__file__).parent.parent /
97+
"lib/matplotlib/backends/web_backend")
98+
if len(sys.argv) > 2:
99+
license_path = Path(sys.argv[2])
100+
else:
101+
license_path = Path(__file__).parent.parent / "LICENSE"
102+
build_mpljs(web_backend_path, license_path)

0 commit comments

Comments
 (0)