@diagrammo/dgmo 0.3.1 → 0.3.2

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.cjs CHANGED
@@ -4217,19 +4217,36 @@ function resolveAxisLabels(parsed) {
4217
4217
  yLabel: parsed.ylabel ?? (isHorizontal ? void 0 : parsed.label)
4218
4218
  };
4219
4219
  }
4220
- function makeGridAxis(type, textColor, axisLineColor, splitLineColor, gridOpacity, label, data, nameGapOverride) {
4220
+ function makeGridAxis(type, textColor, axisLineColor, splitLineColor, gridOpacity, label, data, nameGapOverride, chartWidthHint) {
4221
4221
  const defaultGap = type === "value" ? 75 : 40;
4222
+ let catFontSize = 16;
4223
+ let catLabelExtras = {};
4224
+ if (type === "category" && data && data.length > 0) {
4225
+ const maxLabelLen = Math.max(...data.map((l) => l.length));
4226
+ const count = data.length;
4227
+ if (count > 10 || maxLabelLen > 20) catFontSize = 10;
4228
+ else if (count > 5 || maxLabelLen > 14) catFontSize = 11;
4229
+ else if (maxLabelLen > 8) catFontSize = 12;
4230
+ if (chartWidthHint && count > 0) {
4231
+ const availPerLabel = Math.floor(chartWidthHint * 0.85 / count);
4232
+ catLabelExtras = {
4233
+ width: availPerLabel,
4234
+ overflow: "break"
4235
+ };
4236
+ }
4237
+ }
4222
4238
  return {
4223
4239
  type,
4224
4240
  ...data && { data },
4225
4241
  axisLine: { lineStyle: { color: axisLineColor } },
4226
4242
  axisLabel: {
4227
4243
  color: textColor,
4228
- fontSize: type === "category" && data ? data.length > 10 ? 11 : data.length > 5 ? 12 : 16 : 16,
4244
+ fontSize: type === "category" && data ? catFontSize : 16,
4229
4245
  fontFamily: FONT_FAMILY,
4230
4246
  ...type === "category" && {
4231
4247
  interval: 0,
4232
- formatter: (value) => value.replace(/([a-z])([A-Z])/g, "$1\n$2").replace(/ /g, "\n")
4248
+ formatter: (value) => value.replace(/([a-z])([A-Z])/g, "$1\n$2"),
4249
+ ...catLabelExtras
4233
4250
  }
4234
4251
  },
4235
4252
  splitLine: { lineStyle: { color: splitLineColor, opacity: gridOpacity } },
@@ -4241,7 +4258,7 @@ function makeGridAxis(type, textColor, axisLineColor, splitLineColor, gridOpacit
4241
4258
  }
4242
4259
  };
4243
4260
  }
4244
- function buildEChartsOptionFromChart(parsed, palette, isDark) {
4261
+ function buildEChartsOptionFromChart(parsed, palette, isDark, chartWidth) {
4245
4262
  if (parsed.error) return {};
4246
4263
  const textColor = palette.text;
4247
4264
  const axisLineColor = palette.border;
@@ -4266,13 +4283,13 @@ function buildEChartsOptionFromChart(parsed, palette, isDark) {
4266
4283
  };
4267
4284
  switch (parsed.type) {
4268
4285
  case "bar":
4269
- return buildBarOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme);
4286
+ return buildBarOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme, chartWidth);
4270
4287
  case "bar-stacked":
4271
- return buildBarStackedOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme);
4288
+ return buildBarStackedOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme, chartWidth);
4272
4289
  case "line":
4273
- return parsed.seriesNames ? buildMultiLineOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme) : buildLineOption(parsed, palette, textColor, axisLineColor, splitLineColor, gridOpacity, titleConfig, tooltipTheme);
4290
+ return parsed.seriesNames ? buildMultiLineOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme, chartWidth) : buildLineOption(parsed, palette, textColor, axisLineColor, splitLineColor, gridOpacity, titleConfig, tooltipTheme, chartWidth);
4274
4291
  case "area":
4275
- return buildAreaOption(parsed, palette, textColor, axisLineColor, splitLineColor, gridOpacity, titleConfig, tooltipTheme);
4292
+ return buildAreaOption(parsed, palette, textColor, axisLineColor, splitLineColor, gridOpacity, titleConfig, tooltipTheme, chartWidth);
4276
4293
  case "pie":
4277
4294
  return buildPieOption(parsed, textColor, getSegmentColors(palette, parsed.data.length), titleConfig, tooltipTheme, false);
4278
4295
  case "doughnut":
@@ -4283,7 +4300,7 @@ function buildEChartsOptionFromChart(parsed, palette, isDark) {
4283
4300
  return buildPolarAreaOption(parsed, textColor, getSegmentColors(palette, parsed.data.length), titleConfig, tooltipTheme);
4284
4301
  }
4285
4302
  }
4286
- function buildBarOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme) {
4303
+ function buildBarOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme, chartWidth) {
4287
4304
  const { xLabel, yLabel } = resolveAxisLabels(parsed);
4288
4305
  const isHorizontal = parsed.orientation === "horizontal";
4289
4306
  const labels = parsed.data.map((d) => d.label);
@@ -4292,7 +4309,7 @@ function buildBarOption(parsed, textColor, axisLineColor, splitLineColor, gridOp
4292
4309
  itemStyle: { color: d.color ?? colors[i % colors.length] }
4293
4310
  }));
4294
4311
  const hCatGap = isHorizontal && yLabel ? Math.max(40, Math.max(...labels.map((l) => l.length)) * 8 + 16) : void 0;
4295
- const categoryAxis = makeGridAxis("category", textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? yLabel : xLabel, labels, hCatGap);
4312
+ const categoryAxis = makeGridAxis("category", textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? yLabel : xLabel, labels, hCatGap, !isHorizontal ? chartWidth : void 0);
4296
4313
  const valueAxis = makeGridAxis("value", textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? xLabel : yLabel);
4297
4314
  return {
4298
4315
  backgroundColor: "transparent",
@@ -4324,7 +4341,7 @@ function buildBarOption(parsed, textColor, axisLineColor, splitLineColor, gridOp
4324
4341
  ]
4325
4342
  };
4326
4343
  }
4327
- function buildLineOption(parsed, palette, textColor, axisLineColor, splitLineColor, gridOpacity, titleConfig, tooltipTheme) {
4344
+ function buildLineOption(parsed, palette, textColor, axisLineColor, splitLineColor, gridOpacity, titleConfig, tooltipTheme, chartWidth) {
4328
4345
  const { xLabel, yLabel } = resolveAxisLabels(parsed);
4329
4346
  const lineColor = parsed.color ?? parsed.seriesNameColors?.[0] ?? palette.primary;
4330
4347
  const labels = parsed.data.map((d) => d.label);
@@ -4345,7 +4362,7 @@ function buildLineOption(parsed, palette, textColor, axisLineColor, splitLineCol
4345
4362
  top: parsed.title ? "15%" : "5%",
4346
4363
  containLabel: true
4347
4364
  },
4348
- xAxis: makeGridAxis("category", textColor, axisLineColor, splitLineColor, gridOpacity, xLabel, labels),
4365
+ xAxis: makeGridAxis("category", textColor, axisLineColor, splitLineColor, gridOpacity, xLabel, labels, void 0, chartWidth),
4349
4366
  yAxis: makeGridAxis("value", textColor, axisLineColor, splitLineColor, gridOpacity, yLabel),
4350
4367
  series: [
4351
4368
  {
@@ -4363,7 +4380,7 @@ function buildLineOption(parsed, palette, textColor, axisLineColor, splitLineCol
4363
4380
  ]
4364
4381
  };
4365
4382
  }
4366
- function buildMultiLineOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme) {
4383
+ function buildMultiLineOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme, chartWidth) {
4367
4384
  const { xLabel, yLabel } = resolveAxisLabels(parsed);
4368
4385
  const seriesNames = parsed.seriesNames ?? [];
4369
4386
  const labels = parsed.data.map((d) => d.label);
@@ -4407,12 +4424,12 @@ function buildMultiLineOption(parsed, textColor, axisLineColor, splitLineColor,
4407
4424
  top: parsed.title ? "15%" : "5%",
4408
4425
  containLabel: true
4409
4426
  },
4410
- xAxis: makeGridAxis("category", textColor, axisLineColor, splitLineColor, gridOpacity, xLabel, labels),
4427
+ xAxis: makeGridAxis("category", textColor, axisLineColor, splitLineColor, gridOpacity, xLabel, labels, void 0, chartWidth),
4411
4428
  yAxis: makeGridAxis("value", textColor, axisLineColor, splitLineColor, gridOpacity, yLabel),
4412
4429
  series
4413
4430
  };
4414
4431
  }
4415
- function buildAreaOption(parsed, palette, textColor, axisLineColor, splitLineColor, gridOpacity, titleConfig, tooltipTheme) {
4432
+ function buildAreaOption(parsed, palette, textColor, axisLineColor, splitLineColor, gridOpacity, titleConfig, tooltipTheme, chartWidth) {
4416
4433
  const { xLabel, yLabel } = resolveAxisLabels(parsed);
4417
4434
  const lineColor = parsed.color ?? parsed.seriesNameColors?.[0] ?? palette.primary;
4418
4435
  const labels = parsed.data.map((d) => d.label);
@@ -4433,7 +4450,7 @@ function buildAreaOption(parsed, palette, textColor, axisLineColor, splitLineCol
4433
4450
  top: parsed.title ? "15%" : "5%",
4434
4451
  containLabel: true
4435
4452
  },
4436
- xAxis: makeGridAxis("category", textColor, axisLineColor, splitLineColor, gridOpacity, xLabel, labels),
4453
+ xAxis: makeGridAxis("category", textColor, axisLineColor, splitLineColor, gridOpacity, xLabel, labels, void 0, chartWidth),
4437
4454
  yAxis: makeGridAxis("value", textColor, axisLineColor, splitLineColor, gridOpacity, yLabel),
4438
4455
  series: [
4439
4456
  {
@@ -4593,7 +4610,7 @@ function buildPolarAreaOption(parsed, textColor, colors, titleConfig, tooltipThe
4593
4610
  ]
4594
4611
  };
4595
4612
  }
4596
- function buildBarStackedOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme) {
4613
+ function buildBarStackedOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme, chartWidth) {
4597
4614
  const { xLabel, yLabel } = resolveAxisLabels(parsed);
4598
4615
  const isHorizontal = parsed.orientation === "horizontal";
4599
4616
  const seriesNames = parsed.seriesNames ?? [];
@@ -4625,8 +4642,9 @@ function buildBarStackedOption(parsed, textColor, axisLineColor, splitLineColor,
4625
4642
  };
4626
4643
  });
4627
4644
  const hCatGap = isHorizontal && yLabel ? Math.max(40, Math.max(...labels.map((l) => l.length)) * 8 + 16) : void 0;
4628
- const categoryAxis = makeGridAxis("category", textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? yLabel : xLabel, labels, hCatGap);
4629
- const valueAxis = makeGridAxis("value", textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? xLabel : yLabel);
4645
+ const categoryAxis = makeGridAxis("category", textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? yLabel : xLabel, labels, hCatGap, !isHorizontal ? chartWidth : void 0);
4646
+ const hValueGap = isHorizontal && xLabel ? 40 : void 0;
4647
+ const valueAxis = makeGridAxis("value", textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? xLabel : yLabel, void 0, hValueGap);
4630
4648
  return {
4631
4649
  backgroundColor: "transparent",
4632
4650
  animation: false,
@@ -4663,7 +4681,7 @@ async function renderEChartsForExport(content, theme, palette, options) {
4663
4681
  if (chartType && STANDARD_CHART_TYPES.has(chartType)) {
4664
4682
  const parsed = parseChart(content, effectivePalette);
4665
4683
  if (parsed.error) return "";
4666
- option = buildEChartsOptionFromChart(parsed, effectivePalette, isDark);
4684
+ option = buildEChartsOptionFromChart(parsed, effectivePalette, isDark, ECHART_EXPORT_WIDTH);
4667
4685
  } else {
4668
4686
  const parsed = parseEChart(content, effectivePalette);
4669
4687
  if (parsed.error) return "";
@@ -13797,6 +13815,19 @@ function tokenizeFreeformText(text) {
13797
13815
  }
13798
13816
  return Array.from(counts.entries()).map(([text2, count]) => ({ text: text2, weight: count, lineNumber: 0 })).sort((a, b) => b.weight - a.weight);
13799
13817
  }
13818
+ function resolveVerticalCollisions(items, minGap) {
13819
+ if (items.length === 0) return [];
13820
+ const sorted = items.map((it, i) => ({ ...it, idx: i })).sort((a, b) => a.naturalY - b.naturalY);
13821
+ const adjustedY = new Array(items.length);
13822
+ let prevBottom = -Infinity;
13823
+ for (const item of sorted) {
13824
+ const halfH = item.height / 2;
13825
+ const top = Math.max(item.naturalY - halfH, prevBottom + minGap);
13826
+ adjustedY[item.idx] = top + halfH;
13827
+ prevBottom = top + item.height;
13828
+ }
13829
+ return adjustedY;
13830
+ }
13800
13831
  function renderSlopeChart(container, parsed, palette, isDark, onClickItem, exportDims) {
13801
13832
  d3Selection9.select(container).selectAll(":not([data-d3-tooltip])").remove();
13802
13833
  const { periods, data, title } = parsed;
@@ -13847,25 +13878,80 @@ function renderSlopeChart(container, parsed, palette, isDark, onClickItem, expor
13847
13878
  g.append("line").attr("x1", x).attr("y1", 0).attr("x2", x).attr("y2", innerHeight).attr("stroke", mutedColor).attr("stroke-width", 1).attr("stroke-dasharray", "4,4");
13848
13879
  }
13849
13880
  const lineGen = d3Shape6.line().x((_d, i) => xScale(periods[i])).y((d) => yScale(d));
13850
- data.forEach((item, idx) => {
13881
+ const seriesInfo = data.map((item, idx) => {
13851
13882
  const color = item.color ?? colors[idx % colors.length];
13852
- const seriesG = g.append("g").attr("class", "slope-series").attr("data-line-number", String(item.lineNumber));
13853
13883
  const firstVal = item.values[0];
13854
13884
  const lastVal = item.values[item.values.length - 1];
13855
13885
  const absChange = lastVal - firstVal;
13856
13886
  const pctChange = firstVal !== 0 ? absChange / firstVal * 100 : null;
13857
13887
  const sign = absChange > 0 ? "+" : "";
13858
- const pctPart = pctChange !== null ? ` (${sign}${pctChange.toFixed(1)}%)` : "";
13859
- const tipLines = [`${sign}${absChange}`];
13888
+ const tipLines = [`${sign}${parseFloat(absChange.toFixed(2))}`];
13860
13889
  if (pctChange !== null) tipLines.push(`${sign}${pctChange.toFixed(1)}%`);
13861
13890
  const tipHtml = tipLines.join("<br>");
13891
+ const lastX = xScale(periods[periods.length - 1]);
13892
+ const labelText = `${lastVal} \u2014 ${item.label}`;
13893
+ const availableWidth = rightMargin - 15;
13894
+ const maxChars = Math.floor(availableWidth / SLOPE_CHAR_WIDTH);
13895
+ let labelLineCount = 1;
13896
+ let wrappedLines = null;
13897
+ if (labelText.length > maxChars) {
13898
+ const words = labelText.split(/\s+/);
13899
+ const lines = [];
13900
+ let current = "";
13901
+ for (const word of words) {
13902
+ const test = current ? `${current} ${word}` : word;
13903
+ if (test.length > maxChars && current) {
13904
+ lines.push(current);
13905
+ current = word;
13906
+ } else {
13907
+ current = test;
13908
+ }
13909
+ }
13910
+ if (current) lines.push(current);
13911
+ labelLineCount = lines.length;
13912
+ wrappedLines = lines;
13913
+ }
13914
+ const lineHeight = SLOPE_LABEL_FONT_SIZE * 1.2;
13915
+ const labelHeight = labelLineCount === 1 ? SLOPE_LABEL_FONT_SIZE : labelLineCount * lineHeight;
13916
+ return {
13917
+ item,
13918
+ idx,
13919
+ color,
13920
+ firstVal,
13921
+ lastVal,
13922
+ tipHtml,
13923
+ lastX,
13924
+ labelText,
13925
+ maxChars,
13926
+ wrappedLines,
13927
+ labelHeight
13928
+ };
13929
+ });
13930
+ const leftLabelHeight = 20;
13931
+ const leftLabelCollisions = /* @__PURE__ */ new Map();
13932
+ for (let pi = 0; pi < periods.length - 1; pi++) {
13933
+ const entries = data.map((item) => ({
13934
+ naturalY: yScale(item.values[pi]),
13935
+ height: leftLabelHeight
13936
+ }));
13937
+ leftLabelCollisions.set(pi, resolveVerticalCollisions(entries, 4));
13938
+ }
13939
+ const rightEntries = seriesInfo.map((si) => ({
13940
+ naturalY: yScale(si.lastVal),
13941
+ height: Math.max(si.labelHeight, SLOPE_LABEL_FONT_SIZE * 1.4)
13942
+ }));
13943
+ const rightAdjustedY = resolveVerticalCollisions(rightEntries, 4);
13944
+ data.forEach((item, idx) => {
13945
+ const si = seriesInfo[idx];
13946
+ const color = si.color;
13947
+ const seriesG = g.append("g").attr("class", "slope-series").attr("data-line-number", String(item.lineNumber));
13862
13948
  seriesG.append("path").datum(item.values).attr("fill", "none").attr("stroke", color).attr("stroke-width", 2.5).attr("d", lineGen);
13863
13949
  seriesG.append("path").datum(item.values).attr("fill", "none").attr("stroke", "transparent").attr("stroke-width", 14).attr("d", lineGen).style("cursor", onClickItem ? "pointer" : "default").on(
13864
13950
  "mouseenter",
13865
- (event) => showTooltip(tooltip, tipHtml, event)
13951
+ (event) => showTooltip(tooltip, si.tipHtml, event)
13866
13952
  ).on(
13867
13953
  "mousemove",
13868
- (event) => showTooltip(tooltip, tipHtml, event)
13954
+ (event) => showTooltip(tooltip, si.tipHtml, event)
13869
13955
  ).on("mouseleave", () => hideTooltip(tooltip)).on("click", () => {
13870
13956
  if (onClickItem && item.lineNumber) onClickItem(item.lineNumber);
13871
13957
  });
@@ -13874,46 +13960,30 @@ function renderSlopeChart(container, parsed, palette, isDark, onClickItem, expor
13874
13960
  const y = yScale(val);
13875
13961
  seriesG.append("circle").attr("cx", x).attr("cy", y).attr("r", 4).attr("fill", color).attr("stroke", bgColor).attr("stroke-width", 1.5).style("cursor", onClickItem ? "pointer" : "default").on(
13876
13962
  "mouseenter",
13877
- (event) => showTooltip(tooltip, tipHtml, event)
13963
+ (event) => showTooltip(tooltip, si.tipHtml, event)
13878
13964
  ).on(
13879
13965
  "mousemove",
13880
- (event) => showTooltip(tooltip, tipHtml, event)
13966
+ (event) => showTooltip(tooltip, si.tipHtml, event)
13881
13967
  ).on("mouseleave", () => hideTooltip(tooltip)).on("click", () => {
13882
13968
  if (onClickItem && item.lineNumber) onClickItem(item.lineNumber);
13883
13969
  });
13884
13970
  const isFirst = i === 0;
13885
13971
  const isLast = i === periods.length - 1;
13886
13972
  if (!isLast) {
13887
- seriesG.append("text").attr("x", isFirst ? x - 10 : x).attr("y", y).attr("dy", "0.35em").attr("text-anchor", isFirst ? "end" : "middle").attr("fill", textColor).attr("font-size", "16px").text(val.toString());
13973
+ const adjustedY = leftLabelCollisions.get(i)[idx];
13974
+ seriesG.append("text").attr("x", isFirst ? x - 10 : x).attr("y", adjustedY).attr("dy", "0.35em").attr("text-anchor", isFirst ? "end" : "middle").attr("fill", color).attr("font-size", "16px").text(val.toString());
13888
13975
  }
13889
13976
  });
13890
- const lastX = xScale(periods[periods.length - 1]);
13891
- const lastY = yScale(lastVal);
13892
- const labelText = `${lastVal} \u2014 ${item.label}`;
13893
- const availableWidth = rightMargin - 15;
13894
- const maxChars = Math.floor(availableWidth / SLOPE_CHAR_WIDTH);
13895
- const labelEl = seriesG.append("text").attr("x", lastX + 10).attr("y", lastY).attr("text-anchor", "start").attr("fill", color).attr("font-size", `${SLOPE_LABEL_FONT_SIZE}px`).attr("font-weight", "500");
13896
- if (labelText.length <= maxChars) {
13897
- labelEl.attr("dy", "0.35em").text(labelText);
13977
+ const adjustedLastY = rightAdjustedY[idx];
13978
+ const labelEl = seriesG.append("text").attr("x", si.lastX + 10).attr("y", adjustedLastY).attr("text-anchor", "start").attr("fill", color).attr("font-size", `${SLOPE_LABEL_FONT_SIZE}px`).attr("font-weight", "500");
13979
+ if (!si.wrappedLines) {
13980
+ labelEl.attr("dy", "0.35em").text(si.labelText);
13898
13981
  } else {
13899
- const words = labelText.split(/\s+/);
13900
- const lines = [];
13901
- let current = "";
13902
- for (const word of words) {
13903
- const test = current ? `${current} ${word}` : word;
13904
- if (test.length > maxChars && current) {
13905
- lines.push(current);
13906
- current = word;
13907
- } else {
13908
- current = test;
13909
- }
13910
- }
13911
- if (current) lines.push(current);
13912
13982
  const lineHeight = SLOPE_LABEL_FONT_SIZE * 1.2;
13913
- const totalHeight = (lines.length - 1) * lineHeight;
13983
+ const totalHeight = (si.wrappedLines.length - 1) * lineHeight;
13914
13984
  const startDy = -totalHeight / 2;
13915
- lines.forEach((line7, li) => {
13916
- labelEl.append("tspan").attr("x", lastX + 10).attr(
13985
+ si.wrappedLines.forEach((line7, li) => {
13986
+ labelEl.append("tspan").attr("x", si.lastX + 10).attr(
13917
13987
  "dy",
13918
13988
  li === 0 ? `${startDy + SLOPE_LABEL_FONT_SIZE * 0.35}px` : `${lineHeight}px`
13919
13989
  ).text(line7);