pptx-angular-viewer 4.24.2 → 4.25.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 (41) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +9 -9
  3. package/ng-package.json +25 -0
  4. package/package.json +45 -20
  5. package/scripts/finalize-dist.mjs +24 -0
  6. package/scripts/inline-shared.mjs +43 -0
  7. package/tsconfig.lib.json +13 -0
  8. package/vitest-setup.ts +12 -0
  9. package/fesm2022/pptx-angular-viewer-bar-chart-3d-hit-test-B6eCwecE.mjs +0 -9855
  10. package/fesm2022/pptx-angular-viewer-bar-chart-3d-hit-test-B6eCwecE.mjs.map +0 -1
  11. package/fesm2022/pptx-angular-viewer-chart-3d-view-scene-IqColhvd.mjs +0 -1594
  12. package/fesm2022/pptx-angular-viewer-chart-3d-view-scene-IqColhvd.mjs.map +0 -1
  13. package/fesm2022/pptx-angular-viewer-chat-history-idb-7dzD77Ju.mjs +0 -73
  14. package/fesm2022/pptx-angular-viewer-chat-history-idb-7dzD77Ju.mjs.map +0 -1
  15. package/fesm2022/pptx-angular-viewer-chat-history-store-Ckeu-olx.mjs +0 -121
  16. package/fesm2022/pptx-angular-viewer-chat-history-store-Ckeu-olx.mjs.map +0 -1
  17. package/fesm2022/pptx-angular-viewer-view-overflow-BBD8nZlq.mjs +0 -1418
  18. package/fesm2022/pptx-angular-viewer-view-overflow-BBD8nZlq.mjs.map +0 -1
  19. package/fesm2022/pptx-angular-viewer-view-scene-htTUkvvA.mjs +0 -836
  20. package/fesm2022/pptx-angular-viewer-view-scene-htTUkvvA.mjs.map +0 -1
  21. package/fesm2022/pptx-angular-viewer.mjs +0 -169787
  22. package/fesm2022/pptx-angular-viewer.mjs.map +0 -1
  23. package/i18n/de.cjs +0 -4165
  24. package/i18n/de.d.cts +0 -3
  25. package/i18n/de.d.mts +0 -3
  26. package/i18n/de.mjs +0 -4138
  27. package/i18n/es.cjs +0 -4165
  28. package/i18n/es.d.cts +0 -3
  29. package/i18n/es.d.mts +0 -3
  30. package/i18n/es.mjs +0 -4138
  31. package/i18n/fr.cjs +0 -4165
  32. package/i18n/fr.d.cts +0 -3
  33. package/i18n/fr.d.mts +0 -3
  34. package/i18n/fr.mjs +0 -4138
  35. package/i18n/zh-CN.cjs +0 -4198
  36. package/i18n/zh-CN.d.cts +0 -3
  37. package/i18n/zh-CN.d.mts +0 -3
  38. package/i18n/zh-CN.mjs +0 -4171
  39. package/pptx-angular-viewer.css +0 -2
  40. package/types/pptx-angular-viewer.d.ts +0 -59990
  41. package/types/pptx-angular-viewer.d.ts.map +0 -1
@@ -1,1594 +0,0 @@
1
- import { I as resolvePieSliceShareValue, N as roundDragValue, Rt as DEFAULT_PALETTE, Yt as shade, _ as renderChartLineSvg, a as buildPerspCamera, g as appendChartLegendSvg, h as obliqueBarTaper, i as widenPieViewModel, l as perspScreenToPlaneY, n as computePieChartLayout, r as pieRimPoint, s as perspBoxMatrix, t as buildBarChart3DHoverTooltip, u as perspToScreen, v as renderChartTextSvg, w as isChartInteractionArmed, y as createSvgEl } from "./pptx-angular-viewer-bar-chart-3d-hit-test-B6eCwecE.mjs";
2
- const FACE_SHADE_AMOUNT = {
3
- front: 0,
4
- top: .25,
5
- right: .36,
6
- left: .36,
7
- back: .36,
8
- bottom: .5
9
- };
10
- function shadeChart3DFace(baseColor, face) {
11
- const amount = FACE_SHADE_AMOUNT[face];
12
- return amount === 0 ? baseColor : shade(baseColor, amount);
13
- }
14
- function buildChart3DBoxFaceColors(baseColor) {
15
- return [
16
- shadeChart3DFace(baseColor, "right"),
17
- shadeChart3DFace(baseColor, "left"),
18
- shadeChart3DFace(baseColor, "top"),
19
- shadeChart3DFace(baseColor, "bottom"),
20
- shadeChart3DFace(baseColor, "front"),
21
- shadeChart3DFace(baseColor, "back")
22
- ];
23
- }
24
- const NORMAL_SHADE = {
25
- box: [
26
- .64,
27
- .75,
28
- 1
29
- ],
30
- cylinder: [
31
- .62,
32
- .75,
33
- 1.07
34
- ],
35
- cone: [
36
- .7,
37
- .75,
38
- 1.16
39
- ],
40
- pyramid: [
41
- .68,
42
- .75,
43
- 1.03
44
- ]
45
- };
46
- const UNDERSIDE_SHADE = .5;
47
- function shadeFamily(shape) {
48
- switch (shape) {
49
- case "cylinder": return "cylinder";
50
- case "cone":
51
- case "coneToMax": return "cone";
52
- case "pyramid":
53
- case "pyramidToMax": return "pyramid";
54
- default: return "box";
55
- }
56
- }
57
- function chart3DNormalShade(shape, nx, ny, nz) {
58
- const [cx, cy, cz] = NORMAL_SHADE[shadeFamily(shape)];
59
- return cx * nx * nx + (ny >= 0 ? cy : UNDERSIDE_SHADE) * ny * ny + cz * nz * nz;
60
- }
61
- const ROUND_SEGMENTS = 48;
62
- function isShapedObliqueBar(bar) {
63
- return bar.shape !== "box";
64
- }
65
- function obliqueShapeKey(bar, horizontal) {
66
- return `${bar.shape}:${bar.taper.bottom.toFixed(4)}:${bar.taper.top.toFixed(4)}:${horizontal ? "h" : "v"}`;
67
- }
68
- function buildObliqueShapeGeometry(three, bar, horizontal) {
69
- const pointed = bar.shape === "pyramid" || bar.shape === "pyramidToMax";
70
- let geometry;
71
- if (pointed) {
72
- const r = Math.SQRT1_2;
73
- const faceted = new three.CylinderGeometry(r * bar.taper.top, r * bar.taper.bottom, 1, 4).rotateY(Math.PI / 4);
74
- geometry = faceted.toNonIndexed();
75
- faceted.dispose();
76
- geometry.computeVertexNormals();
77
- } else geometry = new three.CylinderGeometry(.5 * bar.taper.top, .5 * bar.taper.bottom, 1, ROUND_SEGMENTS);
78
- if (horizontal) geometry.rotateZ(-Math.PI / 2);
79
- return geometry;
80
- }
81
- function shadeObliqueShapeGeometry(three, geometry, bar, scale, viewYaw) {
82
- const round = bar.shape !== "pyramid" && bar.shape !== "pyramidToMax";
83
- const cos = Math.cos(round ? viewYaw : 0);
84
- const sin = Math.sin(round ? viewYaw : 0);
85
- const normals = geometry.getAttribute("normal");
86
- const count = normals.count;
87
- let colors = geometry.getAttribute("color");
88
- if (!colors || colors.count !== count) {
89
- colors = new three.Float32BufferAttribute(new Float32Array(count * 3), 3);
90
- geometry.setAttribute("color", colors);
91
- }
92
- const srgb = new three.Color(bar.color).getRGB(new three.Color(), three.SRGBColorSpace);
93
- const out = new three.Color();
94
- for (let i = 0; i < count; i++) {
95
- let nx = normals.getX(i) / Math.max(scale.x, 1e-6);
96
- let ny = normals.getY(i) / Math.max(scale.y, 1e-6);
97
- let nz = normals.getZ(i) / Math.max(scale.z, 1e-6);
98
- const len = Math.hypot(nx, ny, nz) || 1;
99
- nx /= len;
100
- ny /= len;
101
- nz /= len;
102
- const f = chart3DNormalShade(bar.shape, nx * cos - nz * sin, ny, nx * sin + nz * cos);
103
- out.setRGB(Math.min(1, srgb.r * f), Math.min(1, srgb.g * f), Math.min(1, srgb.b * f), three.SRGBColorSpace);
104
- colors.setXYZ(i, out.r, out.g, out.b);
105
- }
106
- colors.needsUpdate = true;
107
- }
108
- const GRIDLINE_COLOR$1 = 14277081;
109
- function obliqueToScene(layout, svgWidth, svgHeight, p) {
110
- return [
111
- layout.origin.x + p[0] - svgWidth / 2,
112
- svgHeight / 2 - layout.origin.y + p[1],
113
- -p[2]
114
- ];
115
- }
116
- function placeObliqueBarMesh(three, mesh, layout, svgWidth, svgHeight, bar) {
117
- mesh.position.set(...obliqueToScene(layout, svgWidth, svgHeight, [
118
- bar.x + bar.w / 2,
119
- bar.y + bar.h / 2,
120
- bar.z + bar.d / 2
121
- ]));
122
- mesh.scale.set(Math.max(bar.w, .001), Math.max(bar.h, .001), bar.d);
123
- if (!isShapedObliqueBar(bar)) return;
124
- const key = obliqueShapeKey(bar, layout.horizontal);
125
- const data = mesh.userData;
126
- if (data.shapeKey !== key) {
127
- const previous = mesh.geometry;
128
- mesh.geometry = buildObliqueShapeGeometry(three, bar, layout.horizontal);
129
- previous.dispose();
130
- data.shapeKey = key;
131
- }
132
- shadeObliqueShapeGeometry(three, mesh.geometry, bar, mesh.scale, Math.atan(layout.shear.x));
133
- }
134
- function buildChart3DBarMeshes(three, layout, svgWidth, svgHeight) {
135
- const group = new three.Group();
136
- const meshes = [];
137
- const lineGeometries = [];
138
- const materials = [];
139
- const points = [];
140
- for (const line of layout.gridlines) points.push(...obliqueToScene(layout, svgWidth, svgHeight, line.from), ...obliqueToScene(layout, svgWidth, svgHeight, line.to));
141
- if (points.length > 0) {
142
- const lineGeometry = new three.BufferGeometry();
143
- lineGeometry.setAttribute("position", new three.Float32BufferAttribute(points, 3));
144
- const lineMaterial = new three.LineBasicMaterial({ color: GRIDLINE_COLOR$1 });
145
- lineGeometries.push(lineGeometry);
146
- materials.push(lineMaterial);
147
- group.add(new three.LineSegments(lineGeometry, lineMaterial));
148
- }
149
- for (const bar of layout.bars) {
150
- let mesh;
151
- if (isShapedObliqueBar(bar)) {
152
- const material = new three.MeshBasicMaterial({ vertexColors: true });
153
- materials.push(material);
154
- mesh = new three.Mesh(new three.BufferGeometry(), material);
155
- } else {
156
- const faceMaterials = buildChart3DBoxFaceColors(bar.color).map((color) => new three.MeshBasicMaterial({ color }));
157
- materials.push(...faceMaterials);
158
- mesh = new three.Mesh(new three.BoxGeometry(1, 1, 1), faceMaterials);
159
- }
160
- mesh.userData = {
161
- seriesIndex: bar.seriesIndex,
162
- categoryIndex: bar.categoryIndex
163
- };
164
- placeObliqueBarMesh(three, mesh, layout, svgWidth, svgHeight, bar);
165
- group.add(mesh);
166
- meshes.push(mesh);
167
- }
168
- return {
169
- group,
170
- meshes,
171
- dispose: () => {
172
- for (const geometry of [...lineGeometries, ...meshes.map((m) => m.geometry)]) geometry.dispose();
173
- for (const material of materials) material.dispose();
174
- }
175
- };
176
- }
177
- function renderChart3DChromeOverlaySvg(doc, source, box) {
178
- const vm = box ? withBoxChromePositions(source) : source;
179
- const svg = createSvgEl(doc, "svg", {
180
- class: "pptxv-chart-3d-chrome",
181
- viewBox: `0 0 ${vm.svgWidth} ${vm.svgHeight}`,
182
- preserveAspectRatio: "none"
183
- });
184
- svg.style.setProperty("width", "100%");
185
- svg.style.setProperty("height", "100%");
186
- svg.style.setProperty("display", "block");
187
- svg.style.setProperty("position", "absolute");
188
- svg.style.setProperty("inset", "0");
189
- svg.style.setProperty("pointer-events", "none");
190
- if (vm.areaFill) svg.appendChild(createSvgEl(doc, "rect", {
191
- x: 0,
192
- y: 0,
193
- width: vm.svgWidth,
194
- height: vm.svgHeight,
195
- rx: vm.areaRadius,
196
- fill: vm.areaFill
197
- }));
198
- if (vm.title) {
199
- const title = createSvgEl(doc, "text", {
200
- x: vm.titleX,
201
- y: vm.titleY,
202
- "text-anchor": "middle",
203
- "font-size": vm.titleStyle?.fontSize ?? 12,
204
- "font-weight": vm.titleStyle?.fontWeight ?? 600,
205
- "font-family": vm.titleStyle?.fontFamily,
206
- fill: vm.titleStyle?.fill ?? "#1e293b",
207
- "data-chart-part": "title"
208
- });
209
- if (vm.titleRunSpans && vm.titleRunSpans.length > 0) for (const run of vm.titleRunSpans) {
210
- const tspan = createSvgEl(doc, "tspan", {
211
- "font-size": run.fontSize,
212
- "font-weight": run.fontWeight,
213
- "font-style": run.fontStyle,
214
- "font-family": run.fontFamily,
215
- fill: run.fill
216
- });
217
- tspan.textContent = run.text;
218
- title.appendChild(tspan);
219
- }
220
- else title.textContent = vm.title;
221
- svg.appendChild(title);
222
- }
223
- if (box) {
224
- appendBoxLabels(doc, svg, vm, box.labels);
225
- const legend = box.legend ?? vm.legend.map((item) => ({
226
- ...item,
227
- lineSwatch: void 0
228
- }));
229
- const legendVm = {
230
- ...vm,
231
- legend: box.reverseLegend ? legend.reverse() : legend
232
- };
233
- appendChartLegendSvg(doc, svg, legendVm);
234
- return svg;
235
- }
236
- for (const gl of vm.gridlines) svg.appendChild(renderChartLineSvg(doc, gl));
237
- for (const gl of vm.secondaryGridlines ?? []) svg.appendChild(renderChartLineSvg(doc, gl));
238
- for (const lbl of vm.axisLabels) svg.appendChild(renderChartTextSvg(doc, lbl));
239
- for (const lbl of vm.secondaryAxisLabels ?? []) svg.appendChild(renderChartTextSvg(doc, lbl));
240
- if (vm.zeroLine) svg.appendChild(renderChartLineSvg(doc, vm.zeroLine));
241
- for (const lbl of vm.categoryLabels) svg.appendChild(renderChartTextSvg(doc, lbl));
242
- appendChartLegendSvg(doc, svg, vm);
243
- return svg;
244
- }
245
- const PT = 4 / 3;
246
- const OBLIQUE_TITLE_BASELINE = 24 * PT;
247
- const OBLIQUE_LEGEND_BASELINE = 14 * PT;
248
- function withBoxChromePositions(vm) {
249
- const legendBottom = (vm.legendY ?? vm.svgHeight - 8) >= vm.svgHeight / 2;
250
- return {
251
- ...vm,
252
- titleY: OBLIQUE_TITLE_BASELINE,
253
- legendY: legendBottom ? vm.svgHeight - OBLIQUE_LEGEND_BASELINE - 3 : vm.legendY
254
- };
255
- }
256
- function appendBoxLabels(doc, svg, vm, labels) {
257
- const style = vm.axisLabels[0];
258
- for (const label of labels) svg.appendChild(renderChartTextSvg(doc, {
259
- kind: "text",
260
- x: label.x,
261
- y: label.y,
262
- text: label.text,
263
- fontSize: label.fontSize,
264
- fill: style?.fill ?? "#595959",
265
- fontFamily: style?.fontFamily,
266
- textAnchor: label.anchor,
267
- dominantBaseline: label.baseline ?? "central"
268
- }));
269
- }
270
- function partOf(point) {
271
- return point ? {
272
- role: "dataPoint",
273
- seriesIndex: point.seriesIndex,
274
- pointIndex: point.pointIndex
275
- } : null;
276
- }
277
- function attachChart3DMarkInteraction(options) {
278
- const { ctx, camera, adapter, chartData } = options;
279
- const three = ctx.three;
280
- const target = ctx.eventTarget;
281
- const raycaster = new three.Raycaster();
282
- const far = new three.Vector3();
283
- let interactive = ctx.interactive;
284
- let press = null;
285
- let hoveredTooltip;
286
- function pointAt(clientX, clientY) {
287
- const rect = target.getBoundingClientRect();
288
- if (rect.width <= 0 || rect.height <= 0) return null;
289
- options.scene.updateMatrixWorld();
290
- camera.updateMatrixWorld();
291
- const x = (clientX - rect.left) / rect.width * 2 - 1;
292
- const y = -((clientY - rect.top) / rect.height) * 2 + 1;
293
- raycaster.ray.origin.set(x, y, -1).unproject(camera);
294
- far.set(x, y, 1).unproject(camera);
295
- raycaster.ray.direction.copy(far).sub(raycaster.ray.origin).normalize();
296
- const hit = raycaster.intersectObjects(adapter.targets, false)[0];
297
- return hit ? adapter.pointAt(hit) : null;
298
- }
299
- const onPointerMove = (event) => {
300
- if (press?.drag) {
301
- const dx = event.clientX - press.startX;
302
- const dy = event.clientY - press.startY;
303
- if (!press.drag.moved && Math.hypot(dx, dy) < 3) return;
304
- press.drag.moved = true;
305
- const k = press.chartPxPerClientPx;
306
- const rect = target.getBoundingClientRect();
307
- const value = press.drag.session.preview(dx * k, dy * k, {
308
- x: (event.clientX - rect.left) * k,
309
- y: (event.clientY - rect.top) * k
310
- });
311
- press.lastValue = value;
312
- ctx.requestRender();
313
- const part = partOf(press.point);
314
- if (part) ctx.emit({
315
- type: "drag",
316
- detail: {
317
- part,
318
- value,
319
- phase: "move"
320
- }
321
- });
322
- return;
323
- }
324
- const point = pointAt(event.clientX, event.clientY);
325
- const value = point ? adapter.valueOf(point) : void 0;
326
- const tooltip = point && value !== void 0 ? buildBarChart3DHoverTooltip({
327
- seriesIndex: point.seriesIndex,
328
- categoryIndex: point.pointIndex,
329
- value
330
- }, {
331
- categoryLabels: options.categoryLabels,
332
- seriesNames: options.seriesNames,
333
- numberFormats: chartData.series.map((s) => s.numberFormat)
334
- }) : void 0;
335
- if (tooltip !== hoveredTooltip) {
336
- hoveredTooltip = tooltip;
337
- target.title = tooltip ?? "";
338
- }
339
- };
340
- const onPointerDown = (event) => {
341
- if (!interactive) return;
342
- const point = pointAt(event.clientX, event.clientY);
343
- const armed = point !== null && isChartInteractionArmed(target);
344
- if (armed) event.stopPropagation();
345
- const session = armed && point ? adapter.beginDrag(point) : null;
346
- if (session) {
347
- event.preventDefault();
348
- target.setPointerCapture?.(event.pointerId);
349
- }
350
- const rect = target.getBoundingClientRect();
351
- press = {
352
- point,
353
- drag: session ? {
354
- session,
355
- moved: false
356
- } : null,
357
- startX: event.clientX,
358
- startY: event.clientY,
359
- chartPxPerClientPx: rect.height > 0 ? options.svgHeight / rect.height : 1,
360
- lastValue: null
361
- };
362
- };
363
- const onPointerUp = (event) => {
364
- const current = press;
365
- press = null;
366
- if (!current || !interactive) return;
367
- const part = partOf(current.point);
368
- if (current.drag?.moved && current.lastValue !== null && part) {
369
- ctx.emit({
370
- type: "drag",
371
- detail: {
372
- part,
373
- value: current.lastValue,
374
- phase: "commit"
375
- }
376
- });
377
- return;
378
- }
379
- if (Math.hypot(event.clientX - current.startX, event.clientY - current.startY) < 3) ctx.emit({
380
- type: "select",
381
- part
382
- });
383
- };
384
- const onPointerLeave = () => {
385
- if (hoveredTooltip !== void 0) {
386
- hoveredTooltip = void 0;
387
- target.title = "";
388
- }
389
- };
390
- target.addEventListener("pointermove", onPointerMove);
391
- target.addEventListener("pointerdown", onPointerDown);
392
- target.addEventListener("pointerup", onPointerUp);
393
- target.addEventListener("pointerleave", onPointerLeave);
394
- return {
395
- setInteractive(on) {
396
- interactive = on;
397
- if (!on) press = null;
398
- },
399
- setSelectedPart(part) {
400
- adapter.highlight(part?.role === "dataPoint" && part.pointIndex !== void 0 ? {
401
- seriesIndex: part.seriesIndex,
402
- pointIndex: part.pointIndex
403
- } : null);
404
- ctx.requestRender();
405
- },
406
- dispose() {
407
- target.removeEventListener("pointermove", onPointerMove);
408
- target.removeEventListener("pointerdown", onPointerDown);
409
- target.removeEventListener("pointerup", onPointerUp);
410
- target.removeEventListener("pointerleave", onPointerLeave);
411
- adapter.dispose?.();
412
- }
413
- };
414
- }
415
- function isObliqueBarDraggable(layout) {
416
- return layout.grouping === "clustered" || layout.grouping === "standard";
417
- }
418
- function obliqueDragValue(layout, startValue, dx, dy) {
419
- const raw = startValue + (layout.horizontal ? dx : -dy) / layout.valueScale;
420
- const span = layout.range.max - layout.range.min;
421
- return roundDragValue(raw, {
422
- min: layout.range.min,
423
- max: layout.range.max,
424
- span
425
- });
426
- }
427
- function obliqueDraggedExtent(layout, value) {
428
- const { min, max } = layout.range;
429
- const base = Math.min(Math.max(0, min), max);
430
- const clamp = (v) => Math.min(Math.max(v, min), max);
431
- const lo = (clamp(Math.min(base, value)) - min) * layout.valueScale;
432
- return {
433
- from: lo,
434
- length: (clamp(Math.max(base, value)) - min) * layout.valueScale - lo
435
- };
436
- }
437
- function obliqueBarAtValue(layout, bar, value) {
438
- const { from, length } = obliqueDraggedExtent(layout, value);
439
- const extent = (layout.range.max - layout.range.min) * layout.valueScale;
440
- const taper = obliqueBarTaper(bar.shape, from, from + length, extent, value);
441
- return layout.horizontal ? {
442
- ...bar,
443
- x: from,
444
- w: length,
445
- value,
446
- taper
447
- } : {
448
- ...bar,
449
- y: from,
450
- h: length,
451
- value,
452
- taper
453
- };
454
- }
455
- const SELECTED_OUTLINE_COLOR = 2450411;
456
- function attachObliqueBarInteraction(options) {
457
- const { ctx, meshes, layout } = options;
458
- const bars = layout.bars;
459
- const three = ctx.three;
460
- const outline = new three.LineSegments(new three.EdgesGeometry(new three.BoxGeometry(1, 1, 1)), new three.LineBasicMaterial({ color: SELECTED_OUTLINE_COLOR }));
461
- outline.visible = false;
462
- options.scene.add(outline);
463
- let selected = -1;
464
- const indexOf = (point) => bars.findIndex((b) => b.seriesIndex === point.seriesIndex && b.categoryIndex === point.pointIndex);
465
- function syncOutline() {
466
- outline.visible = selected >= 0;
467
- if (selected >= 0) {
468
- outline.position.copy(meshes[selected].position);
469
- outline.scale.copy(meshes[selected].scale);
470
- }
471
- }
472
- return attachChart3DMarkInteraction({
473
- ctx,
474
- camera: options.camera,
475
- scene: options.scene,
476
- svgHeight: options.svgHeight,
477
- chartData: options.chartData,
478
- categoryLabels: options.categoryLabels,
479
- seriesNames: options.seriesNames,
480
- adapter: {
481
- targets: meshes,
482
- pointAt(hit) {
483
- const data = hit.object.userData;
484
- if (data.seriesIndex === void 0 || data.categoryIndex === void 0) return null;
485
- return {
486
- seriesIndex: data.seriesIndex,
487
- pointIndex: data.categoryIndex
488
- };
489
- },
490
- valueOf(point) {
491
- return bars[indexOf(point)]?.value;
492
- },
493
- beginDrag(point) {
494
- const index = indexOf(point);
495
- if (index < 0 || !isObliqueBarDraggable(layout)) return null;
496
- const start = bars[index];
497
- return { preview(dx, dy) {
498
- const value = obliqueDragValue(layout, start.value, dx, dy);
499
- placeObliqueBarMesh(three, meshes[index], layout, options.svgWidth, options.svgHeight, obliqueBarAtValue(layout, start, value));
500
- syncOutline();
501
- return value;
502
- } };
503
- },
504
- highlight(point) {
505
- selected = point ? indexOf(point) : -1;
506
- syncOutline();
507
- },
508
- dispose() {
509
- outline.geometry.dispose();
510
- outline.material.dispose();
511
- outline.removeFromParent();
512
- }
513
- }
514
- });
515
- }
516
- const DEFAULT_GAP_WIDTH = 150;
517
- function valueY(layout, v) {
518
- return (Math.min(Math.max(v, layout.range.min), layout.range.max) - layout.range.min) * layout.valueScale;
519
- }
520
- function buildPerspBarPrisms(chartData, layout, override) {
521
- const nCat = layout.categoryX.length;
522
- const nSer = chartData.series.length;
523
- const catExtent = layout.horizontal ? layout.view.box.h : layout.view.box.w;
524
- const slot = nCat > 0 ? catExtent / nCat : catExtent;
525
- const gapWidth = (chartData.barGapWidth ?? DEFAULT_GAP_WIDTH) / 100;
526
- const barW = slot / ((layout.grouping === "clustered" ? nSer : 1) + gapWidth);
527
- const depth = Math.min(barW, layout.rowDepth);
528
- const stacked = layout.grouping === "stacked" || layout.grouping === "percentStacked";
529
- const baseline = Math.min(Math.max(0, layout.range.min), layout.range.max);
530
- const prisms = [];
531
- for (let c = 0; c < nCat; c++) {
532
- const valueOf = (s) => {
533
- if (override && override.seriesIndex === s && override.pointIndex === c) return override.value;
534
- const v = chartData.series[s]?.values[c];
535
- return v !== void 0 && Number.isFinite(v) ? v : 0;
536
- };
537
- const total = chartData.series.reduce((t, _s, s) => t + Math.abs(valueOf(s)), 0);
538
- let pos = baseline;
539
- let neg = baseline;
540
- for (let s = 0; s < nSer; s++) {
541
- const raw = valueOf(s);
542
- const value = layout.grouping === "percentStacked" ? total ? raw / total : 0 : raw;
543
- let from = baseline;
544
- let to = value;
545
- if (stacked) {
546
- from = value >= 0 ? pos : neg;
547
- to = from + value;
548
- if (value >= 0) pos = to;
549
- else neg = to;
550
- }
551
- const shape = chartData.series[s]?.shape ?? chartData.barShape ?? "box";
552
- const slotIndex = layout.grouping === "clustered" ? s : 0;
553
- const row = layout.grouping === "standard" ? s : 0;
554
- const c0 = c * slot + gapWidth * barW / 2 + slotIndex * barW;
555
- const v0 = valueY(layout, Math.min(from, to));
556
- const v1 = Math.max(valueY(layout, Math.max(from, to)), v0 + 1e-6);
557
- const [x0, x1, y0, y1] = layout.horizontal ? [
558
- v0,
559
- v1,
560
- c0,
561
- c0 + barW
562
- ] : [
563
- c0,
564
- c0 + barW,
565
- v0,
566
- v1
567
- ];
568
- const z0 = row * layout.rowDepth + (layout.rowDepth - depth) / 2;
569
- prisms.push({
570
- seriesIndex: s,
571
- pointIndex: c,
572
- color: layout.colors[s],
573
- outline: [
574
- [x0, y0],
575
- [x1, y0],
576
- [x1, y1],
577
- [x0, y1]
578
- ],
579
- z0,
580
- z1: z0 + depth,
581
- shape,
582
- taper: obliqueBarTaper(shape, v0, v1, (layout.range.max - layout.range.min) * layout.valueScale, raw),
583
- horizontal: layout.horizontal
584
- });
585
- }
586
- }
587
- return prisms;
588
- }
589
- const RIBBON_THICKNESS = .012;
590
- function perspValueY(layout, v) {
591
- return (Math.min(Math.max(v, layout.range.min), layout.range.max) - layout.range.min) * layout.valueScale;
592
- }
593
- function perspRowSpan(layout, row) {
594
- const z0 = row * layout.rowDepth + (layout.rowDepth - layout.markDepth) / 2;
595
- return [z0, z0 + layout.markDepth];
596
- }
597
- function finite(v) {
598
- return v !== void 0 && Number.isFinite(v) ? v : 0;
599
- }
600
- function plottedValues(chartData, layout, nCat) {
601
- const raw = chartData.series.map((s) => Array.from({ length: nCat }, (_, c) => finite(s.values[c])));
602
- if (layout.grouping !== "percentStacked") return raw;
603
- const totals = Array.from({ length: nCat }, (_, c) => raw.reduce((t, s) => t + Math.abs(s[c]), 0));
604
- return raw.map((s) => s.map((v, c) => totals[c] ? v / totals[c] : 0));
605
- }
606
- function buildPerspPrisms(chartData, layout, override) {
607
- if (layout.kind === "bar") return buildPerspBarPrisms(chartData, layout, override);
608
- const nCat = layout.categoryX.length;
609
- const values = plottedValues(chartData, layout, nCat);
610
- if (override) {
611
- const row = values[override.seriesIndex];
612
- if (row && override.pointIndex < nCat) row[override.pointIndex] = override.value;
613
- }
614
- const xs = layout.categoryX;
615
- const baseY = perspValueY(layout, Math.max(0, layout.range.min));
616
- const prisms = [];
617
- if (layout.kind === "line") {
618
- const t = layout.view.box.h * RIBBON_THICKNESS / 2;
619
- values.forEach((series, s) => {
620
- const ys = series.map((v) => perspValueY(layout, v));
621
- const [z0, z1] = perspRowSpan(layout, s);
622
- prisms.push({
623
- seriesIndex: s,
624
- color: layout.colors[s],
625
- outline: [...xs.map((x, c) => [x, ys[c] - t]), ...xs.map((x, c) => [x, ys[c] + t]).reverse()],
626
- z0,
627
- z1
628
- });
629
- });
630
- return prisms;
631
- }
632
- const stacked = layout.grouping !== "standard";
633
- const running = new Array(nCat).fill(0);
634
- values.forEach((series, s) => {
635
- const lower = stacked ? running.map((v) => perspValueY(layout, v)) : xs.map(() => baseY);
636
- const upper = series.map((v, c) => perspValueY(layout, stacked ? running[c] + v : v));
637
- if (stacked) series.forEach((v, c) => {
638
- running[c] += v;
639
- });
640
- const [z0, z1] = perspRowSpan(layout, stacked ? 0 : s);
641
- prisms.push({
642
- seriesIndex: s,
643
- color: layout.colors[s],
644
- outline: [...xs.map((x, c) => [x, lower[c]]), ...xs.map((x, c) => [x, upper[c]]).reverse()],
645
- z0,
646
- z1
647
- });
648
- });
649
- return prisms;
650
- }
651
- function isShapedPerspPrism(prism) {
652
- return prism.shape !== void 0 && prism.shape !== "box";
653
- }
654
- function outlineBounds(prism) {
655
- const xs = prism.outline.map(([x]) => x);
656
- const ys = prism.outline.map(([, y]) => y);
657
- return [
658
- Math.min(...xs),
659
- Math.max(...xs),
660
- Math.min(...ys),
661
- Math.max(...ys)
662
- ];
663
- }
664
- function buildShapedPrismGeometry(three, prism) {
665
- const shape = prism.shape ?? "box";
666
- const unit = buildObliqueShapeGeometry(three, {
667
- shape,
668
- taper: prism.taper ?? {
669
- bottom: 1,
670
- top: 1
671
- }
672
- }, Boolean(prism.horizontal));
673
- const geometry = unit.index ? unit.toNonIndexed() : unit;
674
- if (geometry !== unit) unit.dispose();
675
- const [x0, x1, y0, y1] = outlineBounds(prism);
676
- const sx = Math.max(x1 - x0, 1e-6);
677
- const sy = Math.max(y1 - y0, 1e-6);
678
- const sz = Math.max(prism.z1 - prism.z0, 1e-6);
679
- geometry.scale(sx, sy, sz);
680
- geometry.translate((x0 + x1) / 2, (y0 + y1) / 2, (prism.z0 + prism.z1) / 2);
681
- geometry.computeVertexNormals();
682
- const normals = geometry.getAttribute("normal");
683
- const colors = new Float32Array(normals.count * 3);
684
- const base = new three.Color(prism.color).getRGB(new three.Color(), three.SRGBColorSpace);
685
- const out = new three.Color();
686
- for (let i = 0; i < normals.count; i++) {
687
- const f = chart3DNormalShade(shape, normals.getX(i), normals.getY(i), -normals.getZ(i));
688
- out.setRGB(Math.min(1, base.r * f), Math.min(1, base.g * f), Math.min(1, base.b * f), three.SRGBColorSpace);
689
- colors.set([
690
- out.r,
691
- out.g,
692
- out.b
693
- ], i * 3);
694
- }
695
- geometry.setAttribute("color", new three.Float32BufferAttribute(colors, 3));
696
- return geometry;
697
- }
698
- const GRIDLINE_COLOR = 14277081;
699
- function shadePerspGeometry(three, geometry, color) {
700
- const normals = geometry.getAttribute("normal");
701
- const colors = new Float32Array(normals.count * 3);
702
- const base = new three.Color(color).getRGB(new three.Color(), three.SRGBColorSpace);
703
- const out = new three.Color();
704
- for (let i = 0; i < normals.count; i++) {
705
- const f = chart3DNormalShade("box", normals.getX(i), normals.getY(i), -normals.getZ(i));
706
- out.setRGB(Math.min(1, base.r * f), Math.min(1, base.g * f), Math.min(1, base.b * f), three.SRGBColorSpace);
707
- colors[i * 3] = out.r;
708
- colors[i * 3 + 1] = out.g;
709
- colors[i * 3 + 2] = out.b;
710
- }
711
- geometry.setAttribute("color", new three.Float32BufferAttribute(colors, 3));
712
- }
713
- function buildPrismGeometry(three, prism) {
714
- if (isShapedPerspPrism(prism)) return buildShapedPrismGeometry(three, prism);
715
- const shape = new three.Shape(prism.outline.map(([x, y]) => new three.Vector2(x, y)));
716
- const extruded = new three.ExtrudeGeometry(shape, {
717
- depth: Math.max(prism.z1 - prism.z0, 1e-6),
718
- bevelEnabled: false,
719
- steps: 1
720
- });
721
- const geometry = extruded.index ? extruded.toNonIndexed() : extruded;
722
- if (geometry !== extruded) extruded.dispose();
723
- geometry.translate(0, 0, prism.z0);
724
- geometry.computeVertexNormals();
725
- shadePerspGeometry(three, geometry, prism.color);
726
- return geometry;
727
- }
728
- function buildPerspGridlines(three, layout) {
729
- const points = [];
730
- for (const line of layout.gridlines) points.push(...line.from, ...line.to);
731
- const geometry = new three.BufferGeometry();
732
- geometry.setAttribute("position", new three.Float32BufferAttribute(points, 3));
733
- return new three.LineSegments(geometry, new three.LineBasicMaterial({ color: GRIDLINE_COLOR }));
734
- }
735
- function buildPerspMeshes(three, layout, prisms) {
736
- const group = new three.Group();
737
- group.matrixAutoUpdate = false;
738
- group.matrix.copy(perspBoxMatrix(three, layout.view));
739
- const gridlines = buildPerspGridlines(three, layout);
740
- group.add(gridlines);
741
- const material = new three.MeshBasicMaterial({
742
- vertexColors: true,
743
- side: three.DoubleSide
744
- });
745
- const meshes = prisms.map((prism) => {
746
- const mesh = new three.Mesh(buildPrismGeometry(three, prism), material);
747
- mesh.userData = {
748
- seriesIndex: prism.seriesIndex,
749
- pointIndex: prism.pointIndex
750
- };
751
- group.add(mesh);
752
- return mesh;
753
- });
754
- return {
755
- group,
756
- meshes,
757
- setPrism(i, prism) {
758
- const mesh = meshes[i];
759
- if (!mesh) return;
760
- const previous = mesh.geometry;
761
- mesh.geometry = buildPrismGeometry(three, prism);
762
- previous.dispose();
763
- },
764
- dispose() {
765
- gridlines.geometry.dispose();
766
- gridlines.material.dispose();
767
- material.dispose();
768
- for (const mesh of meshes) mesh.geometry.dispose();
769
- }
770
- };
771
- }
772
- function surfaceRowZ(layout, s, n) {
773
- return n > 1 ? s / (n - 1) * layout.view.box.d : layout.view.box.d / 2;
774
- }
775
- function bandEdges(range) {
776
- const unit = range.majorUnit > 0 ? range.majorUnit : range.max - range.min || 1;
777
- const edges = [];
778
- for (let v = range.min; v < range.max - unit * 1e-9; v += unit) edges.push(Number(v.toPrecision(12)));
779
- edges.push(range.max);
780
- return edges;
781
- }
782
- function formatEdge(v) {
783
- return String(Number(v.toPrecision(10)));
784
- }
785
- function surfaceBands(chartData, range, palette) {
786
- const edges = bandEdges(range);
787
- return edges.slice(0, -1).map((lo, i) => {
788
- const fmt = chartData.bandFmts?.find((b) => b.index === i)?.spPr;
789
- const cycle = palette.length > 0 ? palette : ["#4472C4"];
790
- const base = cycle[i % cycle.length];
791
- const fallback = Math.floor(i / cycle.length) % 2 === 1 ? shade(base, .4) : base;
792
- return {
793
- label: `${formatEdge(lo)}-${formatEdge(edges[i + 1])}`,
794
- color: fmt?.fillColor ?? fmt?.strokeColor ?? fallback
795
- };
796
- });
797
- }
798
- function clipY(poly, lo, hi) {
799
- const clip = (pts, keep, edge) => {
800
- const out = [];
801
- for (let i = 0; i < pts.length; i++) {
802
- const a = pts[i];
803
- const b = pts[(i + 1) % pts.length];
804
- const ina = keep(a);
805
- const inb = keep(b);
806
- if (ina) out.push(a);
807
- if (ina !== inb) {
808
- const t = (edge - a[1]) / (b[1] - a[1]);
809
- out.push([
810
- a[0] + (b[0] - a[0]) * t,
811
- edge,
812
- a[2] + (b[2] - a[2]) * t
813
- ]);
814
- }
815
- }
816
- return out;
817
- };
818
- return clip(clip(poly, (p) => p[1] >= lo, lo), (p) => p[1] <= hi, hi);
819
- }
820
- function cellTriangles(point, s, c) {
821
- return [[
822
- point(s, c),
823
- point(s, c + 1),
824
- point(s + 1, c + 1)
825
- ], [
826
- point(s, c),
827
- point(s + 1, c + 1),
828
- point(s + 1, c)
829
- ]];
830
- }
831
- function clampedBandSegments(a, b, edgesY, out, keepFlat) {
832
- for (let band = 0; band + 1 < edgesY.length; band++) {
833
- const lo = edgesY[band];
834
- const hi = edgesY[band + 1];
835
- const cuts = [0, 1];
836
- for (const e of [lo, hi]) {
837
- const t = (e - a[1]) / (b[1] - a[1]);
838
- if (t > 0 && t < 1) cuts.push(t);
839
- }
840
- cuts.sort((x, y) => x - y);
841
- const at = (t) => [
842
- a[0] + (b[0] - a[0]) * t,
843
- Math.min(hi, Math.max(lo, a[1] + (b[1] - a[1]) * t)),
844
- a[2] + (b[2] - a[2]) * t
845
- ];
846
- for (let i = 0; i + 1 < cuts.length; i++) {
847
- const from = at(cuts[i]);
848
- const to = at(cuts[i + 1]);
849
- const flat = from[1] === to[1] && (from[1] === lo || from[1] === hi);
850
- if (keepFlat || !flat) out.push({
851
- from,
852
- to,
853
- band
854
- });
855
- }
856
- }
857
- }
858
- function buildSurfaceGeometry(chartData, layout, palette, override) {
859
- const nSer = chartData.series.length;
860
- const nCat = layout.categoryX.length;
861
- const bands = surfaceBands(chartData, layout.range, palette);
862
- const edgesY = bandEdges(layout.range).map((v) => perspValueY(layout, v));
863
- const point = (s, c) => {
864
- let v = chartData.series[s]?.values[c];
865
- if (override && override.seriesIndex === s && override.pointIndex === c) v = override.value;
866
- const value = v !== void 0 && Number.isFinite(v) ? v : layout.range.min;
867
- return [
868
- layout.categoryX[c],
869
- perspValueY(layout, value),
870
- surfaceRowZ(layout, s, nSer)
871
- ];
872
- };
873
- const geometry = {
874
- bands,
875
- triangles: [],
876
- segments: []
877
- };
878
- if (chartData.wireframe) {
879
- for (let s = 0; s < nSer; s++) for (let c = 0; c < nCat; c++) {
880
- if (c + 1 < nCat) clampedBandSegments(point(s, c), point(s, c + 1), edgesY, geometry.segments, s === 0);
881
- if (s + 1 < nSer) clampedBandSegments(point(s, c), point(s + 1, c), edgesY, geometry.segments, false);
882
- }
883
- return geometry;
884
- }
885
- for (let s = 0; s + 1 < nSer; s++) for (let c = 0; c + 1 < nCat; c++) for (const tri of cellTriangles(point, s, c)) for (let b = 0; b < bands.length; b++) {
886
- const piece = clipY(tri, edgesY[b], edgesY[b + 1]);
887
- for (let i = 1; i + 1 < piece.length; i++) geometry.triangles.push({
888
- points: [
889
- piece[0],
890
- piece[i],
891
- piece[i + 1]
892
- ],
893
- band: b
894
- });
895
- }
896
- return geometry;
897
- }
898
- function srgb(three, hex, f) {
899
- const base = new three.Color(hex).getRGB(new three.Color(), three.SRGBColorSpace);
900
- return new three.Color().setRGB(Math.min(1, base.r * f), Math.min(1, base.g * f), Math.min(1, base.b * f), three.SRGBColorSpace);
901
- }
902
- function buildVisual(three, chartData, layout, palette, override) {
903
- const geo = buildSurfaceGeometry(chartData, layout, palette, override);
904
- const positions = [];
905
- const colors = [];
906
- if (chartData.wireframe) {
907
- for (const seg of geo.segments) {
908
- const c = srgb(three, geo.bands[seg.band].color, 1);
909
- positions.push(...seg.from, ...seg.to);
910
- colors.push(c.r, c.g, c.b, c.r, c.g, c.b);
911
- }
912
- const g = new three.BufferGeometry();
913
- g.setAttribute("position", new three.Float32BufferAttribute(positions, 3));
914
- g.setAttribute("color", new three.Float32BufferAttribute(colors, 3));
915
- return new three.LineSegments(g, new three.LineBasicMaterial({ vertexColors: true }));
916
- }
917
- const a = new three.Vector3();
918
- const b = new three.Vector3();
919
- const n = new three.Vector3();
920
- for (const tri of geo.triangles) {
921
- const [p0, p1, p2] = tri.points;
922
- a.set(p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]);
923
- b.set(p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]);
924
- n.crossVectors(a, b).normalize();
925
- if (n.y < 0) n.negate();
926
- const c = srgb(three, geo.bands[tri.band].color, chart3DNormalShade("box", n.x, n.y, -n.z));
927
- positions.push(...p0, ...p1, ...p2);
928
- colors.push(c.r, c.g, c.b, c.r, c.g, c.b, c.r, c.g, c.b);
929
- }
930
- const g = new three.BufferGeometry();
931
- g.setAttribute("position", new three.Float32BufferAttribute(positions, 3));
932
- g.setAttribute("color", new three.Float32BufferAttribute(colors, 3));
933
- return new three.Mesh(g, new three.MeshBasicMaterial({
934
- vertexColors: true,
935
- side: three.DoubleSide
936
- }));
937
- }
938
- function buildPickGeometry(three, chartData, layout) {
939
- const nSer = chartData.series.length;
940
- const nCat = layout.categoryX.length;
941
- const at = (s, c) => {
942
- const v = chartData.series[s]?.values[c];
943
- return [
944
- layout.categoryX[c],
945
- perspValueY(layout, v !== void 0 && Number.isFinite(v) ? v : layout.range.min),
946
- surfaceRowZ(layout, s, nSer)
947
- ];
948
- };
949
- const positions = [];
950
- for (let s = 0; s + 1 < nSer; s++) for (let c = 0; c + 1 < nCat; c++) {
951
- positions.push(...at(s, c), ...at(s, c + 1), ...at(s + 1, c + 1));
952
- positions.push(...at(s, c), ...at(s + 1, c + 1), ...at(s + 1, c));
953
- }
954
- const g = new three.BufferGeometry();
955
- g.setAttribute("position", new three.Float32BufferAttribute(positions, 3));
956
- return g;
957
- }
958
- function disposeObject(object) {
959
- const withGeometry = object;
960
- withGeometry.geometry?.dispose();
961
- withGeometry.material?.dispose();
962
- }
963
- function buildSurfaceMarks(three, chartData, layout, palette) {
964
- const group = new three.Group();
965
- group.matrixAutoUpdate = false;
966
- group.matrix.copy(perspBoxMatrix(three, layout.view));
967
- const gridlines = buildPerspGridlines(three, layout);
968
- group.add(gridlines);
969
- let visual = buildVisual(three, chartData, layout, palette);
970
- group.add(visual);
971
- const pickMaterial = new three.MeshBasicMaterial({
972
- side: three.DoubleSide,
973
- visible: false
974
- });
975
- const pickTarget = new three.Mesh(buildPickGeometry(three, chartData, layout), pickMaterial);
976
- group.add(pickTarget);
977
- return {
978
- group,
979
- pickTarget,
980
- rebuild(override) {
981
- group.remove(visual);
982
- disposeObject(visual);
983
- visual = buildVisual(three, chartData, layout, palette, override);
984
- group.add(visual);
985
- },
986
- dispose() {
987
- disposeObject(visual);
988
- disposeObject(gridlines);
989
- pickTarget.geometry.dispose();
990
- pickMaterial.dispose();
991
- }
992
- };
993
- }
994
- function nearestIndex(xs, x) {
995
- let best = 0;
996
- xs.forEach((v, i) => {
997
- if (Math.abs(v - x) < Math.abs(xs[best] - x)) best = i;
998
- });
999
- return best;
1000
- }
1001
- function buildPerspMarkSet(three, chartData, layout, palette) {
1002
- if (layout.kind === "surface") {
1003
- const nSer = chartData.series.length;
1004
- const zs = Array.from({ length: nSer }, (_, s) => surfaceRowZ(layout, s, nSer));
1005
- const surface = buildSurfaceMarks(three, chartData, layout, palette);
1006
- return {
1007
- group: surface.group,
1008
- targets: [surface.pickTarget],
1009
- pointAt: (_object, local) => ({
1010
- seriesIndex: nearestIndex(zs, local.z),
1011
- pointIndex: nearestIndex(layout.categoryX, local.x)
1012
- }),
1013
- anchor: (point, value) => [
1014
- layout.categoryX[point.pointIndex],
1015
- perspValueY(layout, value),
1016
- zs[point.seriesIndex]
1017
- ],
1018
- draggable: true,
1019
- preview: (point, value) => surface.rebuild({
1020
- ...point,
1021
- value
1022
- }),
1023
- dispose: () => surface.dispose()
1024
- };
1025
- }
1026
- if (layout.kind === "bar") return buildBarMarkSet(three, chartData, layout);
1027
- const stacked = layout.grouping !== "standard" && layout.grouping !== "clustered";
1028
- const meshes = buildPerspMeshes(three, layout, buildPerspPrisms(chartData, layout));
1029
- return {
1030
- group: meshes.group,
1031
- targets: meshes.meshes,
1032
- pointAt(object, local) {
1033
- const seriesIndex = object.userData.seriesIndex;
1034
- return seriesIndex === void 0 ? null : {
1035
- seriesIndex,
1036
- pointIndex: nearestIndex(layout.categoryX, local.x)
1037
- };
1038
- },
1039
- anchor(point, value) {
1040
- const [z0, z1] = perspRowSpan(layout, stacked ? 0 : point.seriesIndex);
1041
- return [
1042
- layout.categoryX[point.pointIndex],
1043
- perspValueY(layout, value),
1044
- (z0 + z1) / 2
1045
- ];
1046
- },
1047
- draggable: !stacked,
1048
- preview(point, value) {
1049
- meshes.setPrism(point.seriesIndex, buildPerspPrisms(chartData, layout, {
1050
- ...point,
1051
- value
1052
- })[point.seriesIndex]);
1053
- },
1054
- dispose: () => meshes.dispose()
1055
- };
1056
- }
1057
- function buildBarMarkSet(three, chartData, layout) {
1058
- const prisms = buildPerspPrisms(chartData, layout);
1059
- const meshes = buildPerspMeshes(three, layout, prisms);
1060
- const indexOf = (point) => prisms.findIndex((p) => p.seriesIndex === point.seriesIndex && p.pointIndex === point.pointIndex);
1061
- return {
1062
- group: meshes.group,
1063
- targets: meshes.meshes,
1064
- pointAt(object) {
1065
- const data = object.userData;
1066
- return data.seriesIndex === void 0 || data.pointIndex === void 0 ? null : {
1067
- seriesIndex: data.seriesIndex,
1068
- pointIndex: data.pointIndex
1069
- };
1070
- },
1071
- anchor(point, value) {
1072
- const prism = prisms[indexOf(point)];
1073
- if (!prism) return [
1074
- 0,
1075
- perspValueY(layout, value),
1076
- 0
1077
- ];
1078
- const along = prism.outline.map(([x, y]) => layout.horizontal ? y : x);
1079
- const mid = (Math.min(...along) + Math.max(...along)) / 2;
1080
- const v = perspValueY(layout, value);
1081
- const z = (prism.z0 + prism.z1) / 2;
1082
- return layout.horizontal ? [
1083
- v,
1084
- mid,
1085
- z
1086
- ] : [
1087
- mid,
1088
- v,
1089
- z
1090
- ];
1091
- },
1092
- draggable: layout.grouping === "clustered" || layout.grouping === "standard",
1093
- preview(point, value) {
1094
- const i = indexOf(point);
1095
- const next = buildPerspPrisms(chartData, layout, {
1096
- ...point,
1097
- value
1098
- })[i];
1099
- if (next) meshes.setPrism(i, next);
1100
- },
1101
- dispose: () => meshes.dispose()
1102
- };
1103
- }
1104
- const SELECTED_COLOR$1 = 2450411;
1105
- function surfaceLegend(chartData, layout, palette, background, textStyle) {
1106
- return surfaceBands(chartData, layout.range, palette).map((band) => ({
1107
- label: band.label,
1108
- color: band.color,
1109
- ...textStyle ? { textStyle } : {},
1110
- ...chartData.wireframe ? { lineSwatch: { primitives: [{
1111
- kind: "rect",
1112
- x: 0,
1113
- y: -7,
1114
- w: 10,
1115
- h: 10,
1116
- fill: band.color
1117
- }, {
1118
- kind: "rect",
1119
- x: 1,
1120
- y: -6,
1121
- w: 8,
1122
- h: 8,
1123
- fill: background
1124
- }] } } : {}
1125
- }));
1126
- }
1127
- function mountPerspChartView(input, ctx) {
1128
- const three = ctx.three;
1129
- const { vm, layout, chartData } = input;
1130
- const palette = chartData.colorPalette?.length ? chartData.colorPalette : DEFAULT_PALETTE;
1131
- const camera = buildPerspCamera(three, layout.view, vm.svgWidth, vm.svgHeight);
1132
- const scene = new three.Scene();
1133
- const marks = buildPerspMarkSet(three, chartData, layout, palette);
1134
- scene.add(marks.group);
1135
- const markerGeometry = new three.OctahedronGeometry(layout.view.box.w * .012);
1136
- const markerMaterial = new three.MeshBasicMaterial({ color: SELECTED_COLOR$1 });
1137
- const marker = new three.Mesh(markerGeometry, markerMaterial);
1138
- marker.visible = false;
1139
- marks.group.add(marker);
1140
- const overlay = renderChart3DChromeOverlaySvg(ctx.document, vm, {
1141
- labels: layout.labels,
1142
- reverseLegend: layout.horizontal && layout.grouping === "clustered",
1143
- legend: layout.kind === "surface" ? surfaceLegend(chartData, layout, palette, vm.areaFill ?? "#ffffff", vm.legend[0]?.textStyle) : void 0
1144
- });
1145
- ctx.overlay.appendChild(overlay);
1146
- const valueAt = (point) => {
1147
- const v = chartData.series[point.seriesIndex]?.values[point.pointIndex];
1148
- return v !== void 0 && Number.isFinite(v) ? v : void 0;
1149
- };
1150
- const placeMarker = (point, value) => {
1151
- marker.position.set(...marks.anchor(point, value));
1152
- };
1153
- const interaction = attachChart3DMarkInteraction({
1154
- ctx,
1155
- camera,
1156
- scene,
1157
- svgHeight: vm.svgHeight,
1158
- chartData,
1159
- categoryLabels: input.categoryLabels,
1160
- seriesNames: chartData.series.map((s) => s.name),
1161
- adapter: {
1162
- targets: marks.targets,
1163
- pointAt: (hit) => marks.pointAt(hit.object, marks.group.worldToLocal(hit.point.clone())),
1164
- valueOf: valueAt,
1165
- beginDrag(point) {
1166
- const start = valueAt(point);
1167
- if (!marks.draggable || start === void 0) return null;
1168
- const unit = layout.range.majorUnit || 1;
1169
- const a = marks.anchor(point, start);
1170
- const step = unit * layout.valueScale;
1171
- const b = layout.horizontal ? [
1172
- a[0] + step,
1173
- a[1],
1174
- a[2]
1175
- ] : [
1176
- a[0],
1177
- a[1] + step,
1178
- a[2]
1179
- ];
1180
- const s0 = perspToScreen(layout.view, a);
1181
- const s1 = perspToScreen(layout.view, b);
1182
- const ux = (s1.x - s0.x) / unit;
1183
- const uy = (s1.y - s0.y) / unit;
1184
- const len2 = ux * ux + uy * uy || 1;
1185
- const span = layout.range.max - layout.range.min;
1186
- return { preview(dx, dy) {
1187
- const value = roundDragValue(start + (dx * ux + dy * uy) / len2, {
1188
- min: layout.range.min,
1189
- max: layout.range.max,
1190
- span
1191
- });
1192
- marks.preview(point, value);
1193
- if (marker.visible) placeMarker(point, value);
1194
- return value;
1195
- } };
1196
- },
1197
- highlight(point) {
1198
- const value = point ? valueAt(point) : void 0;
1199
- marker.visible = point !== null && value !== void 0;
1200
- if (point && value !== void 0) placeMarker(point, value);
1201
- },
1202
- dispose() {
1203
- markerGeometry.dispose();
1204
- markerMaterial.dispose();
1205
- }
1206
- }
1207
- });
1208
- let disposed = false;
1209
- return {
1210
- render(renderer) {
1211
- renderer.render(scene, camera);
1212
- },
1213
- resize() {},
1214
- isAnimating: () => false,
1215
- setInteractive: (on) => interaction.setInteractive(on),
1216
- setSelectedPart: (part) => interaction.setSelectedPart(part),
1217
- dispose() {
1218
- if (disposed) return;
1219
- disposed = true;
1220
- interaction.dispose();
1221
- marks.dispose();
1222
- overlay.remove();
1223
- }
1224
- };
1225
- }
1226
- const SEAM_COLOR = 16777215;
1227
- const SEGMENTS_PER_TURN = 144;
1228
- function pieFaceShade(nx, ny, nz, cut = false) {
1229
- if (ny > .5) return 1;
1230
- if (ny < -.5) return .3;
1231
- const phi = Math.atan2(nx, -nz);
1232
- if (cut) return .5 + .2 * Math.sin(phi);
1233
- return Math.max(.385, .57 - .275 * phi);
1234
- }
1235
- function sliceBuffers(three, layout, slice) {
1236
- const base = new three.Color(slice.color).getRGB(new three.Color(), three.SRGBColorSpace);
1237
- const out = new three.Color();
1238
- const positions = [];
1239
- const colors = [];
1240
- const cx = layout.center.x + slice.offset.x;
1241
- const cz = layout.center.z + slice.offset.z;
1242
- const top = layout.center.y;
1243
- const r = layout.radius;
1244
- const push = (p, n, cut = false) => {
1245
- const f = pieFaceShade(n[0], n[1], n[2], cut);
1246
- out.setRGB(Math.min(1, base.r * f), Math.min(1, base.g * f), Math.min(1, base.b * f), three.SRGBColorSpace);
1247
- positions.push(...p);
1248
- colors.push(out.r, out.g, out.b);
1249
- };
1250
- const span = slice.endAngle - slice.startAngle;
1251
- const steps = Math.max(2, Math.ceil(span / (Math.PI * 2) * SEGMENTS_PER_TURN));
1252
- const angles = Array.from({ length: steps + 1 }, (_, i) => slice.startAngle + span * i / steps);
1253
- const rim = (a, y) => {
1254
- const p = pieRimPoint(cx, cz, r, a);
1255
- return [
1256
- p.x,
1257
- y,
1258
- p.z
1259
- ];
1260
- };
1261
- const radial = (a) => [
1262
- Math.sin(a),
1263
- 0,
1264
- Math.cos(a)
1265
- ];
1266
- for (let i = 0; i < steps; i++) {
1267
- const a0 = angles[i];
1268
- const a1 = angles[i + 1];
1269
- push([
1270
- cx,
1271
- top,
1272
- cz
1273
- ], [
1274
- 0,
1275
- 1,
1276
- 0
1277
- ]);
1278
- push(rim(a0, top), [
1279
- 0,
1280
- 1,
1281
- 0
1282
- ]);
1283
- push(rim(a1, top), [
1284
- 0,
1285
- 1,
1286
- 0
1287
- ]);
1288
- push([
1289
- cx,
1290
- 0,
1291
- cz
1292
- ], [
1293
- 0,
1294
- -1,
1295
- 0
1296
- ]);
1297
- push(rim(a1, 0), [
1298
- 0,
1299
- -1,
1300
- 0
1301
- ]);
1302
- push(rim(a0, 0), [
1303
- 0,
1304
- -1,
1305
- 0
1306
- ]);
1307
- push(rim(a0, top), radial(a0));
1308
- push(rim(a0, 0), radial(a0));
1309
- push(rim(a1, 0), radial(a1));
1310
- push(rim(a0, top), radial(a0));
1311
- push(rim(a1, 0), radial(a1));
1312
- push(rim(a1, top), radial(a1));
1313
- }
1314
- for (const [a, sign] of [[slice.startAngle, -1], [slice.endAngle, 1]]) {
1315
- const n = [
1316
- sign * Math.cos(a),
1317
- 0,
1318
- -sign * Math.sin(a)
1319
- ];
1320
- push([
1321
- cx,
1322
- top,
1323
- cz
1324
- ], n, true);
1325
- push([
1326
- cx,
1327
- 0,
1328
- cz
1329
- ], n, true);
1330
- push(rim(a, 0), n, true);
1331
- push([
1332
- cx,
1333
- top,
1334
- cz
1335
- ], n, true);
1336
- push(rim(a, 0), n, true);
1337
- push(rim(a, top), n, true);
1338
- }
1339
- const seams = [];
1340
- for (let i = 0; i < steps; i++) seams.push(...rim(angles[i], top), ...rim(angles[i + 1], top));
1341
- for (const a of [slice.startAngle, slice.endAngle]) seams.push(cx, top, cz, ...rim(a, top), ...rim(a, top), ...rim(a, 0));
1342
- return {
1343
- positions,
1344
- colors,
1345
- seams
1346
- };
1347
- }
1348
- function buildPieMarks(three, layout) {
1349
- const group = new three.Group();
1350
- group.matrixAutoUpdate = false;
1351
- group.matrix.copy(perspBoxMatrix(three, layout.view));
1352
- const material = new three.MeshBasicMaterial({
1353
- vertexColors: true,
1354
- side: three.DoubleSide,
1355
- polygonOffset: true,
1356
- polygonOffsetFactor: 1,
1357
- polygonOffsetUnits: 1
1358
- });
1359
- const seamMaterial = new three.LineBasicMaterial({ color: SEAM_COLOR });
1360
- const meshes = [];
1361
- let seams = null;
1362
- const clear = () => {
1363
- for (const mesh of meshes) {
1364
- group.remove(mesh);
1365
- mesh.geometry.dispose();
1366
- }
1367
- if (seams) {
1368
- group.remove(seams);
1369
- seams.geometry.dispose();
1370
- }
1371
- };
1372
- const build = (slices) => {
1373
- clear();
1374
- const seamPoints = [];
1375
- const built = slices.map((slice) => {
1376
- const b = sliceBuffers(three, layout, slice);
1377
- const geometry = new three.BufferGeometry();
1378
- geometry.setAttribute("position", new three.Float32BufferAttribute(b.positions, 3));
1379
- geometry.setAttribute("color", new three.Float32BufferAttribute(b.colors, 3));
1380
- const mesh = new three.Mesh(geometry, material);
1381
- mesh.userData = {
1382
- seriesIndex: 0,
1383
- pointIndex: slice.pointIndex
1384
- };
1385
- group.add(mesh);
1386
- seamPoints.push(...b.seams);
1387
- return mesh;
1388
- });
1389
- meshes.splice(0, meshes.length, ...built);
1390
- const seamGeometry = new three.BufferGeometry();
1391
- seamGeometry.setAttribute("position", new three.Float32BufferAttribute(seamPoints, 3));
1392
- seams = new three.LineSegments(seamGeometry, seamMaterial);
1393
- group.add(seams);
1394
- };
1395
- build(layout.slices);
1396
- return {
1397
- group,
1398
- meshes,
1399
- setSlices: build,
1400
- dispose() {
1401
- clear();
1402
- material.dispose();
1403
- seamMaterial.dispose();
1404
- }
1405
- };
1406
- }
1407
- const SELECTED_COLOR = 2450411;
1408
- function slicesWith(input, pointIndex, value) {
1409
- const series = input.chartData.series.map((s, i) => i === 0 ? {
1410
- ...s,
1411
- values: s.values.map((v, j) => j === pointIndex ? value : v)
1412
- } : s);
1413
- const element = {
1414
- ...input.element,
1415
- chartData: {
1416
- ...input.chartData,
1417
- series
1418
- }
1419
- };
1420
- return computePieChartLayout(element, input.vm)?.slices ?? input.layout.slices;
1421
- }
1422
- function mountPieChartView(input, ctx) {
1423
- const three = ctx.three;
1424
- const { vm, layout, chartData } = input;
1425
- const camera = buildPerspCamera(three, layout.view, vm.svgWidth, vm.svgHeight);
1426
- const scene = new three.Scene();
1427
- const marks = buildPieMarks(three, layout);
1428
- scene.add(marks.group);
1429
- const markerGeometry = new three.OctahedronGeometry(.03);
1430
- const markerMaterial = new three.MeshBasicMaterial({ color: SELECTED_COLOR });
1431
- const marker = new three.Mesh(markerGeometry, markerMaterial);
1432
- marker.visible = false;
1433
- marks.group.add(marker);
1434
- const overlay = renderChart3DChromeOverlaySvg(ctx.document, vm, {
1435
- labels: [],
1436
- reverseLegend: false
1437
- });
1438
- ctx.overlay.appendChild(overlay);
1439
- const values = () => chartData.series[0]?.values.slice() ?? [];
1440
- const interaction = attachChart3DMarkInteraction({
1441
- ctx,
1442
- camera,
1443
- scene,
1444
- svgHeight: vm.svgHeight,
1445
- chartData,
1446
- categoryLabels: input.categoryLabels,
1447
- seriesNames: chartData.series.map((s) => s.name),
1448
- adapter: {
1449
- targets: marks.meshes,
1450
- pointAt(hit) {
1451
- const pointIndex = hit.object.userData.pointIndex;
1452
- return pointIndex === void 0 ? null : {
1453
- seriesIndex: 0,
1454
- pointIndex
1455
- };
1456
- },
1457
- valueOf(point) {
1458
- const v = chartData.series[0]?.values[point.pointIndex];
1459
- return v !== void 0 && Number.isFinite(v) ? v : void 0;
1460
- },
1461
- beginDrag(point) {
1462
- const slice = layout.slices[point.pointIndex];
1463
- if (!slice) return null;
1464
- const start = values();
1465
- return { preview(_dx, _dy, at) {
1466
- const hit = perspScreenToPlaneY(layout.view, at.x, at.y, layout.center.y);
1467
- if (!hit) return start[point.pointIndex];
1468
- const angle = Math.atan2(hit.x - layout.center.x - slice.offset.x, hit.z - layout.center.z - slice.offset.z);
1469
- const value = resolvePieSliceShareValue(angle, slice.startAngle, start, point.pointIndex);
1470
- marks.setSlices(slicesWith(input, point.pointIndex, value));
1471
- return value;
1472
- } };
1473
- },
1474
- highlight(point) {
1475
- const slice = point ? layout.slices[point.pointIndex] : void 0;
1476
- marker.visible = slice !== void 0;
1477
- if (slice) {
1478
- const mid = (slice.startAngle + slice.endAngle) / 2;
1479
- const p = pieRimPoint(layout.center.x + slice.offset.x, layout.center.z + slice.offset.z, layout.radius * .6, mid);
1480
- marker.position.set(p.x, layout.center.y, p.z);
1481
- }
1482
- },
1483
- dispose() {
1484
- markerGeometry.dispose();
1485
- markerMaterial.dispose();
1486
- }
1487
- }
1488
- });
1489
- let disposed = false;
1490
- return {
1491
- render(renderer) {
1492
- renderer.render(scene, camera);
1493
- },
1494
- resize() {},
1495
- isAnimating: () => false,
1496
- setInteractive: (on) => interaction.setInteractive(on),
1497
- setSelectedPart: (part) => interaction.setSelectedPart(part),
1498
- dispose() {
1499
- if (disposed) return;
1500
- disposed = true;
1501
- interaction.dispose();
1502
- marks.dispose();
1503
- overlay.remove();
1504
- }
1505
- };
1506
- }
1507
- function buildObliqueCamera(three, svgWidth, svgHeight, shearX, shearY) {
1508
- const halfW = svgWidth / 2;
1509
- const halfH = svgHeight / 2;
1510
- const distance = halfW + halfH + 500;
1511
- const camera = new three.OrthographicCamera(-halfW, halfW, halfH, -halfH, 1, distance + 2e3);
1512
- camera.position.set(0, 0, distance);
1513
- camera.lookAt(0, 0, 0);
1514
- camera.updateProjectionMatrix();
1515
- const m = camera.projectionMatrix.elements;
1516
- const ndcScaleX = m[0];
1517
- const ndcScaleY = m[5];
1518
- m[8] = -shearX * ndcScaleX;
1519
- m[9] = shearY * ndcScaleY;
1520
- m[12] += m[8] * distance;
1521
- m[13] += m[9] * distance;
1522
- camera.projectionMatrixInverse.copy(camera.projectionMatrix).invert();
1523
- return camera;
1524
- }
1525
- async function mountChart3DView(spec, ctx) {
1526
- if (spec.geometry?.kind === "pie") {
1527
- const chartData = spec.element.chartData;
1528
- if (!chartData) throw new Error("3D chart spec without chart data");
1529
- return mountPieChartView({
1530
- element: spec.element,
1531
- vm: widenPieViewModel(spec.vm, spec.element),
1532
- layout: spec.geometry.layout,
1533
- chartData,
1534
- categoryLabels: spec.categoryLabels
1535
- }, ctx);
1536
- }
1537
- if (spec.geometry?.kind === "perspective") {
1538
- const chartData = spec.element.chartData;
1539
- if (!chartData) throw new Error("3D chart spec without chart data");
1540
- return mountPerspChartView({
1541
- vm: spec.vm,
1542
- layout: spec.geometry.layout,
1543
- chartData,
1544
- categoryLabels: spec.categoryLabels
1545
- }, ctx);
1546
- }
1547
- if (spec.projection.mode !== "oblique" || spec.geometry?.kind !== "oblique") throw new Error(`no 3D chart scene for chartType=${spec.chartType}`);
1548
- const three = ctx.three;
1549
- const { vm, geometry } = spec;
1550
- const chartData = spec.element.chartData;
1551
- if (!chartData) throw new Error("3D chart spec without chart data");
1552
- const camera = buildObliqueCamera(three, vm.svgWidth, vm.svgHeight, spec.projection.shearX, spec.projection.shearY);
1553
- const scene = new three.Scene();
1554
- const { layout } = geometry;
1555
- const bars = buildChart3DBarMeshes(three, layout, vm.svgWidth, vm.svgHeight);
1556
- scene.add(bars.group);
1557
- const overlay = renderChart3DChromeOverlaySvg(ctx.document, vm, {
1558
- labels: layout.labels,
1559
- reverseLegend: layout.horizontal && layout.grouping === "clustered"
1560
- });
1561
- ctx.overlay.appendChild(overlay);
1562
- const interaction = attachObliqueBarInteraction({
1563
- ctx,
1564
- camera,
1565
- meshes: bars.meshes,
1566
- layout,
1567
- svgWidth: vm.svgWidth,
1568
- svgHeight: vm.svgHeight,
1569
- chartData,
1570
- categoryLabels: spec.categoryLabels,
1571
- seriesNames: chartData.series.map((series) => series.name),
1572
- scene
1573
- });
1574
- let disposed = false;
1575
- return {
1576
- render(renderer) {
1577
- renderer.render(scene, camera);
1578
- },
1579
- resize() {},
1580
- isAnimating: () => false,
1581
- setInteractive: (on) => interaction.setInteractive(on),
1582
- setSelectedPart: (part) => interaction.setSelectedPart(part),
1583
- dispose() {
1584
- if (disposed) return;
1585
- disposed = true;
1586
- interaction.dispose();
1587
- bars.dispose();
1588
- overlay.remove();
1589
- }
1590
- };
1591
- }
1592
- export { mountChart3DView };
1593
-
1594
- //# sourceMappingURL=pptx-angular-viewer-chart-3d-view-scene-IqColhvd.mjs.map