@pie-players/pie-tool-graph 0.3.43 → 0.3.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -5,4 +5,3 @@
5
5
  * Import the built version for CDN usage, or the .svelte source for Svelte projects.
6
6
  */
7
7
  export {};
8
- //# sourceMappingURL=index.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pie-players/pie-tool-graph",
3
- "version": "0.3.43",
3
+ "version": "0.3.44",
4
4
  "type": "module",
5
5
  "description": "Interactive coordinate grid graphing tool for PIE assessment player",
6
6
  "repository": {
@@ -20,27 +20,24 @@
20
20
  "coordinate",
21
21
  "geometry"
22
22
  ],
23
- "svelte": "./tool-graph.svelte",
24
23
  "main": "./dist/tool-graph.js",
25
24
  "exports": {
26
25
  ".": {
27
26
  "types": "./dist/index.d.ts",
28
- "import": "./dist/tool-graph.js",
29
- "svelte": "./tool-graph.svelte"
27
+ "import": "./dist/tool-graph.js"
30
28
  }
31
29
  },
32
30
  "files": [
33
31
  "dist",
34
- "tool-graph.svelte",
35
32
  "package.json"
36
33
  ],
37
34
  "license": "MIT",
38
35
  "unpkg": "./dist/tool-graph.js",
39
36
  "jsdelivr": "./dist/tool-graph.js",
40
37
  "dependencies": {
41
- "@pie-players/pie-assessment-toolkit": "0.3.43",
42
- "@pie-players/pie-context": "0.3.43",
43
- "@pie-players/pie-players-shared": "0.3.43"
38
+ "@pie-players/pie-assessment-toolkit": "0.3.44",
39
+ "@pie-players/pie-context": "0.3.44",
40
+ "@pie-players/pie-players-shared": "0.3.44"
44
41
  },
45
42
  "types": "./dist/index.d.ts",
46
43
  "scripts": {
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
package/tool-graph.svelte DELETED
@@ -1,734 +0,0 @@
1
- <svelte:options
2
- customElement={{
3
- tag: 'pie-tool-graph',
4
- shadow: 'open',
5
- props: {
6
- visible: { type: 'Boolean', attribute: 'visible' },
7
- toolId: { type: 'String', attribute: 'tool-id' }
8
- }
9
- }}
10
- />
11
-
12
- <script lang="ts">
13
-
14
- import { connectToolRuntimeContext } from '@pie-players/pie-assessment-toolkit';
15
-
16
- // Props
17
- let {
18
- visible = false,
19
- toolId = 'graph'
20
- }: {
21
- visible?: boolean;
22
- toolId?: string;
23
- } = $props();
24
-
25
- // Tool types (matching production implementation)
26
- type Tool = 'selector' | 'point' | 'line' | 'delete';
27
-
28
- // Data structures (matching production implementation)
29
- interface Point {
30
- id: number;
31
- x: number; // Coordinate in the dynamic viewBox space
32
- y: number; // Coordinate in the fixed 0-100 viewBox height
33
- }
34
-
35
- interface Line {
36
- id: number;
37
- p1Id: number;
38
- p2Id: number;
39
- }
40
-
41
- interface Coordinates {
42
- x: number;
43
- y: number;
44
- }
45
-
46
- // State
47
- let canvasWrapperEl = $state<HTMLDivElement | undefined>();
48
- let containerEl = $state<HTMLDivElement | undefined>();
49
- let svgCanvasEl = $state<SVGSVGElement | undefined>();
50
-
51
- // Tool state
52
- let currentTool = $state<Tool>('selector');
53
- let points = $state<Point[]>([]);
54
- let lines = $state<Line[]>([]);
55
- let nextId = $state(1);
56
- let gridOpacity = $state(1);
57
- let tempLineStartPointId = $state<number | null>(null);
58
- let draggingPointId = $state<number | null>(null);
59
- let currentPointerPos = $state<Coordinates | null>(null);
60
-
61
- // Grid configuration (matching production implementation)
62
- const MAJOR_VERTICAL_DIVISIONS = 5; // Fixed number of rows
63
- const SUBGRID_DIVISIONS = 5; // 5x5 minor grid
64
- const DESIRED_MAJOR_CELL_SIZE_SVG = 100 / MAJOR_VERTICAL_DIVISIONS; // 100 / 5 = 20 units
65
- const DESIRED_MINOR_CELL_SIZE_SVG = DESIRED_MAJOR_CELL_SIZE_SVG / SUBGRID_DIVISIONS; // 20 / 5 = 4 units
66
-
67
- // Container pixel dimensions (from ResizeObserver)
68
- let containerPixelWidth = $state(0);
69
- let containerPixelHeight = $state(0);
70
-
71
- // Dynamic viewBox width (matching production implementation)
72
- let viewBoxWidth = $derived.by(() => {
73
- if (containerPixelHeight <= 0 || containerPixelWidth <= 0) {
74
- return 100; // Default width until dimensions are known
75
- }
76
- // Calculate the vertical scaling factor: pixels per SVG unit
77
- const scaleY = containerPixelHeight / 100; // (viewBox height is 100)
78
- // Convert container pixel width back into SVG units using this scale
79
- const requiredWidthSVG = containerPixelWidth / scaleY;
80
- // Ensure a minimum width
81
- return Math.max(100, requiredWidthSVG);
82
- });
83
-
84
- // Tool definitions (matching production implementation)
85
- const tools: Array<{ name: Tool; icon: string; label: string; title: string }> = [
86
- {
87
- name: 'selector',
88
- icon: 'selector',
89
- label: 'Selector',
90
- title: 'Selector: Click and drag points to move them or associated lines.'
91
- },
92
- {
93
- name: 'point',
94
- icon: 'point',
95
- label: 'Point',
96
- title: 'Point: Click on the grid to add points.'
97
- },
98
- {
99
- name: 'line',
100
- icon: 'line',
101
- label: 'Line',
102
- title: 'Line: Click a starting point, then an ending point to draw a line.'
103
- },
104
- {
105
- name: 'delete',
106
- icon: 'delete',
107
- label: 'Delete',
108
- title: 'Delete: Click on a point to delete it and any connected lines.'
109
- }
110
- ];
111
-
112
- $effect(() => {
113
- if (!containerEl) return;
114
- return connectToolRuntimeContext(containerEl, () => undefined);
115
- });
116
-
117
- // Helper functions
118
- function getUniqueId(): number {
119
- return nextId++;
120
- }
121
-
122
- function getPointById(id: number | null): Point | undefined {
123
- if (id === null) return undefined;
124
- return points.find((p) => p.id === id);
125
- }
126
-
127
- function getSVGCoordinates(event: MouseEvent | PointerEvent): Coordinates | null {
128
- if (!svgCanvasEl) return null;
129
- const pt = svgCanvasEl.createSVGPoint();
130
- pt.x = event.clientX;
131
- pt.y = event.clientY;
132
- const svgScreenCTM = svgCanvasEl.getScreenCTM();
133
- if (!svgScreenCTM) return null;
134
-
135
- try {
136
- const transformedPt = pt.matrixTransform(svgScreenCTM.inverse());
137
-
138
- // Clamp Y to 0-100 (fixed viewBox height)
139
- transformedPt.y = Math.max(0, Math.min(100, transformedPt.y));
140
- // Clamp X to 0 to current viewBoxWidth
141
- transformedPt.x = Math.max(0, Math.min(viewBoxWidth, transformedPt.x));
142
-
143
- return { x: transformedPt.x, y: transformedPt.y };
144
- } catch (e) {
145
- console.error('Error transforming point:', e);
146
- return null;
147
- }
148
- }
149
-
150
- function findNearestPoint(coords: Coordinates, threshold: number = 5): Point | null {
151
- const thresholdSq = threshold * threshold;
152
- let nearest: Point | null = null;
153
- let minDistSq = Infinity;
154
-
155
- for (const point of points) {
156
- const dx = point.x - coords.x;
157
- const dy = point.y - coords.y;
158
- const distSq = dx * dx + dy * dy;
159
-
160
- if (distSq < minDistSq && distSq < thresholdSq) {
161
- minDistSq = distSq;
162
- nearest = point;
163
- }
164
- }
165
- return nearest;
166
- }
167
-
168
- function isPointHighlighted(pointId: number): boolean {
169
- return pointId === tempLineStartPointId || pointId === draggingPointId;
170
- }
171
-
172
- function activatePoint(pointId: number) {
173
- if (currentTool === 'delete') {
174
- points = points.filter((p) => p.id !== pointId);
175
- lines = lines.filter((l) => l.p1Id !== pointId && l.p2Id !== pointId);
176
- return;
177
- }
178
-
179
- if (currentTool === 'line') {
180
- if (tempLineStartPointId === null) {
181
- tempLineStartPointId = pointId;
182
- return;
183
- }
184
-
185
- if (tempLineStartPointId !== pointId) {
186
- const exists = lines.some(
187
- (l) =>
188
- (l.p1Id === tempLineStartPointId && l.p2Id === pointId) ||
189
- (l.p1Id === pointId && l.p2Id === tempLineStartPointId)
190
- );
191
- if (!exists) {
192
- lines = [...lines, { id: getUniqueId(), p1Id: tempLineStartPointId, p2Id: pointId }];
193
- }
194
- }
195
- tempLineStartPointId = null;
196
- currentPointerPos = null;
197
- }
198
- }
199
-
200
- // Computed grid lines (matching production implementation)
201
- let gridLines = $derived.by(() => {
202
- const lines = {
203
- majorVertical: [] as number[],
204
- majorHorizontal: [] as number[],
205
- minorVertical: [] as number[],
206
- minorHorizontal: [] as number[]
207
- };
208
- const currentViewBoxWidth = viewBoxWidth;
209
-
210
- // Horizontal Lines (Fixed Y, extend across current viewBox width)
211
- for (let i = 0; i <= MAJOR_VERTICAL_DIVISIONS; i++) {
212
- const yPos = i * DESIRED_MAJOR_CELL_SIZE_SVG; // 0, 20, 40, 60, 80, 100
213
- lines.majorHorizontal.push(yPos);
214
- if (i < MAJOR_VERTICAL_DIVISIONS) {
215
- for (let j = 1; j < SUBGRID_DIVISIONS; j++) {
216
- lines.minorHorizontal.push(yPos + j * DESIRED_MINOR_CELL_SIZE_SVG);
217
- }
218
- }
219
- }
220
-
221
- // Vertical Lines (Fixed X spacing, up to current viewBox width)
222
- let currentX = 0;
223
- while (currentX <= currentViewBoxWidth) {
224
- lines.majorVertical.push(currentX);
225
- if (currentX < currentViewBoxWidth) {
226
- for (let j = 1; j < SUBGRID_DIVISIONS; j++) {
227
- const minorX = currentX + j * DESIRED_MINOR_CELL_SIZE_SVG;
228
- if (minorX <= currentViewBoxWidth) {
229
- lines.minorVertical.push(minorX);
230
- } else {
231
- break;
232
- }
233
- }
234
- }
235
- if (DESIRED_MAJOR_CELL_SIZE_SVG <= 0) break;
236
- currentX += DESIRED_MAJOR_CELL_SIZE_SVG;
237
- }
238
-
239
- return lines;
240
- });
241
-
242
- // Event handlers
243
- function setTool(tool: Tool) {
244
- currentTool = tool;
245
- tempLineStartPointId = null;
246
- draggingPointId = null;
247
- currentPointerPos = null;
248
- }
249
-
250
- function handleCanvasClick(event: MouseEvent) {
251
- const coords = getSVGCoordinates(event);
252
- if (!coords) return;
253
-
254
- const nearestPoint = findNearestPoint(coords, DESIRED_MINOR_CELL_SIZE_SVG);
255
-
256
- switch (currentTool) {
257
- case 'point':
258
- // Add point exactly where clicked in the current viewBox space
259
- points = [...points, { id: getUniqueId(), x: coords.x, y: coords.y }];
260
- break;
261
-
262
- case 'line':
263
- // If near an existing point, use it. Otherwise, create a new one.
264
- const targetPoint = nearestPoint ?? { id: getUniqueId(), x: coords.x, y: coords.y };
265
- if (!nearestPoint) {
266
- points = [...points, targetPoint]; // Add if it's a new location
267
- }
268
-
269
- if (tempLineStartPointId === null) {
270
- tempLineStartPointId = targetPoint.id;
271
- } else {
272
- if (tempLineStartPointId !== targetPoint.id) {
273
- const exists = lines.some(
274
- (l) =>
275
- (l.p1Id === tempLineStartPointId && l.p2Id === targetPoint.id) ||
276
- (l.p1Id === targetPoint.id && l.p2Id === tempLineStartPointId)
277
- );
278
- if (!exists) {
279
- lines = [
280
- ...lines,
281
- {
282
- id: getUniqueId(),
283
- p1Id: tempLineStartPointId,
284
- p2Id: targetPoint.id
285
- }
286
- ];
287
- }
288
- }
289
- tempLineStartPointId = null;
290
- currentPointerPos = null;
291
- }
292
- break;
293
-
294
- case 'delete':
295
- // Use a smaller threshold for precise deletion
296
- const pointToDelete = findNearestPoint(coords, 2);
297
- if (pointToDelete) {
298
- // Remove the point
299
- points = points.filter((p) => p.id !== pointToDelete.id);
300
- // Remove lines connected to this point
301
- lines = lines.filter(
302
- (l) => l.p1Id !== pointToDelete.id && l.p2Id !== pointToDelete.id
303
- );
304
- }
305
- break;
306
-
307
- case 'selector':
308
- tempLineStartPointId = null; // Cancel line drawing if clicking away
309
- currentPointerPos = null;
310
- break;
311
- }
312
- }
313
-
314
- function handlePointPointerDown(pointId: number, event: PointerEvent) {
315
- if (currentTool !== 'selector') return;
316
- draggingPointId = pointId;
317
- (event.target as Element).setPointerCapture(event.pointerId);
318
- }
319
-
320
- function handleCanvasMouseMove(event: PointerEvent) {
321
- const coords = getSVGCoordinates(event);
322
- if (!coords) return;
323
- currentPointerPos = coords; // Update for temp line drawing
324
-
325
- if (draggingPointId !== null && currentTool === 'selector') {
326
- const point = getPointById(draggingPointId);
327
- if (point) {
328
- // Update stored coords (which are in the dynamic viewBox space)
329
- point.x = coords.x;
330
- point.y = coords.y; // Y is clamped 0-100 anyway
331
- points = points; // Trigger reactivity
332
- }
333
- }
334
- }
335
-
336
- function handlePointerUp(event: PointerEvent) {
337
- if (draggingPointId !== null) {
338
- if ((event.target as Element)?.hasPointerCapture(event.pointerId)) {
339
- (event.target as Element).releasePointerCapture(event.pointerId);
340
- }
341
- draggingPointId = null;
342
- }
343
- // Don't reset currentPointerPos if still drawing a line
344
- if (currentTool !== 'line' || tempLineStartPointId === null) {
345
- currentPointerPos = null;
346
- }
347
- }
348
-
349
- // ResizeObserver for dynamic viewBox width (matching production implementation)
350
- let resizeObserver: ResizeObserver | null = null;
351
- $effect(() => {
352
- if (canvasWrapperEl) {
353
- if (!resizeObserver) {
354
- resizeObserver = new ResizeObserver((entries) => {
355
- const entry = entries[0];
356
- const { width: wrapperWidth, height: wrapperHeight } = entry.contentRect;
357
-
358
- // Update pixel dimensions only if they actually changed
359
- if (
360
- Math.abs(containerPixelWidth - wrapperWidth) > 0.1 ||
361
- Math.abs(containerPixelHeight - wrapperHeight) > 0.1
362
- ) {
363
- containerPixelWidth = wrapperWidth;
364
- containerPixelHeight = wrapperHeight;
365
- }
366
- });
367
- }
368
- resizeObserver.observe(canvasWrapperEl);
369
-
370
- return () => {
371
- if (resizeObserver) {
372
- resizeObserver.disconnect();
373
- }
374
- };
375
- }
376
- });
377
-
378
- </script>
379
-
380
- {#if visible}
381
- <div
382
- bind:this={containerEl}
383
- class="pie-tool-graph"
384
- role="dialog"
385
- tabindex="-1"
386
- aria-label="Graph Tool - Draw points and lines on a coordinate grid"
387
- data-tool-id={toolId}
388
- >
389
- <!-- Toolbar (matching production implementation: lighter teal) -->
390
- <div class="pie-tool-graph__toolbar">
391
- <!-- Tool buttons -->
392
- <div class="pie-tool-graph__tool-buttons">
393
- {#each tools as tool (tool.name)}
394
- <button
395
- type="button"
396
- class="pie-tool-graph__tool-button"
397
- class:pie-tool-graph__tool-button--active={currentTool === tool.name}
398
- onclick={() => setTool(tool.name)}
399
- title={tool.title}
400
- aria-label={tool.title}
401
- aria-pressed={currentTool === tool.name}
402
- >
403
- <span class="pie-tool-graph__tool-icon" aria-hidden="true">
404
- {#if tool.name === 'selector'}
405
- <!-- Selector icon (swirling arrow) -->
406
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
407
- <path d="M3 3l7.07 16.97 2.51-7.39 7.39-2.51L3 3z" />
408
- </svg>
409
- {:else if tool.name === 'point'}
410
- <!-- Point icon (pushpin) -->
411
- <svg viewBox="0 0 24 24" fill="currentColor">
412
- <path
413
- d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"
414
- />
415
- </svg>
416
- {:else if tool.name === 'line'}
417
- <!-- Line icon (pencil) -->
418
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
419
- <path
420
- d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"
421
- />
422
- </svg>
423
- {:else if tool.name === 'delete'}
424
- <!-- Delete icon (trash) -->
425
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
426
- <path
427
- d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"
428
- />
429
- </svg>
430
- {/if}
431
- </span>
432
- <span class="pie-tool-graph__tool-label">{tool.label}</span>
433
- </button>
434
- {/each}
435
- </div>
436
-
437
- <!-- Grid opacity slider (matching production implementation) -->
438
- <div class="pie-tool-graph__transparency-control">
439
- <label for="grid-opacity">Grid:</label>
440
- <input
441
- type="range"
442
- id="grid-opacity"
443
- min="0"
444
- max="1"
445
- step="0.1"
446
- bind:value={gridOpacity}
447
- aria-label="Grid opacity"
448
- />
449
- </div>
450
- </div>
451
-
452
- <!-- Canvas wrapper -->
453
- <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
454
- <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
455
- <div
456
- bind:this={canvasWrapperEl}
457
- class="pie-tool-graph__canvas-wrapper"
458
- role="img"
459
- tabindex="0"
460
- aria-label="Graph canvas - Use tools to add points and draw lines"
461
- onclick={handleCanvasClick}
462
- onkeydown={(e) => {
463
- if (e.key === 'Enter' || e.key === ' ') {
464
- e.preventDefault();
465
- handleCanvasClick(e as any);
466
- }
467
- }}
468
- >
469
- <svg
470
- bind:this={svgCanvasEl}
471
- class="pie-tool-graph__canvas"
472
- viewBox="0 0 {viewBoxWidth} 100"
473
- preserveAspectRatio="xMinYMin meet"
474
- aria-hidden="true"
475
- onpointermove={handleCanvasMouseMove}
476
- onpointerup={handlePointerUp}
477
- onpointerleave={handlePointerUp}
478
- >
479
- <!-- Grid Lines -->
480
- <g class="pie-tool-graph__grid-lines" style="opacity: {gridOpacity}" aria-hidden="true">
481
- <!-- Minor Horizontal Lines -->
482
- {#each gridLines.minorHorizontal as yPos, index (index)}
483
- <line
484
- x1="0"
485
- y1={yPos}
486
- x2={viewBoxWidth}
487
- y2={yPos}
488
- class="pie-tool-graph__grid-line pie-tool-graph__grid-line--minor"
489
- />
490
- {/each}
491
- <!-- Major Horizontal Lines -->
492
- {#each gridLines.majorHorizontal as yPos, index (index)}
493
- <line
494
- x1="0"
495
- y1={yPos}
496
- x2={viewBoxWidth}
497
- y2={yPos}
498
- class="pie-tool-graph__grid-line pie-tool-graph__grid-line--major"
499
- />
500
- {/each}
501
-
502
- <!-- Minor Vertical Lines -->
503
- {#each gridLines.minorVertical as xPos, index (index)}
504
- <line
505
- x1={xPos}
506
- y1="0"
507
- x2={xPos}
508
- y2="100"
509
- class="pie-tool-graph__grid-line pie-tool-graph__grid-line--minor"
510
- />
511
- {/each}
512
- <!-- Major Vertical Lines -->
513
- {#each gridLines.majorVertical as xPos, index (index)}
514
- <line
515
- x1={xPos}
516
- y1="0"
517
- x2={xPos}
518
- y2="100"
519
- class="pie-tool-graph__grid-line pie-tool-graph__grid-line--major"
520
- />
521
- {/each}
522
- </g>
523
-
524
- <!-- Lines -->
525
- <g class="pie-tool-graph__lines">
526
- {#each lines as line (line.id)}
527
- {@const p1 = getPointById(line.p1Id)}
528
- {@const p2 = getPointById(line.p2Id)}
529
- {#if p1 && p2}
530
- <line
531
- x1={p1.x}
532
- y1={p1.y}
533
- x2={p2.x}
534
- y2={p2.y}
535
- class="pie-tool-graph__user-line"
536
- />
537
- {/if}
538
- {/each}
539
- </g>
540
-
541
- <!-- Points -->
542
- <g class="pie-tool-graph__points">
543
- {#each points as point (point.id)}
544
- <circle
545
- cx={point.x}
546
- cy={point.y}
547
- r="2"
548
- class="pie-tool-graph__user-point"
549
- class:pie-tool-graph__user-point--highlight={isPointHighlighted(point.id)}
550
- data-id={point.id}
551
- role="button"
552
- tabindex="0"
553
- aria-label="Graph point {point.id}"
554
- onpointerdown={(e) => {
555
- e.stopPropagation();
556
- handlePointPointerDown(point.id, e);
557
- }}
558
- onkeydown={(e) => {
559
- if (e.key === 'Enter' || e.key === ' ') {
560
- e.preventDefault();
561
- activatePoint(point.id);
562
- }
563
- }}
564
- />
565
- {/each}
566
- </g>
567
-
568
- <!-- Temporary line feedback -->
569
- {#if tempLineStartPointId && currentPointerPos}
570
- {@const startPoint = getPointById(tempLineStartPointId)}
571
- {#if startPoint}
572
- <line
573
- x1={startPoint.x}
574
- y1={startPoint.y}
575
- x2={currentPointerPos.x}
576
- y2={currentPointerPos.y}
577
- class="pie-tool-graph__temp-line"
578
- />
579
- {/if}
580
- {/if}
581
- </svg>
582
- </div>
583
- </div>
584
-
585
- {/if}
586
-
587
- <style>
588
- .pie-tool-graph {
589
- position: relative;
590
- background: var(--pie-background, #fff);
591
- color: var(--pie-text, #111827);
592
- width: 100%;
593
- height: 100%;
594
- min-height: 0;
595
- overflow: hidden;
596
- display: flex;
597
- flex-direction: column;
598
- }
599
-
600
- /* Toolbar (matching production implementation: lighter teal) */
601
- .pie-tool-graph__toolbar {
602
- padding: 8px;
603
- background: var(--pie-primary-light, #5a7fa3); /* Lighter teal-like color */
604
- display: flex;
605
- gap: 16px;
606
- align-items: center;
607
- flex-wrap: wrap;
608
- }
609
-
610
- .pie-tool-graph__tool-buttons {
611
- display: flex;
612
- gap: 4px;
613
- flex: 1;
614
- }
615
-
616
- .pie-tool-graph__tool-button {
617
- flex: 1;
618
- display: flex;
619
- flex-direction: column;
620
- align-items: center;
621
- gap: 4px;
622
- padding: 8px 12px;
623
- background: color-mix(in srgb, var(--pie-white, #fff) 20%, transparent);
624
- border: 2px solid transparent;
625
- border-radius: 4px;
626
- cursor: pointer;
627
- color: var(--pie-white, #fff);
628
- font-size: 12px;
629
- transition: all 0.2s;
630
- }
631
-
632
- .pie-tool-graph__tool-button:hover {
633
- background: color-mix(in srgb, var(--pie-white, #fff) 30%, transparent);
634
- }
635
-
636
- .pie-tool-graph__tool-button.pie-tool-graph__tool-button--active {
637
- background: var(--pie-background, #fff);
638
- color: var(--pie-primary-dark, #2c3e50);
639
- border-color: var(--pie-primary-dark, #2c3e50);
640
- }
641
-
642
- .pie-tool-graph__tool-icon {
643
- width: 20px;
644
- height: 20px;
645
- display: flex;
646
- align-items: center;
647
- justify-content: center;
648
- }
649
-
650
- .pie-tool-graph__tool-label {
651
- font-size: 11px;
652
- font-weight: 500;
653
- }
654
-
655
- .pie-tool-graph__transparency-control {
656
- display: flex;
657
- align-items: center;
658
- gap: 8px;
659
- color: var(--pie-white, #fff);
660
- font-size: 12px;
661
- padding-left: 8px;
662
- }
663
-
664
- .pie-tool-graph__transparency-control label {
665
- font-weight: 500;
666
- }
667
-
668
- .pie-tool-graph__transparency-control input[type='range'] {
669
- width: 100px;
670
- cursor: pointer;
671
- }
672
-
673
- /* Canvas wrapper */
674
- .pie-tool-graph__canvas-wrapper {
675
- flex: 1;
676
- background: var(--pie-background, #fff);
677
- display: flex;
678
- align-items: center;
679
- justify-content: center;
680
- overflow: hidden;
681
- }
682
-
683
- .pie-tool-graph__canvas {
684
- display: block;
685
- width: 100%;
686
- height: 100%;
687
- }
688
-
689
- /* Grid lines (matching production implementation: dark gray) */
690
- .pie-tool-graph__grid-line {
691
- stroke: var(--pie-border-dark, #666);
692
- vector-effect: non-scaling-stroke;
693
- }
694
-
695
- .pie-tool-graph__grid-line--major {
696
- stroke: var(--pie-border-dark, #333);
697
- stroke-width: 0.75;
698
- }
699
-
700
- .pie-tool-graph__grid-line--minor {
701
- stroke: var(--pie-border-light, #ccc);
702
- stroke-width: 0.5;
703
- }
704
-
705
- /* User drawing styles */
706
- .pie-tool-graph__user-point {
707
- cursor: pointer;
708
- fill: var(--pie-primary, #3f51b5);
709
- stroke: var(--pie-primary-dark, #2c3e50);
710
- stroke-width: 0.5;
711
- vector-effect: non-scaling-stroke;
712
- }
713
-
714
- .pie-tool-graph__user-point.pie-tool-graph__user-point--highlight {
715
- fill: var(--pie-missing, #ffc107);
716
- stroke: var(--pie-missing-icon, #ff9800);
717
- }
718
-
719
- .pie-tool-graph__user-line {
720
- stroke: var(--pie-text, #333);
721
- stroke-linecap: round;
722
- stroke-width: 1;
723
- vector-effect: non-scaling-stroke;
724
- }
725
-
726
- .pie-tool-graph__temp-line {
727
- pointer-events: none;
728
- stroke: var(--pie-correct, #4caf50);
729
- stroke-dasharray: 2, 2;
730
- stroke-width: 0.75;
731
- vector-effect: non-scaling-stroke;
732
- }
733
-
734
- </style>