pluton-2d 0.1.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.
- package/LICENSE +21 -0
- package/README.md +403 -0
- package/dist/index.css +1 -0
- package/dist/index.d.ts +359 -0
- package/dist/index.js +1119 -0
- package/package.json +46 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aleksandar Gjoreski
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
# pluton-2d
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
I built this for a specific need in another project—crisp vector output for technical drawings with minimal DOM churn. It's SVG-based, so lines stay sharp at any zoom, and it includes built-in helpers for grids, dimensions, and drafting annotations. Named after Pluton, the battleship from One Piece (blueprints worth keeping). I'm sharing it in case you have a similar need.
|
|
6
|
+
|
|
7
|
+
- [What you get](#what-you-get)
|
|
8
|
+
- [Getting started](#getting-started)
|
|
9
|
+
- [How rendering works](#how-rendering-works)
|
|
10
|
+
- [Coordinate system](#coordinate-system)
|
|
11
|
+
- [API](#api)
|
|
12
|
+
- [Camera controls](#camera-controls)
|
|
13
|
+
- [Styling](#styling)
|
|
14
|
+
- [Performance](#performance)
|
|
15
|
+
- [When to use](#when-to-use)
|
|
16
|
+
- [Troubleshooting](#troubleshooting)
|
|
17
|
+
- [SSR](#ssr)
|
|
18
|
+
|
|
19
|
+
## What you get
|
|
20
|
+
|
|
21
|
+
**SVG-first rendering**
|
|
22
|
+
Native SVG output means crisp lines at any zoom level. You can inspect the DOM, export to files, or integrate with other SVG tools easily.
|
|
23
|
+
|
|
24
|
+
**Drafting helpers included**
|
|
25
|
+
Grid, axes, hatch fill, and dimension builders come out of the box. Focus on your geometry, not boilerplate.
|
|
26
|
+
|
|
27
|
+
**Low-churn rendering**
|
|
28
|
+
The engine records draw commands and reuses DOM elements via activeIndex tracking. Only changed attributes get updated, keeping interactions smooth.
|
|
29
|
+
|
|
30
|
+
**Reactive parameters**
|
|
31
|
+
Params are wrapped in a Proxy. Mutate them anywhere and redraws trigger automatically—no manual scheduling needed.
|
|
32
|
+
|
|
33
|
+
**Simple camera controls**
|
|
34
|
+
Pan and zoom are opt-in, with smoothing tuned for natural interaction. Reset to initial view anytime.
|
|
35
|
+
|
|
36
|
+
**CSS theming**
|
|
37
|
+
All styling is driven by CSS variables on the SVG root. Change themes without touching JavaScript.
|
|
38
|
+
|
|
39
|
+
## Getting started
|
|
40
|
+
|
|
41
|
+
Install the package and import the default stylesheet:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm install pluton-2d
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import "pluton-2d/style.css";
|
|
49
|
+
import { Pluton2D } from "pluton-2d";
|
|
50
|
+
|
|
51
|
+
const svg = document.querySelector("svg")!;
|
|
52
|
+
const scene = new Pluton2D(svg, { width: 240, height: 120 });
|
|
53
|
+
|
|
54
|
+
scene.enablePan(true);
|
|
55
|
+
scene.enableZoom(true);
|
|
56
|
+
|
|
57
|
+
const geom = scene.geometry.group();
|
|
58
|
+
|
|
59
|
+
scene.draw((p) => {
|
|
60
|
+
const path = geom.path();
|
|
61
|
+
path
|
|
62
|
+
.moveToAbs(-p.width / 2, -p.height / 2)
|
|
63
|
+
.lineTo(p.width, 0)
|
|
64
|
+
.lineTo(0, p.height)
|
|
65
|
+
.lineTo(-p.width, 0)
|
|
66
|
+
.close();
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Params are reactive** — mutations trigger redraws automatically:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
// Single property
|
|
74
|
+
scene.params.width = 150;
|
|
75
|
+
|
|
76
|
+
// Multiple properties
|
|
77
|
+
Object.assign(scene.params, { width: 200, height: 100 });
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Draw callbacks run every frame when params change. The engine handles scheduling at 60 FPS.
|
|
81
|
+
|
|
82
|
+
## How rendering works
|
|
83
|
+
|
|
84
|
+
Understanding the render cycle helps you use the library effectively.
|
|
85
|
+
|
|
86
|
+
**1. Reactive params**
|
|
87
|
+
Params are wrapped in a Proxy. Any mutation triggers `scheduleRender()`, which queues a frame-limited update.
|
|
88
|
+
|
|
89
|
+
**2. Render cycle** (capped at 60 FPS)
|
|
90
|
+
- `beginRecord()` — reset activeIndex on all groups
|
|
91
|
+
- Draw callbacks run — you request builders with `path()` or `dimension()`
|
|
92
|
+
- `commit()` — update DOM with changes (skipped for static groups)
|
|
93
|
+
|
|
94
|
+
**3. Group reuse**
|
|
95
|
+
Create groups once outside draw callbacks, request builders inside:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
const g = scene.geometry.group(); // create once
|
|
99
|
+
|
|
100
|
+
scene.draw(() => {
|
|
101
|
+
const path = g.path(); // request builder every frame
|
|
102
|
+
path.moveToAbs(0, 0).lineTo(10, 10);
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Builders are reused internally via activeIndex tracking. This minimizes DOM churn—elements are only created when needed and attributes only update when values change.
|
|
107
|
+
|
|
108
|
+
## Coordinate system
|
|
109
|
+
|
|
110
|
+
Pluton uses a **center origin with Y-axis pointing up** (math convention, not screen coordinates).
|
|
111
|
+
|
|
112
|
+
- Origin is at the center of the viewport
|
|
113
|
+
- Positive X is right, positive Y is **up**
|
|
114
|
+
- Example: `lineTo(10, 20)` moves right 10 units, up 20 units
|
|
115
|
+
|
|
116
|
+
The viewport layer applies `scale(1, -1)` to flip the Y-axis for SVG rendering. This matters when placing dimensions and text—use the dimension helpers to ensure correct orientation.
|
|
117
|
+
|
|
118
|
+
## API
|
|
119
|
+
|
|
120
|
+
### Construction
|
|
121
|
+
|
|
122
|
+
Create an instance with an SVG element and initial params:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
const scene = new Pluton2D<{ width: number; height: number }>(svg, {
|
|
126
|
+
width: 240,
|
|
127
|
+
height: 120,
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Params can be any shape. The type is inferred from the initial value.
|
|
132
|
+
|
|
133
|
+
An optional third argument controls instance-level settings:
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
const scene = new Pluton2D(svg, { width: 240, height: 120 }, {
|
|
137
|
+
filterIntensity: 1.5, // pencil filter strength (default: 1.25)
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Draw loop
|
|
142
|
+
|
|
143
|
+
Register a callback that runs on each render:
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
const unsubscribe = scene.draw((params) => {
|
|
147
|
+
// build paths here
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// later, if needed
|
|
151
|
+
unsubscribe();
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Unsubscribing is optional. When you're done with a scene, call `dispose()` to clean up event listeners and stop rendering.
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
scene.dispose();
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Controls
|
|
161
|
+
|
|
162
|
+
Controls are explicit and opt-in:
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
scene.enableFilter(true); // pencil-like filter (default: false)
|
|
166
|
+
scene.enableHatchFill(true); // hatch pattern fill (default: false)
|
|
167
|
+
scene.enableGrid(true); // background grid (default: true)
|
|
168
|
+
scene.enableAxes(true); // center axes (default: true)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Filters can be expensive on Safari during zoom—disable them if you see lag.
|
|
172
|
+
|
|
173
|
+
### Geometry
|
|
174
|
+
|
|
175
|
+
Geometry groups manage SVG paths and handle reuse. Create groups outside draw callbacks, request builders inside.
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
const g = scene.geometry.group();
|
|
179
|
+
|
|
180
|
+
scene.draw((p) => {
|
|
181
|
+
const path = g.path({ className: "my-shape" });
|
|
182
|
+
path.moveToAbs(0, 0).lineTo(40, 0).lineTo(0, 40).close();
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**Group methods:**
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
group.translate(x, y) // offset entire group
|
|
190
|
+
group.setDrawUsage(mode) // "static" or "dynamic" (default: "dynamic")
|
|
191
|
+
group.clear() // remove all paths and reset
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**PathBuilder methods:**
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
path.moveTo(dx, dy) // relative move (m)
|
|
198
|
+
path.moveToAbs(x, y) // absolute move (M)
|
|
199
|
+
path.lineTo(dx, dy) // relative line (l)
|
|
200
|
+
path.lineToAbs(x, y) // absolute line (L)
|
|
201
|
+
path.arcTo(dx, dy, r, clockwise?, largeArc?) // relative arc (a)
|
|
202
|
+
path.arcToAbs(x, y, r, clockwise?, largeArc?) // absolute arc (A)
|
|
203
|
+
path.close() // close path (z)
|
|
204
|
+
path.reset() // clear builder
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Dimensions
|
|
208
|
+
|
|
209
|
+
Dimensions are a separate layer with helpers for annotations, arrows, ticks, and labels. Create groups outside draw callbacks, request builders inside.
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
const d = scene.dimensions.group();
|
|
213
|
+
|
|
214
|
+
scene.draw(() => {
|
|
215
|
+
const dim = d.dimension();
|
|
216
|
+
dim.moveToAbs(-40, 0).tick(0).lineTo(80, 0).tick(0).textAt(0, -10, "80");
|
|
217
|
+
});
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
**Group methods:**
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
group.translate(x, y) // offset entire group
|
|
224
|
+
group.setDrawUsage(mode) // "static" or "dynamic" (default: "dynamic")
|
|
225
|
+
group.clear() // remove all dimensions and reset
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**DimensionBuilder positioning:**
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
dim.moveTo(dx, dy) // relative move
|
|
232
|
+
dim.moveToAbs(x, y) // absolute move
|
|
233
|
+
dim.lineTo(dx, dy) // relative line
|
|
234
|
+
dim.lineToAbs(x, y) // absolute line
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**DimensionBuilder primitives:**
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
dim.arc(r, startAngle, endAngle) // arc centered at current point
|
|
241
|
+
dim.arrow(angleRad, size?) // open arrow (default size: 8)
|
|
242
|
+
dim.arrowFilled(angleRad, size?) // filled arrow (default size: 8)
|
|
243
|
+
dim.tick(angleRad, size?) // architectural tick (default size: 15)
|
|
244
|
+
dim.centerMark(size?) // crosshair with center dot
|
|
245
|
+
dim.close() // close path
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**DimensionBuilder text:**
|
|
249
|
+
|
|
250
|
+
```ts
|
|
251
|
+
dim.textAt(dx, dy, text, align?, className?) // relative text placement
|
|
252
|
+
dim.textAtAbs(x, y, text, align?, className?) // absolute text placement
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Text align can be `"start"`, `"middle"`, or `"end"` (default: `"middle"`).
|
|
256
|
+
|
|
257
|
+
## Camera controls
|
|
258
|
+
|
|
259
|
+
Pan and zoom are opt-in and can be reset at any time:
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
scene.enablePan(true); // middle-mouse or shift+left-click to pan
|
|
263
|
+
scene.enableZoom(true); // mouse wheel to zoom (1-20x scale)
|
|
264
|
+
scene.resetCamera(); // return to initial view
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Styling
|
|
268
|
+
|
|
269
|
+
All visual styling is controlled by CSS variables on `.pluton-root`. You can customize per-instance:
|
|
270
|
+
|
|
271
|
+
```css
|
|
272
|
+
.pluton-root {
|
|
273
|
+
/* Grid */
|
|
274
|
+
--pluton-grid-minor-stroke: rgba(0, 0, 0, 0.025);
|
|
275
|
+
--pluton-grid-major-stroke: rgba(0, 0, 0, 0.12);
|
|
276
|
+
--pluton-grid-stroke-width: 0.5;
|
|
277
|
+
|
|
278
|
+
/* Axes */
|
|
279
|
+
--pluton-axis-color: rgba(0, 0, 0, 0.2);
|
|
280
|
+
--pluton-axis-stroke-width: 1;
|
|
281
|
+
--pluton-axis-dash: 5 5;
|
|
282
|
+
|
|
283
|
+
/* Geometry */
|
|
284
|
+
--pluton-geometry-stroke: rgba(0, 0, 0, 0.7);
|
|
285
|
+
--pluton-geometry-stroke-width: 1;
|
|
286
|
+
|
|
287
|
+
/* Hatch fill (opt-in via enableHatchFill) */
|
|
288
|
+
--pluton-hatch-color: rgba(0, 39, 50, 0.14);
|
|
289
|
+
|
|
290
|
+
/* Dimensions */
|
|
291
|
+
--pluton-dim-color: black;
|
|
292
|
+
--pluton-dim-stroke-width: 1;
|
|
293
|
+
--pluton-dim-text-color: rgba(0, 0, 0, 0.6);
|
|
294
|
+
--pluton-dim-font-size: 12px;
|
|
295
|
+
--pluton-dim-font-family: system-ui, sans-serif;
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
**Custom classes:**
|
|
300
|
+
|
|
301
|
+
Pass custom classes to paths and dimensions for fine-grained control:
|
|
302
|
+
|
|
303
|
+
```ts
|
|
304
|
+
g.path({ className: "my-custom-path" });
|
|
305
|
+
d.dimension({ className: "highlighted-dim" });
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
**Hatch fill:**
|
|
309
|
+
|
|
310
|
+
Hatch fill is opt-in. Enable it at runtime:
|
|
311
|
+
|
|
312
|
+
```ts
|
|
313
|
+
scene.enableHatchFill(true);
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Or add the `pluton-fill-hatch` class to specific paths:
|
|
317
|
+
|
|
318
|
+
```ts
|
|
319
|
+
g.path({ className: "pluton-fill-hatch" });
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## Performance
|
|
323
|
+
|
|
324
|
+
**Prefer one draw callback**
|
|
325
|
+
It keeps ordering explicit and avoids confusion when multiple callbacks touch the same layer. Multiple callbacks are supported and run in registration order.
|
|
326
|
+
|
|
327
|
+
**Mark static groups**
|
|
328
|
+
For geometry that never changes, mark the group as static. The engine optimizes it automatically:
|
|
329
|
+
|
|
330
|
+
```ts
|
|
331
|
+
const staticGroup = scene.geometry.group();
|
|
332
|
+
staticGroup.setDrawUsage("static"); // mark as static outside draw callback
|
|
333
|
+
|
|
334
|
+
const dynamicGroup = scene.geometry.group();
|
|
335
|
+
|
|
336
|
+
scene.draw((p) => {
|
|
337
|
+
// Static group - always call path(), engine skips commit
|
|
338
|
+
const staticPath = staticGroup.path();
|
|
339
|
+
staticPath.moveToAbs(0, 0).lineTo(100, 0).lineTo(0, 100).close();
|
|
340
|
+
|
|
341
|
+
// Dynamic group - updates every frame
|
|
342
|
+
const dynamicPath = dynamicGroup.path();
|
|
343
|
+
dynamicPath.moveToAbs(0, 0).lineTo(p.width, 0).lineTo(0, p.height).close();
|
|
344
|
+
});
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
**How it works:**
|
|
348
|
+
- Set `setDrawUsage("static")` on the group before rendering starts
|
|
349
|
+
- Draw callbacks run normally—you still call `path()` every frame
|
|
350
|
+
- The engine skips `commit()` for static groups, avoiding DOM updates
|
|
351
|
+
- No flags, no RAF, no conditionals needed in your draw logic
|
|
352
|
+
|
|
353
|
+
**When to use:**
|
|
354
|
+
- **Static:** background elements, fixed annotations, unchanging reference shapes
|
|
355
|
+
- **Dynamic (default):** anything reactive to params or user input
|
|
356
|
+
|
|
357
|
+
**Safari filter performance**
|
|
358
|
+
SVG filters can be expensive during zoom on Safari. Disable them if you notice lag:
|
|
359
|
+
|
|
360
|
+
```ts
|
|
361
|
+
scene.enableFilter(false);
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
## When to use
|
|
365
|
+
|
|
366
|
+
**Good fit:**
|
|
367
|
+
- Technical drawings, blueprints
|
|
368
|
+
- Engineering diagrams, schematics
|
|
369
|
+
- Floor plans, architectural views
|
|
370
|
+
- Any crisp vector output at arbitrary zoom levels
|
|
371
|
+
|
|
372
|
+
**Not ideal for:**
|
|
373
|
+
- Charts and graphs (D3, Chart.js are better suited)
|
|
374
|
+
- High-frequency animation (Canvas performs better)
|
|
375
|
+
- Raster effects or photos (use Canvas or WebGL)
|
|
376
|
+
|
|
377
|
+
## Troubleshooting
|
|
378
|
+
|
|
379
|
+
**SVG is blank**
|
|
380
|
+
- Check CSS import: `import "pluton-2d/style.css"`
|
|
381
|
+
- Ensure SVG has size: set CSS width/height or use a viewBox
|
|
382
|
+
|
|
383
|
+
**Y-axis feels inverted**
|
|
384
|
+
- Library uses Y-up (math coords), not Y-down (screen coords)
|
|
385
|
+
- `lineTo(0, 10)` moves UP, not down
|
|
386
|
+
|
|
387
|
+
**Params changes don't trigger redraw**
|
|
388
|
+
- Mutate params: `scene.params.width = 100` ✓
|
|
389
|
+
- Don't reassign: `scene.params = { ... }` ✗
|
|
390
|
+
- Params must be flat — nested objects throw at construction
|
|
391
|
+
|
|
392
|
+
**Dimensions not visible**
|
|
393
|
+
- Check layer is created: `scene.dimensions.group()`
|
|
394
|
+
- Verify draw callback is registered
|
|
395
|
+
- Check stroke color CSS variable: `--pluton-dim-color`
|
|
396
|
+
|
|
397
|
+
**Performance issues during zoom (Safari)**
|
|
398
|
+
- Disable pencil filter: `scene.enableFilter(false)`
|
|
399
|
+
- SVG filters can be expensive at high zoom levels
|
|
400
|
+
|
|
401
|
+
## SSR
|
|
402
|
+
|
|
403
|
+
Pluton2D is DOM-dependent and does not support SSR. Instantiate it on the client after mount.
|
package/dist/index.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.pluton-root{--pluton-grid-minor-stroke: rgba(0, 0, 0, .025);--pluton-grid-major-stroke: rgba(0, 0, 0, .12);--pluton-grid-stroke-width: .5;--pluton-axis-color: rgba(0, 0, 0, .2);--pluton-axis-stroke-width: 1;--pluton-axis-dash: 5 5;--pluton-geometry-stroke: rgba(0, 0, 0, .7);--pluton-geometry-stroke-width: 1;--pluton-hatch-color: rgba(0, 39, 50, .14);--pluton-dim-color: black;--pluton-dim-stroke-width: 1;--pluton-dim-text-color: rgba(0, 0, 0, .6);--pluton-dim-font-size: 12px;--pluton-dim-font-family: system-ui, sans-serif}.pluton-root .pluton-background-layer,.pluton-root .pluton-world-layer{will-change:transform}.pluton-root .pluton-pattern-hatch{stroke:var(--pluton-hatch-color)}.pluton-root .pluton-pattern-graph-paper-minor{fill:none;stroke:var(--pluton-grid-minor-stroke);stroke-width:var(--pluton-grid-stroke-width);shape-rendering:crispEdges}.pluton-root .pluton-pattern-graph-paper-major{fill:none;stroke:var(--pluton-grid-major-stroke);stroke-width:var(--pluton-grid-stroke-width);shape-rendering:crispEdges}.pluton-root .pluton-axes .pluton-axis{stroke:var(--pluton-axis-color);stroke-width:var(--pluton-axis-stroke-width);stroke-dasharray:var(--pluton-axis-dash);shape-rendering:crispEdges}.pluton-root .pluton-geometry path{fill:none;stroke:var(--pluton-geometry-stroke);stroke-width:var(--pluton-geometry-stroke-width);shape-rendering:crispEdges}.pluton-root.pluton-fill-hatch .pluton-geometry path{fill:url(#pluton-pattern-fill-hatch-45);fill-rule:evenodd}.pluton-root .pluton-dimensions .pluton-dim-stroke{fill:none;stroke:var(--pluton-dim-color);stroke-width:var(--pluton-dim-stroke-width);shape-rendering:crispEdges}.pluton-root .pluton-dimensions .pluton-dim-filled{fill:var(--pluton-dim-color);stroke:none;shape-rendering:crispEdges}.pluton-root .pluton-dimensions text{fill:var(--pluton-dim-text-color);font-size:var(--pluton-dim-font-size);font-family:var(--pluton-dim-font-family);letter-spacing:.02em;text-rendering:geometricPrecision}
|