pptx-glimpse 0.4.1 → 0.6.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.
Files changed (3) hide show
  1. package/dist/index.cjs +102 -27
  2. package/dist/index.js +102 -17
  3. package/package.json +4 -3
package/dist/index.cjs CHANGED
@@ -1,9 +1,7 @@
1
1
  "use strict";
2
- var __create = Object.create;
3
2
  var __defProp = Object.defineProperty;
4
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
6
  var __export = (target, all) => {
9
7
  for (var name in all)
@@ -17,14 +15,6 @@ var __copyProps = (to, from, except, desc) => {
17
15
  }
18
16
  return to;
19
17
  };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
19
 
30
20
  // src/index.ts
@@ -1580,17 +1570,20 @@ function parseXmlOrdered(xml) {
1580
1570
  }
1581
1571
 
1582
1572
  // src/png/png-converter.ts
1583
- var import_sharp = __toESM(require("sharp"), 1);
1573
+ var import_resvg_js = require("@resvg/resvg-js");
1584
1574
  async function svgToPng(svgString, options) {
1585
- const svgBuffer = Buffer.from(svgString);
1586
- let pipeline = (0, import_sharp.default)(svgBuffer);
1575
+ const resvgOptions = {};
1587
1576
  if (options?.width) {
1588
- pipeline = pipeline.resize(options.width);
1577
+ resvgOptions.fitTo = { mode: "width", value: options.width };
1589
1578
  } else if (options?.height) {
1590
- pipeline = pipeline.resize(null, options.height);
1579
+ resvgOptions.fitTo = { mode: "height", value: options.height };
1591
1580
  }
1592
- const result = await pipeline.png().toBuffer({ resolveWithObject: true });
1593
- return { png: result.data, width: result.info.width, height: result.info.height };
1581
+ const rendered = await (0, import_resvg_js.renderAsync)(svgString, resvgOptions);
1582
+ return {
1583
+ png: Buffer.from(rendered.asPng()),
1584
+ width: rendered.width,
1585
+ height: rendered.height
1586
+ };
1594
1587
  }
1595
1588
 
1596
1589
  // src/warning-logger.ts
@@ -2491,10 +2484,19 @@ function extractCategories(serList) {
2491
2484
  if (!cat) continue;
2492
2485
  const strRef = cat.strRef;
2493
2486
  const numRef = cat.numRef;
2487
+ const multiLvlStrRef = cat.multiLvlStrRef;
2494
2488
  const strCache = strRef?.strCache ?? numRef?.numCache;
2495
2489
  if (strCache?.pt) {
2496
2490
  return strCache.pt.slice().sort((a, b) => Number(a["@_idx"]) - Number(b["@_idx"])).map((pt) => String(pt.v ?? ""));
2497
2491
  }
2492
+ const multiCache = multiLvlStrRef?.multiLvlStrCache;
2493
+ if (multiCache?.lvl) {
2494
+ const lvls = Array.isArray(multiCache.lvl) ? multiCache.lvl : [multiCache.lvl];
2495
+ const firstLvl = lvls[0];
2496
+ if (firstLvl?.pt) {
2497
+ return firstLvl.pt.slice().sort((a, b) => Number(a["@_idx"]) - Number(b["@_idx"])).map((pt) => String(pt.v ?? ""));
2498
+ }
2499
+ }
2498
2500
  }
2499
2501
  return [];
2500
2502
  }
@@ -4983,12 +4985,26 @@ function renderBarChart(chart, x, y, w, h) {
4983
4985
  return "";
4984
4986
  }
4985
4987
  const isHorizontal = chart.barDirection === "bar";
4988
+ const ticks = computeNiceTicks(0, maxVal);
4989
+ const scaleMax = ticks[ticks.length - 1];
4986
4990
  parts.push(
4987
4991
  `<line x1="${round(x)}" y1="${round(y + h)}" x2="${round(x + w)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
4988
4992
  );
4989
4993
  parts.push(
4990
4994
  `<line x1="${round(x)}" y1="${round(y)}" x2="${round(x)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
4991
4995
  );
4996
+ if (isHorizontal) {
4997
+ for (const tick of ticks) {
4998
+ const ratio = tick / scaleMax;
4999
+ if (ratio < -1e-3 || ratio > 1.001) continue;
5000
+ const tickX = x + ratio * w;
5001
+ parts.push(
5002
+ `<text x="${round(tickX)}" y="${round(y + h + 15)}" text-anchor="middle" font-size="10" fill="#595959">${escapeXml(formatTickValue(tick))}</text>`
5003
+ );
5004
+ }
5005
+ } else {
5006
+ parts.push(renderValueAxisLabels(ticks, 0, scaleMax, x, y, h));
5007
+ }
4992
5008
  if (isHorizontal) {
4993
5009
  const groupHeight = h / catCount;
4994
5010
  const barHeight = groupHeight * 0.7 / series.length;
@@ -5004,7 +5020,7 @@ function renderBarChart(chart, x, y, w, h) {
5004
5020
  const color = series[s].color;
5005
5021
  for (let c = 0; c < series[s].values.length; c++) {
5006
5022
  const val = series[s].values[c];
5007
- const barW = val / maxVal * w;
5023
+ const barW = val / scaleMax * w;
5008
5024
  const barX = x;
5009
5025
  const barY = y + c * groupHeight + groupPadding + s * barHeight;
5010
5026
  parts.push(
@@ -5027,7 +5043,7 @@ function renderBarChart(chart, x, y, w, h) {
5027
5043
  const color = series[s].color;
5028
5044
  for (let c = 0; c < series[s].values.length; c++) {
5029
5045
  const val = series[s].values[c];
5030
- const barH = val / maxVal * h;
5046
+ const barH = val / scaleMax * h;
5031
5047
  const barX = x + c * groupWidth + groupPadding + s * barWidth;
5032
5048
  const barY = y + h - barH;
5033
5049
  parts.push(
@@ -5055,12 +5071,15 @@ function renderLineChart(chart, x, y, w, h) {
5055
5071
  debug("chart.line", "category count is 0");
5056
5072
  return "";
5057
5073
  }
5074
+ const ticks = computeNiceTicks(0, maxVal);
5075
+ const scaleMax = ticks[ticks.length - 1];
5058
5076
  parts.push(
5059
5077
  `<line x1="${round(x)}" y1="${round(y + h)}" x2="${round(x + w)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5060
5078
  );
5061
5079
  parts.push(
5062
5080
  `<line x1="${round(x)}" y1="${round(y)}" x2="${round(x)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5063
5081
  );
5082
+ parts.push(renderValueAxisLabels(ticks, 0, scaleMax, x, y, h));
5064
5083
  for (let c = 0; c < catCount; c++) {
5065
5084
  const label = categories[c] ?? "";
5066
5085
  const divisor = catCount > 1 ? catCount - 1 : 1;
@@ -5074,7 +5093,7 @@ function renderLineChart(chart, x, y, w, h) {
5074
5093
  const divisor = catCount > 1 ? catCount - 1 : 1;
5075
5094
  const points = series[s].values.map((val, i) => {
5076
5095
  const px = round(x + i / divisor * w);
5077
- const py = round(y + h - val / maxVal * h);
5096
+ const py = round(y + h - val / scaleMax * h);
5078
5097
  return `${px},${py}`;
5079
5098
  });
5080
5099
  parts.push(
@@ -5104,12 +5123,15 @@ function renderAreaChart(chart, x, y, w, h) {
5104
5123
  debug("chart.area", "category count is 0");
5105
5124
  return "";
5106
5125
  }
5126
+ const ticks = computeNiceTicks(0, maxVal);
5127
+ const scaleMax = ticks[ticks.length - 1];
5107
5128
  parts.push(
5108
5129
  `<line x1="${round(x)}" y1="${round(y + h)}" x2="${round(x + w)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5109
5130
  );
5110
5131
  parts.push(
5111
5132
  `<line x1="${round(x)}" y1="${round(y)}" x2="${round(x)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5112
5133
  );
5134
+ parts.push(renderValueAxisLabels(ticks, 0, scaleMax, x, y, h));
5113
5135
  for (let c = 0; c < catCount; c++) {
5114
5136
  const label = categories[c] ?? "";
5115
5137
  const divisor = catCount > 1 ? catCount - 1 : 1;
@@ -5124,7 +5146,7 @@ function renderAreaChart(chart, x, y, w, h) {
5124
5146
  const divisor = catCount > 1 ? catCount - 1 : 1;
5125
5147
  const dataPoints = series[s].values.map((val, i) => {
5126
5148
  const px = round(x + i / divisor * w);
5127
- const py = round(y + h - val / maxVal * h);
5149
+ const py = round(y + h - val / scaleMax * h);
5128
5150
  return { px, py };
5129
5151
  });
5130
5152
  const topPoints = dataPoints.map((p) => `${p.px},${p.py}`).join(" ");
@@ -5238,12 +5260,15 @@ function renderScatterChart(chart, x, y, w, h) {
5238
5260
  }
5239
5261
  if (maxX === 0) maxX = 1;
5240
5262
  if (maxY === 0) maxY = 1;
5263
+ const yTicks = computeNiceTicks(0, maxY);
5264
+ const scaleMaxY = yTicks[yTicks.length - 1];
5241
5265
  parts.push(
5242
5266
  `<line x1="${round(x)}" y1="${round(y + h)}" x2="${round(x + w)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5243
5267
  );
5244
5268
  parts.push(
5245
5269
  `<line x1="${round(x)}" y1="${round(y)}" x2="${round(x)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5246
5270
  );
5271
+ parts.push(renderValueAxisLabels(yTicks, 0, scaleMaxY, x, y, h));
5247
5272
  for (let s = 0; s < series.length; s++) {
5248
5273
  const color = series[s].color;
5249
5274
  const xVals = series[s].xValues ?? [];
@@ -5251,7 +5276,7 @@ function renderScatterChart(chart, x, y, w, h) {
5251
5276
  const xVal = xVals[i] ?? i;
5252
5277
  const yVal = series[s].values[i];
5253
5278
  const px = x + xVal / maxX * w;
5254
- const py = y + h - yVal / maxY * h;
5279
+ const py = y + h - yVal / scaleMaxY * h;
5255
5280
  parts.push(`<circle cx="${round(px)}" cy="${round(py)}" r="4" ${fillAttr(color)}/>`);
5256
5281
  }
5257
5282
  }
@@ -5278,12 +5303,15 @@ function renderBubbleChart(chart, x, y, w, h) {
5278
5303
  if (maxY === 0) maxY = 1;
5279
5304
  if (maxBubble === 0) maxBubble = 1;
5280
5305
  const maxRadius = Math.min(w, h) * 0.08;
5306
+ const yTicks = computeNiceTicks(0, maxY);
5307
+ const scaleMaxY = yTicks[yTicks.length - 1];
5281
5308
  parts.push(
5282
5309
  `<line x1="${round(x)}" y1="${round(y + h)}" x2="${round(x + w)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5283
5310
  );
5284
5311
  parts.push(
5285
5312
  `<line x1="${round(x)}" y1="${round(y)}" x2="${round(x)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5286
5313
  );
5314
+ parts.push(renderValueAxisLabels(yTicks, 0, scaleMaxY, x, y, h));
5287
5315
  for (let s = 0; s < series.length; s++) {
5288
5316
  const color = series[s].color;
5289
5317
  const xVals = series[s].xValues ?? [];
@@ -5293,7 +5321,7 @@ function renderBubbleChart(chart, x, y, w, h) {
5293
5321
  const yVal = series[s].values[i];
5294
5322
  const size = sizes[i] ?? 1;
5295
5323
  const px = x + xVal / maxX * w;
5296
- const py = y + h - yVal / maxY * h;
5324
+ const py = y + h - yVal / scaleMaxY * h;
5297
5325
  const r = Math.max(2, Math.sqrt(size / maxBubble) * maxRadius);
5298
5326
  parts.push(
5299
5327
  `<circle cx="${round(px)}" cy="${round(py)}" r="${round(r)}" ${fillAttr(color)} fill-opacity="0.6"/>`
@@ -5406,12 +5434,16 @@ function renderStockChart(chart, x, y, w, h) {
5406
5434
  debug("chart.stock", "max equals min value");
5407
5435
  return "";
5408
5436
  }
5437
+ const ticks = computeNiceTicks(minVal, maxVal);
5438
+ const scaleMin = ticks[0];
5439
+ const scaleMax = ticks[ticks.length - 1];
5409
5440
  parts.push(
5410
5441
  `<line x1="${round(x)}" y1="${round(y + h)}" x2="${round(x + w)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5411
5442
  );
5412
5443
  parts.push(
5413
5444
  `<line x1="${round(x)}" y1="${round(y)}" x2="${round(x)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5414
5445
  );
5446
+ parts.push(renderValueAxisLabels(ticks, scaleMin, scaleMax, x, y, h));
5415
5447
  for (let c = 0; c < catCount; c++) {
5416
5448
  const label = categories[c] ?? "";
5417
5449
  const labelX = x + (c + 0.5) * (w / catCount);
@@ -5419,15 +5451,15 @@ function renderStockChart(chart, x, y, w, h) {
5419
5451
  `<text x="${round(labelX)}" y="${round(y + h + 15)}" text-anchor="middle" font-size="10" fill="#595959">${escapeXml(label)}</text>`
5420
5452
  );
5421
5453
  }
5422
- const range = maxVal - minVal;
5454
+ const range = scaleMax - scaleMin;
5423
5455
  for (let c = 0; c < catCount; c++) {
5424
5456
  const cx = x + (c + 0.5) * (w / catCount);
5425
5457
  const highVal = highSeries.values[c] ?? 0;
5426
5458
  const lowVal = lowSeries.values[c] ?? 0;
5427
5459
  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;
5460
+ const highY = y + h - (highVal - scaleMin) / range * h;
5461
+ const lowY = y + h - (lowVal - scaleMin) / range * h;
5462
+ const closeY = y + h - (closeVal - scaleMin) / range * h;
5431
5463
  parts.push(
5432
5464
  `<line x1="${round(cx)}" y1="${round(highY)}" x2="${round(cx)}" y2="${round(lowY)}" stroke="#404040" stroke-width="2"/>`
5433
5465
  );
@@ -5660,6 +5692,49 @@ function getPieSliceColor(index, chart) {
5660
5692
  }
5661
5693
  return DEFAULT_SERIES_COLORS[index % DEFAULT_SERIES_COLORS.length];
5662
5694
  }
5695
+ function computeNiceTicks(minVal, maxVal, targetCount = 5) {
5696
+ const range = maxVal - minVal;
5697
+ if (range === 0) return [minVal];
5698
+ const roughStep = range / targetCount;
5699
+ const magnitude = Math.pow(10, Math.floor(Math.log10(roughStep)));
5700
+ const residual = roughStep / magnitude;
5701
+ let niceStep;
5702
+ if (residual <= 1.5) niceStep = magnitude;
5703
+ else if (residual <= 3) niceStep = 2 * magnitude;
5704
+ else if (residual <= 7) niceStep = 5 * magnitude;
5705
+ else niceStep = 10 * magnitude;
5706
+ const niceMin = Math.floor(minVal / niceStep) * niceStep;
5707
+ const niceMax = Math.ceil(maxVal / niceStep) * niceStep;
5708
+ const ticks = [];
5709
+ for (let v = niceMin; v <= niceMax + niceStep * 0.5; v += niceStep) {
5710
+ ticks.push(Math.round(v * 1e10) / 1e10);
5711
+ }
5712
+ return ticks;
5713
+ }
5714
+ function renderValueAxisLabels(ticks, scaleMin, scaleMax, x, y, h) {
5715
+ const parts = [];
5716
+ const range = scaleMax - scaleMin;
5717
+ if (range === 0) return "";
5718
+ for (const tick of ticks) {
5719
+ const ratio = (tick - scaleMin) / range;
5720
+ if (ratio < -1e-3 || ratio > 1.001) continue;
5721
+ const tickY = y + h - ratio * h;
5722
+ parts.push(
5723
+ `<text x="${round(x - 5)}" y="${round(tickY + 4)}" text-anchor="end" font-size="10" fill="#595959">${escapeXml(formatTickValue(tick))}</text>`
5724
+ );
5725
+ parts.push(
5726
+ `<line x1="${round(x - 3)}" y1="${round(tickY)}" x2="${round(x)}" y2="${round(tickY)}" stroke="#D9D9D9" stroke-width="1"/>`
5727
+ );
5728
+ }
5729
+ return parts.join("");
5730
+ }
5731
+ function formatTickValue(value) {
5732
+ if (Math.abs(value) >= 1e9) return `${round(value / 1e9)}B`;
5733
+ if (Math.abs(value) >= 1e6) return `${round(value / 1e6)}M`;
5734
+ if (Math.abs(value) >= 1e4) return `${round(value / 1e3)}K`;
5735
+ if (Number.isInteger(value)) return String(value);
5736
+ return String(round(value));
5737
+ }
5663
5738
  function getMaxValue(series) {
5664
5739
  let max = 0;
5665
5740
  for (const s of series) {
package/dist/index.js CHANGED
@@ -1535,17 +1535,20 @@ function parseXmlOrdered(xml) {
1535
1535
  }
1536
1536
 
1537
1537
  // src/png/png-converter.ts
1538
- import sharp from "sharp";
1538
+ import { renderAsync } from "@resvg/resvg-js";
1539
1539
  async function svgToPng(svgString, options) {
1540
- const svgBuffer = Buffer.from(svgString);
1541
- let pipeline = sharp(svgBuffer);
1540
+ const resvgOptions = {};
1542
1541
  if (options?.width) {
1543
- pipeline = pipeline.resize(options.width);
1542
+ resvgOptions.fitTo = { mode: "width", value: options.width };
1544
1543
  } else if (options?.height) {
1545
- pipeline = pipeline.resize(null, options.height);
1544
+ resvgOptions.fitTo = { mode: "height", value: options.height };
1546
1545
  }
1547
- const result = await pipeline.png().toBuffer({ resolveWithObject: true });
1548
- return { png: result.data, width: result.info.width, height: result.info.height };
1546
+ const rendered = await renderAsync(svgString, resvgOptions);
1547
+ return {
1548
+ png: Buffer.from(rendered.asPng()),
1549
+ width: rendered.width,
1550
+ height: rendered.height
1551
+ };
1549
1552
  }
1550
1553
 
1551
1554
  // src/warning-logger.ts
@@ -2446,10 +2449,19 @@ function extractCategories(serList) {
2446
2449
  if (!cat) continue;
2447
2450
  const strRef = cat.strRef;
2448
2451
  const numRef = cat.numRef;
2452
+ const multiLvlStrRef = cat.multiLvlStrRef;
2449
2453
  const strCache = strRef?.strCache ?? numRef?.numCache;
2450
2454
  if (strCache?.pt) {
2451
2455
  return strCache.pt.slice().sort((a, b) => Number(a["@_idx"]) - Number(b["@_idx"])).map((pt) => String(pt.v ?? ""));
2452
2456
  }
2457
+ const multiCache = multiLvlStrRef?.multiLvlStrCache;
2458
+ if (multiCache?.lvl) {
2459
+ const lvls = Array.isArray(multiCache.lvl) ? multiCache.lvl : [multiCache.lvl];
2460
+ const firstLvl = lvls[0];
2461
+ if (firstLvl?.pt) {
2462
+ return firstLvl.pt.slice().sort((a, b) => Number(a["@_idx"]) - Number(b["@_idx"])).map((pt) => String(pt.v ?? ""));
2463
+ }
2464
+ }
2453
2465
  }
2454
2466
  return [];
2455
2467
  }
@@ -4938,12 +4950,26 @@ function renderBarChart(chart, x, y, w, h) {
4938
4950
  return "";
4939
4951
  }
4940
4952
  const isHorizontal = chart.barDirection === "bar";
4953
+ const ticks = computeNiceTicks(0, maxVal);
4954
+ const scaleMax = ticks[ticks.length - 1];
4941
4955
  parts.push(
4942
4956
  `<line x1="${round(x)}" y1="${round(y + h)}" x2="${round(x + w)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
4943
4957
  );
4944
4958
  parts.push(
4945
4959
  `<line x1="${round(x)}" y1="${round(y)}" x2="${round(x)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
4946
4960
  );
4961
+ if (isHorizontal) {
4962
+ for (const tick of ticks) {
4963
+ const ratio = tick / scaleMax;
4964
+ if (ratio < -1e-3 || ratio > 1.001) continue;
4965
+ const tickX = x + ratio * w;
4966
+ parts.push(
4967
+ `<text x="${round(tickX)}" y="${round(y + h + 15)}" text-anchor="middle" font-size="10" fill="#595959">${escapeXml(formatTickValue(tick))}</text>`
4968
+ );
4969
+ }
4970
+ } else {
4971
+ parts.push(renderValueAxisLabels(ticks, 0, scaleMax, x, y, h));
4972
+ }
4947
4973
  if (isHorizontal) {
4948
4974
  const groupHeight = h / catCount;
4949
4975
  const barHeight = groupHeight * 0.7 / series.length;
@@ -4959,7 +4985,7 @@ function renderBarChart(chart, x, y, w, h) {
4959
4985
  const color = series[s].color;
4960
4986
  for (let c = 0; c < series[s].values.length; c++) {
4961
4987
  const val = series[s].values[c];
4962
- const barW = val / maxVal * w;
4988
+ const barW = val / scaleMax * w;
4963
4989
  const barX = x;
4964
4990
  const barY = y + c * groupHeight + groupPadding + s * barHeight;
4965
4991
  parts.push(
@@ -4982,7 +5008,7 @@ function renderBarChart(chart, x, y, w, h) {
4982
5008
  const color = series[s].color;
4983
5009
  for (let c = 0; c < series[s].values.length; c++) {
4984
5010
  const val = series[s].values[c];
4985
- const barH = val / maxVal * h;
5011
+ const barH = val / scaleMax * h;
4986
5012
  const barX = x + c * groupWidth + groupPadding + s * barWidth;
4987
5013
  const barY = y + h - barH;
4988
5014
  parts.push(
@@ -5010,12 +5036,15 @@ function renderLineChart(chart, x, y, w, h) {
5010
5036
  debug("chart.line", "category count is 0");
5011
5037
  return "";
5012
5038
  }
5039
+ const ticks = computeNiceTicks(0, maxVal);
5040
+ const scaleMax = ticks[ticks.length - 1];
5013
5041
  parts.push(
5014
5042
  `<line x1="${round(x)}" y1="${round(y + h)}" x2="${round(x + w)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5015
5043
  );
5016
5044
  parts.push(
5017
5045
  `<line x1="${round(x)}" y1="${round(y)}" x2="${round(x)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5018
5046
  );
5047
+ parts.push(renderValueAxisLabels(ticks, 0, scaleMax, x, y, h));
5019
5048
  for (let c = 0; c < catCount; c++) {
5020
5049
  const label = categories[c] ?? "";
5021
5050
  const divisor = catCount > 1 ? catCount - 1 : 1;
@@ -5029,7 +5058,7 @@ function renderLineChart(chart, x, y, w, h) {
5029
5058
  const divisor = catCount > 1 ? catCount - 1 : 1;
5030
5059
  const points = series[s].values.map((val, i) => {
5031
5060
  const px = round(x + i / divisor * w);
5032
- const py = round(y + h - val / maxVal * h);
5061
+ const py = round(y + h - val / scaleMax * h);
5033
5062
  return `${px},${py}`;
5034
5063
  });
5035
5064
  parts.push(
@@ -5059,12 +5088,15 @@ function renderAreaChart(chart, x, y, w, h) {
5059
5088
  debug("chart.area", "category count is 0");
5060
5089
  return "";
5061
5090
  }
5091
+ const ticks = computeNiceTicks(0, maxVal);
5092
+ const scaleMax = ticks[ticks.length - 1];
5062
5093
  parts.push(
5063
5094
  `<line x1="${round(x)}" y1="${round(y + h)}" x2="${round(x + w)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5064
5095
  );
5065
5096
  parts.push(
5066
5097
  `<line x1="${round(x)}" y1="${round(y)}" x2="${round(x)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5067
5098
  );
5099
+ parts.push(renderValueAxisLabels(ticks, 0, scaleMax, x, y, h));
5068
5100
  for (let c = 0; c < catCount; c++) {
5069
5101
  const label = categories[c] ?? "";
5070
5102
  const divisor = catCount > 1 ? catCount - 1 : 1;
@@ -5079,7 +5111,7 @@ function renderAreaChart(chart, x, y, w, h) {
5079
5111
  const divisor = catCount > 1 ? catCount - 1 : 1;
5080
5112
  const dataPoints = series[s].values.map((val, i) => {
5081
5113
  const px = round(x + i / divisor * w);
5082
- const py = round(y + h - val / maxVal * h);
5114
+ const py = round(y + h - val / scaleMax * h);
5083
5115
  return { px, py };
5084
5116
  });
5085
5117
  const topPoints = dataPoints.map((p) => `${p.px},${p.py}`).join(" ");
@@ -5193,12 +5225,15 @@ function renderScatterChart(chart, x, y, w, h) {
5193
5225
  }
5194
5226
  if (maxX === 0) maxX = 1;
5195
5227
  if (maxY === 0) maxY = 1;
5228
+ const yTicks = computeNiceTicks(0, maxY);
5229
+ const scaleMaxY = yTicks[yTicks.length - 1];
5196
5230
  parts.push(
5197
5231
  `<line x1="${round(x)}" y1="${round(y + h)}" x2="${round(x + w)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5198
5232
  );
5199
5233
  parts.push(
5200
5234
  `<line x1="${round(x)}" y1="${round(y)}" x2="${round(x)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5201
5235
  );
5236
+ parts.push(renderValueAxisLabels(yTicks, 0, scaleMaxY, x, y, h));
5202
5237
  for (let s = 0; s < series.length; s++) {
5203
5238
  const color = series[s].color;
5204
5239
  const xVals = series[s].xValues ?? [];
@@ -5206,7 +5241,7 @@ function renderScatterChart(chart, x, y, w, h) {
5206
5241
  const xVal = xVals[i] ?? i;
5207
5242
  const yVal = series[s].values[i];
5208
5243
  const px = x + xVal / maxX * w;
5209
- const py = y + h - yVal / maxY * h;
5244
+ const py = y + h - yVal / scaleMaxY * h;
5210
5245
  parts.push(`<circle cx="${round(px)}" cy="${round(py)}" r="4" ${fillAttr(color)}/>`);
5211
5246
  }
5212
5247
  }
@@ -5233,12 +5268,15 @@ function renderBubbleChart(chart, x, y, w, h) {
5233
5268
  if (maxY === 0) maxY = 1;
5234
5269
  if (maxBubble === 0) maxBubble = 1;
5235
5270
  const maxRadius = Math.min(w, h) * 0.08;
5271
+ const yTicks = computeNiceTicks(0, maxY);
5272
+ const scaleMaxY = yTicks[yTicks.length - 1];
5236
5273
  parts.push(
5237
5274
  `<line x1="${round(x)}" y1="${round(y + h)}" x2="${round(x + w)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5238
5275
  );
5239
5276
  parts.push(
5240
5277
  `<line x1="${round(x)}" y1="${round(y)}" x2="${round(x)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5241
5278
  );
5279
+ parts.push(renderValueAxisLabels(yTicks, 0, scaleMaxY, x, y, h));
5242
5280
  for (let s = 0; s < series.length; s++) {
5243
5281
  const color = series[s].color;
5244
5282
  const xVals = series[s].xValues ?? [];
@@ -5248,7 +5286,7 @@ function renderBubbleChart(chart, x, y, w, h) {
5248
5286
  const yVal = series[s].values[i];
5249
5287
  const size = sizes[i] ?? 1;
5250
5288
  const px = x + xVal / maxX * w;
5251
- const py = y + h - yVal / maxY * h;
5289
+ const py = y + h - yVal / scaleMaxY * h;
5252
5290
  const r = Math.max(2, Math.sqrt(size / maxBubble) * maxRadius);
5253
5291
  parts.push(
5254
5292
  `<circle cx="${round(px)}" cy="${round(py)}" r="${round(r)}" ${fillAttr(color)} fill-opacity="0.6"/>`
@@ -5361,12 +5399,16 @@ function renderStockChart(chart, x, y, w, h) {
5361
5399
  debug("chart.stock", "max equals min value");
5362
5400
  return "";
5363
5401
  }
5402
+ const ticks = computeNiceTicks(minVal, maxVal);
5403
+ const scaleMin = ticks[0];
5404
+ const scaleMax = ticks[ticks.length - 1];
5364
5405
  parts.push(
5365
5406
  `<line x1="${round(x)}" y1="${round(y + h)}" x2="${round(x + w)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5366
5407
  );
5367
5408
  parts.push(
5368
5409
  `<line x1="${round(x)}" y1="${round(y)}" x2="${round(x)}" y2="${round(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
5369
5410
  );
5411
+ parts.push(renderValueAxisLabels(ticks, scaleMin, scaleMax, x, y, h));
5370
5412
  for (let c = 0; c < catCount; c++) {
5371
5413
  const label = categories[c] ?? "";
5372
5414
  const labelX = x + (c + 0.5) * (w / catCount);
@@ -5374,15 +5416,15 @@ function renderStockChart(chart, x, y, w, h) {
5374
5416
  `<text x="${round(labelX)}" y="${round(y + h + 15)}" text-anchor="middle" font-size="10" fill="#595959">${escapeXml(label)}</text>`
5375
5417
  );
5376
5418
  }
5377
- const range = maxVal - minVal;
5419
+ const range = scaleMax - scaleMin;
5378
5420
  for (let c = 0; c < catCount; c++) {
5379
5421
  const cx = x + (c + 0.5) * (w / catCount);
5380
5422
  const highVal = highSeries.values[c] ?? 0;
5381
5423
  const lowVal = lowSeries.values[c] ?? 0;
5382
5424
  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;
5425
+ const highY = y + h - (highVal - scaleMin) / range * h;
5426
+ const lowY = y + h - (lowVal - scaleMin) / range * h;
5427
+ const closeY = y + h - (closeVal - scaleMin) / range * h;
5386
5428
  parts.push(
5387
5429
  `<line x1="${round(cx)}" y1="${round(highY)}" x2="${round(cx)}" y2="${round(lowY)}" stroke="#404040" stroke-width="2"/>`
5388
5430
  );
@@ -5615,6 +5657,49 @@ function getPieSliceColor(index, chart) {
5615
5657
  }
5616
5658
  return DEFAULT_SERIES_COLORS[index % DEFAULT_SERIES_COLORS.length];
5617
5659
  }
5660
+ function computeNiceTicks(minVal, maxVal, targetCount = 5) {
5661
+ const range = maxVal - minVal;
5662
+ if (range === 0) return [minVal];
5663
+ const roughStep = range / targetCount;
5664
+ const magnitude = Math.pow(10, Math.floor(Math.log10(roughStep)));
5665
+ const residual = roughStep / magnitude;
5666
+ let niceStep;
5667
+ if (residual <= 1.5) niceStep = magnitude;
5668
+ else if (residual <= 3) niceStep = 2 * magnitude;
5669
+ else if (residual <= 7) niceStep = 5 * magnitude;
5670
+ else niceStep = 10 * magnitude;
5671
+ const niceMin = Math.floor(minVal / niceStep) * niceStep;
5672
+ const niceMax = Math.ceil(maxVal / niceStep) * niceStep;
5673
+ const ticks = [];
5674
+ for (let v = niceMin; v <= niceMax + niceStep * 0.5; v += niceStep) {
5675
+ ticks.push(Math.round(v * 1e10) / 1e10);
5676
+ }
5677
+ return ticks;
5678
+ }
5679
+ function renderValueAxisLabels(ticks, scaleMin, scaleMax, x, y, h) {
5680
+ const parts = [];
5681
+ const range = scaleMax - scaleMin;
5682
+ if (range === 0) return "";
5683
+ for (const tick of ticks) {
5684
+ const ratio = (tick - scaleMin) / range;
5685
+ if (ratio < -1e-3 || ratio > 1.001) continue;
5686
+ const tickY = y + h - ratio * h;
5687
+ parts.push(
5688
+ `<text x="${round(x - 5)}" y="${round(tickY + 4)}" text-anchor="end" font-size="10" fill="#595959">${escapeXml(formatTickValue(tick))}</text>`
5689
+ );
5690
+ parts.push(
5691
+ `<line x1="${round(x - 3)}" y1="${round(tickY)}" x2="${round(x)}" y2="${round(tickY)}" stroke="#D9D9D9" stroke-width="1"/>`
5692
+ );
5693
+ }
5694
+ return parts.join("");
5695
+ }
5696
+ function formatTickValue(value) {
5697
+ if (Math.abs(value) >= 1e9) return `${round(value / 1e9)}B`;
5698
+ if (Math.abs(value) >= 1e6) return `${round(value / 1e6)}M`;
5699
+ if (Math.abs(value) >= 1e4) return `${round(value / 1e3)}K`;
5700
+ if (Number.isInteger(value)) return String(value);
5701
+ return String(round(value));
5702
+ }
5618
5703
  function getMaxValue(series) {
5619
5704
  let max = 0;
5620
5705
  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.6.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",
@@ -64,10 +64,10 @@
64
64
  "author": "hirokisakabe",
65
65
  "license": "MIT",
66
66
  "dependencies": {
67
+ "@resvg/resvg-js": "^2.6.2",
67
68
  "fast-xml-parser": "^5.3.6",
68
69
  "fflate": "^0.8.2",
69
- "opentype.js": "^1.3.4",
70
- "sharp": "^0.34.5"
70
+ "opentype.js": "^1.3.4"
71
71
  },
72
72
  "devDependencies": {
73
73
  "@changesets/cli": "^2.29.8",
@@ -81,6 +81,7 @@
81
81
  "knip": "^5.85.0",
82
82
  "pixelmatch": "^7.1.0",
83
83
  "prettier": "^3.0.0",
84
+ "sharp": "^0.34.5",
84
85
  "tsup": "^8.0.0",
85
86
  "tsx": "^4.21.0",
86
87
  "typescript": "^5.9.3",