pptx-angular-viewer 1.1.42 → 1.1.43

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.
@@ -23966,6 +23966,516 @@ function buildSmartArt3DModel(layout, options = {}) {
23966
23966
  return options.spatial ? applySpatialLayout(model) : model;
23967
23967
  }
23968
23968
 
23969
+ /**
23970
+ * Vanilla three.js GLTF/GLB model scene controller (framework-agnostic).
23971
+ *
23972
+ * Mounts an interactive 3D model into a caller-provided container element:
23973
+ * dynamically imports `three` plus its `GLTFLoader` and `OrbitControls` addons,
23974
+ * builds a scene (perspective camera, lights), auto-centres and fits the model
23975
+ * to the view via a `Box3`, renders with a RAF loop, and exposes `resize()` /
23976
+ * `dispose()` for deterministic teardown of GPU resources.
23977
+ *
23978
+ * `three` is an OPTIONAL peer dependency: every import is dynamic and guarded.
23979
+ * When `three` is not installed, {@link mountModel3D} resolves to a sentinel
23980
+ * handle ({@link THREE_UNAVAILABLE}) so the caller can fall back to a poster.
23981
+ * No framework imports - the React, Vue, and Angular bindings can all mount it.
23982
+ */
23983
+ /**
23984
+ * Sentinel handle returned when `three` is unavailable or the model fails to
23985
+ * load. All methods are no-ops; `ok` is `false` so callers can show a poster.
23986
+ */
23987
+ const THREE_UNAVAILABLE = {
23988
+ ok: false,
23989
+ resize: () => { },
23990
+ setInteractive: () => { },
23991
+ dispose: () => { },
23992
+ };
23993
+ const FOV$1 = 50;
23994
+ /** Dynamically load `three`; returns `null` when the package is not installed. */
23995
+ async function loadThree$1() {
23996
+ try {
23997
+ return (await import('three'));
23998
+ }
23999
+ catch {
24000
+ return null;
24001
+ }
24002
+ }
24003
+ /** Dynamically load the GLTFLoader addon; returns `null` when unavailable. */
24004
+ async function loadGltfLoaderCtor() {
24005
+ try {
24006
+ const mod = await import('three/examples/jsm/loaders/GLTFLoader.js');
24007
+ return mod.GLTFLoader;
24008
+ }
24009
+ catch {
24010
+ return null;
24011
+ }
24012
+ }
24013
+ /** Dynamically load the OrbitControls addon; returns `null` when unavailable. */
24014
+ async function loadOrbitControlsCtor$1() {
24015
+ try {
24016
+ const mod = await import('three/examples/jsm/controls/OrbitControls.js');
24017
+ return mod.OrbitControls;
24018
+ }
24019
+ catch {
24020
+ return null;
24021
+ }
24022
+ }
24023
+ /** Promisified `GLTFLoader.load`. */
24024
+ function loadGltf(loader, url) {
24025
+ return new Promise((resolve, reject) => {
24026
+ loader.load(url, resolve, undefined, reject);
24027
+ });
24028
+ }
24029
+ /**
24030
+ * Auto-centre the model at the origin and uniformly scale it so its largest
24031
+ * dimension fills a 2-unit cube, mirroring the previous react-three-fiber fit.
24032
+ */
24033
+ function centerAndFit(three, root) {
24034
+ const box = new three.Box3().setFromObject(root);
24035
+ const size = new three.Vector3();
24036
+ box.getSize(size);
24037
+ const maxDim = Math.max(size.x, size.y, size.z);
24038
+ if (maxDim > 0) {
24039
+ root.scale.setScalar(2 / maxDim);
24040
+ }
24041
+ const center = new three.Vector3();
24042
+ box.getCenter(center);
24043
+ root.position.sub(center.multiplyScalar(root.scale.x));
24044
+ }
24045
+ /**
24046
+ * Mount an interactive GLTF/GLB model into `container` and start rendering.
24047
+ *
24048
+ * Resolves to {@link THREE_UNAVAILABLE} (a no-op handle with `ok === false`)
24049
+ * when `three` or its addons cannot be loaded, or when the model fails to parse.
24050
+ */
24051
+ async function mountModel3D(container, modelUrl, options) {
24052
+ const three = await loadThree$1();
24053
+ const GltfLoaderCtor = three ? await loadGltfLoaderCtor() : null;
24054
+ if (!three || !GltfLoaderCtor) {
24055
+ return THREE_UNAVAILABLE;
24056
+ }
24057
+ const width = Math.max(1, options.width);
24058
+ const height = Math.max(1, options.height);
24059
+ const renderer = new three.WebGLRenderer({ antialias: true, alpha: !options.background });
24060
+ renderer.setPixelRatio(Math.min(typeof window === 'undefined' ? 1 : window.devicePixelRatio || 1, options.maxPixelRatio ?? 2));
24061
+ renderer.setSize(width, height, false);
24062
+ const canvas = renderer.domElement;
24063
+ canvas.style.width = `${width}px`;
24064
+ canvas.style.height = `${height}px`;
24065
+ canvas.style.display = 'block';
24066
+ canvas.style.willChange = 'transform';
24067
+ container.appendChild(canvas);
24068
+ const scene = new three.Scene();
24069
+ if (options.background) {
24070
+ scene.background = new three.Color(options.background);
24071
+ }
24072
+ const camera = new three.PerspectiveCamera(FOV$1, width / height, 0.1, 1000);
24073
+ camera.position.set(0, 0, 5);
24074
+ camera.lookAt(0, 0, 0);
24075
+ scene.add(new three.AmbientLight(0xffffff, 0.5));
24076
+ const key = new three.DirectionalLight(0xffffff, 1);
24077
+ key.position.set(5, 5, 5);
24078
+ scene.add(key);
24079
+ const fill = new three.DirectionalLight(0xffffff, 0.3);
24080
+ fill.position.set(-3, -3, 2);
24081
+ scene.add(fill);
24082
+ let gltf;
24083
+ try {
24084
+ gltf = await loadGltf(new GltfLoaderCtor(), modelUrl);
24085
+ }
24086
+ catch {
24087
+ renderer.dispose();
24088
+ canvas.remove();
24089
+ return THREE_UNAVAILABLE;
24090
+ }
24091
+ const model = gltf.scene;
24092
+ centerAndFit(three, model);
24093
+ scene.add(model);
24094
+ let controls = null;
24095
+ const OrbitCtrlCtor = await loadOrbitControlsCtor$1();
24096
+ const enableControls = (on) => {
24097
+ if (on && !controls && OrbitCtrlCtor) {
24098
+ controls = new OrbitCtrlCtor(camera, canvas);
24099
+ controls.enablePan = false;
24100
+ controls.enableZoom = true;
24101
+ controls.enableRotate = true;
24102
+ controls.minDistance = 2;
24103
+ controls.maxDistance = 20;
24104
+ controls.update();
24105
+ }
24106
+ else if (!on && controls) {
24107
+ controls.dispose();
24108
+ controls = null;
24109
+ }
24110
+ };
24111
+ enableControls(options.interactive ?? true);
24112
+ let frame = 0;
24113
+ let disposed = false;
24114
+ const renderLoop = () => {
24115
+ if (disposed) {
24116
+ return;
24117
+ }
24118
+ frame = requestAnimationFrame(renderLoop);
24119
+ controls?.update();
24120
+ renderer.render(scene, camera);
24121
+ };
24122
+ frame = requestAnimationFrame(renderLoop);
24123
+ /** Recursively dispose every geometry/material/texture under the model. */
24124
+ const disposeModel = () => {
24125
+ model.traverse((obj) => {
24126
+ const mesh = obj;
24127
+ mesh.geometry?.dispose();
24128
+ const material = mesh.material;
24129
+ if (Array.isArray(material)) {
24130
+ for (const m of material) {
24131
+ m.dispose();
24132
+ }
24133
+ }
24134
+ else {
24135
+ material?.dispose();
24136
+ }
24137
+ });
24138
+ };
24139
+ return {
24140
+ ok: true,
24141
+ resize(w, h) {
24142
+ const nextW = Math.max(1, w);
24143
+ const nextH = Math.max(1, h);
24144
+ camera.aspect = nextW / nextH;
24145
+ camera.updateProjectionMatrix();
24146
+ renderer.setSize(nextW, nextH, false);
24147
+ canvas.style.width = `${nextW}px`;
24148
+ canvas.style.height = `${nextH}px`;
24149
+ },
24150
+ setInteractive(on) {
24151
+ enableControls(on);
24152
+ },
24153
+ dispose() {
24154
+ if (disposed) {
24155
+ return;
24156
+ }
24157
+ disposed = true;
24158
+ cancelAnimationFrame(frame);
24159
+ controls?.dispose();
24160
+ disposeModel();
24161
+ scene.clear();
24162
+ renderer.dispose();
24163
+ canvas.remove();
24164
+ },
24165
+ };
24166
+ }
24167
+
24168
+ /**
24169
+ * Pure surface-chart 3D geometry helpers (framework- and three-agnostic).
24170
+ *
24171
+ * The vanilla three scene controller ({@link mountSurfaceChart3D}) feeds these
24172
+ * the loaded `three` module so the heavy library stays a dynamic, optional
24173
+ * import while the grid maths (plane subdivision -> height/colour displacement,
24174
+ * world-space label anchors) lives here as small, separately testable units.
24175
+ */
24176
+ /** Half-unit spacing between adjacent grid points, in world units. */
24177
+ const GRID_SPACING = 0.5;
24178
+ /** Maximum height displacement (world units) for a normalised value of 1. */
24179
+ const MAX_HEIGHT = 1.5;
24180
+ /** Compute the world-space width/depth of the data grid. */
24181
+ function computeGridExtent(cols, rows) {
24182
+ return {
24183
+ gridWidth: Math.max(cols - 1, 1) * GRID_SPACING,
24184
+ gridDepth: Math.max(rows - 1, 1) * GRID_SPACING,
24185
+ };
24186
+ }
24187
+ /**
24188
+ * Build the surface mesh + wireframe geometries from the normalised height and
24189
+ * colour maps. The plane is subdivided to match the data grid, rotated into the
24190
+ * XZ plane, then each vertex is displaced in Y by its height and tinted by its
24191
+ * colour. Returns disposable geometries the caller adds to the scene.
24192
+ */
24193
+ function buildSurfaceGeometry(three, cols, rows, heightMap, colorMap) {
24194
+ const widthSegs = Math.max(cols - 1, 0);
24195
+ const depthSegs = Math.max(rows - 1, 0);
24196
+ const { gridWidth, gridDepth } = computeGridExtent(cols, rows);
24197
+ const geo = new three.PlaneGeometry(gridWidth, gridDepth, widthSegs, depthSegs);
24198
+ // PlaneGeometry lies in the XY plane by default; rotate it flat into XZ.
24199
+ geo.rotateX(-Math.PI / 2);
24200
+ const pos = geo.attributes.position;
24201
+ const vertexCount = pos.count;
24202
+ const colors = new Float32Array(vertexCount * 3);
24203
+ for (let i = 0; i < vertexCount; i++) {
24204
+ // After the rotation, the (widthSegs+1) x (depthSegs+1) vertices stay in
24205
+ // row-major order, so col = i % cols and row = floor(i / cols) map onto the
24206
+ // data grid directly.
24207
+ const row = Math.floor(i / cols);
24208
+ const col = i % cols;
24209
+ const idx = row * cols + col;
24210
+ const h = idx < heightMap.length ? heightMap[idx] : 0;
24211
+ pos.setY(i, h * MAX_HEIGHT);
24212
+ const ci = idx * 3;
24213
+ colors[i * 3] = ci < colorMap.length ? colorMap[ci] : 0.5;
24214
+ colors[i * 3 + 1] = ci + 1 < colorMap.length ? colorMap[ci + 1] : 0.5;
24215
+ colors[i * 3 + 2] = ci + 2 < colorMap.length ? colorMap[ci + 2] : 0.5;
24216
+ }
24217
+ geo.setAttribute('color', new three.BufferAttribute(colors, 3));
24218
+ geo.computeVertexNormals();
24219
+ pos.needsUpdate = true;
24220
+ const wireGeometry = new three.WireframeGeometry(geo);
24221
+ return { geometry: geo, wireGeometry };
24222
+ }
24223
+ /**
24224
+ * Build the axis labels (category along the front edge, series along the right
24225
+ * edge, plus a single "Value" Y-axis label) as world-anchored {@link
24226
+ * SurfaceLabel}s. Category/series labels are thinned to at most `maxCat`/`maxSer`
24227
+ * entries to avoid clutter, mirroring the prior drei `Html` layout.
24228
+ */
24229
+ function buildSurfaceLabels(cols, rows, categoryLabels, seriesNames, maxCat = 8, maxSer = 6) {
24230
+ const { gridWidth, gridDepth } = computeGridExtent(cols, rows);
24231
+ const labels = [];
24232
+ const catStep = Math.max(1, Math.ceil(categoryLabels.length / maxCat));
24233
+ for (let i = 0; i < categoryLabels.length; i += catStep) {
24234
+ const x = -gridWidth / 2 + (i / Math.max(cols - 1, 1)) * gridWidth;
24235
+ labels.push({
24236
+ key: `cat-${i}`,
24237
+ text: categoryLabels[i],
24238
+ anchor: [x, -0.15, gridDepth / 2 + 0.25],
24239
+ axis: 'category',
24240
+ });
24241
+ }
24242
+ const serStep = Math.max(1, Math.ceil(seriesNames.length / maxSer));
24243
+ for (let i = 0; i < seriesNames.length; i += serStep) {
24244
+ const z = -gridDepth / 2 + (i / Math.max(rows - 1, 1)) * gridDepth;
24245
+ labels.push({
24246
+ key: `ser-${i}`,
24247
+ text: seriesNames[i],
24248
+ anchor: [gridWidth / 2 + 0.3, -0.15, z],
24249
+ axis: 'series',
24250
+ });
24251
+ }
24252
+ labels.push({
24253
+ key: 'value-axis',
24254
+ text: 'Value',
24255
+ anchor: [-gridWidth / 2 - 0.35, 0.75, -gridDepth / 2],
24256
+ axis: 'value',
24257
+ });
24258
+ return labels;
24259
+ }
24260
+ /** Isometric-like camera placement that frames the whole grid. */
24261
+ function computeCameraPlacement(cols, rows) {
24262
+ const { gridWidth, gridDepth } = computeGridExtent(cols, rows);
24263
+ const maxExtent = Math.max(gridWidth, gridDepth, MAX_HEIGHT);
24264
+ const dist = maxExtent * 1.8;
24265
+ return {
24266
+ position: [dist * 0.8, dist * 0.7, dist * 0.8],
24267
+ target: [0, 0.3, 0],
24268
+ };
24269
+ }
24270
+
24271
+ /**
24272
+ * Vanilla three.js 3D surface-chart scene controller (framework-agnostic).
24273
+ *
24274
+ * Mounts an interactive surface chart into a caller-provided container element:
24275
+ * dynamically imports `three` plus its `OrbitControls` addon, builds a colour-
24276
+ * displaced surface mesh (with optional wireframe), grid floor, lights, and an
24277
+ * isometric camera, renders with a RAF loop, and overlays axis labels as DOM
24278
+ * nodes that are re-projected to screen each frame. Exposes `dispose()` for
24279
+ * deterministic teardown of GPU resources, listeners, and overlay nodes.
24280
+ *
24281
+ * `three` is an OPTIONAL peer dependency: every import is dynamic and guarded.
24282
+ * When `three` (or its OrbitControls addon) is missing, {@link mountSurfaceChart3D}
24283
+ * resolves to a sentinel handle ({@link SURFACE_THREE_UNAVAILABLE}) so the caller
24284
+ * can fall back to the 2D renderer. No framework imports - React, Vue, and
24285
+ * Angular bindings can all mount it.
24286
+ */
24287
+ /** No-op sentinel returned when `three` or its OrbitControls addon is missing. */
24288
+ const SURFACE_THREE_UNAVAILABLE = {
24289
+ ok: false,
24290
+ resize: () => { },
24291
+ dispose: () => { },
24292
+ };
24293
+ const FOV = 45;
24294
+ /** Dynamically load `three`; returns `null` when the package is not installed. */
24295
+ async function loadThree() {
24296
+ try {
24297
+ return (await import('three'));
24298
+ }
24299
+ catch {
24300
+ return null;
24301
+ }
24302
+ }
24303
+ /** Dynamically load the OrbitControls addon; returns `null` when unavailable. */
24304
+ async function loadOrbitControlsCtor() {
24305
+ try {
24306
+ const mod = await import('three/examples/jsm/controls/OrbitControls.js');
24307
+ return mod.OrbitControls;
24308
+ }
24309
+ catch {
24310
+ return null;
24311
+ }
24312
+ }
24313
+ /** Create the DOM overlay nodes for the axis labels, returned with the layer. */
24314
+ function createLabelOverlay(doc, labels) {
24315
+ const layer = doc.createElement('div');
24316
+ Object.assign(layer.style, {
24317
+ position: 'absolute',
24318
+ inset: '0',
24319
+ pointerEvents: 'none',
24320
+ overflow: 'hidden',
24321
+ });
24322
+ const nodes = labels.map((label) => {
24323
+ const node = doc.createElement('div');
24324
+ node.textContent = label.text;
24325
+ const color = label.axis === 'value' ? '#999' : '#666';
24326
+ Object.assign(node.style, {
24327
+ position: 'absolute',
24328
+ fontSize: '9px',
24329
+ color,
24330
+ whiteSpace: 'nowrap',
24331
+ userSelect: 'none',
24332
+ transform: 'translate(-50%, -50%)',
24333
+ willChange: 'left, top',
24334
+ });
24335
+ if (label.axis === 'value') {
24336
+ node.style.writingMode = 'vertical-rl';
24337
+ }
24338
+ layer.appendChild(node);
24339
+ return node;
24340
+ });
24341
+ return { layer, nodes };
24342
+ }
24343
+ /**
24344
+ * Mount an interactive 3D surface chart into `container` and start rendering.
24345
+ *
24346
+ * Resolves to {@link SURFACE_THREE_UNAVAILABLE} when `three` or its OrbitControls
24347
+ * addon cannot be loaded, so the caller can fall back to a 2D surface renderer.
24348
+ */
24349
+ async function mountSurfaceChart3D(container, options) {
24350
+ const three = await loadThree();
24351
+ const OrbitCtrlCtor = three ? await loadOrbitControlsCtor() : null;
24352
+ if (!three || !OrbitCtrlCtor) {
24353
+ return SURFACE_THREE_UNAVAILABLE;
24354
+ }
24355
+ const { cols, rows } = options;
24356
+ let width = Math.max(1, options.width);
24357
+ let height = Math.max(1, options.height);
24358
+ const renderer = new three.WebGLRenderer({ antialias: true, alpha: true });
24359
+ renderer.setPixelRatio(Math.min(typeof window === 'undefined' ? 1 : window.devicePixelRatio || 1, options.maxPixelRatio ?? 2));
24360
+ renderer.setSize(width, height, false);
24361
+ const canvas = renderer.domElement;
24362
+ canvas.style.width = `${width}px`;
24363
+ canvas.style.height = `${height}px`;
24364
+ canvas.style.display = 'block';
24365
+ canvas.style.willChange = 'transform';
24366
+ container.appendChild(canvas);
24367
+ const scene = new three.Scene();
24368
+ scene.add(new three.AmbientLight(0xffffff, 0.6));
24369
+ const key = new three.DirectionalLight(0xffffff, 0.8);
24370
+ key.position.set(5, 8, 5);
24371
+ scene.add(key);
24372
+ const fill = new three.DirectionalLight(0xffffff, 0.3);
24373
+ fill.position.set(-3, 4, -2);
24374
+ scene.add(fill);
24375
+ const camera = new three.PerspectiveCamera(FOV, width / height, 0.1, 1000);
24376
+ const placement = computeCameraPlacement(cols, rows);
24377
+ camera.position.set(...placement.position);
24378
+ const target = new three.Vector3(...placement.target);
24379
+ camera.lookAt(target);
24380
+ // Grid floor under the surface.
24381
+ const { gridWidth, gridDepth } = computeGridExtent(cols, rows);
24382
+ const floorSize = Math.max(gridWidth, gridDepth) * 1.2;
24383
+ const gridFloor = new three.GridHelper(floorSize, Math.max(cols, rows), 0xcccccc, 0xe8e8e8);
24384
+ gridFloor.position.y = -0.02;
24385
+ scene.add(gridFloor);
24386
+ // Surface mesh + optional wireframe.
24387
+ const { geometry, wireGeometry } = buildSurfaceGeometry(three, cols, rows, options.heightMap, options.colorMap);
24388
+ const surfaceMaterial = new three.MeshPhongMaterial({
24389
+ vertexColors: true,
24390
+ side: three.DoubleSide,
24391
+ shininess: 30,
24392
+ transparent: true,
24393
+ opacity: 0.92,
24394
+ });
24395
+ const surfaceMesh = new three.Mesh(geometry, surfaceMaterial);
24396
+ scene.add(surfaceMesh);
24397
+ let wireMaterial = null;
24398
+ if (options.wireframe) {
24399
+ wireMaterial = new three.LineBasicMaterial({
24400
+ color: 0x333333,
24401
+ transparent: true,
24402
+ opacity: 0.25,
24403
+ });
24404
+ scene.add(new three.LineSegments(wireGeometry, wireMaterial));
24405
+ }
24406
+ const controls = new OrbitCtrlCtor(camera, canvas);
24407
+ controls.enablePan = true;
24408
+ controls.enableZoom = true;
24409
+ controls.enableRotate = true;
24410
+ controls.minDistance = 1;
24411
+ controls.maxDistance = 20;
24412
+ controls.maxPolarAngle = Math.PI / 2 + 0.3;
24413
+ controls.target.copy(target);
24414
+ controls.update();
24415
+ // Axis-label DOM overlay, re-projected to screen each frame.
24416
+ const doc = container.ownerDocument ?? document;
24417
+ const labels = buildSurfaceLabels(cols, rows, options.categoryLabels, options.seriesNames);
24418
+ const { layer, nodes } = createLabelOverlay(doc, labels);
24419
+ container.appendChild(layer);
24420
+ const anchors = labels.map((l) => new three.Vector3(...l.anchor));
24421
+ const projected = new three.Vector3();
24422
+ const updateLabels = () => {
24423
+ for (let i = 0; i < nodes.length; i++) {
24424
+ projected.copy(anchors[i]).project(camera);
24425
+ const node = nodes[i];
24426
+ // Hide labels behind the camera.
24427
+ if (projected.z > 1) {
24428
+ node.style.display = 'none';
24429
+ continue;
24430
+ }
24431
+ node.style.display = '';
24432
+ node.style.left = `${((projected.x + 1) / 2) * width}px`;
24433
+ node.style.top = `${((-projected.y + 1) / 2) * height}px`;
24434
+ }
24435
+ };
24436
+ let frame = 0;
24437
+ let disposed = false;
24438
+ const renderLoop = () => {
24439
+ if (disposed) {
24440
+ return;
24441
+ }
24442
+ frame = requestAnimationFrame(renderLoop);
24443
+ controls.update();
24444
+ renderer.render(scene, camera);
24445
+ updateLabels();
24446
+ };
24447
+ frame = requestAnimationFrame(renderLoop);
24448
+ return {
24449
+ ok: true,
24450
+ resize(w, h) {
24451
+ width = Math.max(1, w);
24452
+ height = Math.max(1, h);
24453
+ camera.aspect = width / height;
24454
+ camera.updateProjectionMatrix();
24455
+ renderer.setSize(width, height, false);
24456
+ canvas.style.width = `${width}px`;
24457
+ canvas.style.height = `${height}px`;
24458
+ },
24459
+ dispose() {
24460
+ if (disposed) {
24461
+ return;
24462
+ }
24463
+ disposed = true;
24464
+ cancelAnimationFrame(frame);
24465
+ controls.dispose();
24466
+ geometry.dispose();
24467
+ wireGeometry.dispose();
24468
+ surfaceMaterial.dispose();
24469
+ wireMaterial?.dispose();
24470
+ gridFloor.dispose();
24471
+ scene.clear();
24472
+ renderer.dispose();
24473
+ canvas.remove();
24474
+ layer.remove();
24475
+ },
24476
+ };
24477
+ }
24478
+
23969
24479
  /**
23970
24480
  * smartart-drawing.ts: Drawing-shape view-model helpers for the SmartArt
23971
24481
  * renderer, shared across the React, Vue, and Angular bindings.