Skip to content

Commit 09e1ad9

Browse files
johnchaseJohn Chaselizgehret
authored
MAINT: vendor vega assets (#94)
<!-- PR guidance and examples: https://github.com/rachis-org/governance/blob/main/pr_template_guidelines.md --> <!-- rachis Generative AI Policy: https://github.com/rachis-org/governance/blob/main/ai_policy.md --> # Description * Fixes issue #1 by removing jsDelivr runtime dependencies. * Vendors Vega 5.22.1 and vega-embed 6.20.8. * Copies both JavaScript bundles and their licenses into every generated visualization. * Applies to scatterplot, heatmap, lineplot, and boxplot outputs. * Adds byte-for-byte output verification and pinned SHA-256 tests. # AI Disclosure <!-- REQUIRED: Check exactly one option. --> - [ ] NO AI USED. - [x] AI USED. # AI Usage Details Tool: Codex, using GPT-5. Codex inspected the issue and contribution guidance. Codex produced all new Python implementation, tests, and vendor documentation. Codex downloaded the pinned upstream Vega assets, verified checksums, ran tests/linting, and pushed the commits. User independently tested plugin, verified visualization, tested offline usage. User defined implementation details such as package structure and naming User independently reviewed PR --------- Co-authored-by: John Chase <johnchase@Johns-MacBook-Pro.local> Co-authored-by: Liz Gehret <54517601+lizgehret@users.noreply.github.com> Co-authored-by: Liz Gehret <elizabeth.gehret@gmail.com>
1 parent d1dc4d3 commit 09e1ad9

19 files changed

Lines changed: 396 additions & 137 deletions

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
pyproject.toml export-subst
2+
q2_vizard/assets/vendor/*.min.js binary linguist-vendored

q2_vizard/_examples.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,3 +179,58 @@ def heatmap(use):
179179
visualization='heatmap'
180180
)
181181
)
182+
183+
184+
# boxplot examples
185+
def boxplot_horizontal_percentile_whisker_range(use):
186+
metadata = use.init_metadata('metadata', md_factory)
187+
188+
boxplot_viz, = use.action(
189+
use.UsageAction('vizard', 'boxplot'),
190+
use.UsageInputs(
191+
metadata=metadata,
192+
distribution_measure='x',
193+
group_by='group',
194+
whisker_range='percentile',
195+
box_orientation='horizontal'
196+
),
197+
use.UsageOutputNames(
198+
visualization='boxplot'
199+
)
200+
)
201+
202+
203+
def boxplot_horizontal_tukeys_iqr_whisker_range(use):
204+
metadata = use.init_metadata('metadata', md_factory)
205+
206+
boxplot_viz, = use.action(
207+
use.UsageAction('vizard', 'boxplot'),
208+
use.UsageInputs(
209+
metadata=metadata,
210+
distribution_measure='x',
211+
group_by='group',
212+
whisker_range='tukeys_iqr',
213+
box_orientation='horizontal'
214+
),
215+
use.UsageOutputNames(
216+
visualization='boxplot'
217+
)
218+
)
219+
220+
221+
def boxplot_vertical_minmax_whisker_range(use):
222+
metadata = use.init_metadata('metadata', md_factory)
223+
224+
boxplot_viz, = use.action(
225+
use.UsageAction('vizard', 'boxplot'),
226+
use.UsageInputs(
227+
metadata=metadata,
228+
distribution_measure='x',
229+
group_by='group',
230+
whisker_range='minmax',
231+
box_orientation='vertical'
232+
),
233+
use.UsageOutputNames(
234+
visualization='boxplot'
235+
)
236+
)

q2_vizard/_render.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# ----------------------------------------------------------------------------
2+
# Copyright (c) 2023-2026, QIIME 2 development team.
3+
#
4+
# Distributed under the terms of the Modified BSD License.
5+
#
6+
# The full license is in the file LICENSE, distributed with this software.
7+
# ----------------------------------------------------------------------------
8+
9+
10+
import importlib.resources
11+
import json
12+
import os
13+
import shutil
14+
15+
import jinja2
16+
17+
18+
_VENDORED_FILES = (
19+
'vega.min.js',
20+
'vega-embed.min.js',
21+
'LICENSE-vega',
22+
'LICENSE-vega-embed',
23+
)
24+
25+
26+
def _copy_vendored_assets(output_dir):
27+
vendor_dir = importlib.resources.files(
28+
'q2_vizard') / 'assets' / 'vendor'
29+
30+
for filename in _VENDORED_FILES:
31+
source = vendor_dir / filename
32+
destination = os.path.join(output_dir, filename)
33+
34+
with source.open('rb') as source_fh, \
35+
open(destination, 'wb') as dest_fh:
36+
shutil.copyfileobj(source_fh, dest_fh)
37+
38+
39+
def _json_replace(json_obj, /, **values):
40+
"""
41+
Search for elements of `{"{{REPLACE_PARAM}}": "some_key"}` and replace
42+
with the result of `values["some_key"]`.
43+
Expects positional args for json_obj so as to avoid any future
44+
namespace collisions that may appear in the values dict
45+
"""
46+
if type(json_obj) is dict and list(json_obj) == ["{{REPLACE_PARAM}}"]:
47+
param_name = json_obj["{{REPLACE_PARAM}}"]
48+
return values[param_name]
49+
50+
if type(json_obj) is list:
51+
return [_json_replace(x, **values) for x in json_obj]
52+
53+
elif type(json_obj) is dict:
54+
return {key: _json_replace(value, **values)
55+
for key, value in json_obj.items()}
56+
57+
else:
58+
return json_obj
59+
60+
61+
def _render_visualization(output_dir, name, spec_filename='spec.json', /,
62+
**spec_params):
63+
"""
64+
Base template for rendering all visualizations.
65+
Expects positional args for output_dir, name & spec_filename
66+
so as to avoid any future namespace collisions
67+
that may appear in the spec_params dict.
68+
69+
Assumes the same general file structure for each viz as shown below:
70+
71+
per-viz assets
72+
--------------
73+
`q2_vizard/assets/{viz_name}`
74+
75+
index.html
76+
----------
77+
`q2_vizard/assets/{viz_name}/index.html`
78+
79+
spec(s)
80+
-------
81+
`q2_vizard/assets/{viz_name}/{spec_name}.json`
82+
"""
83+
assets = importlib.resources.files('q2_vizard') / 'assets' / name
84+
85+
with (assets / spec_filename).open() as fh:
86+
full_spec = _json_replace(json.load(fh), **spec_params)
87+
88+
J_ENV = jinja2.Environment(
89+
loader=jinja2.PackageLoader('q2_vizard', f'assets/{name}')
90+
)
91+
index = J_ENV.get_template('index.html')
92+
93+
with open(os.path.join(output_dir, 'index.html'), 'w') as fh:
94+
spec_string = json.dumps(full_spec)
95+
fh.write(index.render(spec=spec_string))
96+
97+
_copy_vendored_assets(output_dir)

q2_vizard/_util.py

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,6 @@
66
# The full license is in the file LICENSE, distributed with this software.
77
# ----------------------------------------------------------------------------
88

9-
def _json_replace(json_obj, **values):
10-
"""
11-
Search for elements of `{"{{REPLACE_PARAM}}": "some_key"}` and replace
12-
with the result of `values["some_key"]`.
13-
"""
14-
if type(json_obj) is dict and list(json_obj) == ["{{REPLACE_PARAM}}"]:
15-
param_name = json_obj["{{REPLACE_PARAM}}"]
16-
return values[param_name]
17-
18-
if type(json_obj) is list:
19-
return [_json_replace(x, **values) for x in json_obj]
20-
21-
elif type(json_obj) is dict:
22-
return {key: _json_replace(value, **values)
23-
for key, value in json_obj.items()}
24-
25-
else:
26-
return json_obj
27-
289

2910
def _col_type_validation(metadata, measure, col_type):
3011
if col_type == 'categorical':

q2_vizard/assets/boxplot/index.html

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
<!DOCTYPE html>
22
<html>
33
<head>
4-
<!-- Import Vega & Vega-Lite (does not have to be from CDN) -->
5-
<script src="https://cdn.jsdelivr.net/npm/vega@5.22.1"></script>
6-
<!-- Import vega-embed -->
7-
<script src="https://cdn.jsdelivr.net/npm/vega-embed@6.20.8"></script>
4+
<script src="vega.min.js"></script>
5+
<script src="vega-embed.min.js"></script>
86
<script type="application/json" id="spec">
97
{{ spec }}
108
</script>

q2_vizard/assets/heatmap/index.html

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
<!DOCTYPE html>
22
<html>
33
<head>
4-
<!-- Import Vega & Vega-Lite (does not have to be from CDN) -->
5-
<script src="https://cdn.jsdelivr.net/npm/vega@5.22.1"></script>
6-
<!-- Import vega-embed -->
7-
<script src="https://cdn.jsdelivr.net/npm/vega-embed@6.20.8"></script>
4+
<script src="vega.min.js"></script>
5+
<script src="vega-embed.min.js"></script>
86
<script type="application/json" id="spec">
97
{{ spec }}
108
</script>

q2_vizard/assets/lineplot/index.html

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
<!DOCTYPE html>
22
<html>
33
<head>
4-
<!-- Import Vega & Vega-Lite (does not have to be from CDN) -->
5-
<script src="https://cdn.jsdelivr.net/npm/vega@5.22.1"></script>
6-
<!-- Import vega-embed -->
7-
<script src="https://cdn.jsdelivr.net/npm/vega-embed@6.20.8"></script>
4+
<script src="vega.min.js"></script>
5+
<script src="vega-embed.min.js"></script>
86
<script type="application/json" id="spec">
97
{{ spec }}
108
</script>

q2_vizard/assets/scatterplot_2d/index.html

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
<!DOCTYPE html>
22
<html>
33
<head>
4-
<!-- Import Vega & Vega-Lite (does not have to be from CDN) -->
5-
<script src="https://cdn.jsdelivr.net/npm/vega@5.22.1"></script>
6-
<!-- Import vega-embed -->
7-
<script src="https://cdn.jsdelivr.net/npm/vega-embed@6.20.8"></script>
4+
<script src="vega.min.js"></script>
5+
<script src="vega-embed.min.js"></script>
86
<script type="application/json" id="spec">
97
{{ spec }}
108
</script>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
Copyright (c) 2015-2021, University of Washington Interactive Data Lab
2+
All rights reserved.
3+
4+
Redistribution and use in source and binary forms, with or without
5+
modification, are permitted provided that the following conditions are met:
6+
7+
1. Redistributions of source code must retain the above copyright notice, this
8+
list of conditions and the following disclaimer.
9+
10+
2. Redistributions in binary form must reproduce the above copyright notice,
11+
this list of conditions and the following disclaimer in the documentation
12+
and/or other materials provided with the distribution.
13+
14+
3. Neither the name of the copyright holder nor the names of its contributors
15+
may be used to endorse or promote products derived from this software
16+
without specific prior written permission.
17+
18+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
22+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
Copyright (c) 2015, University of Washington Interactive Data Lab
2+
All rights reserved.
3+
4+
Redistribution and use in source and binary forms, with or without
5+
modification, are permitted provided that the following conditions are met:
6+
7+
1. Redistributions of source code must retain the above copyright notice, this
8+
list of conditions and the following disclaimer.
9+
10+
2. Redistributions in binary form must reproduce the above copyright notice,
11+
this list of conditions and the following disclaimer in the documentation
12+
and/or other materials provided with the distribution.
13+
14+
3. Neither the name of the copyright holder nor the names of its contributors
15+
may be used to endorse or promote products derived from this software
16+
without specific prior written permission.
17+
18+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
22+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

0 commit comments

Comments
 (0)