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