datalog-theme 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +162 -0
- data/CITATION.cff +29 -0
- data/LICENSE +21 -0
- data/README.md +257 -0
- data/_data/academic.yml +217 -0
- data/_data/cdn-integrity.yml +51 -0
- data/_data/config/author.yml +121 -0
- data/_data/config/features.yml +262 -0
- data/_data/config/site.yml +42 -0
- data/_data/config/theme.yml +181 -0
- data/_data/datasets.yml +28 -0
- data/_data/i18n/en.yml +438 -0
- data/_data/i18n/es.yml +438 -0
- data/_data/i18n/pt.yml +438 -0
- data/_data/js_manifest.json +22 -0
- data/_data/js_meta.json +371 -0
- data/_data/navigation.yml +145 -0
- data/_data/projects.yml +41 -0
- data/_data/publications.yml +28 -0
- data/_data/social.yml +73 -0
- data/_data/visualizations.yml +51 -0
- data/_includes/analytics/dashboard.html +149 -0
- data/_includes/analytics.html +9 -0
- data/_includes/components/academic-dashboard.html +347 -0
- data/_includes/components/advanced-search.html +682 -0
- data/_includes/components/api-function.html +94 -0
- data/_includes/components/author-bio.html +166 -0
- data/_includes/components/bookmark-system.html +96 -0
- data/_includes/components/breadcrumbs.html +89 -0
- data/_includes/components/citation-tools.html +94 -0
- data/_includes/components/comments.html +244 -0
- data/_includes/components/content-provenance.html +43 -0
- data/_includes/components/content-recommendations.html +228 -0
- data/_includes/components/difficulty-badge.html +84 -0
- data/_includes/components/email-preferences.html +200 -0
- data/_includes/components/enhanced-code-block.html +212 -0
- data/_includes/components/enhanced-metadata.html +228 -0
- data/_includes/components/enhanced-toc.html +209 -0
- data/_includes/components/hero.html +15 -0
- data/_includes/components/language-switcher.html +396 -0
- data/_includes/components/math-fallback.html +19 -0
- data/_includes/components/navigation-enhancements.html +454 -0
- data/_includes/components/newsletter-signup.html +178 -0
- data/_includes/components/open-science-badges.html +16 -0
- data/_includes/components/package-install.html +194 -0
- data/_includes/components/performance-monitor.html +170 -0
- data/_includes/components/popular-posts.html +233 -0
- data/_includes/components/post-hero.html +62 -0
- data/_includes/components/reading-progress.html +133 -0
- data/_includes/components/reading-time.html +121 -0
- data/_includes/components/responsive-image.html +63 -0
- data/_includes/components/search-facets.html +307 -0
- data/_includes/components/series-navigation.html +124 -0
- data/_includes/components/social-proof.html +34 -0
- data/_includes/components/social-share.html +119 -0
- data/_includes/components/user-preferences.html +566 -0
- data/_includes/components/visualization-card.html +24 -0
- data/_includes/components/viz-table-fallback.html +19 -0
- data/_includes/critical-css/default.html +0 -0
- data/_includes/critical-css/home.html +0 -0
- data/_includes/critical-css/post.html +0 -0
- data/_includes/csp-meta.html +17 -0
- data/_includes/footer/nav-column.html +31 -0
- data/_includes/footer.html +76 -0
- data/_includes/head.html +337 -0
- data/_includes/header/navigation.html +48 -0
- data/_includes/header.html +70 -0
- data/_includes/helpers/array-to-sentence.html +21 -0
- data/_includes/helpers/date-format.html +16 -0
- data/_includes/helpers/link-with-icon.html +26 -0
- data/_includes/layouts/default/article.html +67 -0
- data/_includes/meta/language-attributes.html +25 -0
- data/_includes/meta/math-config.html +21 -0
- data/_includes/meta/schema.html +153 -0
- data/_includes/meta/scripts-loader.html +52 -0
- data/_includes/post/related-posts.html +63 -0
- data/_includes/scripts.html +72 -0
- data/_includes/skip-link.html +1 -0
- data/_includes/toc.html +2 -0
- data/_layouts/archive.html +282 -0
- data/_layouts/dataset.html +52 -0
- data/_layouts/default.html +33 -0
- data/_layouts/home.html +48 -0
- data/_layouts/notebook.html +173 -0
- data/_layouts/package.html +238 -0
- data/_layouts/page.html +16 -0
- data/_layouts/portfolio.html +41 -0
- data/_layouts/post-sidebar.html +183 -0
- data/_layouts/post.html +338 -0
- data/_layouts/project.html +252 -0
- data/_layouts/research.html +504 -0
- data/_plugins/analytics_dashboard.rb +315 -0
- data/_plugins/config_validator.rb +397 -0
- data/_plugins/csp_generator.rb +115 -0
- data/_plugins/datalog_bibliography.rb +19 -0
- data/_plugins/datalog_comments.rb +18 -0
- data/_plugins/datalog_slides.rb +20 -0
- data/_plugins/front_matter_compat.rb +109 -0
- data/_plugins/i18n.rb +160 -0
- data/_plugins/image_optimizer.rb +445 -0
- data/_plugins/math_preprocessor.rb +185 -0
- data/_plugins/notebook_converter.rb +1107 -0
- data/_plugins/plugin_loader.rb +185 -0
- data/_plugins/publications_generator.rb +234 -0
- data/_plugins/reading_time.rb +15 -0
- data/_plugins/search_normalizer.rb +74 -0
- data/_plugins/sri_filter.rb +18 -0
- data/_plugins/toc_filter.rb +68 -0
- data/_plugins/warning_filter.rb +3 -0
- data/_sass/_base.scss +142 -0
- data/_sass/_components.scss +2380 -0
- data/_sass/_header.scss +470 -0
- data/_sass/_interactive.scss +344 -0
- data/_sass/_layout.scss +1760 -0
- data/_sass/_mathematical.scss +368 -0
- data/_sass/_mixins.scss +99 -0
- data/_sass/_package-docs.scss +715 -0
- data/_sass/_phase1-enhancements.scss +721 -0
- data/_sass/_phase3-enhancements.scss +874 -0
- data/_sass/_phase4-enhancements.scss +1214 -0
- data/_sass/_phase5-enhancements.scss +414 -0
- data/_sass/_search.scss +654 -0
- data/_sass/_syntax-highlighting.scss +128 -0
- data/_sass/_theme.scss +79 -0
- data/_sass/_typography.scss +148 -0
- data/_sass/_utilities.scss +334 -0
- data/_sass/_variables.scss +153 -0
- data/_sass/_visualizations.scss +242 -0
- data/assets/css/main.scss +4 -0
- data/assets/img/20220607123041_detail.001.png +0 -0
- data/assets/img/favicons/android-chrome-192x192.png +0 -0
- data/assets/img/favicons/android-chrome-512x512.png +0 -0
- data/assets/img/favicons/apple-touch-icon.png +0 -0
- data/assets/img/favicons/favicon-16x16.png +0 -0
- data/assets/img/favicons/favicon-32x32.png +0 -0
- data/assets/img/favicons/favicon.ico +0 -0
- data/assets/img/favicons/site.webmanifest +21 -0
- data/assets/img/portfolio-placeholder.svg +20 -0
- data/assets/js/academic.js +262 -0
- data/assets/js/analytics-dashboard.js +382 -0
- data/assets/js/core/dark-mode.js +79 -0
- data/assets/js/core/github-cards.js +123 -0
- data/assets/js/core/language-filter.js +69 -0
- data/assets/js/core/navigation.js +184 -0
- data/assets/js/core/scroll-progress.js +45 -0
- data/assets/js/core/search-hotkeys.js +62 -0
- data/assets/js/core/skip-links.js +62 -0
- data/assets/js/loader.js +163 -0
- data/assets/js/main.js +23 -0
- data/assets/js/math.js +818 -0
- data/assets/js/notebook.js +158 -0
- data/assets/js/search/analytics.js +91 -0
- data/assets/js/search/app.js +271 -0
- data/assets/js/search/autocomplete.js +120 -0
- data/assets/js/search/engine.js +260 -0
- data/assets/js/search/filters.js +38 -0
- data/assets/js/search/render.js +217 -0
- data/assets/js/search/utils.js +99 -0
- data/assets/js/search.js +354 -0
- data/assets/js/visualizations.js +816 -0
- data/assets/publications/datalog-publications.bib +8 -0
- data/assets/publications/datalog-publications.ris +9 -0
- data/assets/publications/publications.bib +30 -0
- data/assets/templates/diogo-ribeiro-cv.md +31 -0
- data/assets/templates/diogo-ribeiro-cv.tex +32 -0
- data/bin/datalog +26 -0
- data/datalog-theme.gemspec +72 -0
- data/lib/datalog/cli.rb +563 -0
- data/lib/datalog/plugin_system/dependency_resolver.rb +223 -0
- data/lib/datalog/plugin_system.rb +237 -0
- data/lib/datalog/plugins/citations.rb +182 -0
- data/lib/datalog/plugins/comments.rb +183 -0
- data/lib/datalog/plugins/search.rb +45 -0
- data/lib/datalog/plugins/slides.rb +121 -0
- data/lib/datalog/theme/theme.rb +18 -0
- data/lib/datalog/theme/version.rb +7 -0
- data/lib/datalog/warning_filter.rb +28 -0
- data/lib/datalog-theme.rb +17 -0
- metadata +555 -0
|
@@ -0,0 +1,816 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Visualization rendering for multiple chart libraries.
|
|
3
|
+
* Supports Plotly, D3, Observable, Vega, Bokeh, Shiny, and IPyWidgets.
|
|
4
|
+
* @module visualizations
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
(function () {
|
|
8
|
+
const isTestEnvironment =
|
|
9
|
+
typeof process !== 'undefined' &&
|
|
10
|
+
process.env &&
|
|
11
|
+
(process.env.NODE_ENV === 'test' || process.env.VITEST === 'true');
|
|
12
|
+
const VIZ_SELECTOR = '[data-viz-type]';
|
|
13
|
+
const SPEC_SELECTOR = 'script[type="application/json"]';
|
|
14
|
+
const WIDGET_STATE_SELECTOR = 'script[type="application/vnd.jupyter.widget-state+json"]';
|
|
15
|
+
const WIDGET_VIEW_SELECTOR = 'script[type="application/vnd.jupyter.widget-view+json"]';
|
|
16
|
+
|
|
17
|
+
const loadedScripts = new Map();
|
|
18
|
+
const loadedStyles = new Set();
|
|
19
|
+
const TABLE_TEMPLATE_ID = 'viz-table-fallback-template';
|
|
20
|
+
|
|
21
|
+
const loadScript = (src, options = {}) => {
|
|
22
|
+
if (!src) {
|
|
23
|
+
return Promise.reject(new Error('Missing script source'));
|
|
24
|
+
}
|
|
25
|
+
if (loadedScripts.has(src)) {
|
|
26
|
+
return loadedScripts.get(src);
|
|
27
|
+
}
|
|
28
|
+
const promise = new Promise((resolve, reject) => {
|
|
29
|
+
const script = document.createElement('script');
|
|
30
|
+
script.src = src;
|
|
31
|
+
script.async = options.async !== false;
|
|
32
|
+
if (options.integrity) {
|
|
33
|
+
script.integrity = options.integrity;
|
|
34
|
+
script.crossOrigin = options.crossOrigin || 'anonymous';
|
|
35
|
+
}
|
|
36
|
+
script.onload = () => resolve(script);
|
|
37
|
+
script.onerror = (event) => reject(event);
|
|
38
|
+
document.head.appendChild(script);
|
|
39
|
+
});
|
|
40
|
+
loadedScripts.set(src, promise);
|
|
41
|
+
return promise;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const loadStyle = (href) => {
|
|
45
|
+
if (!href || loadedStyles.has(href)) {
|
|
46
|
+
return Promise.resolve();
|
|
47
|
+
}
|
|
48
|
+
return new Promise((resolve, reject) => {
|
|
49
|
+
const link = document.createElement('link');
|
|
50
|
+
link.rel = 'stylesheet';
|
|
51
|
+
link.href = href;
|
|
52
|
+
link.onload = () => {
|
|
53
|
+
loadedStyles.add(href);
|
|
54
|
+
resolve(link);
|
|
55
|
+
};
|
|
56
|
+
link.onerror = (event) => reject(event);
|
|
57
|
+
document.head.appendChild(link);
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const isPlainObject = (value) =>
|
|
62
|
+
Object.prototype.toString.call(value) === '[object Object]';
|
|
63
|
+
|
|
64
|
+
const stringifyValue = (value) => {
|
|
65
|
+
if (value === null || typeof value === 'undefined') {
|
|
66
|
+
return '';
|
|
67
|
+
}
|
|
68
|
+
if (value instanceof Date) {
|
|
69
|
+
return value.toISOString();
|
|
70
|
+
}
|
|
71
|
+
if (Array.isArray(value)) {
|
|
72
|
+
return value.map((entry) => stringifyValue(entry)).join(', ');
|
|
73
|
+
}
|
|
74
|
+
if (isPlainObject(value)) {
|
|
75
|
+
return Object.entries(value)
|
|
76
|
+
.map(([key, entry]) => `${key}: ${stringifyValue(entry)}`)
|
|
77
|
+
.join('; ');
|
|
78
|
+
}
|
|
79
|
+
if (typeof value === 'boolean') {
|
|
80
|
+
return value ? 'Yes' : 'No';
|
|
81
|
+
}
|
|
82
|
+
if (typeof value === 'number') {
|
|
83
|
+
return Number.isFinite(value) ? `${value}` : '';
|
|
84
|
+
}
|
|
85
|
+
return String(value);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const flattenObject = (input, trail = []) => {
|
|
89
|
+
if (!isPlainObject(input)) {
|
|
90
|
+
return { [trail.join(' › ') || 'Value']: input };
|
|
91
|
+
}
|
|
92
|
+
return Object.entries(input).reduce((accumulator, [key, value]) => {
|
|
93
|
+
const path = trail.concat([key]);
|
|
94
|
+
if (isPlainObject(value)) {
|
|
95
|
+
Object.assign(accumulator, flattenObject(value, path));
|
|
96
|
+
} else if (Array.isArray(value)) {
|
|
97
|
+
accumulator[path.join(' › ')] = value
|
|
98
|
+
.map((entry) => stringifyValue(entry))
|
|
99
|
+
.join(', ');
|
|
100
|
+
} else {
|
|
101
|
+
accumulator[path.join(' › ')] = value;
|
|
102
|
+
}
|
|
103
|
+
return accumulator;
|
|
104
|
+
}, {});
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const normalizeRecords = (payload) => {
|
|
108
|
+
if (!payload) {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (Array.isArray(payload)) {
|
|
113
|
+
if (!payload.length) {
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (payload.every((entry) => isPlainObject(entry))) {
|
|
118
|
+
return payload.map((entry) => flattenObject(entry));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (Array.isArray(payload[0])) {
|
|
122
|
+
const candidateHeader = payload[0];
|
|
123
|
+
const headerLooksLabel = candidateHeader.every(
|
|
124
|
+
(cell) => typeof cell === 'string' || typeof cell === 'number'
|
|
125
|
+
);
|
|
126
|
+
const headers = candidateHeader.map((cell, index) =>
|
|
127
|
+
headerLooksLabel ? stringifyValue(cell) : `Column ${index + 1}`
|
|
128
|
+
);
|
|
129
|
+
const startIndex = headerLooksLabel ? 1 : 0;
|
|
130
|
+
|
|
131
|
+
return payload.slice(startIndex).map((row, rowIndex) => {
|
|
132
|
+
const record = {};
|
|
133
|
+
if (Array.isArray(row)) {
|
|
134
|
+
headers.forEach((header, columnIndex) => {
|
|
135
|
+
record[header] = row[columnIndex];
|
|
136
|
+
});
|
|
137
|
+
if (!headers.length) {
|
|
138
|
+
row.forEach((value, columnIndex) => {
|
|
139
|
+
record[`Column ${columnIndex + 1}`] = value;
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
record[`Row ${rowIndex + 1}`] = row;
|
|
144
|
+
}
|
|
145
|
+
return record;
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return payload.map((value, index) => ({
|
|
150
|
+
Index: index + 1,
|
|
151
|
+
Value: value
|
|
152
|
+
}));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (isPlainObject(payload)) {
|
|
156
|
+
if (Array.isArray(payload.data)) {
|
|
157
|
+
return normalizeRecords(payload.data);
|
|
158
|
+
}
|
|
159
|
+
if (Array.isArray(payload.values)) {
|
|
160
|
+
return normalizeRecords(payload.values);
|
|
161
|
+
}
|
|
162
|
+
if (Array.isArray(payload.rows)) {
|
|
163
|
+
return normalizeRecords(payload.rows);
|
|
164
|
+
}
|
|
165
|
+
return [flattenObject(payload)];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return [
|
|
169
|
+
{
|
|
170
|
+
Value: payload
|
|
171
|
+
}
|
|
172
|
+
];
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const orderColumns = (columns) => {
|
|
176
|
+
const priority = [
|
|
177
|
+
'Series',
|
|
178
|
+
'Trace',
|
|
179
|
+
'Group',
|
|
180
|
+
'Category',
|
|
181
|
+
'Index',
|
|
182
|
+
'X',
|
|
183
|
+
'Y',
|
|
184
|
+
'Z',
|
|
185
|
+
'Value',
|
|
186
|
+
'Text'
|
|
187
|
+
];
|
|
188
|
+
const result = [];
|
|
189
|
+
priority.forEach((key) => {
|
|
190
|
+
if (columns.includes(key)) {
|
|
191
|
+
result.push(key);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
columns
|
|
195
|
+
.filter((column) => !priority.includes(column))
|
|
196
|
+
.sort()
|
|
197
|
+
.forEach((column) => result.push(column));
|
|
198
|
+
return result;
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const getVizTitle = (element) => {
|
|
202
|
+
const explicitTitle = element.getAttribute('data-viz-title');
|
|
203
|
+
if (explicitTitle) {
|
|
204
|
+
return explicitTitle;
|
|
205
|
+
}
|
|
206
|
+
const heading = element.querySelector('.viz-title');
|
|
207
|
+
if (heading && heading.textContent) {
|
|
208
|
+
return heading.textContent.trim();
|
|
209
|
+
}
|
|
210
|
+
return element.getAttribute('data-viz-slug') || 'Visualization data';
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const ensureTableTemplate = () => {
|
|
214
|
+
let template = document.getElementById(TABLE_TEMPLATE_ID);
|
|
215
|
+
if (template) {
|
|
216
|
+
return template;
|
|
217
|
+
}
|
|
218
|
+
template = document.createElement('template');
|
|
219
|
+
template.id = TABLE_TEMPLATE_ID;
|
|
220
|
+
const templateContent = document.createElement('div');
|
|
221
|
+
templateContent.insertAdjacentHTML('afterbegin',
|
|
222
|
+
'<div class="viz-table-container" data-viz-table-container>' +
|
|
223
|
+
'<button type="button" class="viz-table-toggle" data-viz-table-toggle aria-expanded="false">Show data table</button>' +
|
|
224
|
+
'<div class="viz-table-wrapper" data-viz-table-wrapper hidden>' +
|
|
225
|
+
'<table class="viz-table" data-viz-table>' +
|
|
226
|
+
'<caption data-viz-table-caption></caption>' +
|
|
227
|
+
'<thead data-viz-table-head></thead>' +
|
|
228
|
+
'<tbody data-viz-table-body></tbody>' +
|
|
229
|
+
'</table></div></div>');
|
|
230
|
+
template.content.appendChild(templateContent.firstElementChild);
|
|
231
|
+
document.body.appendChild(template);
|
|
232
|
+
return template;
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const buildDataTable = (element, options = {}) => {
|
|
236
|
+
const { caption, rows } = options;
|
|
237
|
+
const records = normalizeRecords(rows);
|
|
238
|
+
if (!records.length) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const columns = Array.from(
|
|
243
|
+
records.reduce((set, record) => {
|
|
244
|
+
Object.keys(record).forEach((key) => set.add(key));
|
|
245
|
+
return set;
|
|
246
|
+
}, new Set())
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
if (!columns.length) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const orderedColumns = orderColumns(columns);
|
|
254
|
+
const template = ensureTableTemplate();
|
|
255
|
+
const clone = template.content.firstElementChild.cloneNode(true);
|
|
256
|
+
const toggle = clone.querySelector('[data-viz-table-toggle]');
|
|
257
|
+
const wrapper = clone.querySelector('[data-viz-table-wrapper]');
|
|
258
|
+
const table = clone.querySelector('[data-viz-table]');
|
|
259
|
+
const captionNode = clone.querySelector('[data-viz-table-caption]');
|
|
260
|
+
const head = clone.querySelector('[data-viz-table-head]');
|
|
261
|
+
const body = clone.querySelector('[data-viz-table-body]');
|
|
262
|
+
|
|
263
|
+
const tableId = `${element.getAttribute('data-viz-slug') || element.id || 'viz'}-table`;
|
|
264
|
+
table.id = tableId;
|
|
265
|
+
toggle.setAttribute('aria-controls', tableId);
|
|
266
|
+
captionNode.textContent = caption || getVizTitle(element);
|
|
267
|
+
|
|
268
|
+
const headerRow = document.createElement('tr');
|
|
269
|
+
orderedColumns.forEach((column) => {
|
|
270
|
+
const th = document.createElement('th');
|
|
271
|
+
th.scope = 'col';
|
|
272
|
+
th.textContent = column;
|
|
273
|
+
headerRow.appendChild(th);
|
|
274
|
+
});
|
|
275
|
+
head.appendChild(headerRow);
|
|
276
|
+
|
|
277
|
+
records.forEach((record) => {
|
|
278
|
+
const tr = document.createElement('tr');
|
|
279
|
+
orderedColumns.forEach((column, index) => {
|
|
280
|
+
const value = stringifyValue(record[column]);
|
|
281
|
+
if (index === 0) {
|
|
282
|
+
const th = document.createElement('th');
|
|
283
|
+
th.scope = 'row';
|
|
284
|
+
th.textContent = value;
|
|
285
|
+
tr.appendChild(th);
|
|
286
|
+
} else {
|
|
287
|
+
const td = document.createElement('td');
|
|
288
|
+
td.textContent = value;
|
|
289
|
+
tr.appendChild(td);
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
body.appendChild(tr);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
toggle.addEventListener('click', () => {
|
|
296
|
+
const expanded = toggle.getAttribute('aria-expanded') === 'true';
|
|
297
|
+
toggle.setAttribute('aria-expanded', String(!expanded));
|
|
298
|
+
toggle.textContent = expanded ? 'Show data table' : 'Hide data table';
|
|
299
|
+
if (expanded) {
|
|
300
|
+
wrapper.setAttribute('hidden', '');
|
|
301
|
+
} else {
|
|
302
|
+
wrapper.removeAttribute('hidden');
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
const existing = element.querySelector('[data-viz-table-container]');
|
|
307
|
+
if (existing) {
|
|
308
|
+
existing.remove();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
element.appendChild(clone);
|
|
312
|
+
|
|
313
|
+
const canvas = element.querySelector('[data-viz-canvas]') || element;
|
|
314
|
+
canvas.setAttribute('aria-describedby', tableId);
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const buildPlotlyRecords = (traces = []) => {
|
|
318
|
+
const records = [];
|
|
319
|
+
traces.forEach((trace, index) => {
|
|
320
|
+
const name = trace.name || `Series ${index + 1}`;
|
|
321
|
+
const xValues = Array.isArray(trace.x) ? trace.x : [];
|
|
322
|
+
const yValues = Array.isArray(trace.y) ? trace.y : [];
|
|
323
|
+
const textValues = Array.isArray(trace.text) ? trace.text : [];
|
|
324
|
+
const zValues = trace.z;
|
|
325
|
+
|
|
326
|
+
if (Array.isArray(zValues) && zValues.length && Array.isArray(zValues[0])) {
|
|
327
|
+
const xLabels = xValues.length ? xValues : zValues[0].map((_, column) => column + 1);
|
|
328
|
+
const yLabels = yValues.length ? yValues : zValues.map((_, row) => row + 1);
|
|
329
|
+
yLabels.forEach((yLabel, rowIndex) => {
|
|
330
|
+
const row = zValues[rowIndex] || [];
|
|
331
|
+
xLabels.forEach((xLabel, columnIndex) => {
|
|
332
|
+
records.push({
|
|
333
|
+
Series: name,
|
|
334
|
+
Y: yLabel,
|
|
335
|
+
X: xLabel,
|
|
336
|
+
Value: Array.isArray(row) ? row[columnIndex] : row,
|
|
337
|
+
Text: textValues[rowIndex] ? stringifyValue(textValues[rowIndex]) : ''
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const length = Math.max(xValues.length, yValues.length, textValues.length);
|
|
345
|
+
for (let cursor = 0; cursor < length; cursor += 1) {
|
|
346
|
+
records.push({
|
|
347
|
+
Series: name,
|
|
348
|
+
X: xValues[cursor],
|
|
349
|
+
Y: yValues[cursor],
|
|
350
|
+
Text: textValues[cursor]
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
return records.length ? records : null;
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
const buildD3Records = (captured) => {
|
|
358
|
+
if (!captured) {
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
if (Array.isArray(captured)) {
|
|
362
|
+
return captured;
|
|
363
|
+
}
|
|
364
|
+
if (isPlainObject(captured) && Array.isArray(captured.data)) {
|
|
365
|
+
return captured.data;
|
|
366
|
+
}
|
|
367
|
+
return captured;
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
const buildObservableRecords = (element) => {
|
|
371
|
+
const dataNode = element.querySelector('script[data-observable-data]');
|
|
372
|
+
if (!dataNode) {
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
try {
|
|
376
|
+
const parsed = JSON.parse(dataNode.textContent || 'null');
|
|
377
|
+
return parsed;
|
|
378
|
+
} catch (error) {
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
const ensurePlotly = () =>
|
|
384
|
+
(typeof window.Plotly !== 'undefined'
|
|
385
|
+
? Promise.resolve()
|
|
386
|
+
: loadScript('https://cdn.plot.ly/plotly-2.27.0.min.js')).then(() => window.Plotly);
|
|
387
|
+
|
|
388
|
+
const ensureD3 = () =>
|
|
389
|
+
(typeof window.d3 !== 'undefined'
|
|
390
|
+
? Promise.resolve(window.d3)
|
|
391
|
+
: loadScript('https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js').then(() => window.d3));
|
|
392
|
+
|
|
393
|
+
const ensureBokeh = () => {
|
|
394
|
+
const script = 'https://cdn.bokeh.org/bokeh/release/bokeh-3.3.3.min.js';
|
|
395
|
+
const style = 'https://cdn.bokeh.org/bokeh/release/bokeh-3.3.3.min.css';
|
|
396
|
+
return Promise.all([loadScript(script), loadStyle(style)]).then(() => window.Bokeh);
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
const ensureRequire = () => {
|
|
400
|
+
if (typeof window.requirejs !== 'undefined') {
|
|
401
|
+
return Promise.resolve(window.requirejs);
|
|
402
|
+
}
|
|
403
|
+
return loadScript('https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js', {
|
|
404
|
+
async: false
|
|
405
|
+
}).then(() => window.requirejs);
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
const ensureWidgetManager = () =>
|
|
409
|
+
ensureRequire().then((requirejs) => {
|
|
410
|
+
const existing = window.__datalogWidgetManagerPromise;
|
|
411
|
+
if (existing) {
|
|
412
|
+
return existing;
|
|
413
|
+
}
|
|
414
|
+
const promise = new Promise((resolve, reject) => {
|
|
415
|
+
requirejs.config({
|
|
416
|
+
paths: {
|
|
417
|
+
'@jupyter-widgets/html-manager':
|
|
418
|
+
'https://cdn.jsdelivr.net/npm/@jupyter-widgets/html-manager@1.0.8/dist/embed-amd',
|
|
419
|
+
'@jupyter-widgets/base':
|
|
420
|
+
'https://cdn.jsdelivr.net/npm/@jupyter-widgets/base@6.0.7/dist/index',
|
|
421
|
+
'@jupyter-widgets/controls':
|
|
422
|
+
'https://cdn.jsdelivr.net/npm/@jupyter-widgets/controls@5.0.7/dist/index',
|
|
423
|
+
'@lumino/coreutils': 'https://cdn.jsdelivr.net/npm/@lumino/coreutils@2.1.1/dist/index',
|
|
424
|
+
'@lumino/widgets': 'https://cdn.jsdelivr.net/npm/@lumino/widgets@2.3.1/dist/index'
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
requirejs(
|
|
428
|
+
['@jupyter-widgets/html-manager'],
|
|
429
|
+
(widgets) => resolve(widgets),
|
|
430
|
+
(error) => reject(error)
|
|
431
|
+
);
|
|
432
|
+
});
|
|
433
|
+
window.__datalogWidgetManagerPromise = promise;
|
|
434
|
+
return promise;
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
const createToolbarButton = (label, onClick) => {
|
|
438
|
+
const button = document.createElement('button');
|
|
439
|
+
button.type = 'button';
|
|
440
|
+
button.className = 'viz-toolbar__button';
|
|
441
|
+
button.textContent = label;
|
|
442
|
+
button.addEventListener('click', (event) => {
|
|
443
|
+
event.preventDefault();
|
|
444
|
+
onClick(event);
|
|
445
|
+
});
|
|
446
|
+
return button;
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
const setStatus = (element, status, tone = 'info') => {
|
|
450
|
+
const statusNode = element.querySelector('[data-viz-status]');
|
|
451
|
+
if (statusNode) {
|
|
452
|
+
statusNode.textContent = status;
|
|
453
|
+
statusNode.dataset.tone = tone;
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
const annotateVersionMetadata = (element) => {
|
|
458
|
+
const version = element.getAttribute('data-viz-version');
|
|
459
|
+
const updated = element.getAttribute('data-viz-updated');
|
|
460
|
+
const metaTarget = element.querySelector('[data-viz-meta]');
|
|
461
|
+
if (!metaTarget) {
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
const fragments = [];
|
|
465
|
+
if (version) {
|
|
466
|
+
fragments.push(`v${version}`);
|
|
467
|
+
}
|
|
468
|
+
if (updated) {
|
|
469
|
+
fragments.push(`Updated ${updated}`);
|
|
470
|
+
}
|
|
471
|
+
metaTarget.textContent = fragments.join(' · ');
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
const renderPlotly = (element) => {
|
|
475
|
+
const canvas = element.querySelector('[data-viz-canvas]') || element;
|
|
476
|
+
const specNode = element.querySelector(SPEC_SELECTOR);
|
|
477
|
+
if (!specNode) {
|
|
478
|
+
setStatus(element, 'Missing Plotly specification', 'error');
|
|
479
|
+
return Promise.resolve();
|
|
480
|
+
}
|
|
481
|
+
let spec = {};
|
|
482
|
+
try {
|
|
483
|
+
spec = JSON.parse(specNode.textContent || '{}');
|
|
484
|
+
} catch (error) {
|
|
485
|
+
setStatus(element, 'Invalid Plotly specification', 'error');
|
|
486
|
+
return Promise.resolve();
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const data = Array.isArray(spec.data) ? spec.data : [];
|
|
490
|
+
const layout = Object.assign(
|
|
491
|
+
{
|
|
492
|
+
autosize: true
|
|
493
|
+
},
|
|
494
|
+
spec.layout || {}
|
|
495
|
+
);
|
|
496
|
+
|
|
497
|
+
return ensurePlotly()
|
|
498
|
+
.then((Plotly) => Plotly.newPlot(canvas, data, layout, { responsive: true, displaylogo: false }))
|
|
499
|
+
.then(() => {
|
|
500
|
+
setStatus(element, 'Interactive');
|
|
501
|
+
const exportTarget = element.querySelector('[data-viz-export]');
|
|
502
|
+
if (exportTarget) {
|
|
503
|
+
exportTarget.replaceChildren();
|
|
504
|
+
exportTarget.appendChild(
|
|
505
|
+
createToolbarButton('Download PNG', () => {
|
|
506
|
+
ensurePlotly().then((Plotly) => {
|
|
507
|
+
Plotly.downloadImage(canvas, {
|
|
508
|
+
format: 'png',
|
|
509
|
+
filename: element.getAttribute('data-viz-slug') || 'visualization'
|
|
510
|
+
});
|
|
511
|
+
});
|
|
512
|
+
})
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
const records = buildPlotlyRecords(data);
|
|
516
|
+
if (records) {
|
|
517
|
+
buildDataTable(element, {
|
|
518
|
+
caption: layout && layout.title ? stringifyValue(layout.title.text || layout.title) : undefined,
|
|
519
|
+
rows: records
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
})
|
|
523
|
+
.catch(() => {
|
|
524
|
+
setStatus(element, 'Plotly failed to load', 'error');
|
|
525
|
+
});
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
const renderD3 = (element) => {
|
|
529
|
+
const scriptNode = element.querySelector('[data-d3-script]');
|
|
530
|
+
if (!scriptNode) {
|
|
531
|
+
setStatus(element, 'Missing D3 rendering instructions', 'error');
|
|
532
|
+
return Promise.resolve();
|
|
533
|
+
}
|
|
534
|
+
return ensureD3()
|
|
535
|
+
.then((d3) => {
|
|
536
|
+
const code = scriptNode.textContent || '';
|
|
537
|
+
const canvas = element.querySelector('[data-viz-canvas]') || element;
|
|
538
|
+
canvas.replaceChildren();
|
|
539
|
+
const captured = [];
|
|
540
|
+
const captureData = (payload) => {
|
|
541
|
+
if (typeof payload !== 'undefined') {
|
|
542
|
+
captured.push(payload);
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
try {
|
|
546
|
+
const instrumented = `${code}\n;try { if (typeof data !== 'undefined') captureData(data); } catch (instrumentationError) {}`;
|
|
547
|
+
const runner = new Function('d3', 'element', 'captureData', instrumented);
|
|
548
|
+
const result = runner(d3, canvas, captureData);
|
|
549
|
+
if (typeof result !== 'undefined') {
|
|
550
|
+
captureData(result);
|
|
551
|
+
}
|
|
552
|
+
const harvested = captured.length ? captured[captured.length - 1] : null;
|
|
553
|
+
const records = buildD3Records(harvested);
|
|
554
|
+
if (records) {
|
|
555
|
+
buildDataTable(element, { rows: records });
|
|
556
|
+
}
|
|
557
|
+
setStatus(element, 'Interactive');
|
|
558
|
+
} catch (error) {
|
|
559
|
+
canvas.replaceChildren();
|
|
560
|
+
setStatus(element, 'D3 rendering error', 'error');
|
|
561
|
+
}
|
|
562
|
+
})
|
|
563
|
+
.catch(() => setStatus(element, 'D3 assets failed to load', 'error'));
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
const renderObservable = (element) => {
|
|
567
|
+
const src = element.getAttribute('data-observable-src');
|
|
568
|
+
if (!src) {
|
|
569
|
+
setStatus(element, 'Missing Observable notebook source', 'error');
|
|
570
|
+
return Promise.resolve();
|
|
571
|
+
}
|
|
572
|
+
const iframe = document.createElement('iframe');
|
|
573
|
+
iframe.src = src;
|
|
574
|
+
iframe.loading = 'lazy';
|
|
575
|
+
iframe.title = element.getAttribute('data-viz-title') || 'Observable visualization';
|
|
576
|
+
iframe.setAttribute('allowfullscreen', '');
|
|
577
|
+
iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-popups allow-forms');
|
|
578
|
+
iframe.className = 'viz-embed';
|
|
579
|
+
const canvas = element.querySelector('[data-viz-canvas]') || element;
|
|
580
|
+
canvas.replaceChildren(iframe);
|
|
581
|
+
setStatus(element, 'Embedded');
|
|
582
|
+
const records = buildObservableRecords(element);
|
|
583
|
+
if (records) {
|
|
584
|
+
buildDataTable(element, { rows: records });
|
|
585
|
+
}
|
|
586
|
+
return Promise.resolve();
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
const renderBokeh = (element) => {
|
|
590
|
+
const specNode = element.querySelector(SPEC_SELECTOR);
|
|
591
|
+
const scriptNode = element.querySelector('[data-bokeh-script]');
|
|
592
|
+
const canvas = element.querySelector('[data-viz-canvas]') || element;
|
|
593
|
+
|
|
594
|
+
if (!specNode && !scriptNode) {
|
|
595
|
+
setStatus(element, 'Missing Bokeh specification', 'error');
|
|
596
|
+
return Promise.resolve();
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
return ensureBokeh()
|
|
600
|
+
.then((Bokeh) => {
|
|
601
|
+
if (specNode) {
|
|
602
|
+
let spec = {};
|
|
603
|
+
try {
|
|
604
|
+
spec = JSON.parse(specNode.textContent || '{}');
|
|
605
|
+
} catch (error) {
|
|
606
|
+
setStatus(element, 'Invalid Bokeh specification', 'error');
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
const targetId = canvas.id || `bokeh-${Math.random().toString(36).slice(2)}`;
|
|
610
|
+
canvas.id = targetId;
|
|
611
|
+
try {
|
|
612
|
+
Bokeh.embed.embed_item(spec, targetId);
|
|
613
|
+
setStatus(element, 'Interactive');
|
|
614
|
+
} catch (error) {
|
|
615
|
+
setStatus(element, 'Bokeh rendering error', 'error');
|
|
616
|
+
}
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
if (scriptNode) {
|
|
621
|
+
const code = scriptNode.textContent || '';
|
|
622
|
+
canvas.replaceChildren();
|
|
623
|
+
try {
|
|
624
|
+
const runner = new Function('Bokeh', 'element', code);
|
|
625
|
+
runner(Bokeh, canvas);
|
|
626
|
+
setStatus(element, 'Interactive');
|
|
627
|
+
} catch (error) {
|
|
628
|
+
console.error('Bokeh script error', error);
|
|
629
|
+
setStatus(element, 'Bokeh rendering error', 'error');
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
})
|
|
633
|
+
.catch(() => {
|
|
634
|
+
setStatus(element, 'Bokeh assets failed to load', 'error');
|
|
635
|
+
});
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
const renderShiny = (element) => {
|
|
639
|
+
const src = element.getAttribute('data-shiny-src');
|
|
640
|
+
if (!src) {
|
|
641
|
+
setStatus(element, 'Missing Shiny application source', 'error');
|
|
642
|
+
return Promise.resolve();
|
|
643
|
+
}
|
|
644
|
+
const iframe = document.createElement('iframe');
|
|
645
|
+
iframe.src = src;
|
|
646
|
+
iframe.loading = 'lazy';
|
|
647
|
+
iframe.title = element.getAttribute('data-viz-title') || 'R Shiny application';
|
|
648
|
+
iframe.className = 'viz-embed';
|
|
649
|
+
iframe.setAttribute('allow', 'fullscreen');
|
|
650
|
+
iframe.setAttribute('sandbox', 'allow-scripts allow-forms allow-same-origin');
|
|
651
|
+
const height = element.getAttribute('data-shiny-height');
|
|
652
|
+
iframe.style.height = height || '640px';
|
|
653
|
+
const canvas = element.querySelector('[data-viz-canvas]') || element;
|
|
654
|
+
canvas.replaceChildren(iframe);
|
|
655
|
+
setStatus(element, 'Embedded');
|
|
656
|
+
return Promise.resolve();
|
|
657
|
+
};
|
|
658
|
+
|
|
659
|
+
const renderWidgets = (element) => {
|
|
660
|
+
const stateNode = element.querySelector(WIDGET_STATE_SELECTOR);
|
|
661
|
+
const viewNode = element.querySelector(WIDGET_VIEW_SELECTOR);
|
|
662
|
+
if (!stateNode || !viewNode) {
|
|
663
|
+
setStatus(element, 'Missing widget state payload', 'error');
|
|
664
|
+
return Promise.resolve();
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
let state;
|
|
668
|
+
let view;
|
|
669
|
+
try {
|
|
670
|
+
state = JSON.parse(stateNode.textContent || '{}');
|
|
671
|
+
view = JSON.parse(viewNode.textContent || '{}');
|
|
672
|
+
} catch (error) {
|
|
673
|
+
setStatus(element, 'Invalid widget payload', 'error');
|
|
674
|
+
return Promise.resolve();
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
const canvas = element.querySelector('[data-viz-canvas]') || element;
|
|
678
|
+
canvas.replaceChildren();
|
|
679
|
+
|
|
680
|
+
return ensureWidgetManager()
|
|
681
|
+
.then((widgets) => {
|
|
682
|
+
const manager = new widgets.WidgetManager();
|
|
683
|
+
return manager
|
|
684
|
+
.set_state(state)
|
|
685
|
+
.then(() => manager.get_model(view.model_id))
|
|
686
|
+
.then((model) => manager.create_view(model))
|
|
687
|
+
.then((widgetView) => manager.display_view(undefined, widgetView, { el: canvas }))
|
|
688
|
+
.then(() => setStatus(element, 'Interactive'))
|
|
689
|
+
.catch((error) => {
|
|
690
|
+
console.error('Widget render error', error);
|
|
691
|
+
setStatus(element, 'Widget rendering error', 'error');
|
|
692
|
+
});
|
|
693
|
+
})
|
|
694
|
+
.catch(() => setStatus(element, 'Widget assets failed to load', 'error'));
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
const renderVisualization = (element) => {
|
|
698
|
+
const type = element.getAttribute('data-viz-type');
|
|
699
|
+
annotateVersionMetadata(element);
|
|
700
|
+
switch (type) {
|
|
701
|
+
case 'plotly':
|
|
702
|
+
return renderPlotly(element);
|
|
703
|
+
case 'observable':
|
|
704
|
+
return renderObservable(element);
|
|
705
|
+
case 'd3':
|
|
706
|
+
return renderD3(element);
|
|
707
|
+
case 'bokeh':
|
|
708
|
+
return renderBokeh(element);
|
|
709
|
+
case 'shiny':
|
|
710
|
+
return renderShiny(element);
|
|
711
|
+
case 'ipywidgets':
|
|
712
|
+
return renderWidgets(element);
|
|
713
|
+
default:
|
|
714
|
+
setStatus(element, 'Unsupported visualization type', 'error');
|
|
715
|
+
return Promise.resolve();
|
|
716
|
+
}
|
|
717
|
+
};
|
|
718
|
+
|
|
719
|
+
const initializeVisualizations = () => {
|
|
720
|
+
if (!('IntersectionObserver' in window)) {
|
|
721
|
+
document.querySelectorAll(VIZ_SELECTOR).forEach((element) => {
|
|
722
|
+
renderVisualization(element);
|
|
723
|
+
});
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
const observer = new IntersectionObserver(
|
|
728
|
+
(entries, entryObserver) => {
|
|
729
|
+
entries
|
|
730
|
+
.filter((entry) => entry.isIntersecting)
|
|
731
|
+
.forEach((entry) => {
|
|
732
|
+
const element = entry.target;
|
|
733
|
+
entryObserver.unobserve(element);
|
|
734
|
+
renderVisualization(element);
|
|
735
|
+
});
|
|
736
|
+
},
|
|
737
|
+
{ rootMargin: '200px 0px' }
|
|
738
|
+
);
|
|
739
|
+
|
|
740
|
+
document.querySelectorAll(VIZ_SELECTOR).forEach((element) => {
|
|
741
|
+
observer.observe(element);
|
|
742
|
+
});
|
|
743
|
+
};
|
|
744
|
+
|
|
745
|
+
const filterGallery = (filters) => {
|
|
746
|
+
const items = document.querySelectorAll('[data-viz-card]');
|
|
747
|
+
items.forEach((item) => {
|
|
748
|
+
const matchesTech = !filters.tech || item.dataset.vizTech === filters.tech;
|
|
749
|
+
const tags = (item.dataset.vizTags || '')
|
|
750
|
+
.split(',')
|
|
751
|
+
.map((tag) => tag.trim())
|
|
752
|
+
.filter(Boolean);
|
|
753
|
+
const matchesTag = !filters.tag || tags.includes(filters.tag);
|
|
754
|
+
item.hidden = !(matchesTech && matchesTag);
|
|
755
|
+
});
|
|
756
|
+
};
|
|
757
|
+
|
|
758
|
+
const initializeGallery = () => {
|
|
759
|
+
const gallery = document.querySelector('[data-viz-gallery]');
|
|
760
|
+
if (!gallery) {
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
const techSelect = gallery.querySelector('[data-viz-filter="tech"]');
|
|
765
|
+
const tagSelect = gallery.querySelector('[data-viz-filter="tag"]');
|
|
766
|
+
|
|
767
|
+
const update = () => {
|
|
768
|
+
filterGallery({
|
|
769
|
+
tech: techSelect && techSelect.value !== 'all' ? techSelect.value : '',
|
|
770
|
+
tag: tagSelect && tagSelect.value !== 'all' ? tagSelect.value : ''
|
|
771
|
+
});
|
|
772
|
+
};
|
|
773
|
+
|
|
774
|
+
if (techSelect) {
|
|
775
|
+
techSelect.addEventListener('change', update);
|
|
776
|
+
}
|
|
777
|
+
if (tagSelect) {
|
|
778
|
+
tagSelect.addEventListener('change', update);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
update();
|
|
782
|
+
};
|
|
783
|
+
|
|
784
|
+
const boot = () => {
|
|
785
|
+
initializeVisualizations();
|
|
786
|
+
initializeGallery();
|
|
787
|
+
};
|
|
788
|
+
|
|
789
|
+
const internals = {
|
|
790
|
+
loadScript,
|
|
791
|
+
loadStyle,
|
|
792
|
+
normalizeRecords,
|
|
793
|
+
stringifyValue,
|
|
794
|
+
buildDataTable,
|
|
795
|
+
annotateVersionMetadata,
|
|
796
|
+
buildPlotlyRecords,
|
|
797
|
+
buildD3Records,
|
|
798
|
+
buildObservableRecords,
|
|
799
|
+
filterGallery,
|
|
800
|
+
renderVisualization,
|
|
801
|
+
initializeVisualizations,
|
|
802
|
+
initializeGallery
|
|
803
|
+
};
|
|
804
|
+
|
|
805
|
+
if (typeof globalThis !== 'undefined') {
|
|
806
|
+
globalThis.__DATALOG_VIZ_INTERNALS__ = internals;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
if (!isTestEnvironment) {
|
|
810
|
+
if (document.readyState === 'loading') {
|
|
811
|
+
document.addEventListener('DOMContentLoaded', boot);
|
|
812
|
+
} else {
|
|
813
|
+
boot();
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
})();
|