@bezda/rhp-mcp 0.1.4 → 0.1.5

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.
Files changed (36) hide show
  1. package/package.json +1 -6
  2. package/skill/recipes/area.html +6 -2
  3. package/skill/recipes/bar.html +6 -2
  4. package/skill/recipes/box-plot.html +6 -2
  5. package/skill/recipes/bubble.html +6 -2
  6. package/skill/recipes/bullet.html +6 -2
  7. package/skill/recipes/candlestick.html +6 -2
  8. package/skill/recipes/column.html +6 -2
  9. package/skill/recipes/dial.html +6 -2
  10. package/skill/recipes/diverging-bars.html +6 -2
  11. package/skill/recipes/donut.html +6 -2
  12. package/skill/recipes/dumbbell.html +6 -2
  13. package/skill/recipes/gantt.html +6 -2
  14. package/skill/recipes/grouped-bars.html +6 -2
  15. package/skill/recipes/heatmap.html +6 -2
  16. package/skill/recipes/histogram.html +6 -2
  17. package/skill/recipes/line.html +6 -2
  18. package/skill/recipes/live.html +6 -2
  19. package/skill/recipes/log-line.html +6 -2
  20. package/skill/recipes/lollipop.html +6 -2
  21. package/skill/recipes/multi-line.html +6 -2
  22. package/skill/recipes/pyramid.html +6 -2
  23. package/skill/recipes/race.html +6 -2
  24. package/skill/recipes/radial-bars.html +6 -2
  25. package/skill/recipes/scatter.html +6 -2
  26. package/skill/recipes/silhouettes.html +6 -2
  27. package/skill/recipes/slope.html +6 -2
  28. package/skill/recipes/sparklines.html +6 -2
  29. package/skill/recipes/stacked-100.html +6 -2
  30. package/skill/recipes/stacked-bars.html +6 -2
  31. package/skill/recipes/strip.html +6 -2
  32. package/skill/recipes/unit-stack.html +6 -2
  33. package/skill/recipes/violin.html +6 -2
  34. package/skill/recipes/waffle.html +6 -2
  35. package/skill/recipes/waterfall.html +6 -2
  36. package/skill/references/download.md +6 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bezda/rhp-mcp",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Make charts with rhp from any AI agent: an MCP server with rhp's guide, references and recipes, and a checker that renders a chart in a browser and reports what is wrong, with screenshots",
5
5
  "keywords": [
6
6
  "rhp",
@@ -44,11 +44,6 @@
44
44
  "publishConfig": {
45
45
  "access": "public"
46
46
  },
47
- "scripts": {
48
- "build": "node scripts/build.js",
49
- "prepack": "npm run build",
50
- "test": "node test/network.js && node test/settle-regression.js && node test/isolation.js && node test/selection-regression.js && node test/mcp.js && node test/run.js"
51
- },
52
47
  "dependencies": {
53
48
  "@babel/core": "^7.29.7",
54
49
  "@bezda/rhp": "^2.0.5",
@@ -199,6 +199,8 @@ function addPosterDownloads() {
199
199
  event.stopPropagation();
200
200
  });
201
201
  button.addEventListener('mousedown', event => {
202
+ // Pointer activation downloads without taking focus from the selected chart item.
203
+ event.preventDefault();
202
204
  event.stopPropagation();
203
205
  });
204
206
  poster.addEventListener('focusout', event => {
@@ -240,12 +242,14 @@ function addPosterDownloads() {
240
242
  const margins = [...document.styleSheets].map(sheet => {
241
243
  try { return marginRules(sheet.cssRules); } catch (cause) { return ''; }
242
244
  }).join('\n');
245
+ // Omit display-none HTML subtrees, such as collapsed chart rows, from the image and its SVG payload.
246
+ const includeNode = node => !(node instanceof HTMLElement) || getComputedStyle(node).display !== 'none';
243
247
  const restoreMargins = (source, clone) => {
244
248
  if (clone.style && source !== poster && [...autoMargins].some(selector => source.matches(selector))) {
245
249
  [...clone.style].filter(name => name === 'margin' || name.startsWith('margin-')).forEach(name => clone.style.removeProperty(name));
246
250
  [...source.style].filter(name => name === 'margin' || name.startsWith('margin-')).forEach(name => clone.style.setProperty(name, source.style.getPropertyValue(name), source.style.getPropertyPriority(name)));
247
251
  }
248
- const children = [...source.children].filter(node => !['STYLE', 'SCRIPT'].includes(node.tagName));
252
+ const children = [...source.children].filter(node => !['STYLE', 'SCRIPT'].includes(node.tagName) && includeNode(node));
249
253
  [...clone.children].forEach((child, i) => { if (children[i]) restoreMargins(children[i], child); });
250
254
  };
251
255
  // Cross-origin font stylesheets are readable through fetch, but not through cssRules.
@@ -301,7 +305,7 @@ function addPosterDownloads() {
301
305
  node.style.backgroundImage = background;
302
306
  };
303
307
  const blob = await domToBlob(poster, {
304
- scale: 2, style: { margin: '0' },
308
+ scale: 2, style: { margin: '0' }, filter: includeNode,
305
309
  font: { cssText: fontRules.join('\n') }, onCloneEachNode: embedTexture,
306
310
  onCloneNode: clone => {
307
311
  restoreMargins(poster, clone);
@@ -190,6 +190,8 @@ function addPosterDownloads() {
190
190
  event.stopPropagation();
191
191
  });
192
192
  button.addEventListener('mousedown', event => {
193
+ // Pointer activation downloads without taking focus from the selected chart item.
194
+ event.preventDefault();
193
195
  event.stopPropagation();
194
196
  });
195
197
  poster.addEventListener('focusout', event => {
@@ -231,12 +233,14 @@ function addPosterDownloads() {
231
233
  const margins = [...document.styleSheets].map(sheet => {
232
234
  try { return marginRules(sheet.cssRules); } catch (cause) { return ''; }
233
235
  }).join('\n');
236
+ // Omit display-none HTML subtrees, such as collapsed chart rows, from the image and its SVG payload.
237
+ const includeNode = node => !(node instanceof HTMLElement) || getComputedStyle(node).display !== 'none';
234
238
  const restoreMargins = (source, clone) => {
235
239
  if (clone.style && source !== poster && [...autoMargins].some(selector => source.matches(selector))) {
236
240
  [...clone.style].filter(name => name === 'margin' || name.startsWith('margin-')).forEach(name => clone.style.removeProperty(name));
237
241
  [...source.style].filter(name => name === 'margin' || name.startsWith('margin-')).forEach(name => clone.style.setProperty(name, source.style.getPropertyValue(name), source.style.getPropertyPriority(name)));
238
242
  }
239
- const children = [...source.children].filter(node => !['STYLE', 'SCRIPT'].includes(node.tagName));
243
+ const children = [...source.children].filter(node => !['STYLE', 'SCRIPT'].includes(node.tagName) && includeNode(node));
240
244
  [...clone.children].forEach((child, i) => { if (children[i]) restoreMargins(children[i], child); });
241
245
  };
242
246
  // Cross-origin font stylesheets are readable through fetch, but not through cssRules.
@@ -292,7 +296,7 @@ function addPosterDownloads() {
292
296
  node.style.backgroundImage = background;
293
297
  };
294
298
  const blob = await domToBlob(poster, {
295
- scale: 2, style: { margin: '0' },
299
+ scale: 2, style: { margin: '0' }, filter: includeNode,
296
300
  font: { cssText: fontRules.join('\n') }, onCloneEachNode: embedTexture,
297
301
  onCloneNode: clone => {
298
302
  restoreMargins(poster, clone);
@@ -209,6 +209,8 @@ function addPosterDownloads() {
209
209
  event.stopPropagation();
210
210
  });
211
211
  button.addEventListener('mousedown', event => {
212
+ // Pointer activation downloads without taking focus from the selected chart item.
213
+ event.preventDefault();
212
214
  event.stopPropagation();
213
215
  });
214
216
  poster.addEventListener('focusout', event => {
@@ -250,12 +252,14 @@ function addPosterDownloads() {
250
252
  const margins = [...document.styleSheets].map(sheet => {
251
253
  try { return marginRules(sheet.cssRules); } catch (cause) { return ''; }
252
254
  }).join('\n');
255
+ // Omit display-none HTML subtrees, such as collapsed chart rows, from the image and its SVG payload.
256
+ const includeNode = node => !(node instanceof HTMLElement) || getComputedStyle(node).display !== 'none';
253
257
  const restoreMargins = (source, clone) => {
254
258
  if (clone.style && source !== poster && [...autoMargins].some(selector => source.matches(selector))) {
255
259
  [...clone.style].filter(name => name === 'margin' || name.startsWith('margin-')).forEach(name => clone.style.removeProperty(name));
256
260
  [...source.style].filter(name => name === 'margin' || name.startsWith('margin-')).forEach(name => clone.style.setProperty(name, source.style.getPropertyValue(name), source.style.getPropertyPriority(name)));
257
261
  }
258
- const children = [...source.children].filter(node => !['STYLE', 'SCRIPT'].includes(node.tagName));
262
+ const children = [...source.children].filter(node => !['STYLE', 'SCRIPT'].includes(node.tagName) && includeNode(node));
259
263
  [...clone.children].forEach((child, i) => { if (children[i]) restoreMargins(children[i], child); });
260
264
  };
261
265
  // Cross-origin font stylesheets are readable through fetch, but not through cssRules.
@@ -311,7 +315,7 @@ function addPosterDownloads() {
311
315
  node.style.backgroundImage = background;
312
316
  };
313
317
  const blob = await domToBlob(poster, {
314
- scale: 2, style: { margin: '0' },
318
+ scale: 2, style: { margin: '0' }, filter: includeNode,
315
319
  font: { cssText: fontRules.join('\n') }, onCloneEachNode: embedTexture,
316
320
  onCloneNode: clone => {
317
321
  restoreMargins(poster, clone);
@@ -231,6 +231,8 @@ function addPosterDownloads() {
231
231
  event.stopPropagation();
232
232
  });
233
233
  button.addEventListener('mousedown', event => {
234
+ // Pointer activation downloads without taking focus from the selected chart item.
235
+ event.preventDefault();
234
236
  event.stopPropagation();
235
237
  });
236
238
  poster.addEventListener('focusout', event => {
@@ -272,12 +274,14 @@ function addPosterDownloads() {
272
274
  const margins = [...document.styleSheets].map(sheet => {
273
275
  try { return marginRules(sheet.cssRules); } catch (cause) { return ''; }
274
276
  }).join('\n');
277
+ // Omit display-none HTML subtrees, such as collapsed chart rows, from the image and its SVG payload.
278
+ const includeNode = node => !(node instanceof HTMLElement) || getComputedStyle(node).display !== 'none';
275
279
  const restoreMargins = (source, clone) => {
276
280
  if (clone.style && source !== poster && [...autoMargins].some(selector => source.matches(selector))) {
277
281
  [...clone.style].filter(name => name === 'margin' || name.startsWith('margin-')).forEach(name => clone.style.removeProperty(name));
278
282
  [...source.style].filter(name => name === 'margin' || name.startsWith('margin-')).forEach(name => clone.style.setProperty(name, source.style.getPropertyValue(name), source.style.getPropertyPriority(name)));
279
283
  }
280
- const children = [...source.children].filter(node => !['STYLE', 'SCRIPT'].includes(node.tagName));
284
+ const children = [...source.children].filter(node => !['STYLE', 'SCRIPT'].includes(node.tagName) && includeNode(node));
281
285
  [...clone.children].forEach((child, i) => { if (children[i]) restoreMargins(children[i], child); });
282
286
  };
283
287
  // Cross-origin font stylesheets are readable through fetch, but not through cssRules.
@@ -333,7 +337,7 @@ function addPosterDownloads() {
333
337
  node.style.backgroundImage = background;
334
338
  };
335
339
  const blob = await domToBlob(poster, {
336
- scale: 2, style: { margin: '0' },
340
+ scale: 2, style: { margin: '0' }, filter: includeNode,
337
341
  font: { cssText: fontRules.join('\n') }, onCloneEachNode: embedTexture,
338
342
  onCloneNode: clone => {
339
343
  restoreMargins(poster, clone);
@@ -181,6 +181,8 @@ function addPosterDownloads() {
181
181
  event.stopPropagation();
182
182
  });
183
183
  button.addEventListener('mousedown', event => {
184
+ // Pointer activation downloads without taking focus from the selected chart item.
185
+ event.preventDefault();
184
186
  event.stopPropagation();
185
187
  });
186
188
  poster.addEventListener('focusout', event => {
@@ -222,12 +224,14 @@ function addPosterDownloads() {
222
224
  const margins = [...document.styleSheets].map(sheet => {
223
225
  try { return marginRules(sheet.cssRules); } catch (cause) { return ''; }
224
226
  }).join('\n');
227
+ // Omit display-none HTML subtrees, such as collapsed chart rows, from the image and its SVG payload.
228
+ const includeNode = node => !(node instanceof HTMLElement) || getComputedStyle(node).display !== 'none';
225
229
  const restoreMargins = (source, clone) => {
226
230
  if (clone.style && source !== poster && [...autoMargins].some(selector => source.matches(selector))) {
227
231
  [...clone.style].filter(name => name === 'margin' || name.startsWith('margin-')).forEach(name => clone.style.removeProperty(name));
228
232
  [...source.style].filter(name => name === 'margin' || name.startsWith('margin-')).forEach(name => clone.style.setProperty(name, source.style.getPropertyValue(name), source.style.getPropertyPriority(name)));
229
233
  }
230
- const children = [...source.children].filter(node => !['STYLE', 'SCRIPT'].includes(node.tagName));
234
+ const children = [...source.children].filter(node => !['STYLE', 'SCRIPT'].includes(node.tagName) && includeNode(node));
231
235
  [...clone.children].forEach((child, i) => { if (children[i]) restoreMargins(children[i], child); });
232
236
  };
233
237
  // Cross-origin font stylesheets are readable through fetch, but not through cssRules.
@@ -283,7 +287,7 @@ function addPosterDownloads() {
283
287
  node.style.backgroundImage = background;
284
288
  };
285
289
  const blob = await domToBlob(poster, {
286
- scale: 2, style: { margin: '0' },
290
+ scale: 2, style: { margin: '0' }, filter: includeNode,
287
291
  font: { cssText: fontRules.join('\n') }, onCloneEachNode: embedTexture,
288
292
  onCloneNode: clone => {
289
293
  restoreMargins(poster, clone);
@@ -224,6 +224,8 @@ function addPosterDownloads() {
224
224
  event.stopPropagation();
225
225
  });
226
226
  button.addEventListener('mousedown', event => {
227
+ // Pointer activation downloads without taking focus from the selected chart item.
228
+ event.preventDefault();
227
229
  event.stopPropagation();
228
230
  });
229
231
  poster.addEventListener('focusout', event => {
@@ -265,12 +267,14 @@ function addPosterDownloads() {
265
267
  const margins = [...document.styleSheets].map(sheet => {
266
268
  try { return marginRules(sheet.cssRules); } catch (cause) { return ''; }
267
269
  }).join('\n');
270
+ // Omit display-none HTML subtrees, such as collapsed chart rows, from the image and its SVG payload.
271
+ const includeNode = node => !(node instanceof HTMLElement) || getComputedStyle(node).display !== 'none';
268
272
  const restoreMargins = (source, clone) => {
269
273
  if (clone.style && source !== poster && [...autoMargins].some(selector => source.matches(selector))) {
270
274
  [...clone.style].filter(name => name === 'margin' || name.startsWith('margin-')).forEach(name => clone.style.removeProperty(name));
271
275
  [...source.style].filter(name => name === 'margin' || name.startsWith('margin-')).forEach(name => clone.style.setProperty(name, source.style.getPropertyValue(name), source.style.getPropertyPriority(name)));
272
276
  }
273
- const children = [...source.children].filter(node => !['STYLE', 'SCRIPT'].includes(node.tagName));
277
+ const children = [...source.children].filter(node => !['STYLE', 'SCRIPT'].includes(node.tagName) && includeNode(node));
274
278
  [...clone.children].forEach((child, i) => { if (children[i]) restoreMargins(children[i], child); });
275
279
  };
276
280
  // Cross-origin font stylesheets are readable through fetch, but not through cssRules.
@@ -326,7 +330,7 @@ function addPosterDownloads() {
326
330
  node.style.backgroundImage = background;
327
331
  };
328
332
  const blob = await domToBlob(poster, {
329
- scale: 2, style: { margin: '0' },
333
+ scale: 2, style: { margin: '0' }, filter: includeNode,
330
334
  font: { cssText: fontRules.join('\n') }, onCloneEachNode: embedTexture,
331
335
  onCloneNode: clone => {
332
336
  restoreMargins(poster, clone);
@@ -181,6 +181,8 @@ function addPosterDownloads() {
181
181
  event.stopPropagation();
182
182
  });
183
183
  button.addEventListener('mousedown', event => {
184
+ // Pointer activation downloads without taking focus from the selected chart item.
185
+ event.preventDefault();
184
186
  event.stopPropagation();
185
187
  });
186
188
  poster.addEventListener('focusout', event => {
@@ -222,12 +224,14 @@ function addPosterDownloads() {
222
224
  const margins = [...document.styleSheets].map(sheet => {
223
225
  try { return marginRules(sheet.cssRules); } catch (cause) { return ''; }
224
226
  }).join('\n');
227
+ // Omit display-none HTML subtrees, such as collapsed chart rows, from the image and its SVG payload.
228
+ const includeNode = node => !(node instanceof HTMLElement) || getComputedStyle(node).display !== 'none';
225
229
  const restoreMargins = (source, clone) => {
226
230
  if (clone.style && source !== poster && [...autoMargins].some(selector => source.matches(selector))) {
227
231
  [...clone.style].filter(name => name === 'margin' || name.startsWith('margin-')).forEach(name => clone.style.removeProperty(name));
228
232
  [...source.style].filter(name => name === 'margin' || name.startsWith('margin-')).forEach(name => clone.style.setProperty(name, source.style.getPropertyValue(name), source.style.getPropertyPriority(name)));
229
233
  }
230
- const children = [...source.children].filter(node => !['STYLE', 'SCRIPT'].includes(node.tagName));
234
+ const children = [...source.children].filter(node => !['STYLE', 'SCRIPT'].includes(node.tagName) && includeNode(node));
231
235
  [...clone.children].forEach((child, i) => { if (children[i]) restoreMargins(children[i], child); });
232
236
  };
233
237
  // Cross-origin font stylesheets are readable through fetch, but not through cssRules.
@@ -283,7 +287,7 @@ function addPosterDownloads() {
283
287
  node.style.backgroundImage = background;
284
288
  };
285
289
  const blob = await domToBlob(poster, {
286
- scale: 2, style: { margin: '0' },
290
+ scale: 2, style: { margin: '0' }, filter: includeNode,
287
291
  font: { cssText: fontRules.join('\n') }, onCloneEachNode: embedTexture,
288
292
  onCloneNode: clone => {
289
293
  restoreMargins(poster, clone);
@@ -392,6 +392,8 @@ function addPosterDownloads() {
392
392
  event.stopPropagation();
393
393
  });
394
394
  button.addEventListener('mousedown', event => {
395
+ // Pointer activation downloads without taking focus from the selected chart item.
396
+ event.preventDefault();
395
397
  event.stopPropagation();
396
398
  });
397
399
  poster.addEventListener('focusout', event => {
@@ -433,12 +435,14 @@ function addPosterDownloads() {
433
435
  const margins = [...document.styleSheets].map(sheet => {
434
436
  try { return marginRules(sheet.cssRules); } catch (cause) { return ''; }
435
437
  }).join('\n');
438
+ // Omit display-none HTML subtrees, such as collapsed chart rows, from the image and its SVG payload.
439
+ const includeNode = node => !(node instanceof HTMLElement) || getComputedStyle(node).display !== 'none';
436
440
  const restoreMargins = (source, clone) => {
437
441
  if (clone.style && source !== poster && [...autoMargins].some(selector => source.matches(selector))) {
438
442
  [...clone.style].filter(name => name === 'margin' || name.startsWith('margin-')).forEach(name => clone.style.removeProperty(name));
439
443
  [...source.style].filter(name => name === 'margin' || name.startsWith('margin-')).forEach(name => clone.style.setProperty(name, source.style.getPropertyValue(name), source.style.getPropertyPriority(name)));
440
444
  }
441
- const children = [...source.children].filter(node => !['STYLE', 'SCRIPT'].includes(node.tagName));
445
+ const children = [...source.children].filter(node => !['STYLE', 'SCRIPT'].includes(node.tagName) && includeNode(node));
442
446
  [...clone.children].forEach((child, i) => { if (children[i]) restoreMargins(children[i], child); });
443
447
  };
444
448
  // Cross-origin font stylesheets are readable through fetch, but not through cssRules.
@@ -494,7 +498,7 @@ function addPosterDownloads() {
494
498
  node.style.backgroundImage = background;
495
499
  };
496
500
  const blob = await domToBlob(poster, {
497
- scale: 2, style: { margin: '0' },
501
+ scale: 2, style: { margin: '0' }, filter: includeNode,
498
502
  font: { cssText: fontRules.join('\n') }, onCloneEachNode: embedTexture,
499
503
  onCloneNode: clone => {
500
504
  restoreMargins(poster, clone);
@@ -193,6 +193,8 @@ function addPosterDownloads() {
193
193
  event.stopPropagation();
194
194
  });
195
195
  button.addEventListener('mousedown', event => {
196
+ // Pointer activation downloads without taking focus from the selected chart item.
197
+ event.preventDefault();
196
198
  event.stopPropagation();
197
199
  });
198
200
  poster.addEventListener('focusout', event => {
@@ -234,12 +236,14 @@ function addPosterDownloads() {
234
236
  const margins = [...document.styleSheets].map(sheet => {
235
237
  try { return marginRules(sheet.cssRules); } catch (cause) { return ''; }
236
238
  }).join('\n');
239
+ // Omit display-none HTML subtrees, such as collapsed chart rows, from the image and its SVG payload.
240
+ const includeNode = node => !(node instanceof HTMLElement) || getComputedStyle(node).display !== 'none';
237
241
  const restoreMargins = (source, clone) => {
238
242
  if (clone.style && source !== poster && [...autoMargins].some(selector => source.matches(selector))) {
239
243
  [...clone.style].filter(name => name === 'margin' || name.startsWith('margin-')).forEach(name => clone.style.removeProperty(name));
240
244
  [...source.style].filter(name => name === 'margin' || name.startsWith('margin-')).forEach(name => clone.style.setProperty(name, source.style.getPropertyValue(name), source.style.getPropertyPriority(name)));
241
245
  }
242
- const children = [...source.children].filter(node => !['STYLE', 'SCRIPT'].includes(node.tagName));
246
+ const children = [...source.children].filter(node => !['STYLE', 'SCRIPT'].includes(node.tagName) && includeNode(node));
243
247
  [...clone.children].forEach((child, i) => { if (children[i]) restoreMargins(children[i], child); });
244
248
  };
245
249
  // Cross-origin font stylesheets are readable through fetch, but not through cssRules.
@@ -295,7 +299,7 @@ function addPosterDownloads() {
295
299
  node.style.backgroundImage = background;
296
300
  };
297
301
  const blob = await domToBlob(poster, {
298
- scale: 2, style: { margin: '0' },
302
+ scale: 2, style: { margin: '0' }, filter: includeNode,
299
303
  font: { cssText: fontRules.join('\n') }, onCloneEachNode: embedTexture,
300
304
  onCloneNode: clone => {
301
305
  restoreMargins(poster, clone);
@@ -276,6 +276,8 @@ function addPosterDownloads() {
276
276
  event.stopPropagation();
277
277
  });
278
278
  button.addEventListener('mousedown', event => {
279
+ // Pointer activation downloads without taking focus from the selected chart item.
280
+ event.preventDefault();
279
281
  event.stopPropagation();
280
282
  });
281
283
  poster.addEventListener('focusout', event => {
@@ -317,12 +319,14 @@ function addPosterDownloads() {
317
319
  const margins = [...document.styleSheets].map(sheet => {
318
320
  try { return marginRules(sheet.cssRules); } catch (cause) { return ''; }
319
321
  }).join('\n');
322
+ // Omit display-none HTML subtrees, such as collapsed chart rows, from the image and its SVG payload.
323
+ const includeNode = node => !(node instanceof HTMLElement) || getComputedStyle(node).display !== 'none';
320
324
  const restoreMargins = (source, clone) => {
321
325
  if (clone.style && source !== poster && [...autoMargins].some(selector => source.matches(selector))) {
322
326
  [...clone.style].filter(name => name === 'margin' || name.startsWith('margin-')).forEach(name => clone.style.removeProperty(name));
323
327
  [...source.style].filter(name => name === 'margin' || name.startsWith('margin-')).forEach(name => clone.style.setProperty(name, source.style.getPropertyValue(name), source.style.getPropertyPriority(name)));
324
328
  }
325
- const children = [...source.children].filter(node => !['STYLE', 'SCRIPT'].includes(node.tagName));
329
+ const children = [...source.children].filter(node => !['STYLE', 'SCRIPT'].includes(node.tagName) && includeNode(node));
326
330
  [...clone.children].forEach((child, i) => { if (children[i]) restoreMargins(children[i], child); });
327
331
  };
328
332
  // Cross-origin font stylesheets are readable through fetch, but not through cssRules.
@@ -378,7 +382,7 @@ function addPosterDownloads() {
378
382
  node.style.backgroundImage = background;
379
383
  };
380
384
  const blob = await domToBlob(poster, {
381
- scale: 2, style: { margin: '0' },
385
+ scale: 2, style: { margin: '0' }, filter: includeNode,
382
386
  font: { cssText: fontRules.join('\n') }, onCloneEachNode: embedTexture,
383
387
  onCloneNode: clone => {
384
388
  restoreMargins(poster, clone);
@@ -180,6 +180,8 @@ function addPosterDownloads() {
180
180
  event.stopPropagation();
181
181
  });
182
182
  button.addEventListener('mousedown', event => {
183
+ // Pointer activation downloads without taking focus from the selected chart item.
184
+ event.preventDefault();
183
185
  event.stopPropagation();
184
186
  });
185
187
  poster.addEventListener('focusout', event => {
@@ -221,12 +223,14 @@ function addPosterDownloads() {
221
223
  const margins = [...document.styleSheets].map(sheet => {
222
224
  try { return marginRules(sheet.cssRules); } catch (cause) { return ''; }
223
225
  }).join('\n');
226
+ // Omit display-none HTML subtrees, such as collapsed chart rows, from the image and its SVG payload.
227
+ const includeNode = node => !(node instanceof HTMLElement) || getComputedStyle(node).display !== 'none';
224
228
  const restoreMargins = (source, clone) => {
225
229
  if (clone.style && source !== poster && [...autoMargins].some(selector => source.matches(selector))) {
226
230
  [...clone.style].filter(name => name === 'margin' || name.startsWith('margin-')).forEach(name => clone.style.removeProperty(name));
227
231
  [...source.style].filter(name => name === 'margin' || name.startsWith('margin-')).forEach(name => clone.style.setProperty(name, source.style.getPropertyValue(name), source.style.getPropertyPriority(name)));
228
232
  }
229
- const children = [...source.children].filter(node => !['STYLE', 'SCRIPT'].includes(node.tagName));
233
+ const children = [...source.children].filter(node => !['STYLE', 'SCRIPT'].includes(node.tagName) && includeNode(node));
230
234
  [...clone.children].forEach((child, i) => { if (children[i]) restoreMargins(children[i], child); });
231
235
  };
232
236
  // Cross-origin font stylesheets are readable through fetch, but not through cssRules.
@@ -282,7 +286,7 @@ function addPosterDownloads() {
282
286
  node.style.backgroundImage = background;
283
287
  };
284
288
  const blob = await domToBlob(poster, {
285
- scale: 2, style: { margin: '0' },
289
+ scale: 2, style: { margin: '0' }, filter: includeNode,
286
290
  font: { cssText: fontRules.join('\n') }, onCloneEachNode: embedTexture,
287
291
  onCloneNode: clone => {
288
292
  restoreMargins(poster, clone);
@@ -241,6 +241,8 @@ function addPosterDownloads() {
241
241
  event.stopPropagation();
242
242
  });
243
243
  button.addEventListener('mousedown', event => {
244
+ // Pointer activation downloads without taking focus from the selected chart item.
245
+ event.preventDefault();
244
246
  event.stopPropagation();
245
247
  });
246
248
  poster.addEventListener('focusout', event => {
@@ -282,12 +284,14 @@ function addPosterDownloads() {
282
284
  const margins = [...document.styleSheets].map(sheet => {
283
285
  try { return marginRules(sheet.cssRules); } catch (cause) { return ''; }
284
286
  }).join('\n');
287
+ // Omit display-none HTML subtrees, such as collapsed chart rows, from the image and its SVG payload.
288
+ const includeNode = node => !(node instanceof HTMLElement) || getComputedStyle(node).display !== 'none';
285
289
  const restoreMargins = (source, clone) => {
286
290
  if (clone.style && source !== poster && [...autoMargins].some(selector => source.matches(selector))) {
287
291
  [...clone.style].filter(name => name === 'margin' || name.startsWith('margin-')).forEach(name => clone.style.removeProperty(name));
288
292
  [...source.style].filter(name => name === 'margin' || name.startsWith('margin-')).forEach(name => clone.style.setProperty(name, source.style.getPropertyValue(name), source.style.getPropertyPriority(name)));
289
293
  }
290
- const children = [...source.children].filter(node => !['STYLE', 'SCRIPT'].includes(node.tagName));
294
+ const children = [...source.children].filter(node => !['STYLE', 'SCRIPT'].includes(node.tagName) && includeNode(node));
291
295
  [...clone.children].forEach((child, i) => { if (children[i]) restoreMargins(children[i], child); });
292
296
  };
293
297
  // Cross-origin font stylesheets are readable through fetch, but not through cssRules.
@@ -343,7 +347,7 @@ function addPosterDownloads() {
343
347
  node.style.backgroundImage = background;
344
348
  };
345
349
  const blob = await domToBlob(poster, {
346
- scale: 2, style: { margin: '0' },
350
+ scale: 2, style: { margin: '0' }, filter: includeNode,
347
351
  font: { cssText: fontRules.join('\n') }, onCloneEachNode: embedTexture,
348
352
  onCloneNode: clone => {
349
353
  restoreMargins(poster, clone);
@@ -185,6 +185,8 @@ function addPosterDownloads() {
185
185
  event.stopPropagation();
186
186
  });
187
187
  button.addEventListener('mousedown', event => {
188
+ // Pointer activation downloads without taking focus from the selected chart item.
189
+ event.preventDefault();
188
190
  event.stopPropagation();
189
191
  });
190
192
  poster.addEventListener('focusout', event => {
@@ -226,12 +228,14 @@ function addPosterDownloads() {
226
228
  const margins = [...document.styleSheets].map(sheet => {
227
229
  try { return marginRules(sheet.cssRules); } catch (cause) { return ''; }
228
230
  }).join('\n');
231
+ // Omit display-none HTML subtrees, such as collapsed chart rows, from the image and its SVG payload.
232
+ const includeNode = node => !(node instanceof HTMLElement) || getComputedStyle(node).display !== 'none';
229
233
  const restoreMargins = (source, clone) => {
230
234
  if (clone.style && source !== poster && [...autoMargins].some(selector => source.matches(selector))) {
231
235
  [...clone.style].filter(name => name === 'margin' || name.startsWith('margin-')).forEach(name => clone.style.removeProperty(name));
232
236
  [...source.style].filter(name => name === 'margin' || name.startsWith('margin-')).forEach(name => clone.style.setProperty(name, source.style.getPropertyValue(name), source.style.getPropertyPriority(name)));
233
237
  }
234
- const children = [...source.children].filter(node => !['STYLE', 'SCRIPT'].includes(node.tagName));
238
+ const children = [...source.children].filter(node => !['STYLE', 'SCRIPT'].includes(node.tagName) && includeNode(node));
235
239
  [...clone.children].forEach((child, i) => { if (children[i]) restoreMargins(children[i], child); });
236
240
  };
237
241
  // Cross-origin font stylesheets are readable through fetch, but not through cssRules.
@@ -287,7 +291,7 @@ function addPosterDownloads() {
287
291
  node.style.backgroundImage = background;
288
292
  };
289
293
  const blob = await domToBlob(poster, {
290
- scale: 2, style: { margin: '0' },
294
+ scale: 2, style: { margin: '0' }, filter: includeNode,
291
295
  font: { cssText: fontRules.join('\n') }, onCloneEachNode: embedTexture,
292
296
  onCloneNode: clone => {
293
297
  restoreMargins(poster, clone);
@@ -228,6 +228,8 @@ function addPosterDownloads() {
228
228
  event.stopPropagation();
229
229
  });
230
230
  button.addEventListener('mousedown', event => {
231
+ // Pointer activation downloads without taking focus from the selected chart item.
232
+ event.preventDefault();
231
233
  event.stopPropagation();
232
234
  });
233
235
  poster.addEventListener('focusout', event => {
@@ -269,12 +271,14 @@ function addPosterDownloads() {
269
271
  const margins = [...document.styleSheets].map(sheet => {
270
272
  try { return marginRules(sheet.cssRules); } catch (cause) { return ''; }
271
273
  }).join('\n');
274
+ // Omit display-none HTML subtrees, such as collapsed chart rows, from the image and its SVG payload.
275
+ const includeNode = node => !(node instanceof HTMLElement) || getComputedStyle(node).display !== 'none';
272
276
  const restoreMargins = (source, clone) => {
273
277
  if (clone.style && source !== poster && [...autoMargins].some(selector => source.matches(selector))) {
274
278
  [...clone.style].filter(name => name === 'margin' || name.startsWith('margin-')).forEach(name => clone.style.removeProperty(name));
275
279
  [...source.style].filter(name => name === 'margin' || name.startsWith('margin-')).forEach(name => clone.style.setProperty(name, source.style.getPropertyValue(name), source.style.getPropertyPriority(name)));
276
280
  }
277
- const children = [...source.children].filter(node => !['STYLE', 'SCRIPT'].includes(node.tagName));
281
+ const children = [...source.children].filter(node => !['STYLE', 'SCRIPT'].includes(node.tagName) && includeNode(node));
278
282
  [...clone.children].forEach((child, i) => { if (children[i]) restoreMargins(children[i], child); });
279
283
  };
280
284
  // Cross-origin font stylesheets are readable through fetch, but not through cssRules.
@@ -330,7 +334,7 @@ function addPosterDownloads() {
330
334
  node.style.backgroundImage = background;
331
335
  };
332
336
  const blob = await domToBlob(poster, {
333
- scale: 2, style: { margin: '0' },
337
+ scale: 2, style: { margin: '0' }, filter: includeNode,
334
338
  font: { cssText: fontRules.join('\n') }, onCloneEachNode: embedTexture,
335
339
  onCloneNode: clone => {
336
340
  restoreMargins(poster, clone);
@@ -244,6 +244,8 @@ function addPosterDownloads() {
244
244
  event.stopPropagation();
245
245
  });
246
246
  button.addEventListener('mousedown', event => {
247
+ // Pointer activation downloads without taking focus from the selected chart item.
248
+ event.preventDefault();
247
249
  event.stopPropagation();
248
250
  });
249
251
  poster.addEventListener('focusout', event => {
@@ -285,12 +287,14 @@ function addPosterDownloads() {
285
287
  const margins = [...document.styleSheets].map(sheet => {
286
288
  try { return marginRules(sheet.cssRules); } catch (cause) { return ''; }
287
289
  }).join('\n');
290
+ // Omit display-none HTML subtrees, such as collapsed chart rows, from the image and its SVG payload.
291
+ const includeNode = node => !(node instanceof HTMLElement) || getComputedStyle(node).display !== 'none';
288
292
  const restoreMargins = (source, clone) => {
289
293
  if (clone.style && source !== poster && [...autoMargins].some(selector => source.matches(selector))) {
290
294
  [...clone.style].filter(name => name === 'margin' || name.startsWith('margin-')).forEach(name => clone.style.removeProperty(name));
291
295
  [...source.style].filter(name => name === 'margin' || name.startsWith('margin-')).forEach(name => clone.style.setProperty(name, source.style.getPropertyValue(name), source.style.getPropertyPriority(name)));
292
296
  }
293
- const children = [...source.children].filter(node => !['STYLE', 'SCRIPT'].includes(node.tagName));
297
+ const children = [...source.children].filter(node => !['STYLE', 'SCRIPT'].includes(node.tagName) && includeNode(node));
294
298
  [...clone.children].forEach((child, i) => { if (children[i]) restoreMargins(children[i], child); });
295
299
  };
296
300
  // Cross-origin font stylesheets are readable through fetch, but not through cssRules.
@@ -346,7 +350,7 @@ function addPosterDownloads() {
346
350
  node.style.backgroundImage = background;
347
351
  };
348
352
  const blob = await domToBlob(poster, {
349
- scale: 2, style: { margin: '0' },
353
+ scale: 2, style: { margin: '0' }, filter: includeNode,
350
354
  font: { cssText: fontRules.join('\n') }, onCloneEachNode: embedTexture,
351
355
  onCloneNode: clone => {
352
356
  restoreMargins(poster, clone);
@@ -213,6 +213,8 @@ function addPosterDownloads() {
213
213
  event.stopPropagation();
214
214
  });
215
215
  button.addEventListener('mousedown', event => {
216
+ // Pointer activation downloads without taking focus from the selected chart item.
217
+ event.preventDefault();
216
218
  event.stopPropagation();
217
219
  });
218
220
  poster.addEventListener('focusout', event => {
@@ -254,12 +256,14 @@ function addPosterDownloads() {
254
256
  const margins = [...document.styleSheets].map(sheet => {
255
257
  try { return marginRules(sheet.cssRules); } catch (cause) { return ''; }
256
258
  }).join('\n');
259
+ // Omit display-none HTML subtrees, such as collapsed chart rows, from the image and its SVG payload.
260
+ const includeNode = node => !(node instanceof HTMLElement) || getComputedStyle(node).display !== 'none';
257
261
  const restoreMargins = (source, clone) => {
258
262
  if (clone.style && source !== poster && [...autoMargins].some(selector => source.matches(selector))) {
259
263
  [...clone.style].filter(name => name === 'margin' || name.startsWith('margin-')).forEach(name => clone.style.removeProperty(name));
260
264
  [...source.style].filter(name => name === 'margin' || name.startsWith('margin-')).forEach(name => clone.style.setProperty(name, source.style.getPropertyValue(name), source.style.getPropertyPriority(name)));
261
265
  }
262
- const children = [...source.children].filter(node => !['STYLE', 'SCRIPT'].includes(node.tagName));
266
+ const children = [...source.children].filter(node => !['STYLE', 'SCRIPT'].includes(node.tagName) && includeNode(node));
263
267
  [...clone.children].forEach((child, i) => { if (children[i]) restoreMargins(children[i], child); });
264
268
  };
265
269
  // Cross-origin font stylesheets are readable through fetch, but not through cssRules.
@@ -315,7 +319,7 @@ function addPosterDownloads() {
315
319
  node.style.backgroundImage = background;
316
320
  };
317
321
  const blob = await domToBlob(poster, {
318
- scale: 2, style: { margin: '0' },
322
+ scale: 2, style: { margin: '0' }, filter: includeNode,
319
323
  font: { cssText: fontRules.join('\n') }, onCloneEachNode: embedTexture,
320
324
  onCloneNode: clone => {
321
325
  restoreMargins(poster, clone);
@@ -298,6 +298,8 @@ function addPosterDownloads() {
298
298
  event.stopPropagation();
299
299
  });
300
300
  button.addEventListener('mousedown', event => {
301
+ // Pointer activation downloads without taking focus from the selected chart item.
302
+ event.preventDefault();
301
303
  event.stopPropagation();
302
304
  });
303
305
  poster.addEventListener('focusout', event => {
@@ -339,12 +341,14 @@ function addPosterDownloads() {
339
341
  const margins = [...document.styleSheets].map(sheet => {
340
342
  try { return marginRules(sheet.cssRules); } catch (cause) { return ''; }
341
343
  }).join('\n');
344
+ // Omit display-none HTML subtrees, such as collapsed chart rows, from the image and its SVG payload.
345
+ const includeNode = node => !(node instanceof HTMLElement) || getComputedStyle(node).display !== 'none';
342
346
  const restoreMargins = (source, clone) => {
343
347
  if (clone.style && source !== poster && [...autoMargins].some(selector => source.matches(selector))) {
344
348
  [...clone.style].filter(name => name === 'margin' || name.startsWith('margin-')).forEach(name => clone.style.removeProperty(name));
345
349
  [...source.style].filter(name => name === 'margin' || name.startsWith('margin-')).forEach(name => clone.style.setProperty(name, source.style.getPropertyValue(name), source.style.getPropertyPriority(name)));
346
350
  }
347
- const children = [...source.children].filter(node => !['STYLE', 'SCRIPT'].includes(node.tagName));
351
+ const children = [...source.children].filter(node => !['STYLE', 'SCRIPT'].includes(node.tagName) && includeNode(node));
348
352
  [...clone.children].forEach((child, i) => { if (children[i]) restoreMargins(children[i], child); });
349
353
  };
350
354
  // Cross-origin font stylesheets are readable through fetch, but not through cssRules.
@@ -400,7 +404,7 @@ function addPosterDownloads() {
400
404
  node.style.backgroundImage = background;
401
405
  };
402
406
  const blob = await domToBlob(poster, {
403
- scale: 2, style: { margin: '0' },
407
+ scale: 2, style: { margin: '0' }, filter: includeNode,
404
408
  font: { cssText: fontRules.join('\n') }, onCloneEachNode: embedTexture,
405
409
  onCloneNode: clone => {
406
410
  restoreMargins(poster, clone);