pptx-glimpse 0.4.1 → 0.5.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.cjs CHANGED
@@ -2491,10 +2491,19 @@ function extractCategories(serList) {
2491
2491
  if (!cat) continue;
2492
2492
  const strRef = cat.strRef;
2493
2493
  const numRef = cat.numRef;
2494
+ const multiLvlStrRef = cat.multiLvlStrRef;
2494
2495
  const strCache = strRef?.strCache ?? numRef?.numCache;
2495
2496
  if (strCache?.pt) {
2496
2497
  return strCache.pt.slice().sort((a, b) => Number(a["@_idx"]) - Number(b["@_idx"])).map((pt) => String(pt.v ?? ""));
2497
2498
  }
2499
+ const multiCache = multiLvlStrRef?.multiLvlStrCache;
2500
+ if (multiCache?.lvl) {
2501
+ const lvls = Array.isArray(multiCache.lvl) ? multiCache.lvl : [multiCache.lvl];
2502
+ const firstLvl = lvls[0];
2503
+ if (firstLvl?.pt) {
2504
+ return firstLvl.pt.slice().sort((a, b) => Number(a["@_idx"]) - Number(b["@_idx"])).map((pt) => String(pt.v ?? ""));
2505
+ }
2506
+ }
2498
2507
  }
2499
2508
  return [];
2500
2509
  }
@@ -4983,12 +4992,26 @@ function renderBarChart(chart, x, y, w, h) {
4983
4992
  return "";
4984
4993
  }
4985
4994
  const isHorizontal = chart.barDirection === "bar";
4995
+ const ticks = computeNiceTicks(0, maxVal);
4996
+ const scaleMax = ticks[ticks.length - 1];
4986
4997
  parts.push(
4987
4998
  `<line x1="${round(x)}" y1="${round(y + h)}" x2="${round(x + w)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
4988
4999
  );
4989
5000
  parts.push(
4990
5001
  `<line x1="${round(x)}" y1="${round(y)}" x2="${round(x)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
4991
5002
  );
5003
+ if (isHorizontal) {
5004
+ for (const tick of ticks) {
5005
+ const ratio = tick / scaleMax;
5006
+ if (ratio < -1e-3 || ratio > 1.001) continue;
5007
+ const tickX = x + ratio * w;
5008
+ parts.push(
5009
+ `<text x="${round(tickX)}" y="${round(y + h + 15)}" text-anchor="middle" font-size="10" fill="#595959">${escapeXml(formatTickValue(tick))}</text>`
5010
+ );
5011
+ }
5012
+ } else {
5013
+ parts.push(renderValueAxisLabels(ticks, 0, scaleMax, x, y, h));
5014
+ }
4992
5015
  if (isHorizontal) {
4993
5016
  const groupHeight = h / catCount;
4994
5017
  const barHeight = groupHeight * 0.7 / series.length;
@@ -5004,7 +5027,7 @@ function renderBarChart(chart, x, y, w, h) {
5004
5027
  const color = series[s].color;
5005
5028
  for (let c = 0; c < series[s].values.length; c++) {
5006
5029
  const val = series[s].values[c];
5007
- const barW = val / maxVal * w;
5030
+ const barW = val / scaleMax * w;
5008
5031
  const barX = x;
5009
5032
  const barY = y + c * groupHeight + groupPadding + s * barHeight;
5010
5033
  parts.push(
@@ -5027,7 +5050,7 @@ function renderBarChart(chart, x, y, w, h) {
5027
5050
  const color = series[s].color;
5028
5051
  for (let c = 0; c < series[s].values.length; c++) {
5029
5052
  const val = series[s].values[c];
5030
- const barH = val / maxVal * h;
5053
+ const barH = val / scaleMax * h;
5031
5054
  const barX = x + c * groupWidth + groupPadding + s * barWidth;
5032
5055
  const barY = y + h - barH;
5033
5056
  parts.push(
@@ -5055,12 +5078,15 @@ function renderLineChart(chart, x, y, w, h) {
5055
5078
  debug("chart.line", "category count is 0");
5056
5079
  return "";
5057
5080
  }
5081
+ const ticks = computeNiceTicks(0, maxVal);
5082
+ const scaleMax = ticks[ticks.length - 1];
5058
5083
  parts.push(
5059
5084
  `<line x1="${round(x)}" y1="${round(y + h)}" x2="${round(x + w)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5060
5085
  );
5061
5086
  parts.push(
5062
5087
  `<line x1="${round(x)}" y1="${round(y)}" x2="${round(x)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5063
5088
  );
5089
+ parts.push(renderValueAxisLabels(ticks, 0, scaleMax, x, y, h));
5064
5090
  for (let c = 0; c < catCount; c++) {
5065
5091
  const label = categories[c] ?? "";
5066
5092
  const divisor = catCount > 1 ? catCount - 1 : 1;
@@ -5074,7 +5100,7 @@ function renderLineChart(chart, x, y, w, h) {
5074
5100
  const divisor = catCount > 1 ? catCount - 1 : 1;
5075
5101
  const points = series[s].values.map((val, i) => {
5076
5102
  const px = round(x + i / divisor * w);
5077
- const py = round(y + h - val / maxVal * h);
5103
+ const py = round(y + h - val / scaleMax * h);
5078
5104
  return `${px},${py}`;
5079
5105
  });
5080
5106
  parts.push(
@@ -5104,12 +5130,15 @@ function renderAreaChart(chart, x, y, w, h) {
5104
5130
  debug("chart.area", "category count is 0");
5105
5131
  return "";
5106
5132
  }
5133
+ const ticks = computeNiceTicks(0, maxVal);
5134
+ const scaleMax = ticks[ticks.length - 1];
5107
5135
  parts.push(
5108
5136
  `<line x1="${round(x)}" y1="${round(y + h)}" x2="${round(x + w)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5109
5137
  );
5110
5138
  parts.push(
5111
5139
  `<line x1="${round(x)}" y1="${round(y)}" x2="${round(x)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5112
5140
  );
5141
+ parts.push(renderValueAxisLabels(ticks, 0, scaleMax, x, y, h));
5113
5142
  for (let c = 0; c < catCount; c++) {
5114
5143
  const label = categories[c] ?? "";
5115
5144
  const divisor = catCount > 1 ? catCount - 1 : 1;
@@ -5124,7 +5153,7 @@ function renderAreaChart(chart, x, y, w, h) {
5124
5153
  const divisor = catCount > 1 ? catCount - 1 : 1;
5125
5154
  const dataPoints = series[s].values.map((val, i) => {
5126
5155
  const px = round(x + i / divisor * w);
5127
- const py = round(y + h - val / maxVal * h);
5156
+ const py = round(y + h - val / scaleMax * h);
5128
5157
  return { px, py };
5129
5158
  });
5130
5159
  const topPoints = dataPoints.map((p) => `${p.px},${p.py}`).join(" ");
@@ -5238,12 +5267,15 @@ function renderScatterChart(chart, x, y, w, h) {
5238
5267
  }
5239
5268
  if (maxX === 0) maxX = 1;
5240
5269
  if (maxY === 0) maxY = 1;
5270
+ const yTicks = computeNiceTicks(0, maxY);
5271
+ const scaleMaxY = yTicks[yTicks.length - 1];
5241
5272
  parts.push(
5242
5273
  `<line x1="${round(x)}" y1="${round(y + h)}" x2="${round(x + w)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5243
5274
  );
5244
5275
  parts.push(
5245
5276
  `<line x1="${round(x)}" y1="${round(y)}" x2="${round(x)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5246
5277
  );
5278
+ parts.push(renderValueAxisLabels(yTicks, 0, scaleMaxY, x, y, h));
5247
5279
  for (let s = 0; s < series.length; s++) {
5248
5280
  const color = series[s].color;
5249
5281
  const xVals = series[s].xValues ?? [];
@@ -5251,7 +5283,7 @@ function renderScatterChart(chart, x, y, w, h) {
5251
5283
  const xVal = xVals[i] ?? i;
5252
5284
  const yVal = series[s].values[i];
5253
5285
  const px = x + xVal / maxX * w;
5254
- const py = y + h - yVal / maxY * h;
5286
+ const py = y + h - yVal / scaleMaxY * h;
5255
5287
  parts.push(`<circle cx="${round(px)}" cy="${round(py)}" r="4" ${fillAttr(color)}/>`);
5256
5288
  }
5257
5289
  }
@@ -5278,12 +5310,15 @@ function renderBubbleChart(chart, x, y, w, h) {
5278
5310
  if (maxY === 0) maxY = 1;
5279
5311
  if (maxBubble === 0) maxBubble = 1;
5280
5312
  const maxRadius = Math.min(w, h) * 0.08;
5313
+ const yTicks = computeNiceTicks(0, maxY);
5314
+ const scaleMaxY = yTicks[yTicks.length - 1];
5281
5315
  parts.push(
5282
5316
  `<line x1="${round(x)}" y1="${round(y + h)}" x2="${round(x + w)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5283
5317
  );
5284
5318
  parts.push(
5285
5319
  `<line x1="${round(x)}" y1="${round(y)}" x2="${round(x)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5286
5320
  );
5321
+ parts.push(renderValueAxisLabels(yTicks, 0, scaleMaxY, x, y, h));
5287
5322
  for (let s = 0; s < series.length; s++) {
5288
5323
  const color = series[s].color;
5289
5324
  const xVals = series[s].xValues ?? [];
@@ -5293,7 +5328,7 @@ function renderBubbleChart(chart, x, y, w, h) {
5293
5328
  const yVal = series[s].values[i];
5294
5329
  const size = sizes[i] ?? 1;
5295
5330
  const px = x + xVal / maxX * w;
5296
- const py = y + h - yVal / maxY * h;
5331
+ const py = y + h - yVal / scaleMaxY * h;
5297
5332
  const r = Math.max(2, Math.sqrt(size / maxBubble) * maxRadius);
5298
5333
  parts.push(
5299
5334
  `<circle cx="${round(px)}" cy="${round(py)}" r="${round(r)}" ${fillAttr(color)} fill-opacity="0.6"/>`
@@ -5406,12 +5441,16 @@ function renderStockChart(chart, x, y, w, h) {
5406
5441
  debug("chart.stock", "max equals min value");
5407
5442
  return "";
5408
5443
  }
5444
+ const ticks = computeNiceTicks(minVal, maxVal);
5445
+ const scaleMin = ticks[0];
5446
+ const scaleMax = ticks[ticks.length - 1];
5409
5447
  parts.push(
5410
5448
  `<line x1="${round(x)}" y1="${round(y + h)}" x2="${round(x + w)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5411
5449
  );
5412
5450
  parts.push(
5413
5451
  `<line x1="${round(x)}" y1="${round(y)}" x2="${round(x)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5414
5452
  );
5453
+ parts.push(renderValueAxisLabels(ticks, scaleMin, scaleMax, x, y, h));
5415
5454
  for (let c = 0; c < catCount; c++) {
5416
5455
  const label = categories[c] ?? "";
5417
5456
  const labelX = x + (c + 0.5) * (w / catCount);
@@ -5419,15 +5458,15 @@ function renderStockChart(chart, x, y, w, h) {
5419
5458
  `<text x="${round(labelX)}" y="${round(y + h + 15)}" text-anchor="middle" font-size="10" fill="#595959">${escapeXml(label)}</text>`
5420
5459
  );
5421
5460
  }
5422
- const range = maxVal - minVal;
5461
+ const range = scaleMax - scaleMin;
5423
5462
  for (let c = 0; c < catCount; c++) {
5424
5463
  const cx = x + (c + 0.5) * (w / catCount);
5425
5464
  const highVal = highSeries.values[c] ?? 0;
5426
5465
  const lowVal = lowSeries.values[c] ?? 0;
5427
5466
  const closeVal = closeSeries.values[c] ?? 0;
5428
- const highY = y + h - (highVal - minVal) / range * h;
5429
- const lowY = y + h - (lowVal - minVal) / range * h;
5430
- const closeY = y + h - (closeVal - minVal) / range * h;
5467
+ const highY = y + h - (highVal - scaleMin) / range * h;
5468
+ const lowY = y + h - (lowVal - scaleMin) / range * h;
5469
+ const closeY = y + h - (closeVal - scaleMin) / range * h;
5431
5470
  parts.push(
5432
5471
  `<line x1="${round(cx)}" y1="${round(highY)}" x2="${round(cx)}" y2="${round(lowY)}" stroke="#404040" stroke-width="2"/>`
5433
5472
  );
@@ -5660,6 +5699,49 @@ function getPieSliceColor(index, chart) {
5660
5699
  }
5661
5700
  return DEFAULT_SERIES_COLORS[index % DEFAULT_SERIES_COLORS.length];
5662
5701
  }
5702
+ function computeNiceTicks(minVal, maxVal, targetCount = 5) {
5703
+ const range = maxVal - minVal;
5704
+ if (range === 0) return [minVal];
5705
+ const roughStep = range / targetCount;
5706
+ const magnitude = Math.pow(10, Math.floor(Math.log10(roughStep)));
5707
+ const residual = roughStep / magnitude;
5708
+ let niceStep;
5709
+ if (residual <= 1.5) niceStep = magnitude;
5710
+ else if (residual <= 3) niceStep = 2 * magnitude;
5711
+ else if (residual <= 7) niceStep = 5 * magnitude;
5712
+ else niceStep = 10 * magnitude;
5713
+ const niceMin = Math.floor(minVal / niceStep) * niceStep;
5714
+ const niceMax = Math.ceil(maxVal / niceStep) * niceStep;
5715
+ const ticks = [];
5716
+ for (let v = niceMin; v <= niceMax + niceStep * 0.5; v += niceStep) {
5717
+ ticks.push(Math.round(v * 1e10) / 1e10);
5718
+ }
5719
+ return ticks;
5720
+ }
5721
+ function renderValueAxisLabels(ticks, scaleMin, scaleMax, x, y, h) {
5722
+ const parts = [];
5723
+ const range = scaleMax - scaleMin;
5724
+ if (range === 0) return "";
5725
+ for (const tick of ticks) {
5726
+ const ratio = (tick - scaleMin) / range;
5727
+ if (ratio < -1e-3 || ratio > 1.001) continue;
5728
+ const tickY = y + h - ratio * h;
5729
+ parts.push(
5730
+ `<text x="${round(x - 5)}" y="${round(tickY + 4)}" text-anchor="end" font-size="10" fill="#595959">${escapeXml(formatTickValue(tick))}</text>`
5731
+ );
5732
+ parts.push(
5733
+ `<line x1="${round(x - 3)}" y1="${round(tickY)}" x2="${round(x)}" y2="${round(tickY)}" stroke="#D9D9D9" stroke-width="1"/>`
5734
+ );
5735
+ }
5736
+ return parts.join("");
5737
+ }
5738
+ function formatTickValue(value) {
5739
+ if (Math.abs(value) >= 1e9) return `${round(value / 1e9)}B`;
5740
+ if (Math.abs(value) >= 1e6) return `${round(value / 1e6)}M`;
5741
+ if (Math.abs(value) >= 1e4) return `${round(value / 1e3)}K`;
5742
+ if (Number.isInteger(value)) return String(value);
5743
+ return String(round(value));
5744
+ }
5663
5745
  function getMaxValue(series) {
5664
5746
  let max = 0;
5665
5747
  for (const s of series) {
package/dist/index.js CHANGED
@@ -2446,10 +2446,19 @@ function extractCategories(serList) {
2446
2446
  if (!cat) continue;
2447
2447
  const strRef = cat.strRef;
2448
2448
  const numRef = cat.numRef;
2449
+ const multiLvlStrRef = cat.multiLvlStrRef;
2449
2450
  const strCache = strRef?.strCache ?? numRef?.numCache;
2450
2451
  if (strCache?.pt) {
2451
2452
  return strCache.pt.slice().sort((a, b) => Number(a["@_idx"]) - Number(b["@_idx"])).map((pt) => String(pt.v ?? ""));
2452
2453
  }
2454
+ const multiCache = multiLvlStrRef?.multiLvlStrCache;
2455
+ if (multiCache?.lvl) {
2456
+ const lvls = Array.isArray(multiCache.lvl) ? multiCache.lvl : [multiCache.lvl];
2457
+ const firstLvl = lvls[0];
2458
+ if (firstLvl?.pt) {
2459
+ return firstLvl.pt.slice().sort((a, b) => Number(a["@_idx"]) - Number(b["@_idx"])).map((pt) => String(pt.v ?? ""));
2460
+ }
2461
+ }
2453
2462
  }
2454
2463
  return [];
2455
2464
  }
@@ -4938,12 +4947,26 @@ function renderBarChart(chart, x, y, w, h) {
4938
4947
  return "";
4939
4948
  }
4940
4949
  const isHorizontal = chart.barDirection === "bar";
4950
+ const ticks = computeNiceTicks(0, maxVal);
4951
+ const scaleMax = ticks[ticks.length - 1];
4941
4952
  parts.push(
4942
4953
  `<line x1="${round(x)}" y1="${round(y + h)}" x2="${round(x + w)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
4943
4954
  );
4944
4955
  parts.push(
4945
4956
  `<line x1="${round(x)}" y1="${round(y)}" x2="${round(x)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
4946
4957
  );
4958
+ if (isHorizontal) {
4959
+ for (const tick of ticks) {
4960
+ const ratio = tick / scaleMax;
4961
+ if (ratio < -1e-3 || ratio > 1.001) continue;
4962
+ const tickX = x + ratio * w;
4963
+ parts.push(
4964
+ `<text x="${round(tickX)}" y="${round(y + h + 15)}" text-anchor="middle" font-size="10" fill="#595959">${escapeXml(formatTickValue(tick))}</text>`
4965
+ );
4966
+ }
4967
+ } else {
4968
+ parts.push(renderValueAxisLabels(ticks, 0, scaleMax, x, y, h));
4969
+ }
4947
4970
  if (isHorizontal) {
4948
4971
  const groupHeight = h / catCount;
4949
4972
  const barHeight = groupHeight * 0.7 / series.length;
@@ -4959,7 +4982,7 @@ function renderBarChart(chart, x, y, w, h) {
4959
4982
  const color = series[s].color;
4960
4983
  for (let c = 0; c < series[s].values.length; c++) {
4961
4984
  const val = series[s].values[c];
4962
- const barW = val / maxVal * w;
4985
+ const barW = val / scaleMax * w;
4963
4986
  const barX = x;
4964
4987
  const barY = y + c * groupHeight + groupPadding + s * barHeight;
4965
4988
  parts.push(
@@ -4982,7 +5005,7 @@ function renderBarChart(chart, x, y, w, h) {
4982
5005
  const color = series[s].color;
4983
5006
  for (let c = 0; c < series[s].values.length; c++) {
4984
5007
  const val = series[s].values[c];
4985
- const barH = val / maxVal * h;
5008
+ const barH = val / scaleMax * h;
4986
5009
  const barX = x + c * groupWidth + groupPadding + s * barWidth;
4987
5010
  const barY = y + h - barH;
4988
5011
  parts.push(
@@ -5010,12 +5033,15 @@ function renderLineChart(chart, x, y, w, h) {
5010
5033
  debug("chart.line", "category count is 0");
5011
5034
  return "";
5012
5035
  }
5036
+ const ticks = computeNiceTicks(0, maxVal);
5037
+ const scaleMax = ticks[ticks.length - 1];
5013
5038
  parts.push(
5014
5039
  `<line x1="${round(x)}" y1="${round(y + h)}" x2="${round(x + w)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5015
5040
  );
5016
5041
  parts.push(
5017
5042
  `<line x1="${round(x)}" y1="${round(y)}" x2="${round(x)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5018
5043
  );
5044
+ parts.push(renderValueAxisLabels(ticks, 0, scaleMax, x, y, h));
5019
5045
  for (let c = 0; c < catCount; c++) {
5020
5046
  const label = categories[c] ?? "";
5021
5047
  const divisor = catCount > 1 ? catCount - 1 : 1;
@@ -5029,7 +5055,7 @@ function renderLineChart(chart, x, y, w, h) {
5029
5055
  const divisor = catCount > 1 ? catCount - 1 : 1;
5030
5056
  const points = series[s].values.map((val, i) => {
5031
5057
  const px = round(x + i / divisor * w);
5032
- const py = round(y + h - val / maxVal * h);
5058
+ const py = round(y + h - val / scaleMax * h);
5033
5059
  return `${px},${py}`;
5034
5060
  });
5035
5061
  parts.push(
@@ -5059,12 +5085,15 @@ function renderAreaChart(chart, x, y, w, h) {
5059
5085
  debug("chart.area", "category count is 0");
5060
5086
  return "";
5061
5087
  }
5088
+ const ticks = computeNiceTicks(0, maxVal);
5089
+ const scaleMax = ticks[ticks.length - 1];
5062
5090
  parts.push(
5063
5091
  `<line x1="${round(x)}" y1="${round(y + h)}" x2="${round(x + w)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5064
5092
  );
5065
5093
  parts.push(
5066
5094
  `<line x1="${round(x)}" y1="${round(y)}" x2="${round(x)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5067
5095
  );
5096
+ parts.push(renderValueAxisLabels(ticks, 0, scaleMax, x, y, h));
5068
5097
  for (let c = 0; c < catCount; c++) {
5069
5098
  const label = categories[c] ?? "";
5070
5099
  const divisor = catCount > 1 ? catCount - 1 : 1;
@@ -5079,7 +5108,7 @@ function renderAreaChart(chart, x, y, w, h) {
5079
5108
  const divisor = catCount > 1 ? catCount - 1 : 1;
5080
5109
  const dataPoints = series[s].values.map((val, i) => {
5081
5110
  const px = round(x + i / divisor * w);
5082
- const py = round(y + h - val / maxVal * h);
5111
+ const py = round(y + h - val / scaleMax * h);
5083
5112
  return { px, py };
5084
5113
  });
5085
5114
  const topPoints = dataPoints.map((p) => `${p.px},${p.py}`).join(" ");
@@ -5193,12 +5222,15 @@ function renderScatterChart(chart, x, y, w, h) {
5193
5222
  }
5194
5223
  if (maxX === 0) maxX = 1;
5195
5224
  if (maxY === 0) maxY = 1;
5225
+ const yTicks = computeNiceTicks(0, maxY);
5226
+ const scaleMaxY = yTicks[yTicks.length - 1];
5196
5227
  parts.push(
5197
5228
  `<line x1="${round(x)}" y1="${round(y + h)}" x2="${round(x + w)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5198
5229
  );
5199
5230
  parts.push(
5200
5231
  `<line x1="${round(x)}" y1="${round(y)}" x2="${round(x)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5201
5232
  );
5233
+ parts.push(renderValueAxisLabels(yTicks, 0, scaleMaxY, x, y, h));
5202
5234
  for (let s = 0; s < series.length; s++) {
5203
5235
  const color = series[s].color;
5204
5236
  const xVals = series[s].xValues ?? [];
@@ -5206,7 +5238,7 @@ function renderScatterChart(chart, x, y, w, h) {
5206
5238
  const xVal = xVals[i] ?? i;
5207
5239
  const yVal = series[s].values[i];
5208
5240
  const px = x + xVal / maxX * w;
5209
- const py = y + h - yVal / maxY * h;
5241
+ const py = y + h - yVal / scaleMaxY * h;
5210
5242
  parts.push(`<circle cx="${round(px)}" cy="${round(py)}" r="4" ${fillAttr(color)}/>`);
5211
5243
  }
5212
5244
  }
@@ -5233,12 +5265,15 @@ function renderBubbleChart(chart, x, y, w, h) {
5233
5265
  if (maxY === 0) maxY = 1;
5234
5266
  if (maxBubble === 0) maxBubble = 1;
5235
5267
  const maxRadius = Math.min(w, h) * 0.08;
5268
+ const yTicks = computeNiceTicks(0, maxY);
5269
+ const scaleMaxY = yTicks[yTicks.length - 1];
5236
5270
  parts.push(
5237
5271
  `<line x1="${round(x)}" y1="${round(y + h)}" x2="${round(x + w)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5238
5272
  );
5239
5273
  parts.push(
5240
5274
  `<line x1="${round(x)}" y1="${round(y)}" x2="${round(x)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5241
5275
  );
5276
+ parts.push(renderValueAxisLabels(yTicks, 0, scaleMaxY, x, y, h));
5242
5277
  for (let s = 0; s < series.length; s++) {
5243
5278
  const color = series[s].color;
5244
5279
  const xVals = series[s].xValues ?? [];
@@ -5248,7 +5283,7 @@ function renderBubbleChart(chart, x, y, w, h) {
5248
5283
  const yVal = series[s].values[i];
5249
5284
  const size = sizes[i] ?? 1;
5250
5285
  const px = x + xVal / maxX * w;
5251
- const py = y + h - yVal / maxY * h;
5286
+ const py = y + h - yVal / scaleMaxY * h;
5252
5287
  const r = Math.max(2, Math.sqrt(size / maxBubble) * maxRadius);
5253
5288
  parts.push(
5254
5289
  `<circle cx="${round(px)}" cy="${round(py)}" r="${round(r)}" ${fillAttr(color)} fill-opacity="0.6"/>`
@@ -5361,12 +5396,16 @@ function renderStockChart(chart, x, y, w, h) {
5361
5396
  debug("chart.stock", "max equals min value");
5362
5397
  return "";
5363
5398
  }
5399
+ const ticks = computeNiceTicks(minVal, maxVal);
5400
+ const scaleMin = ticks[0];
5401
+ const scaleMax = ticks[ticks.length - 1];
5364
5402
  parts.push(
5365
5403
  `<line x1="${round(x)}" y1="${round(y + h)}" x2="${round(x + w)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5366
5404
  );
5367
5405
  parts.push(
5368
5406
  `<line x1="${round(x)}" y1="${round(y)}" x2="${round(x)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5369
5407
  );
5408
+ parts.push(renderValueAxisLabels(ticks, scaleMin, scaleMax, x, y, h));
5370
5409
  for (let c = 0; c < catCount; c++) {
5371
5410
  const label = categories[c] ?? "";
5372
5411
  const labelX = x + (c + 0.5) * (w / catCount);
@@ -5374,15 +5413,15 @@ function renderStockChart(chart, x, y, w, h) {
5374
5413
  `<text x="${round(labelX)}" y="${round(y + h + 15)}" text-anchor="middle" font-size="10" fill="#595959">${escapeXml(label)}</text>`
5375
5414
  );
5376
5415
  }
5377
- const range = maxVal - minVal;
5416
+ const range = scaleMax - scaleMin;
5378
5417
  for (let c = 0; c < catCount; c++) {
5379
5418
  const cx = x + (c + 0.5) * (w / catCount);
5380
5419
  const highVal = highSeries.values[c] ?? 0;
5381
5420
  const lowVal = lowSeries.values[c] ?? 0;
5382
5421
  const closeVal = closeSeries.values[c] ?? 0;
5383
- const highY = y + h - (highVal - minVal) / range * h;
5384
- const lowY = y + h - (lowVal - minVal) / range * h;
5385
- const closeY = y + h - (closeVal - minVal) / range * h;
5422
+ const highY = y + h - (highVal - scaleMin) / range * h;
5423
+ const lowY = y + h - (lowVal - scaleMin) / range * h;
5424
+ const closeY = y + h - (closeVal - scaleMin) / range * h;
5386
5425
  parts.push(
5387
5426
  `<line x1="${round(cx)}" y1="${round(highY)}" x2="${round(cx)}" y2="${round(lowY)}" stroke="#404040" stroke-width="2"/>`
5388
5427
  );
@@ -5615,6 +5654,49 @@ function getPieSliceColor(index, chart) {
5615
5654
  }
5616
5655
  return DEFAULT_SERIES_COLORS[index % DEFAULT_SERIES_COLORS.length];
5617
5656
  }
5657
+ function computeNiceTicks(minVal, maxVal, targetCount = 5) {
5658
+ const range = maxVal - minVal;
5659
+ if (range === 0) return [minVal];
5660
+ const roughStep = range / targetCount;
5661
+ const magnitude = Math.pow(10, Math.floor(Math.log10(roughStep)));
5662
+ const residual = roughStep / magnitude;
5663
+ let niceStep;
5664
+ if (residual <= 1.5) niceStep = magnitude;
5665
+ else if (residual <= 3) niceStep = 2 * magnitude;
5666
+ else if (residual <= 7) niceStep = 5 * magnitude;
5667
+ else niceStep = 10 * magnitude;
5668
+ const niceMin = Math.floor(minVal / niceStep) * niceStep;
5669
+ const niceMax = Math.ceil(maxVal / niceStep) * niceStep;
5670
+ const ticks = [];
5671
+ for (let v = niceMin; v <= niceMax + niceStep * 0.5; v += niceStep) {
5672
+ ticks.push(Math.round(v * 1e10) / 1e10);
5673
+ }
5674
+ return ticks;
5675
+ }
5676
+ function renderValueAxisLabels(ticks, scaleMin, scaleMax, x, y, h) {
5677
+ const parts = [];
5678
+ const range = scaleMax - scaleMin;
5679
+ if (range === 0) return "";
5680
+ for (const tick of ticks) {
5681
+ const ratio = (tick - scaleMin) / range;
5682
+ if (ratio < -1e-3 || ratio > 1.001) continue;
5683
+ const tickY = y + h - ratio * h;
5684
+ parts.push(
5685
+ `<text x="${round(x - 5)}" y="${round(tickY + 4)}" text-anchor="end" font-size="10" fill="#595959">${escapeXml(formatTickValue(tick))}</text>`
5686
+ );
5687
+ parts.push(
5688
+ `<line x1="${round(x - 3)}" y1="${round(tickY)}" x2="${round(x)}" y2="${round(tickY)}" stroke="#D9D9D9" stroke-width="1"/>`
5689
+ );
5690
+ }
5691
+ return parts.join("");
5692
+ }
5693
+ function formatTickValue(value) {
5694
+ if (Math.abs(value) >= 1e9) return `${round(value / 1e9)}B`;
5695
+ if (Math.abs(value) >= 1e6) return `${round(value / 1e6)}M`;
5696
+ if (Math.abs(value) >= 1e4) return `${round(value / 1e3)}K`;
5697
+ if (Number.isInteger(value)) return String(value);
5698
+ return String(round(value));
5699
+ }
5618
5700
  function getMaxValue(series) {
5619
5701
  let max = 0;
5620
5702
  for (const s of series) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pptx-glimpse",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "A lightweight JavaScript library for rendering PowerPoint (.pptx) files as SVG or PNG in Node.js. No LibreOffice required.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",