@opendata-ai/openchart-vanilla 8.9.0 → 8.9.1

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.
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-SDJHUELU.js";
9
9
  import {
10
10
  resolvedSurface
11
- } from "./chunk-NG7CVCI2.js";
11
+ } from "./chunk-UC4IHEUO.js";
12
12
 
13
13
  // src/svg-ids.ts
14
14
  var counter = 0;
@@ -594,6 +594,7 @@ function stampAnimationVars(el, v) {
594
594
  // src/renderers/annotations.ts
595
595
  import {
596
596
  ARROWHEAD_LENGTH,
597
+ automaticControlPoint,
597
598
  BOLD_SPAN_FONT_WEIGHT,
598
599
  computeArrowheadPoints,
599
600
  DEFAULT_ANNOTATION_FONT_SIZE,
@@ -619,12 +620,8 @@ function renderArrowhead(parent, tipX, tipY, tangentX, tangentY, stroke) {
619
620
  });
620
621
  parent.appendChild(arrow);
621
622
  }
622
- function renderCurvedConnector(parent, from, to, stroke, showArrow, exit) {
623
- const dx = to.x - from.x;
624
- const dy = to.y - from.y;
625
- const vertical = exit ? exit === "vertical" : Math.abs(dy) >= Math.abs(dx);
626
- const cpx = from.x + dx * (vertical ? 0.2 : 0.6);
627
- const cpy = from.y + dy * (vertical ? 0.6 : 0.2);
623
+ function renderCurvedConnector(parent, from, to, stroke, showArrow, exit, control) {
624
+ const { x: cpx, y: cpy } = control ?? automaticControlPoint(from, to, exit);
628
625
  const tx = to.x - cpx;
629
626
  const ty = to.y - cpy;
630
627
  const tLen = Math.sqrt(tx * tx + ty * ty) || 1;
@@ -665,6 +662,8 @@ function renderAnnotation(parent, annotation, index, bgColor) {
665
662
  const g = createSVGElement("g");
666
663
  g.setAttribute("class", `oc-annotation oc-annotation-${annotation.type}`);
667
664
  g.setAttribute("data-annotation-index", String(annotation.specIndex ?? index));
665
+ const facet = parent.closest("[data-facet]")?.getAttribute("data-facet");
666
+ if (facet !== null && facet !== void 0) g.setAttribute("data-facet-instance", facet);
668
667
  if (annotation.id) {
669
668
  g.setAttribute("data-annotation-id", annotation.id);
670
669
  }
@@ -737,7 +736,7 @@ function renderAnnotation(parent, annotation, index, bgColor) {
737
736
  if (annotation.label.connector) {
738
737
  const c = annotation.label.connector;
739
738
  if (c.style === "curve") {
740
- renderCurvedConnector(g, c.from, c.to, c.stroke, c.arrow, c.exit);
739
+ renderCurvedConnector(g, c.from, c.to, c.stroke, c.arrow, c.exit, c.control);
741
740
  } else if (c.style === "drop-line") {
742
741
  const connector = createSVGElement("line");
743
742
  connector.setAttribute("class", "oc-annotation-connector oc-annotation-drop-line");
@@ -807,7 +806,12 @@ function renderAnnotation(parent, annotation, index, bgColor) {
807
806
  applyTextStyle(text, annotation.label.style);
808
807
  const fontSize = annotation.label.style.fontSize ?? DEFAULT_ANNOTATION_FONT_SIZE;
809
808
  const lineHeight = fontSize * (annotation.label.style.lineHeight ?? DEFAULT_LINE_HEIGHT);
810
- fillRichText(text, annotation.label.text, annotation.label.x, lineHeight);
809
+ fillRichText(
810
+ text,
811
+ annotation.label.lines?.join("\n") ?? annotation.label.text,
812
+ annotation.label.x,
813
+ lineHeight
814
+ );
811
815
  if (annotation.label.background && annotation.label.bounds) {
812
816
  const pad = 3;
813
817
  const b = annotation.label.bounds;
@@ -837,7 +841,12 @@ function renderAnnotation(parent, annotation, index, bgColor) {
837
841
  applyTextStyle(sub, annotation.subtitle.style);
838
842
  const subFontSize = annotation.subtitle.style.fontSize ?? DEFAULT_ANNOTATION_FONT_SIZE;
839
843
  const subLineHeight = subFontSize * (annotation.subtitle.style.lineHeight ?? DEFAULT_LINE_HEIGHT);
840
- fillRichText(sub, annotation.subtitle.text, annotation.subtitle.x, subLineHeight);
844
+ fillRichText(
845
+ sub,
846
+ annotation.subtitle.lines?.join("\n") ?? annotation.subtitle.text,
847
+ annotation.subtitle.x,
848
+ subLineHeight
849
+ );
841
850
  g.appendChild(sub);
842
851
  }
843
852
  }
@@ -847,11 +856,36 @@ function renderAnnotations(parent, layout) {
847
856
  if (layout.annotations.length === 0) return;
848
857
  const g = createSVGElement("g");
849
858
  g.setAttribute("class", "oc-annotations");
859
+ parent.appendChild(g);
850
860
  const bgColor = resolvedSurface(layout.theme);
851
861
  for (let i = 0; i < layout.annotations.length; i++) {
852
862
  renderAnnotation(g, layout.annotations[i], i, bgColor);
853
863
  }
854
- parent.appendChild(g);
864
+ }
865
+ function updateAnnotationGeometry(group, annotation, bgColor) {
866
+ const scratch = createSVGElement("g");
867
+ renderAnnotation(scratch, annotation, annotation.specIndex ?? 0, bgColor);
868
+ const fresh = scratch.firstElementChild;
869
+ const previous = [...group.children].filter(
870
+ (child) => [...child.classList].some((name) => name.startsWith("oc-annotation-"))
871
+ );
872
+ const used = /* @__PURE__ */ new Set();
873
+ for (const child of [...fresh.children]) {
874
+ const target = previous.find(
875
+ (old) => !used.has(old) && old.tagName === child.tagName && old.getAttribute("class") === child.getAttribute("class")
876
+ );
877
+ if (target) {
878
+ used.add(target);
879
+ for (const attr of [...target.attributes])
880
+ if (!child.hasAttribute(attr.name)) target.removeAttribute(attr.name);
881
+ for (const attr of [...child.attributes]) target.setAttribute(attr.name, attr.value);
882
+ target.replaceChildren(...child.childNodes);
883
+ group.appendChild(target);
884
+ } else group.appendChild(child);
885
+ }
886
+ for (const old of previous) if (!used.has(old)) old.remove();
887
+ for (const handle of [...group.querySelectorAll(".oc-connector-handle, .oc-anchor-handle")])
888
+ group.appendChild(handle);
855
889
  }
856
890
 
857
891
  // src/renderers/axes.ts
@@ -1150,25 +1184,55 @@ function renderAxes(parent, layout, opts) {
1150
1184
  }
1151
1185
 
1152
1186
  // src/renderers/brand.ts
1153
- import { BRAND_MIN_WIDTH, textAscent as textAscent3 } from "@opendata-ai/openchart-core";
1187
+ import { BRAND_MIN_WIDTH, computeNoteBandHeight, textAscent as textAscent2 } from "@opendata-ai/openchart-core";
1188
+ var BRAND_URL = "https://tryopendata.ai";
1189
+ function renderBrand(parent, layout) {
1190
+ if (layout.dimensions.width < BRAND_MIN_WIDTH) return;
1191
+ const { width } = layout.dimensions;
1192
+ const padding = layout.theme.spacing.padding;
1193
+ const rightEdge = width - padding;
1194
+ const fill = layout.theme.colors.axis;
1195
+ const { chrome } = layout;
1196
+ const bottomOffset = chrome.bottomAnchorY ?? layout.area.y + layout.area.height;
1197
+ const firstBottom = chrome.source ?? chrome.byline ?? chrome.footer;
1198
+ const { legend } = layout;
1199
+ const bottomLegendOffset = legend.position === "bottom" && legend.bounds.height > 0 ? legend.bounds.height + 8 : 0;
1200
+ const bandHeight = layout.chrome.noteBandHeight ?? computeNoteBandHeight(layout.chrome, layout.theme);
1201
+ const chromeY = firstBottom ? bottomOffset + firstBottom.y + bandHeight : bottomOffset + layout.theme.spacing.chartToFooter + bottomLegendOffset + bandHeight;
1202
+ const a = createSVGElement("a");
1203
+ a.setAttribute("href", BRAND_URL);
1204
+ a.setAttributeNS(XLINK_NS, "xlink:href", BRAND_URL);
1205
+ a.setAttribute("target", "_blank");
1206
+ a.setAttribute("rel", "noopener");
1207
+ a.setAttribute("class", "oc-chrome-ref");
1208
+ const BRAND_LARGE = 16;
1209
+ const text = createSVGElement("text");
1210
+ setAttrs(text, {
1211
+ x: rightEdge,
1212
+ y: chromeY + textAscent2(BRAND_LARGE),
1213
+ "font-family": layout.theme.fonts.family,
1214
+ "font-size": BRAND_LARGE,
1215
+ "font-weight": "600",
1216
+ "text-anchor": "end",
1217
+ "fill-opacity": 0.55
1218
+ });
1219
+ text.style.setProperty("fill", fill);
1220
+ text.textContent = "OpenData";
1221
+ a.appendChild(text);
1222
+ parent.appendChild(a);
1223
+ }
1154
1224
 
1155
1225
  // src/renderers/chrome.ts
1156
1226
  import {
1227
+ computeNoteBandHeight as computeNoteBandHeight2,
1157
1228
  FOOTNOTE_LINE_HEIGHT,
1158
- footnoteBandHeight as footnoteBandHeightFor,
1159
- textAscent as textAscent2,
1229
+ textAscent as textAscent3,
1160
1230
  truncateToWidth,
1161
1231
  wrapText
1162
1232
  } from "@opendata-ai/openchart-core";
1163
- function footnoteBandHeight(layout) {
1164
- return footnoteBandHeightFor(
1165
- (layout.chrome.footnotes?.length ?? 0) + (layout.chrome.seriesSummary?.reduce((count, entry) => count + entry.lines.length, 0) ?? 0),
1166
- layout.theme
1167
- );
1168
- }
1169
1233
  function renderChromeElement(parent, element, className, chromeKey, measureText, uppercase = false) {
1170
1234
  const text = createSVGElement("text");
1171
- setAttrs(text, { x: element.x, y: element.y + textAscent2(element.style.fontSize) });
1235
+ setAttrs(text, { x: element.x, y: element.y + textAscent3(element.style.fontSize) });
1172
1236
  applyTextStyle(text, element.style);
1173
1237
  text.setAttribute("class", className);
1174
1238
  text.setAttribute("data-chrome-key", chromeKey);
@@ -1253,7 +1317,8 @@ function renderChrome(parent, layout) {
1253
1317
  renderChromeElement(g, chrome.subtitle, "oc-subtitle", "subtitle", measureText);
1254
1318
  }
1255
1319
  const bottomOffset = layout.chrome.bottomAnchorY ?? layout.area.y + layout.area.height;
1256
- const bandHeight = footnoteBandHeight(layout);
1320
+ const bandHeight = layout.chrome.noteBandHeight ?? computeNoteBandHeight2(layout.chrome, layout.theme);
1321
+ let noteLine = 0;
1257
1322
  if (chrome.footnotes && chrome.footnotes.length > 0) {
1258
1323
  const fontSize = layout.theme.fonts.sizes.small;
1259
1324
  const pad = layout.theme.spacing.padding;
@@ -1268,16 +1333,26 @@ function renderChrome(parent, layout) {
1268
1333
  };
1269
1334
  for (let i = 0; i < chrome.footnotes.length; i++) {
1270
1335
  const f = chrome.footnotes[i];
1271
- const y = bottomOffset + layout.theme.spacing.chartToFooter + textAscent2(fontSize) + i * lineHeight;
1336
+ const y = bottomOffset + layout.theme.spacing.chartToFooter + textAscent3(fontSize) + noteLine * lineHeight;
1272
1337
  const el = createSVGElement("text");
1273
1338
  el.setAttribute("class", "oc-footnotes");
1339
+ if (f.specIndex !== void 0) el.setAttribute("data-note-spec-index", String(f.specIndex));
1340
+ if (f.id !== void 0) el.setAttribute("data-note-id", f.id);
1274
1341
  setAttrs(el, { x: pad, y });
1275
1342
  applyTextStyle(el, style);
1276
- el.textContent = `${f.index}. ${f.text}`;
1343
+ if (f.lines) {
1344
+ f.lines.forEach((line, index) => {
1345
+ const span = createSVGElement("tspan");
1346
+ setAttrs(span, { x: pad, dy: index === 0 ? 0 : lineHeight });
1347
+ span.textContent = line;
1348
+ el.appendChild(span);
1349
+ });
1350
+ } else el.textContent = `${f.index}. ${f.text}`;
1351
+ noteLine += f.lines?.length ?? 1;
1277
1352
  g.appendChild(el);
1278
1353
  }
1279
1354
  }
1280
- let summaryLine = chrome.footnotes?.length ?? 0;
1355
+ let summaryLine = noteLine;
1281
1356
  for (const entry of chrome.seriesSummary ?? []) {
1282
1357
  const size = layout.theme.fonts.sizes.small;
1283
1358
  const el = createSVGElement("text");
@@ -1285,7 +1360,7 @@ function renderChrome(parent, layout) {
1285
1360
  el.setAttribute("data-series", entry.key);
1286
1361
  setAttrs(el, {
1287
1362
  x: layout.theme.spacing.padding,
1288
- y: bottomOffset + layout.theme.spacing.chartToFooter + textAscent2(size) + summaryLine * size * FOOTNOTE_LINE_HEIGHT
1363
+ y: bottomOffset + layout.theme.spacing.chartToFooter + textAscent3(size) + summaryLine * size * FOOTNOTE_LINE_HEIGHT
1289
1364
  });
1290
1365
  applyTextStyle(el, {
1291
1366
  fontFamily: layout.theme.fonts.family,
@@ -1340,44 +1415,6 @@ function renderChrome(parent, layout) {
1340
1415
  parent.appendChild(g);
1341
1416
  }
1342
1417
 
1343
- // src/renderers/brand.ts
1344
- var BRAND_URL = "https://tryopendata.ai";
1345
- function renderBrand(parent, layout) {
1346
- if (layout.dimensions.width < BRAND_MIN_WIDTH) return;
1347
- const { width } = layout.dimensions;
1348
- const padding = layout.theme.spacing.padding;
1349
- const rightEdge = width - padding;
1350
- const fill = layout.theme.colors.axis;
1351
- const { chrome } = layout;
1352
- const bottomOffset = chrome.bottomAnchorY ?? layout.area.y + layout.area.height;
1353
- const firstBottom = chrome.source ?? chrome.byline ?? chrome.footer;
1354
- const { legend } = layout;
1355
- const bottomLegendOffset = legend.position === "bottom" && legend.bounds.height > 0 ? legend.bounds.height + 8 : 0;
1356
- const bandHeight = footnoteBandHeight(layout);
1357
- const chromeY = firstBottom ? bottomOffset + firstBottom.y + bandHeight : bottomOffset + layout.theme.spacing.chartToFooter + bottomLegendOffset + bandHeight;
1358
- const a = createSVGElement("a");
1359
- a.setAttribute("href", BRAND_URL);
1360
- a.setAttributeNS(XLINK_NS, "xlink:href", BRAND_URL);
1361
- a.setAttribute("target", "_blank");
1362
- a.setAttribute("rel", "noopener");
1363
- a.setAttribute("class", "oc-chrome-ref");
1364
- const BRAND_LARGE = 16;
1365
- const text = createSVGElement("text");
1366
- setAttrs(text, {
1367
- x: rightEdge,
1368
- y: chromeY + textAscent3(BRAND_LARGE),
1369
- "font-family": layout.theme.fonts.family,
1370
- "font-size": BRAND_LARGE,
1371
- "font-weight": "600",
1372
- "text-anchor": "end",
1373
- "fill-opacity": 0.55
1374
- });
1375
- text.style.setProperty("fill", fill);
1376
- text.textContent = "OpenData";
1377
- a.appendChild(text);
1378
- parent.appendChild(a);
1379
- }
1380
-
1381
1418
  // src/renderers/endpoint-labels.ts
1382
1419
  var LEADER_STROKE_WIDTH = 1;
1383
1420
  var LEADER_OPACITY = 0.45;
@@ -2136,10 +2173,11 @@ export {
2136
2173
  resolveMarkFill,
2137
2174
  buildPatternDefs,
2138
2175
  resolvePatternFill,
2176
+ updateAnnotationGeometry,
2139
2177
  renderLegend,
2140
2178
  registerMarkRenderer,
2141
2179
  rectPathWithCorners,
2142
2180
  renderSingleMark,
2143
2181
  renderChartSVG
2144
2182
  };
2145
- //# sourceMappingURL=chunk-TP3GCWCO.js.map
2183
+ //# sourceMappingURL=chunk-APMUBW72.js.map