graphein 0.16.1 → 0.17.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.
package/dist/index.js CHANGED
@@ -219,35 +219,43 @@ function fixedDuration(interval) {
219
219
  }
220
220
  }
221
221
  function floorFixed(value, interval) {
222
- const duration = fixedDuration(interval);
223
- if (interval.unit !== "week") {
224
- return Math.floor(value / duration) * duration;
225
- }
226
222
  const date = new Date(value);
227
- const midnight = Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate());
228
- const day = new Date(midnight).getUTCDay();
229
- const weekStart = midnight - day * DAY;
230
- return weekStart - Math.floor((weekStart % duration + duration) % duration);
223
+ if (interval.unit === "day") {
224
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
225
+ }
226
+ if (interval.unit === "week") {
227
+ const day = date.getDay();
228
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate() - day).getTime();
229
+ }
230
+ const duration = fixedDuration(interval);
231
+ return Math.floor(value / duration) * duration;
231
232
  }
232
233
  function addFixed(value, interval) {
234
+ const date = new Date(value);
235
+ if (interval.unit === "day") {
236
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate() + interval.step).getTime();
237
+ }
238
+ if (interval.unit === "week") {
239
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate() + 7 * interval.step).getTime();
240
+ }
233
241
  return value + fixedDuration(interval);
234
242
  }
235
243
  function floorMonth(value, step) {
236
244
  const date = new Date(value);
237
- const month = Math.floor(date.getUTCMonth() / step) * step;
238
- return Date.UTC(date.getUTCFullYear(), month, 1);
245
+ const month = Math.floor(date.getMonth() / step) * step;
246
+ return new Date(date.getFullYear(), month, 1).getTime();
239
247
  }
240
248
  function floorYear(value, step) {
241
249
  const date = new Date(value);
242
- const year = Math.floor(date.getUTCFullYear() / step) * step;
243
- return Date.UTC(year, 0, 1);
250
+ const year = Math.floor(date.getFullYear() / step) * step;
251
+ return new Date(year, 0, 1).getTime();
244
252
  }
245
253
  function addCalendar(value, interval) {
246
254
  const date = new Date(value);
247
255
  if (interval.unit === "month") {
248
- return Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + interval.step, 1);
256
+ return new Date(date.getFullYear(), date.getMonth() + interval.step, 1).getTime();
249
257
  }
250
- return Date.UTC(date.getUTCFullYear() + interval.step, 0, 1);
258
+ return new Date(date.getFullYear() + interval.step, 0, 1).getTime();
251
259
  }
252
260
  function floorTime(value, interval) {
253
261
  if (interval.unit === "month") {
@@ -295,17 +303,17 @@ function timeTickFormat(values) {
295
303
  const span = ordered.length > 1 ? Math.abs(ordered[1] - ordered[0]) : 0;
296
304
  return (time) => {
297
305
  const date = new Date(time);
298
- const year = date.getUTCFullYear();
299
- const month = MONTH_NAMES[date.getUTCMonth()];
300
- const day = date.getUTCDate();
301
- const hours = pad2(date.getUTCHours());
302
- const minutes = pad2(date.getUTCMinutes());
303
- const seconds = pad2(date.getUTCSeconds());
306
+ const year = date.getFullYear();
307
+ const month = MONTH_NAMES[date.getMonth()];
308
+ const day = date.getDate();
309
+ const hours = pad2(date.getHours());
310
+ const minutes = pad2(date.getMinutes());
311
+ const seconds = pad2(date.getSeconds());
304
312
  if (span >= YEAR * 0.8) {
305
313
  return String(year);
306
314
  }
307
315
  if (span >= MONTH * 0.8) {
308
- return ordered.some((tick) => new Date(tick).getUTCFullYear() !== year) ? `${month} ${year}` : month;
316
+ return ordered.some((tick) => new Date(tick).getFullYear() !== year) ? `${month} ${year}` : month;
309
317
  }
310
318
  if (span >= DAY * 0.8) {
311
319
  return `${month} ${day}`;
@@ -4173,6 +4181,7 @@ var COMBO_MARKS = ["line", "bar", "area", "scatter"];
4173
4181
  var COMBO_AXES = ["left", "right"];
4174
4182
  var NEGATIVE_STYLES = ["sign", "parens", "red", "parens-red"];
4175
4183
  var SHOW_AS = ["value", "percentOfRow", "percentOfColumn", "percentOfTotal"];
4184
+ var DEBUG_SECTIONS = ["preview", "spec", "data", "validation", "report", "summary"];
4176
4185
  function isObject3(v) {
4177
4186
  return typeof v === "object" && v !== null && !Array.isArray(v);
4178
4187
  }
@@ -4658,6 +4667,32 @@ function validateSpec(spec) {
4658
4667
  }
4659
4668
  }
4660
4669
  }
4670
+ const debug = spec.debug;
4671
+ if (debug !== void 0 && typeof debug !== "boolean") {
4672
+ if (!isObject3(debug)) {
4673
+ err("debug", '"debug" must be a boolean or a DebugConfig object.');
4674
+ } else {
4675
+ const sections = debug.sections;
4676
+ if (sections !== void 0) {
4677
+ if (!Array.isArray(sections)) {
4678
+ err("debug.sections", '"sections" must be an array of section names.');
4679
+ } else {
4680
+ for (const s of sections) {
4681
+ if (!DEBUG_SECTIONS.includes(s)) {
4682
+ err(
4683
+ "debug.sections",
4684
+ `Unknown debug section "${String(s)}". Expected one of: ${DEBUG_SECTIONS.join(", ")}.`
4685
+ );
4686
+ }
4687
+ }
4688
+ }
4689
+ }
4690
+ const rows = debug.rows;
4691
+ if (rows !== void 0 && (typeof rows !== "number" || !Number.isFinite(rows) || rows < 0)) {
4692
+ err("debug.rows", '"rows" must be a non-negative number.');
4693
+ }
4694
+ }
4695
+ }
4661
4696
  try {
4662
4697
  for (const finding of lintSpec(spec)) warnings.push(finding);
4663
4698
  } catch {
@@ -17448,6 +17483,356 @@ function facetReport(spec, layout2, tokens, size) {
17448
17483
  };
17449
17484
  }
17450
17485
 
17486
+ // src/runtime/debug.ts
17487
+ var DEFAULT_SECTIONS = ["preview", "summary", "validation", "report", "spec", "data"];
17488
+ var DEFAULT_ROW_CAP = 50;
17489
+ function resolveConfig(debug) {
17490
+ const cfg = typeof debug === "object" && debug != null ? debug : {};
17491
+ const sections = cfg.sections && cfg.sections.length ? cfg.sections : DEFAULT_SECTIONS;
17492
+ const rowCap = cfg.rows != null && Number.isFinite(cfg.rows) ? Math.max(0, Math.floor(cfg.rows)) : DEFAULT_ROW_CAP;
17493
+ return { sections, rowCap };
17494
+ }
17495
+ function specTitle(spec) {
17496
+ const t = spec.title;
17497
+ if (typeof t === "string") return t;
17498
+ if (t && typeof t === "object") return t.text;
17499
+ return void 0;
17500
+ }
17501
+ function cellText2(v) {
17502
+ if (v == null) return "";
17503
+ if (v instanceof Date) return v.toISOString();
17504
+ if (typeof v === "object") return JSON.stringify(v);
17505
+ return String(v);
17506
+ }
17507
+ function highlightJson(json, tokens) {
17508
+ const esc = json.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
17509
+ const syn = tokens.dark ? { key: "#7dd3fc", string: "#86efac", number: "#fca5a5", keyword: "#c4b5fd" } : { key: "#0369a1", string: "#15803d", number: "#b91c1c", keyword: "#7c3aed" };
17510
+ return esc.replace(
17511
+ /("(?:\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(?:true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?)/g,
17512
+ (match) => {
17513
+ let color = syn.number;
17514
+ if (match[0] === '"') color = /:\s*$/.test(match) ? syn.key : syn.string;
17515
+ else if (match === "true" || match === "false" || match === "null") color = syn.keyword;
17516
+ return `<span style="color:${color}">${match}</span>`;
17517
+ }
17518
+ );
17519
+ }
17520
+ function specForDisplay(spec) {
17521
+ const copy = { ...spec };
17522
+ if (Array.isArray(spec.data)) copy.data = `[\u2026 ${spec.data.length} rows \u2014 see the Data panel]`;
17523
+ return copy;
17524
+ }
17525
+ function makePanel(title2, tokens) {
17526
+ const card = createDiv("graphein-debug-panel", {
17527
+ border: `1px solid ${tokens.color.border}`,
17528
+ borderRadius: `${tokens.radius.md}px`,
17529
+ background: tokens.color.surface,
17530
+ overflow: "hidden",
17531
+ marginBottom: `${tokens.spacing.sm}px`
17532
+ });
17533
+ const header = createDiv("graphein-debug-panel-header", {
17534
+ display: "flex",
17535
+ alignItems: "center",
17536
+ gap: `${tokens.spacing.xs}px`,
17537
+ padding: `${tokens.spacing.xs}px ${tokens.spacing.sm}px`,
17538
+ font: fontString(tokens.font.size.small, tokens.font.family, tokens.font.weight.bold),
17539
+ color: tokens.color.textMuted,
17540
+ cursor: "pointer",
17541
+ userSelect: "none",
17542
+ borderBottom: `1px solid ${tokens.color.border}`
17543
+ });
17544
+ const caret = document.createElement("span");
17545
+ caret.textContent = "\u25BE";
17546
+ const label = document.createElement("span");
17547
+ label.textContent = title2;
17548
+ header.append(caret, label);
17549
+ const body = createDiv("graphein-debug-panel-body", {
17550
+ padding: `${tokens.spacing.sm}px`,
17551
+ color: tokens.color.text,
17552
+ font: fontString(tokens.font.size.small, tokens.font.family, tokens.font.weight.normal)
17553
+ });
17554
+ header.addEventListener("click", () => {
17555
+ const collapsed = body.style.display === "none";
17556
+ body.style.display = collapsed ? "" : "none";
17557
+ caret.textContent = collapsed ? "\u25BE" : "\u25B8";
17558
+ });
17559
+ card.append(header, body);
17560
+ return { card, body };
17561
+ }
17562
+ function monoStyle(tokens) {
17563
+ return {
17564
+ margin: "0",
17565
+ whiteSpace: "pre",
17566
+ overflow: "auto",
17567
+ maxHeight: "320px",
17568
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
17569
+ fontSize: `${tokens.font.size.small}px`,
17570
+ lineHeight: "1.5"
17571
+ };
17572
+ }
17573
+ function buildPreviewPanel(spec, tokens, deps) {
17574
+ const { card, body } = makePanel("Live preview", tokens);
17575
+ const host = createDiv("graphein-debug-preview", { position: "relative", width: "100%", height: "260px" });
17576
+ body.appendChild(host);
17577
+ let preview = null;
17578
+ let report = null;
17579
+ try {
17580
+ preview = deps.renderPreview(host, { ...spec, debug: false, animation: false });
17581
+ report = preview.report();
17582
+ } catch (e) {
17583
+ body.replaceChildren();
17584
+ const msg = createDiv(void 0, { color: tokens.color.negative, whiteSpace: "pre-wrap" });
17585
+ msg.textContent = `Preview failed to render:
17586
+ ${e instanceof Error ? e.message : String(e)}`;
17587
+ body.appendChild(msg);
17588
+ }
17589
+ return { card, preview, report };
17590
+ }
17591
+ function buildSummaryPanel(spec, tokens) {
17592
+ const { card, body } = makePanel("Summary", tokens);
17593
+ let text = "";
17594
+ try {
17595
+ text = summarize(spec);
17596
+ } catch {
17597
+ text = "";
17598
+ }
17599
+ body.textContent = text || "\u2014 (no summary for this chart type)";
17600
+ if (!text) setStyle(body, { color: tokens.color.textMuted });
17601
+ return card;
17602
+ }
17603
+ function diagnosticRow(path, message, color, tokens) {
17604
+ const row = createDiv(void 0, { display: "flex", gap: `${tokens.spacing.xs}px`, padding: "2px 0" });
17605
+ const key = document.createElement("code");
17606
+ key.textContent = path;
17607
+ setStyle(key, { color, fontWeight: String(tokens.font.weight.bold), flex: "0 0 auto" });
17608
+ const msg = document.createElement("span");
17609
+ msg.textContent = message;
17610
+ setStyle(msg, { color: tokens.color.text });
17611
+ row.append(key, msg);
17612
+ return row;
17613
+ }
17614
+ function buildValidationPanel(spec, tokens) {
17615
+ const { card, body } = makePanel("Validation", tokens);
17616
+ const result = validateSpec(spec);
17617
+ if (result.valid && result.warnings.length === 0) {
17618
+ body.textContent = "\u2713 Valid \u2014 no errors or warnings";
17619
+ setStyle(body, { color: tokens.color.positive });
17620
+ return card;
17621
+ }
17622
+ const add = (items, color, prefix) => {
17623
+ for (const item of items) {
17624
+ const tag = item.rule ? `${prefix} ${item.rule}` : prefix;
17625
+ body.appendChild(diagnosticRow(item.path ? `${tag} @ ${item.path}` : tag, item.message, color, tokens));
17626
+ }
17627
+ };
17628
+ add(result.errors, tokens.color.negative, "error");
17629
+ add(result.warnings, tokens.dark ? "#fbbf24" : "#b45309", "warn");
17630
+ return card;
17631
+ }
17632
+ function buildReportPanel(report, tokens) {
17633
+ const { card, body } = makePanel("Render report", tokens);
17634
+ if (!report) {
17635
+ body.textContent = "\u2014 (no report available)";
17636
+ setStyle(body, { color: tokens.color.textMuted });
17637
+ return card;
17638
+ }
17639
+ const stats = createDiv(void 0, { marginBottom: `${tokens.spacing.xs}px` });
17640
+ const ok = report.ok ? "\u2713 ok" : "\u2717 issues";
17641
+ stats.textContent = `${ok} \xB7 ${report.type} \xB7 ${Math.round(report.size.width)}\xD7${Math.round(report.size.height)}px \xB7 marks ${report.markCount} \xB7 series ${report.seriesCount} \xB7 colors ${report.colorCount}`;
17642
+ setStyle(stats, { color: report.ok ? tokens.color.positive : tokens.color.negative });
17643
+ body.appendChild(stats);
17644
+ for (const d of report.diagnostics) {
17645
+ const color = d.severity === "error" ? tokens.color.negative : d.severity === "warning" ? tokens.dark ? "#fbbf24" : "#b45309" : tokens.color.textMuted;
17646
+ body.appendChild(diagnosticRow(d.code, d.message, color, tokens));
17647
+ }
17648
+ return card;
17649
+ }
17650
+ function buildSpecPanel(spec, tokens) {
17651
+ const { card, body } = makePanel("Spec", tokens);
17652
+ const pre = document.createElement("pre");
17653
+ setStyle(pre, monoStyle(tokens));
17654
+ pre.innerHTML = highlightJson(JSON.stringify(specForDisplay(spec), null, 2), tokens);
17655
+ body.appendChild(pre);
17656
+ return card;
17657
+ }
17658
+ function buildDataPanel(spec, tokens, rowCap) {
17659
+ const { card, body } = makePanel("Data", tokens);
17660
+ const data = Array.isArray(spec.data) ? spec.data : [];
17661
+ if (data.length === 0) {
17662
+ body.textContent = "\u2014 (no data)";
17663
+ setStyle(body, { color: tokens.color.textMuted });
17664
+ return card;
17665
+ }
17666
+ const columns = Object.keys(data[0] ?? {});
17667
+ const types = new Map(columns.map((c) => [c, inferType(data, c)]));
17668
+ const reads = new Map(columns.map((c) => [c, accessor(c)]));
17669
+ const scroll = createDiv(void 0, { overflow: "auto", maxHeight: "320px" });
17670
+ const table = document.createElement("table");
17671
+ setStyle(table, {
17672
+ borderCollapse: "collapse",
17673
+ width: "100%",
17674
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
17675
+ fontSize: `${tokens.font.size.small}px`
17676
+ });
17677
+ const thead = document.createElement("thead");
17678
+ const htr = document.createElement("tr");
17679
+ for (const c of columns) {
17680
+ const th = document.createElement("th");
17681
+ setStyle(th, {
17682
+ textAlign: "left",
17683
+ padding: "3px 8px",
17684
+ borderBottom: `1px solid ${tokens.color.border}`,
17685
+ color: tokens.color.text,
17686
+ whiteSpace: "nowrap",
17687
+ position: "sticky",
17688
+ top: "0",
17689
+ background: tokens.color.surface
17690
+ });
17691
+ const name = document.createElement("div");
17692
+ name.textContent = c;
17693
+ const type = document.createElement("div");
17694
+ type.textContent = types.get(c) ?? "nominal";
17695
+ setStyle(type, { color: tokens.color.textMuted, fontWeight: "400", fontSize: `${tokens.font.size.tiny}px` });
17696
+ th.append(name, type);
17697
+ htr.appendChild(th);
17698
+ }
17699
+ thead.appendChild(htr);
17700
+ table.appendChild(thead);
17701
+ const tbody = document.createElement("tbody");
17702
+ const shown = Math.min(data.length, rowCap);
17703
+ for (let i = 0; i < shown; i++) {
17704
+ const tr = document.createElement("tr");
17705
+ for (const c of columns) {
17706
+ const td = document.createElement("td");
17707
+ setStyle(td, { padding: "3px 8px", borderBottom: `1px solid ${tokens.color.border}`, color: tokens.color.text, whiteSpace: "nowrap" });
17708
+ td.textContent = cellText2(reads.get(c)(data[i]));
17709
+ tr.appendChild(td);
17710
+ }
17711
+ tbody.appendChild(tr);
17712
+ }
17713
+ table.appendChild(tbody);
17714
+ scroll.appendChild(table);
17715
+ body.appendChild(scroll);
17716
+ const footer = createDiv(void 0, { marginTop: `${tokens.spacing.xs}px`, color: tokens.color.textMuted });
17717
+ footer.textContent = `${data.length} row${data.length === 1 ? "" : "s"} \xD7 ${columns.length} column${columns.length === 1 ? "" : "s"}` + (shown < data.length ? ` (showing first ${shown})` : "");
17718
+ body.appendChild(footer);
17719
+ return card;
17720
+ }
17721
+ function renderDebugView(surface, spec, tokens, size, deps) {
17722
+ const { sections, rowCap } = resolveConfig(spec.debug);
17723
+ const want = (s) => sections.includes(s);
17724
+ const host = createDiv("graphein-debug", {
17725
+ position: "absolute",
17726
+ inset: "0",
17727
+ width: "100%",
17728
+ height: "100%",
17729
+ boxSizing: "border-box",
17730
+ overflow: "auto",
17731
+ padding: `${tokens.spacing.md}px`,
17732
+ background: tokens.color.background,
17733
+ color: tokens.color.text,
17734
+ font: fontString(tokens.font.size.base, tokens.font.family, tokens.font.weight.normal),
17735
+ pointerEvents: "auto"
17736
+ });
17737
+ const header = createDiv(void 0, {
17738
+ display: "flex",
17739
+ alignItems: "center",
17740
+ gap: `${tokens.spacing.sm}px`,
17741
+ marginBottom: `${tokens.spacing.sm}px`
17742
+ });
17743
+ const badge = document.createElement("span");
17744
+ badge.textContent = "DEBUG";
17745
+ setStyle(badge, {
17746
+ background: tokens.color.accent,
17747
+ color: "#ffffff",
17748
+ borderRadius: `${tokens.radius.sm}px`,
17749
+ padding: "2px 6px",
17750
+ font: fontString(tokens.font.size.tiny, tokens.font.family, tokens.font.weight.bold),
17751
+ letterSpacing: "0.06em"
17752
+ });
17753
+ const typeLabel = document.createElement("span");
17754
+ typeLabel.textContent = spec.type;
17755
+ setStyle(typeLabel, { fontWeight: String(tokens.font.weight.bold) });
17756
+ header.append(badge, typeLabel);
17757
+ const title2 = specTitle(spec);
17758
+ if (title2) {
17759
+ const t = document.createElement("span");
17760
+ t.textContent = title2;
17761
+ setStyle(t, { color: tokens.color.textMuted });
17762
+ header.appendChild(t);
17763
+ }
17764
+ host.appendChild(header);
17765
+ const body = createDiv(void 0, { display: "flex", flexWrap: "wrap", gap: `${tokens.spacing.md}px`, alignItems: "flex-start" });
17766
+ const left = createDiv(void 0, { flex: "1 1 320px", minWidth: "0" });
17767
+ const right = createDiv(void 0, { flex: "1 1 320px", minWidth: "0" });
17768
+ body.append(left, right);
17769
+ host.appendChild(body);
17770
+ let preview = null;
17771
+ let previewReport = null;
17772
+ if (want("preview")) {
17773
+ const p = buildPreviewPanel(spec, tokens, deps);
17774
+ left.appendChild(p.card);
17775
+ preview = p.preview;
17776
+ previewReport = p.report;
17777
+ }
17778
+ if (want("summary")) left.appendChild(buildSummaryPanel(spec, tokens));
17779
+ if (want("validation")) left.appendChild(buildValidationPanel(spec, tokens));
17780
+ if (want("report")) left.appendChild(buildReportPanel(previewReport, tokens));
17781
+ if (want("spec")) right.appendChild(buildSpecPanel(spec, tokens));
17782
+ if (want("data")) right.appendChild(buildDataPanel(spec, tokens, rowCap));
17783
+ surface.overlay.appendChild(host);
17784
+ const report = previewReport ?? {
17785
+ type: spec.type,
17786
+ size,
17787
+ markCount: 0,
17788
+ seriesCount: 0,
17789
+ colorCount: 0,
17790
+ ok: true,
17791
+ diagnostics: []
17792
+ };
17793
+ return { preview, report };
17794
+ }
17795
+ function drawDebugCanvas(surface, spec, tokens, size) {
17796
+ const ctx = surface.marks.ctx;
17797
+ const pad3 = tokens.spacing.md;
17798
+ const lineH = tokens.font.size.small * 1.5;
17799
+ let y = pad3 + tokens.font.size.large;
17800
+ ctx.save();
17801
+ ctx.textBaseline = "alphabetic";
17802
+ ctx.fillStyle = tokens.color.text;
17803
+ ctx.font = fontString(tokens.font.size.large, tokens.font.family, tokens.font.weight.bold);
17804
+ ctx.fillText(`DEBUG \xB7 ${spec.type}`, pad3, y);
17805
+ y += lineH * 1.5;
17806
+ const result = validateSpec(spec);
17807
+ const rowCount = Array.isArray(spec.data) ? spec.data.length : 0;
17808
+ const header = [
17809
+ `rows: ${rowCount}`,
17810
+ `errors: ${result.errors.length}`,
17811
+ `warnings: ${result.warnings.length}`
17812
+ ].join(" ");
17813
+ ctx.fillStyle = result.valid ? tokens.color.textMuted : tokens.color.negative;
17814
+ ctx.font = fontString(tokens.font.size.small, tokens.font.family, tokens.font.weight.normal);
17815
+ ctx.fillText(header, pad3, y);
17816
+ y += lineH * 1.5;
17817
+ ctx.fillStyle = tokens.color.text;
17818
+ ctx.font = fontString(
17819
+ tokens.font.size.small,
17820
+ "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
17821
+ tokens.font.weight.normal
17822
+ );
17823
+ const json = JSON.stringify(specForDisplay(spec), null, 2);
17824
+ for (const line2 of json.split("\n")) {
17825
+ if (y > size.height - pad3) {
17826
+ ctx.fillStyle = tokens.color.textMuted;
17827
+ ctx.fillText("\u2026", pad3, y);
17828
+ break;
17829
+ }
17830
+ ctx.fillText(line2, pad3, y);
17831
+ y += lineH;
17832
+ }
17833
+ ctx.restore();
17834
+ }
17835
+
17451
17836
  // src/render/fonts.ts
17452
17837
  function fontFaceSet() {
17453
17838
  try {
@@ -17635,6 +18020,7 @@ function render(target, spec, options) {
17635
18020
  let interaction;
17636
18021
  let entrance;
17637
18022
  let updateTransition;
18023
+ let preview;
17638
18024
  let lastValue = null;
17639
18025
  let lastCompare = null;
17640
18026
  let lastSparkline = null;
@@ -17670,6 +18056,8 @@ function render(target, spec, options) {
17670
18056
  entrance = void 0;
17671
18057
  updateTransition?.cancel();
17672
18058
  updateTransition = void 0;
18059
+ preview?.destroy();
18060
+ preview = void 0;
17673
18061
  const tokens = resolveSketchTokens(currentSpec);
17674
18062
  const size = resolveSize(container, currentSpec);
17675
18063
  lastDrawnW = size.width;
@@ -17693,6 +18081,7 @@ function render(target, spec, options) {
17693
18081
  let interactionModel = null;
17694
18082
  let reportModel;
17695
18083
  let facetRpt;
18084
+ let debugReport;
17696
18085
  let legendSelect = null;
17697
18086
  const type = currentSpec.type;
17698
18087
  if (COUNT_UP_TYPES.has(type) || SWEEP_TYPES.has(type)) {
@@ -17700,8 +18089,15 @@ function render(target, spec, options) {
17700
18089
  lastCompare = SWEEP_TYPES.has(type) ? resolveValueRef(effectiveSpec.compare, effectiveSpec.data ?? EMPTY_DATA) : null;
17701
18090
  lastSparkline = type === "kpi" ? kpiSparklineSeries(effectiveSpec, effectiveSpec.data ?? EMPTY_DATA) : null;
17702
18091
  }
17703
- const facetLayout = isFaceted(effectiveSpec) ? buildFacetModels(effectiveSpec, tokens, size) : null;
17704
- if (facetLayout) {
18092
+ const facetLayout = !currentSpec.debug && isFaceted(effectiveSpec) ? buildFacetModels(effectiveSpec, tokens, size) : null;
18093
+ if (currentSpec.debug) {
18094
+ const result = renderDebugView(surface, effectiveSpec, tokens, size, {
18095
+ renderPreview: (host, previewSpec) => render(host, previewSpec, { inspect: false })
18096
+ });
18097
+ preview = result.preview ?? void 0;
18098
+ debugReport = result.report;
18099
+ interactionModel = null;
18100
+ } else if (facetLayout) {
17705
18101
  drawFacet(surface, effectiveSpec, facetLayout, tokens);
17706
18102
  facetRpt = facetReport(effectiveSpec, facetLayout, tokens, size);
17707
18103
  } else if (CARTESIAN_TYPES.has(type)) {
@@ -17804,7 +18200,7 @@ function render(target, spec, options) {
17804
18200
  }
17805
18201
  applyA11y(surface, currentSpec);
17806
18202
  inspector?.write(currentSpec);
17807
- lastReport = facetRpt ?? buildRenderReport({
18203
+ lastReport = debugReport ?? facetRpt ?? buildRenderReport({
17808
18204
  type,
17809
18205
  spec: currentSpec,
17810
18206
  data: filtered,
@@ -17946,6 +18342,7 @@ function render(target, spec, options) {
17946
18342
  hostListeners.clear();
17947
18343
  entrance?.cancel();
17948
18344
  updateTransition?.cancel();
18345
+ preview?.destroy();
17949
18346
  observer?.disconnect();
17950
18347
  inspector?.destroy();
17951
18348
  interaction?.destroy();
@@ -19011,6 +19408,11 @@ function renderToContext(target, spec) {
19011
19408
  const src = spec.data ?? [];
19012
19409
  const data = spec.transform?.length ? applyTransforms(spec.transform, src) : src;
19013
19410
  const effectiveSpec = data === src ? spec : { ...spec, data };
19411
+ if (spec.debug) {
19412
+ drawDebugCanvas(surface, effectiveSpec, tokens, size);
19413
+ const model = CARTESIAN_TYPES.has(type) ? buildCartesianModel(effectiveSpec, tokens, size) : void 0;
19414
+ return buildRenderReport({ type, spec, data, tokens, size, model });
19415
+ }
19014
19416
  if (isFaceted(effectiveSpec)) {
19015
19417
  const layout2 = buildFacetModels(effectiveSpec, tokens, size);
19016
19418
  if (layout2) {
@@ -19053,6 +19455,6 @@ function renderToContext(target, spec) {
19053
19455
  // src/index.ts
19054
19456
  var VERSION = "0.1.0";
19055
19457
 
19056
- export { CARTESIAN_TYPES, CHART_TYPES, CanvasLayer, DEFAULT_ENTRANCE_DURATION, DEFAULT_ROUGH_STYLE, DEFAULT_UPDATE_DURATION, DIM_ALPHA, ExpressionError, FACETABLE_TYPES2 as FACETABLE_TYPES, FUNCTION_NAMES, InteractionController, NO_PADDING, RoughPen, SKETCH_FONT_FAMILY, SKETCH_FONT_NAME, SLICER_TYPES, SPEC_ATTR, SR_ONLY_STYLE, Surface, TICK_SIZE, TIME_UNITS, TRANSFORM_KINDS, TYPE_ATTR, Tooltip, VERSION, aggregateValues, analyzeChart, animate, applyA11y, applyAggregate, applyBin, applyCalculate, applyFilter, applyFold, applyPatch, applyPick, applyTimeUnit, applyTransform, applyTransforms, arc, area, assertValidSpec, autoInsightAnnotations, backOut, bandScale, bounceOut, buildCartesianInteraction, buildCartesianModel, buildComboModel, buildDataTableFallback, buildFacetModels, buildHistogramModel, buildRenderReport, cartesianInteractionBuilders, cartesianRenderers, categorical, categoricalSchemes, chartTitleText, chartTypeLabel, checkExpression, clamp012 as clamp01, clamp255, compileExpression, compilePredicate, computeBackingSize, computeBins, computeFrame, computeSeriesInsights, contrastRatio, createDiv, createInspector, createRoughPen, createSelectionStore, cubicIn, cubicInOut, cubicOut, curveCatmullRom, curveLinear, curveMonotoneX, curveStep, curveStepAfter, curveStepBefore, customRenderers, darkTheme, decimate, defaultSpan, dependentParams, diverging, divergingColorScale, divergingSchemes, drawAnnotationLabels, drawAnnotations, drawAxesUnderlay, drawFacet, drawLegend, drawOverlay, drawTitle, drawTrendlineLabels, drawTrendlines, easings, elasticOut, ellipsize, ensureSketchFont, expoInOut, expoOut, facetReport, fillParentStyle, filterRows, fontString, formatCountUp, formatDate, formatNumber2 as formatNumber, formatValue, getDevicePixelRatio, groupBy, hashString, inferFieldType, inferFieldTypes, inferValueType, inheritInto, interpolateArray, interpolateNumber, interpolateNumberArray, interpolateObject, interpolateOklab, interpolateRgb, isBrowser, isEmptyValue, isFaceted, isParamClause, isSlicerType, keyFields, legendSwatchWidth, lightTheme, line, linear, linearRegression, linearScale, linearToSrgb, lintSpec, literalToValue, logScale, lttbRun, makeMatcher, matchesValue, measureText, monotoneXTangents, mulberry32, niceDomain, oklabToOklch, oklabToRgb, oklchToOklab, ordinalColorScale, overlayTextToCanvasCmd, paintCanvasText, paintLegendSwatch, parseColor, parseNumberFormat, pivot, placeViews, pointScale, polygonHachureLines, prefersReducedMotion, prettyDate, profileColumns, quadIn, quadInOut, quadOut, rampFromStops, readableTextColor, recommendChart, relativeLuminance, render, renderDashboard, renderToContext, repairReport, repairSpec, resolveCurve, resolveDashboardLayout, resolveDashboardSections, resolveEmphasis, resolveEntrance, resolveFilterValues, resolveInsightOptions, resolveSketch, resolveTheme, resolveUpdate, resolveValueRef, rgbToOklab, rgbaToCss, rotatePoint, roundedRect, sampleArc, sequential, sequentialColorScale, sequentialSchemes, serializeSpec, setMeasureContext, setStyle, sinInOut, slicerParamName, smartDate, specFields, srgbToLinear, stripData, summarize, summarizeChart, themes, tickIncrement, tickStep, ticks, timeScale, timeTickFormat, timeTicks, toHex, toPointer, tooltipEnabled, truncateTo, validateDashboard, validateSpec, wireViews, withAlpha, withSketchFont };
19458
+ export { CARTESIAN_TYPES, CHART_TYPES, CanvasLayer, DEFAULT_ENTRANCE_DURATION, DEFAULT_ROUGH_STYLE, DEFAULT_UPDATE_DURATION, DIM_ALPHA, ExpressionError, FACETABLE_TYPES2 as FACETABLE_TYPES, FUNCTION_NAMES, InteractionController, NO_PADDING, RoughPen, SKETCH_FONT_FAMILY, SKETCH_FONT_NAME, SLICER_TYPES, SPEC_ATTR, SR_ONLY_STYLE, Surface, TICK_SIZE, TIME_UNITS, TRANSFORM_KINDS, TYPE_ATTR, Tooltip, VERSION, aggregateValues, analyzeChart, animate, applyA11y, applyAggregate, applyBin, applyCalculate, applyFilter, applyFold, applyPatch, applyPick, applyTimeUnit, applyTransform, applyTransforms, arc, area, assertValidSpec, autoInsightAnnotations, backOut, bandScale, bounceOut, buildCartesianInteraction, buildCartesianModel, buildComboModel, buildDataTableFallback, buildFacetModels, buildHistogramModel, buildRenderReport, cartesianInteractionBuilders, cartesianRenderers, categorical, categoricalSchemes, chartTitleText, chartTypeLabel, checkExpression, clamp012 as clamp01, clamp255, compileExpression, compilePredicate, computeBackingSize, computeBins, computeFrame, computeSeriesInsights, contrastRatio, createDiv, createInspector, createRoughPen, createSelectionStore, cubicIn, cubicInOut, cubicOut, curveCatmullRom, curveLinear, curveMonotoneX, curveStep, curveStepAfter, curveStepBefore, customRenderers, darkTheme, decimate, defaultSpan, dependentParams, diverging, divergingColorScale, divergingSchemes, drawAnnotationLabels, drawAnnotations, drawAxesUnderlay, drawDebugCanvas, drawFacet, drawLegend, drawOverlay, drawTitle, drawTrendlineLabels, drawTrendlines, easings, elasticOut, ellipsize, ensureSketchFont, expoInOut, expoOut, facetReport, fillParentStyle, filterRows, fontString, formatCountUp, formatDate, formatNumber2 as formatNumber, formatValue, getDevicePixelRatio, groupBy, hashString, inferFieldType, inferFieldTypes, inferValueType, inheritInto, interpolateArray, interpolateNumber, interpolateNumberArray, interpolateObject, interpolateOklab, interpolateRgb, isBrowser, isEmptyValue, isFaceted, isParamClause, isSlicerType, keyFields, legendSwatchWidth, lightTheme, line, linear, linearRegression, linearScale, linearToSrgb, lintSpec, literalToValue, logScale, lttbRun, makeMatcher, matchesValue, measureText, monotoneXTangents, mulberry32, niceDomain, oklabToOklch, oklabToRgb, oklchToOklab, ordinalColorScale, overlayTextToCanvasCmd, paintCanvasText, paintLegendSwatch, parseColor, parseNumberFormat, pivot, placeViews, pointScale, polygonHachureLines, prefersReducedMotion, prettyDate, profileColumns, quadIn, quadInOut, quadOut, rampFromStops, readableTextColor, recommendChart, relativeLuminance, render, renderDashboard, renderDebugView, renderToContext, repairReport, repairSpec, resolveCurve, resolveDashboardLayout, resolveDashboardSections, resolveEmphasis, resolveEntrance, resolveFilterValues, resolveInsightOptions, resolveSketch, resolveTheme, resolveUpdate, resolveValueRef, rgbToOklab, rgbaToCss, rotatePoint, roundedRect, sampleArc, sequential, sequentialColorScale, sequentialSchemes, serializeSpec, setMeasureContext, setStyle, sinInOut, slicerParamName, smartDate, specFields, srgbToLinear, stripData, summarize, summarizeChart, themes, tickIncrement, tickStep, ticks, timeScale, timeTickFormat, timeTicks, toHex, toPointer, tooltipEnabled, truncateTo, validateDashboard, validateSpec, wireViews, withAlpha, withSketchFont };
19057
19459
  //# sourceMappingURL=index.js.map
19058
19460
  //# sourceMappingURL=index.js.map