@fusion.dev/helix-plot 0.193.0 → 0.195.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.
@@ -43,6 +43,16 @@ export interface Place {
43
43
  x: number;
44
44
  y: number;
45
45
  }
46
+ /**
47
+ * Where a spatial heatmap's named corners are drawn: the two ends of its
48
+ * front edge, the long edge at the foot of the slab nearer the reader, and
49
+ * the corners of the box round the relief, which the labels stand clear of.
50
+ */
51
+ export interface Corners {
52
+ end: Place;
53
+ outline?: Place[];
54
+ start: Place;
55
+ }
46
56
  /**
47
57
  * What a renderer has to answer for a 3D chart to drive it, and nothing that
48
58
  * is the same for every renderer: that is the connector's.
@@ -66,14 +76,12 @@ export interface PlotDriver<F> {
66
76
  onPlace?(place: (at: Place | undefined) => void): () => void;
67
77
  /**
68
78
  * Calls `corners` with where the drawing's named corners are drawn, the
69
- * two ends of a spatial heatmap's front edge, whenever a frame moves them,
70
- * and with `undefined` while they are out of view. The chart stands its
71
- * first and last labels there. Returns what stops listening.
79
+ * two ends of a spatial heatmap's front edge and the corners of the box
80
+ * round it, whenever a frame moves them, and with `undefined` while they
81
+ * are out of view. The chart stands its first and last labels there, clear
82
+ * of the box. Returns what stops listening.
72
83
  */
73
- onCorners?(corners: (at: {
74
- end: Place;
75
- start: Place;
76
- } | undefined) => void): () => void;
84
+ onCorners?(corners: (at: Corners | undefined) => void): () => void;
77
85
  /**
78
86
  * Calls `press` with a reading's index when the reader presses it in the
79
87
  * drawing, and with `-1` when they press where none is. Returns what stops
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { type Axes, type Axis, type Place, type PlotColors, type PlotConnection, type PlotDriver, type PointerKind, plotColorsFrom, } from './connect-plot/connect-plot.js';
1
+ export { type Axes, type Axis, type Corners, type Place, type PlotColors, type PlotConnection, type PlotDriver, type PointerKind, plotColorsFrom, } from './connect-plot/connect-plot.js';
2
2
  export { connectSpatialHeatmap, type HeatmapCell, type SpatialHeatmapConnection, type SpatialHeatmapDriver, type SpatialHeatmapField, } from './connect-spatial-heatmap/connect-spatial-heatmap.js';
3
3
  export { connectSpatialScatterChart, type Field, type Point, type SpatialScatterChartConnection, type SpatialScatterChartDriver, } from './connect-spatial-scatter-chart/connect-spatial-scatter-chart.js';
4
4
  export { connectSpatialSurfaceChart, type SpatialSurfaceChartConnection, type SpatialSurfaceChartDriver, type SurfaceCell, type SurfaceField, } from './connect-spatial-surface-chart/connect-spatial-surface-chart.js';
@@ -89,8 +89,24 @@ export const threeHeatmapDriver = (container, options = {}) => {
89
89
  framed = true;
90
90
  unfollow?.();
91
91
  const foot = FLOOR - THICKNESS * unit;
92
- unfollow = stage.track([new Vector3(-wide / 2, foot, deep / 2), new Vector3(wide / 2, foot, deep / 2)], (at) => {
93
- cornered?.(at ? { end: at[1], start: at[0] } : undefined);
92
+ const corners = [];
93
+ for (const x of [-wide / 2, wide / 2]) {
94
+ for (const z of [deep / 2, -deep / 2]) {
95
+ corners.push(new Vector3(x, foot, z), new Vector3(x, foot + high, z));
96
+ }
97
+ }
98
+ unfollow = stage.track(corners, (at) => {
99
+ if (!at) {
100
+ cornered?.(undefined);
101
+ return;
102
+ }
103
+ const [lastStart, firstStart, lastEnd, firstEnd] = [0, 2, 4, 6].map((index) => at[index]);
104
+ const last = lastStart.y + lastEnd.y >= firstStart.y + firstEnd.y;
105
+ cornered?.({
106
+ end: last ? lastEnd : firstEnd,
107
+ outline: at,
108
+ start: last ? lastStart : firstStart,
109
+ });
94
110
  });
95
111
  },
96
112
  focus: (index) => {
@@ -1,4 +1,4 @@
1
- import { pageBudget } from '@fusion.dev/helix-core';
1
+ import { boxLabels, pageBudget, } from '@fusion.dev/helix-core';
2
2
  import { BufferGeometry, CanvasTexture, Float32BufferAttribute, LineBasicMaterial, LineSegments, PerspectiveCamera, Raycaster, Scene, Spherical, Sprite, SpriteMaterial, Vector2, Vector3, WebGLRenderer, } from 'three';
3
3
  import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
4
4
  const STEP = Math.PI / 12;
@@ -63,7 +63,8 @@ export const createStage = (container, options) => {
63
63
  container.append(canvas);
64
64
  const labels = document.createElement('div');
65
65
  labels.setAttribute('aria-hidden', 'true');
66
- labels.style.cssText = 'position:absolute;inset:0;pointer-events:none;overflow:hidden';
66
+ labels.style.cssText =
67
+ 'position:absolute;inset:0;pointer-events:none;overflow:hidden;user-select:none;-webkit-user-select:none';
67
68
  container.append(labels);
68
69
  const scene = new Scene();
69
70
  const camera = new PerspectiveCamera(35, 1, 0.1, 100);
@@ -79,6 +80,8 @@ export const createStage = (container, options) => {
79
80
  controls.minDistance = 2.5;
80
81
  controls.maxDistance = 12;
81
82
  controls.rotateSpeed = 0.7;
83
+ controls.minPolarAngle = 0.15;
84
+ controls.maxPolarAngle = Math.PI / 2 - 0.02;
82
85
  const gridMaterial = new LineBasicMaterial({ opacity: 0.35, transparent: true });
83
86
  const grid = new LineSegments(new BufferGeometry(), gridMaterial);
84
87
  scene.add(grid);
@@ -138,34 +141,30 @@ export const createStage = (container, options) => {
138
141
  const width = container.clientWidth;
139
142
  const height = container.clientHeight;
140
143
  const marks = [];
141
- const label = (at, text, strong = false) => {
142
- const on = at.clone().project(camera);
143
- if (on.z > 1) {
144
- return;
145
- }
144
+ const label = (left, top, text, strong = false) => {
146
145
  const mark = document.createElement('span');
147
146
  mark.textContent = text;
148
147
  const size = strong ? 'var(--helix-text-sm, 13px)' : 'var(--helix-text-xs, 11px)';
149
148
  const weight = strong
150
149
  ? 'var(--helix-weight-semibold, 600)'
151
150
  : 'var(--helix-weight-medium, 500)';
152
- mark.style.cssText = `position:absolute;left:${((on.x + 1) / 2) * width}px;top:${((1 - on.y) / 2) * height}px;translate:-50% -50%;white-space:nowrap;font-family:${font};font-size:${size};font-weight:${weight};line-height:1;color:${strong ? colors?.ink : colors?.quiet};text-shadow:0 0 3px ${colors?.ground},0 0 3px ${colors?.ground}`;
151
+ mark.style.cssText = `position:absolute;left:${left}px;top:${top}px;translate:-50% -50%;white-space:nowrap;font-family:${font};font-size:${size};font-weight:${weight};line-height:1;color:${strong ? colors?.ink : colors?.quiet};text-shadow:0 0 3px ${colors?.ground},0 0 3px ${colors?.ground}`;
153
152
  marks.push(mark);
154
153
  };
155
- const h = HALF;
156
- const { x, y, z } = axes;
157
- for (const tick of x.ticks) {
158
- label(new Vector3(inBox(x, tick), -h, h + 0.12), tickText(tick));
159
- }
160
- for (const tick of z.ticks) {
161
- label(new Vector3(h + 0.12, -h, inBox(z, tick)), tickText(tick));
162
- }
163
- for (const tick of y.ticks.slice(1)) {
164
- label(new Vector3(-h - 0.12, inBox(y, tick), h), tickText(tick));
154
+ const place = new Vector3();
155
+ const seen = ([x, y, z]) => {
156
+ place.set(x * HALF, y * HALF, z * HALF);
157
+ const depth = -place.distanceTo(camera.position);
158
+ const on = place.project(camera);
159
+ return { depth, x: ((on.x + 1) / 2) * width, y: ((1 - on.y) / 2) * height };
160
+ };
161
+ const text = (axis) => ({
162
+ name: axis.name,
163
+ ticks: axis.ticks.map((tick) => ({ at: inBox(axis, tick) / HALF, text: tickText(tick) })),
164
+ });
165
+ for (const one of boxLabels(seen, { x: text(axes.x), y: text(axes.y), z: text(axes.z) }, { gap: 6, name: 13, tick: 11, wide: 0.58 })) {
166
+ label(one.x, one.y, one.text, one.name);
165
167
  }
166
- label(new Vector3(0, -h, h + 0.35), x.name, true);
167
- label(new Vector3(h + 0.62, -h, 0), z.name, true);
168
- label(new Vector3(-h, h + 0.28, h), y.name, true);
169
168
  labels.replaceChildren(...marks);
170
169
  for (const mark of marks) {
171
170
  const half = mark.offsetWidth / 2;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fusion.dev/helix-plot",
3
3
  "description": "Connects the Helix Spatial Heatmap, Spatial Scatter Chart, and Spatial Surface Chart to three.js, which draws them in three dimensions.",
4
- "version": "0.193.0",
4
+ "version": "0.195.0",
5
5
  "license": "MIT",
6
6
  "author": "Ross Sheppard <ross.sheppard@fusion.dev>",
7
7
  "repository": {
@@ -35,11 +35,11 @@
35
35
  },
36
36
  "bugs": "https://github.com/project-fusion/fusion/issues",
37
37
  "dependencies": {
38
- "@fusion.dev/helix-core": "0.193.0"
38
+ "@fusion.dev/helix-core": "0.195.0"
39
39
  },
40
40
  "devDependencies": {
41
- "@fusion.dev/helix-headless": "0.193.0",
42
- "@fusion.dev/helix-testing": "0.193.0",
41
+ "@fusion.dev/helix-headless": "0.195.0",
42
+ "@fusion.dev/helix-testing": "0.195.0",
43
43
  "@types/bun": "^1.4.2",
44
44
  "@types/three": "^0.186.0",
45
45
  "three": "^0.186.1"