svelte-plots-basic 3.3.0 → 4.0.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 +60 -20
- package/package.json +12 -4
- package/src/2d/Area.svelte +8 -9
- package/src/2d/Axes.svelte +78 -20
- package/src/2d/AxisLines.svelte +4 -6
- package/src/2d/AxisTickLabels.svelte +1 -3
- package/src/2d/Bars.svelte +31 -11
- package/src/2d/Box.svelte +5 -2
- package/src/2d/ColormapLegend.svelte +5 -2
- package/src/2d/Heatmap.svelte +98 -25
- package/src/2d/Legend.svelte +83 -31
- package/src/2d/Lines.svelte +6 -7
- package/src/2d/Multilines.svelte +10 -10
- package/src/2d/Points.svelte +9 -10
- package/src/2d/Rectangles.svelte +47 -20
- package/src/2d/Segments.svelte +3 -3
- package/src/2d/TextLabels.svelte +30 -14
- package/src/2d/TextLegend.svelte +52 -6
- package/src/2d/XAxis.svelte +5 -1
- package/src/2d/YAxis.svelte +6 -2
- package/src/3d/Axes.svelte +31 -22
- package/src/3d/AxisLines.svelte +3 -4
- package/src/3d/AxisTickLabels.svelte +2 -2
- package/src/3d/Lines.svelte +8 -8
- package/src/3d/Mesh.svelte +73 -35
- package/src/3d/Points.svelte +8 -9
- package/src/3d/Segments.svelte +11 -13
- package/src/3d/TextLabels.svelte +27 -14
- package/src/3d/XAxis.svelte +7 -6
- package/src/3d/YAxis.svelte +9 -8
- package/src/3d/ZAxis.svelte +9 -8
- package/src/ExportDialog.svelte +237 -65
- package/src/methods.js +342 -176
package/src/2d/Segments.svelte
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
<script>
|
|
32
32
|
import { getContext } from 'svelte';
|
|
33
33
|
import { Colors } from '../constants';
|
|
34
|
-
import { checkCoords, transformCoords, handleClick } from '../methods';
|
|
34
|
+
import { checkCoords, transformCoords, handleClick, normalizeLineType } from '../methods';
|
|
35
35
|
import { LINE_STYLES } from '../constants';
|
|
36
36
|
|
|
37
37
|
let {
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
const sy = $derived(sx ? checkCoords(yStart, 'Segments', sx.length) : null);
|
|
52
52
|
const ex = $derived(sy ? checkCoords(xEnd, 'Segments', sx.length) : null);
|
|
53
53
|
const ey = $derived(ex ? checkCoords(yEnd, 'Segments', sx.length) : null);
|
|
54
|
+
const validLineType = $derived(normalizeLineType(lineType, 'Segments'));
|
|
54
55
|
|
|
55
56
|
// get axes context and reactive variables needed to compute coordinates
|
|
56
57
|
const axes = getContext('axes');
|
|
@@ -60,7 +61,7 @@
|
|
|
60
61
|
const y2 = $derived(ey ? transformCoords(ey, axes.tY()) : null);
|
|
61
62
|
|
|
62
63
|
// reactive variables for coordinates of data points in pixels (and line style)
|
|
63
|
-
const lineStyleStr = $derived(`stroke:${lineColor};stroke-width: ${lineWidth}px;stroke-dasharray:${LINE_STYLES[axes.scales().plot][
|
|
64
|
+
const lineStyleStr = $derived(`stroke:${lineColor};stroke-width: ${lineWidth}px;stroke-dasharray:${LINE_STYLES[axes.scales().plot][validLineType - 1]}`);
|
|
64
65
|
|
|
65
66
|
// local status
|
|
66
67
|
const isOk = $derived(x1 && x2 && y1 && y2);
|
|
@@ -75,4 +76,3 @@
|
|
|
75
76
|
{/each}
|
|
76
77
|
</g>
|
|
77
78
|
{/if}
|
|
78
|
-
|
package/src/2d/TextLabels.svelte
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
@component Adds
|
|
2
|
+
@component Adds text labels at specified plot coordinates.
|
|
3
3
|
|
|
4
4
|
Main properties:
|
|
5
|
-
- `xValues
|
|
6
|
-
- `yValues
|
|
7
|
-
- `labels
|
|
8
|
-
- `pos
|
|
9
|
-
- `faceColor
|
|
10
|
-
- `lineColor
|
|
11
|
-
- `lineWidth
|
|
12
|
-
- `textSize
|
|
13
|
-
- `rotateAngle
|
|
5
|
+
- `xValues` - vector or array with x-coordinates for each label.
|
|
6
|
+
- `yValues` - vector or array with y-coordinates for each label.
|
|
7
|
+
- `labels` - single string used at every coordinate or array with one string per coordinate.
|
|
8
|
+
- `pos` - one position used for every label, or an array/typed array with one position per label (`0` - middle - default, `1` - bottom, `2` - left, `3` - top, `4` - right).
|
|
9
|
+
- `faceColor` - text fill color, default: `Colors.PRIMARY_TEXT`.
|
|
10
|
+
- `lineColor` - text outline color, default: `'transparent'`.
|
|
11
|
+
- `lineWidth` - text outline width in pixels, default: `0`.
|
|
12
|
+
- `textSize` - text size in em, default: `1`.
|
|
13
|
+
- `rotateAngle` - angle to rotate labels, default: `0`.
|
|
14
14
|
- `onclick` - function (callback) to be called when user clicks on a label.
|
|
15
15
|
|
|
16
|
+
Values in `labels` are rendered as raw SVG/HTML markup. Only pass trusted content.
|
|
17
|
+
|
|
16
18
|
Example:
|
|
17
19
|
|
|
18
20
|
```svelte
|
|
@@ -71,8 +73,22 @@
|
|
|
71
73
|
const p = $derived.by(() => {
|
|
72
74
|
if (!l) return null;
|
|
73
75
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
+
const hasIndividualPositions =
|
|
77
|
+
Array.isArray(pos) || ArrayBuffer.isView(pos);
|
|
78
|
+
|
|
79
|
+
if (typeof labels === 'string' && hasIndividualPositions) {
|
|
80
|
+
console.error('TextLabels: when "labels" is a single string, "pos" must be a single number.');
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (hasIndividualPositions && pos.length !== yv.length) {
|
|
85
|
+
console.error('TextLabels: the number of values in "pos" must match the number of coordinates.');
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const positions = hasIndividualPositions ? Array.from(pos) : [pos];
|
|
90
|
+
if (positions.some(v => !Number.isInteger(v) || v < 0 || v > 4)) {
|
|
91
|
+
console.error('TextLabels: values in "pos" must be whole numbers from 0 to 4.');
|
|
76
92
|
return null;
|
|
77
93
|
}
|
|
78
94
|
|
|
@@ -106,14 +122,14 @@
|
|
|
106
122
|
{#each x as v, i}
|
|
107
123
|
<text data-id={i} x={x[i]} y={y[i]} dx={dx[pos]} dy={dy[pos]}
|
|
108
124
|
transform={rotateAngle !== undefined ? `rotate(${rotateAngle}, ${x[i] + dx[pos]}, ${y[i] + dy[pos]})` : ''}
|
|
109
|
-
text-anchor={textAnchors[pos
|
|
125
|
+
text-anchor={textAnchors[pos]}>{@html labels}</text>
|
|
110
126
|
{/each}
|
|
111
127
|
|
|
112
128
|
{:else if (Array.isArray(pos) || ArrayBuffer.isView(pos))}
|
|
113
129
|
|
|
114
130
|
{#each x as v, i}
|
|
115
131
|
<text data-id={i} x={x[i]} y={y[i]} dx={dx[pos[i]]} dy={dy[pos[i]]}
|
|
116
|
-
transform={rotateAngle !== undefined ? `rotate(${rotateAngle}, ${x[i] + dx[pos]}, ${y[i] + dy[pos]})` : ''}
|
|
132
|
+
transform={rotateAngle !== undefined ? `rotate(${rotateAngle}, ${x[i] + dx[pos[i]]}, ${y[i] + dy[pos[i]]})` : ''}
|
|
117
133
|
text-anchor={textAnchors[pos[i]]}>{@html labels[i]}</text>
|
|
118
134
|
{/each}
|
|
119
135
|
|
package/src/2d/TextLegend.svelte
CHANGED
|
@@ -1,7 +1,38 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component Adds a vertical text legend at a specified plot position.
|
|
3
|
+
|
|
4
|
+
Main properties:
|
|
5
|
+
- `left` - x-coordinate of the legend in plot units.
|
|
6
|
+
- `top` - y-coordinate of the legend in plot units.
|
|
7
|
+
- `elements` - non-empty array of text labels. SVG/HTML markup is supported.
|
|
8
|
+
- `dx` - horizontal text offset, default: `"0"`.
|
|
9
|
+
- `dy` - vertical spacing between labels, default: `"1.25em"`.
|
|
10
|
+
- `faceColor` - text fill color, default: `Colors.PRIMARY_TEXT`.
|
|
11
|
+
- `lineColor` - text outline color, default: `"transparent"`.
|
|
12
|
+
- `lineWidth` - text outline width in pixels, default: `0`.
|
|
13
|
+
- `textSize` - text size in em, default: `1`.
|
|
14
|
+
|
|
15
|
+
Values in `elements` are rendered as raw SVG/HTML markup. Only pass trusted content.
|
|
16
|
+
|
|
17
|
+
Example:
|
|
18
|
+
|
|
19
|
+
```svelte
|
|
20
|
+
<script>
|
|
21
|
+
import { Axes, TextLegend } from 'svelte-plots-basic/2d';
|
|
22
|
+
|
|
23
|
+
const elements = ['First group', 'Second group'];
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<Axes limX={[0, 10]} limY={[0, 10]}>
|
|
27
|
+
<TextLegend left={1} top={9} {elements} />
|
|
28
|
+
</Axes>
|
|
29
|
+
```
|
|
30
|
+
-->
|
|
31
|
+
|
|
1
32
|
<script>
|
|
2
33
|
import { getContext } from 'svelte';
|
|
3
34
|
import { Colors } from '../constants';
|
|
4
|
-
import { transformCoords } from '../methods';
|
|
35
|
+
import { checkCoords, transformCoords } from '../methods';
|
|
5
36
|
|
|
6
37
|
let {
|
|
7
38
|
left, // vector/array with coordinates of lef side of each text box
|
|
@@ -16,17 +47,32 @@
|
|
|
16
47
|
} = $props()
|
|
17
48
|
|
|
18
49
|
const axes = getContext('axes');
|
|
19
|
-
const
|
|
20
|
-
|
|
50
|
+
const lx = $derived(
|
|
51
|
+
left !== undefined && left !== null ? checkCoords([left], 'TextLegend (left)') : null
|
|
52
|
+
);
|
|
53
|
+
const ty = $derived(
|
|
54
|
+
top !== undefined && top !== null ? checkCoords([top], 'TextLegend (top)') : null
|
|
55
|
+
);
|
|
56
|
+
const x = $derived(lx ? transformCoords(lx, axes.tX()) : null);
|
|
57
|
+
const y = $derived(ty ? transformCoords(ty, axes.tY()) : null);
|
|
58
|
+
|
|
59
|
+
const validElements = $derived.by(() => {
|
|
60
|
+
if (!Array.isArray(elements) || elements.length < 1) {
|
|
61
|
+
console.error('TextLegend: parameter "elements" must be a non-empty array.');
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return elements;
|
|
66
|
+
});
|
|
21
67
|
|
|
22
68
|
// styles for bars and labels
|
|
23
69
|
const textStyleStr = $derived(`fill:${faceColor};stroke-width:${lineWidth}px;stroke:${lineColor};font-size:${textSize}em;`);
|
|
24
70
|
</script>
|
|
25
71
|
|
|
26
|
-
{#if x && y &&
|
|
72
|
+
{#if x && y && validElements}
|
|
27
73
|
<text style={textStyleStr} x={x} y={y} dx={dx} dy={dy} dominant-baseline="middle" text-anchor="start">
|
|
28
|
-
{#each
|
|
74
|
+
{#each validElements as el, i}
|
|
29
75
|
<tspan {x} {dx} dy={i === 0 ? 0 : dy}>{@html el}</tspan>
|
|
30
76
|
{/each}
|
|
31
77
|
</text>
|
|
32
|
-
{/if}
|
|
78
|
+
{/if}
|
package/src/2d/XAxis.svelte
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
-->
|
|
22
22
|
<script>
|
|
23
23
|
|
|
24
|
-
import { getContext } from 'svelte';
|
|
24
|
+
import { getContext, onDestroy } from 'svelte';
|
|
25
25
|
import { text2svg, validateTicks, validateTickLabels } from '../methods';
|
|
26
26
|
|
|
27
27
|
let {
|
|
@@ -60,4 +60,8 @@
|
|
|
60
60
|
axes.setXAxis({show, error: error, label: axisLabel, ticks: ticksProcessed,
|
|
61
61
|
labelHeight, showGrid, las, whole, tickLabels: tickLabelsProcessed});
|
|
62
62
|
});
|
|
63
|
+
|
|
64
|
+
onDestroy(() => {
|
|
65
|
+
axes.setXAxis({show: false, error: ''});
|
|
66
|
+
});
|
|
63
67
|
</script>
|
package/src/2d/YAxis.svelte
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
Main properties:
|
|
5
5
|
- `label` - text label for the axis (optional).
|
|
6
6
|
- `showGrid` - logical, show or not horizontal grid lines, default: `false`.
|
|
7
|
-
- `ticks` - array or vector with tick positions (by default will be generated automatically based on `
|
|
7
|
+
- `ticks` - array or vector with tick positions (by default will be generated automatically based on `limY`).
|
|
8
8
|
- `tickLabels` - array with tick labels to show at each tick (by default will be generated automatically).
|
|
9
9
|
- `las` - orientation of tick labels (`1` - horizontal - default, `2` - vertical).
|
|
10
10
|
- `whole` - logical, show numeric tick labels as whole numbers (without decimals) or not, default: `false`.
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
-->
|
|
22
22
|
<script>
|
|
23
23
|
|
|
24
|
-
import { getContext } from 'svelte';
|
|
24
|
+
import { getContext, onDestroy } from 'svelte';
|
|
25
25
|
import { text2svg, validateTicks, validateTickLabels } from '../methods';
|
|
26
26
|
|
|
27
27
|
|
|
@@ -63,4 +63,8 @@
|
|
|
63
63
|
axes.setYAxis({show, error: error, label: axisLabel, ticks: ticksProcessed,
|
|
64
64
|
labelHeight, showGrid, las, whole, tickLabels: tickLabelsProcessed, pos: 4});
|
|
65
65
|
});
|
|
66
|
+
|
|
67
|
+
onDestroy(() => {
|
|
68
|
+
axes.setYAxis({show: false, error: ''});
|
|
69
|
+
});
|
|
66
70
|
</script>
|
package/src/3d/Axes.svelte
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
Main properties:
|
|
5
5
|
- `limX` - array with limits for x-axis (in plot units), default: `[0, 1]`.
|
|
6
6
|
- `limY` - array with limits for y-axis (in plot units), default: `[0, 1]`.
|
|
7
|
-
- `limZ` - array with limits for
|
|
7
|
+
- `limZ` - array with limits for z-axis (in plot units), default: `[0, 1]`.
|
|
8
8
|
- `theta` - angle (in radians) for orientation of projection plane, default: `-10`.
|
|
9
9
|
- `phi` - angle (in radians) for orientation of projection plane, default: `-10`.
|
|
10
10
|
- `zoom` - zooming factor, default: `0.5`.
|
|
@@ -15,32 +15,29 @@
|
|
|
15
15
|
- `pngWidth` - width of PNG image in cm, default `8`
|
|
16
16
|
- `pngHeight` - height of PNG image in cm, default `8`.
|
|
17
17
|
- `pngRes` - resolution of PNG image (pixels per inch), default `300`.
|
|
18
|
-
- `clipboardWidth` - width of plot image to copy to clipboard in pixels
|
|
19
|
-
- `clipboardHeight` - height of plot image to copy to clipboard in pixels
|
|
18
|
+
- `clipboardWidth` - width of plot image to copy to clipboard in pixels, default `1200`.
|
|
19
|
+
- `clipboardHeight` - height of plot image to copy to clipboard in pixels, default `800`.
|
|
20
20
|
|
|
21
21
|
Example:
|
|
22
|
-
```
|
|
22
|
+
```svelte
|
|
23
23
|
<script>
|
|
24
24
|
import {Axes} from 'svelte-plots-basic/3d';
|
|
25
25
|
</script>
|
|
26
26
|
|
|
27
|
-
<Axes limX={[-10, 10]} limY={[-10, 10]} limZ={[-10, 10]}
|
|
28
|
-
//all other plotting components are here
|
|
29
|
-
</Axes>
|
|
27
|
+
<Axes limX={[-10, 10]} limY={[-10, 10]} limZ={[-10, 10]} />
|
|
30
28
|
```
|
|
31
29
|
-->
|
|
32
30
|
<script>
|
|
33
31
|
import { matrix, Matrix } from 'mdatools/arrays';
|
|
34
32
|
import { setContext } from 'svelte';
|
|
35
33
|
import { PLOT_FONT_SIZE } from '../constants.js';
|
|
36
|
-
import {
|
|
34
|
+
import { getScale, getXAxisCoords3D, getYAxisCoords3D, getZAxisCoords3D,
|
|
37
35
|
downloadPNG, downloadSVG, copyToClipboard} from '../methods.js';
|
|
38
36
|
|
|
39
37
|
import AxisLines from './AxisLines.svelte';
|
|
40
38
|
import AxisTickLabels from './AxisTickLabels.svelte';
|
|
41
39
|
import TextLabels from './TextLabels.svelte';
|
|
42
40
|
|
|
43
|
-
/** @type {Props} */
|
|
44
41
|
let {
|
|
45
42
|
limX = [0, 1],
|
|
46
43
|
limY = [0, 1],
|
|
@@ -59,6 +56,21 @@
|
|
|
59
56
|
} = $props();
|
|
60
57
|
|
|
61
58
|
|
|
59
|
+
function hasUsableRange(lim) {
|
|
60
|
+
if (!Array.isArray(lim) || lim.length !== 2) return false;
|
|
61
|
+
if (!lim.every(value =>
|
|
62
|
+
typeof value === 'number' && Number.isFinite(value)
|
|
63
|
+
)) return false;
|
|
64
|
+
|
|
65
|
+
const span = lim[1] - lim[0];
|
|
66
|
+
const scale = Math.max(Math.abs(lim[0]), Math.abs(lim[1]));
|
|
67
|
+
|
|
68
|
+
return span > 0 &&
|
|
69
|
+
Number.isFinite(1 / span) &&
|
|
70
|
+
span > Number.EPSILON * scale * 16;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
62
74
|
|
|
63
75
|
/* Copy plot to clipboard as PNG image. */
|
|
64
76
|
async function handleClickCopy(e) {
|
|
@@ -105,12 +117,9 @@
|
|
|
105
117
|
|
|
106
118
|
// check main plot parameters and set status
|
|
107
119
|
const isOk = $derived(
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
limX[0] < limX[1] &&
|
|
112
|
-
limY[0] < limY[1] &&
|
|
113
|
-
limZ[0] < limZ[1] &&
|
|
120
|
+
hasUsableRange(limX) &&
|
|
121
|
+
hasUsableRange(limY) &&
|
|
122
|
+
hasUsableRange(limZ) &&
|
|
114
123
|
width > 50 &&
|
|
115
124
|
height > 50
|
|
116
125
|
);
|
|
@@ -174,9 +183,9 @@
|
|
|
174
183
|
);
|
|
175
184
|
|
|
176
185
|
// containers for axis properties
|
|
177
|
-
let xaxis = $state({show: false, label: ""});
|
|
178
|
-
let yaxis = $state({show: false, label: ""});
|
|
179
|
-
let zaxis = $state({show: false, label: ""});
|
|
186
|
+
let xaxis = $state.raw({show: false, label: ""});
|
|
187
|
+
let yaxis = $state.raw({show: false, label: ""});
|
|
188
|
+
let zaxis = $state.raw({show: false, label: ""});
|
|
180
189
|
|
|
181
190
|
// axes context to share with children
|
|
182
191
|
setContext('axes', {
|
|
@@ -290,9 +299,9 @@
|
|
|
290
299
|
</p>
|
|
291
300
|
{:else}
|
|
292
301
|
<div class="download-links">
|
|
293
|
-
<button onclick={handleClickSVG}>⇩ svg</button>
|
|
294
|
-
<button onclick={handleClickPNG}>⇩ png</button>
|
|
295
|
-
<button onclick={handleClickCopy}>⧉ copy</button>
|
|
302
|
+
<button type="button" onclick={handleClickSVG}>⇩ svg</button>
|
|
303
|
+
<button type="button" onclick={handleClickPNG}>⇩ png</button>
|
|
304
|
+
<button type="button" onclick={handleClickCopy}>⧉ copy</button>
|
|
296
305
|
</div>
|
|
297
306
|
{/if}
|
|
298
307
|
</div>
|
|
@@ -399,4 +408,4 @@
|
|
|
399
408
|
background: #4488ee;
|
|
400
409
|
}
|
|
401
410
|
|
|
402
|
-
</style>
|
|
411
|
+
</style>
|
package/src/3d/AxisLines.svelte
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
@component
|
|
2
|
+
@component Draws line segments for axis elements (ticks, etc.); not intended for direct use.
|
|
3
3
|
-->
|
|
4
4
|
<script>
|
|
5
5
|
import { getContext } from 'svelte';
|
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
lineWidth = 1, // line width (thickness) in pixels.
|
|
14
14
|
} = $props();
|
|
15
15
|
|
|
16
|
-
// get axes context and
|
|
16
|
+
// get axes context and compute screen coordinates
|
|
17
17
|
const axes = getContext('axes');
|
|
18
|
-
const s1 = $derived(lineCoords.length
|
|
18
|
+
const s1 = $derived(lineCoords.length === 2 ? transform3D(lineCoords[0], axes.tM()) : null);
|
|
19
19
|
const s2 = $derived(s1 ? transform3D(lineCoords[1], axes.tM()) : null);
|
|
20
20
|
|
|
21
21
|
// line style
|
|
@@ -29,4 +29,3 @@
|
|
|
29
29
|
{/each}
|
|
30
30
|
</g>
|
|
31
31
|
{/if}
|
|
32
|
-
|
package/src/3d/Lines.svelte
CHANGED
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
@component Adds a series of lines connecting points with provided coordinates (polyline).
|
|
3
3
|
|
|
4
4
|
Main properties:
|
|
5
|
-
- `xValues` - array
|
|
6
|
-
- `yValues` - array
|
|
7
|
-
- `zValues` - array
|
|
5
|
+
- `xValues` - array or vector with x-coordinates of the points.
|
|
6
|
+
- `yValues` - array or vector with y-coordinates of the points.
|
|
7
|
+
- `zValues` - array or vector with z-coordinates of the points.
|
|
8
8
|
- `lineColor` - lines color, default: `Colors.PRIMARY`.
|
|
9
|
-
- `lineWidth` - width (thickness) of the lines in pixels,
|
|
9
|
+
- `lineWidth` - width (thickness) of the lines in pixels, default: `1`.
|
|
10
10
|
- `lineType` - type of lines (`1` - solid, `2` - dashed, `3` - dotted, `4` - dashdot).
|
|
11
11
|
- `title` - title of the line series group.
|
|
12
12
|
|
|
13
13
|
Example:
|
|
14
|
-
```
|
|
14
|
+
```svelte
|
|
15
15
|
<script>
|
|
16
16
|
import { Axes, Lines } from 'svelte-plots-basic/3d';
|
|
17
17
|
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
import { cbind } from 'mdatools/arrays';
|
|
30
30
|
import { getContext } from 'svelte';
|
|
31
31
|
import { Colors, LINE_STYLES } from '../constants';
|
|
32
|
-
import { checkCoords, transform3D } from '../methods';
|
|
32
|
+
import { checkCoords, transform3D, normalizeLineType } from '../methods';
|
|
33
33
|
|
|
34
34
|
let {
|
|
35
35
|
xValues,
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
const ux = $derived(checkCoords(xValues, 'Lines (3D)'));
|
|
56
56
|
const uy = $derived(ux ? checkCoords(yValues, 'Lines (3D)', ux.length) : null);
|
|
57
57
|
const uz = $derived(uy ? checkCoords(zValues, 'Lines (3D)', ux.length) : null);
|
|
58
|
+
const validLineType = $derived(normalizeLineType(lineType, 'Lines (3D)'));
|
|
58
59
|
|
|
59
60
|
// combine world coordinates to matrices
|
|
60
61
|
const uW = $derived(ux && uy && uz ? cbind(ux, uy, uz) : null);
|
|
@@ -65,7 +66,7 @@
|
|
|
65
66
|
const p = $derived(s ? coords2points(s) : null);
|
|
66
67
|
|
|
67
68
|
// reactive variables for coordinates of data points in pixels
|
|
68
|
-
const lineStyleStr = $derived(`fill:transparent;stroke:${lineColor};stroke-width: ${lineWidth}px; stroke-dasharray:${LINE_STYLES[axes.scale()][
|
|
69
|
+
const lineStyleStr = $derived(`fill:transparent;stroke:${lineColor};stroke-width: ${lineWidth}px; stroke-dasharray:${LINE_STYLES[axes.scale()][validLineType - 1]}`);
|
|
69
70
|
</script>
|
|
70
71
|
|
|
71
72
|
{#if p}
|
|
@@ -73,4 +74,3 @@
|
|
|
73
74
|
<polyline class="line" points={p}/>
|
|
74
75
|
</g>
|
|
75
76
|
{/if}
|
|
76
|
-
|
package/src/3d/Mesh.svelte
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
@component Adds a mesh plot as a set of grid points and line segments
|
|
2
|
+
@component Adds a mesh plot as a set of grid points and line segments connecting them.
|
|
3
3
|
|
|
4
4
|
Main properties:
|
|
5
|
-
- `xValues` - array
|
|
6
|
-
- `zValues` - array
|
|
5
|
+
- `xValues` - array or vector with x-coordinates of the mesh points.
|
|
6
|
+
- `zValues` - array or vector with z-coordinates of the mesh points.
|
|
7
7
|
- `yValues` - matrix (!) with y-coordinates of the points defining its height.
|
|
8
|
-
- `colmap` - array with colors to use for different
|
|
9
|
-
- `lineWidth` - width (thickness) of the lines in pixels,
|
|
8
|
+
- `colmap` - array with colors to use for different heights.
|
|
9
|
+
- `lineWidth` - width (thickness) of the lines in pixels, default: `1`.
|
|
10
10
|
- `lineType` - type of lines (`1` - solid, `2` - dashed, `3` - dotted, `4` - dashdot).
|
|
11
|
-
- `title` - title of the
|
|
11
|
+
- `title` - title of the mesh series group.
|
|
12
12
|
|
|
13
13
|
Example:
|
|
14
|
-
```
|
|
14
|
+
```svelte
|
|
15
15
|
<script>
|
|
16
|
-
import { Axes, Mesh } from 'svelte-plots-basic/3d';
|
|
17
|
-
import {
|
|
16
|
+
import { Axes, Mesh, getcolmap } from 'svelte-plots-basic/3d';
|
|
17
|
+
import { Matrix, Vector } from 'mdatools/arrays';
|
|
18
18
|
|
|
19
19
|
const xMesh = Vector.seq(-8, 8, 1);
|
|
20
20
|
const zMesh = Vector.seq(-8, 8, 1);
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
<Axes limX={[-10, 10]} limY={[-10, 10]} limZ={[-10, 10]}>
|
|
25
25
|
<Mesh xValues={xMesh} yValues={yMesh} zValues={zMesh} colmap={getcolmap(16)}/>
|
|
26
26
|
</Axes>
|
|
27
|
+
```
|
|
27
28
|
-->
|
|
28
29
|
<script>
|
|
29
30
|
|
|
@@ -31,7 +32,7 @@
|
|
|
31
32
|
import { range } from 'mdatools/stat';
|
|
32
33
|
import { getContext } from 'svelte';
|
|
33
34
|
import { Colors, LINE_STYLES } from '../constants';
|
|
34
|
-
import { checkCoords, transform3D } from '../methods';
|
|
35
|
+
import { checkCoords, transform3D, normalizeLineType } from '../methods';
|
|
35
36
|
|
|
36
37
|
let {
|
|
37
38
|
title = '',
|
|
@@ -45,25 +46,51 @@
|
|
|
45
46
|
|
|
46
47
|
// check user defined coordinates
|
|
47
48
|
const ux = $derived(checkCoords(xValues, 'Mesh (3D)'));
|
|
48
|
-
const uz = $derived(ux ? checkCoords(zValues, 'Mesh (3D)'
|
|
49
|
+
const uz = $derived(ux ? checkCoords(zValues, 'Mesh (3D)') : null);
|
|
50
|
+
const validLineType = $derived(normalizeLineType(lineType, 'Mesh (3D)'));
|
|
49
51
|
const Uy = $derived.by(() => {
|
|
50
52
|
if (!ux || !uz) return null;
|
|
51
53
|
if (!ismatrix(yValues)) {
|
|
52
54
|
console.error('Mesh: parameter "yValues" must be a matrix with height values of mesh points.');
|
|
53
55
|
return null;
|
|
54
56
|
}
|
|
57
|
+
if (yValues.nrows !== ux.length || yValues.ncols !== uz.length) {
|
|
58
|
+
console.error('Mesh: dimensions of "yValues" must match the lengths of "xValues" and "zValues".');
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
55
61
|
return yValues;
|
|
56
62
|
});
|
|
57
63
|
|
|
58
64
|
|
|
65
|
+
const colors = $derived.by(() => {
|
|
66
|
+
const isValid =
|
|
67
|
+
Array.isArray(colmap) &&
|
|
68
|
+
colmap.length > 0 &&
|
|
69
|
+
colmap.every(color =>
|
|
70
|
+
typeof color === 'string' && color.length > 0
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
if (!isValid) {
|
|
74
|
+
console.error(
|
|
75
|
+
'Mesh: parameter "colmap" must be a non-empty array of color strings.'
|
|
76
|
+
);
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return colmap;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
|
|
59
84
|
// compute coordinates of mesh elements
|
|
60
85
|
const mesh = $derived.by(() => {
|
|
61
86
|
|
|
62
|
-
if (!Uy) return null;
|
|
87
|
+
if (!Uy || !colors) return null;
|
|
63
88
|
|
|
64
|
-
const nbins =
|
|
89
|
+
const nbins = colors.length;
|
|
65
90
|
const nx = Uy.nrows;
|
|
66
91
|
const nz = Uy.ncols;
|
|
92
|
+
const nsegmentsX = (nx - 1) * nz;
|
|
93
|
+
const nsegmentsZ = nx * (nz - 1);
|
|
67
94
|
|
|
68
95
|
const nr = range(Uy.v);
|
|
69
96
|
const dnr = (nr[1] - nr[0]) / 100;
|
|
@@ -71,26 +98,36 @@
|
|
|
71
98
|
const right = nr[1] + dnr;
|
|
72
99
|
const span = right - left;
|
|
73
100
|
|
|
101
|
+
function getColorIndex(value) {
|
|
102
|
+
if (!(span > 0)) return Math.floor(nbins / 2);
|
|
103
|
+
|
|
104
|
+
const index = Math.round(
|
|
105
|
+
(value - left) / span * (nbins - 1)
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
return Math.max(0, Math.min(nbins - 1, index));
|
|
109
|
+
}
|
|
110
|
+
|
|
74
111
|
// compute coordinates for mesh segments lines
|
|
75
|
-
const x1s = Vector.zeros(
|
|
76
|
-
const z1s = Vector.zeros(
|
|
77
|
-
const y1s = Vector.zeros(
|
|
112
|
+
const x1s = Vector.zeros(nsegmentsX);
|
|
113
|
+
const z1s = Vector.zeros(nsegmentsX);
|
|
114
|
+
const y1s = Vector.zeros(nsegmentsX);
|
|
78
115
|
|
|
79
|
-
const x2s = Vector.zeros(
|
|
80
|
-
const z2s = Vector.zeros(
|
|
81
|
-
const y2s = Vector.zeros(
|
|
116
|
+
const x2s = Vector.zeros(nsegmentsZ);
|
|
117
|
+
const z2s = Vector.zeros(nsegmentsZ);
|
|
118
|
+
const y2s = Vector.zeros(nsegmentsZ);
|
|
82
119
|
|
|
83
|
-
const color1 = Vector.zeros(
|
|
120
|
+
const color1 = Vector.zeros(nsegmentsX);
|
|
84
121
|
|
|
85
|
-
const x1e = Vector.zeros(
|
|
86
|
-
const z1e = Vector.zeros(
|
|
87
|
-
const y1e = Vector.zeros(
|
|
122
|
+
const x1e = Vector.zeros(nsegmentsX);
|
|
123
|
+
const z1e = Vector.zeros(nsegmentsX);
|
|
124
|
+
const y1e = Vector.zeros(nsegmentsX);
|
|
88
125
|
|
|
89
|
-
const x2e = Vector.zeros(
|
|
90
|
-
const z2e = Vector.zeros(
|
|
91
|
-
const y2e = Vector.zeros(
|
|
126
|
+
const x2e = Vector.zeros(nsegmentsZ);
|
|
127
|
+
const z2e = Vector.zeros(nsegmentsZ);
|
|
128
|
+
const y2e = Vector.zeros(nsegmentsZ);
|
|
92
129
|
|
|
93
|
-
const color2 = Vector.zeros(
|
|
130
|
+
const color2 = Vector.zeros(nsegmentsZ);
|
|
94
131
|
|
|
95
132
|
let n1 = 0, n2 = 0;
|
|
96
133
|
for (let z = 0; z < nz; z++) {
|
|
@@ -108,7 +145,8 @@
|
|
|
108
145
|
z1e.v[n1] = uz.v[z];
|
|
109
146
|
y1e.v[n1] = Uy.v[z * nx + x + 1];
|
|
110
147
|
|
|
111
|
-
const
|
|
148
|
+
const meanHeight = (Uy.v[z * nx + x] + Uy.v[z * nx + x + 1]) / 2;
|
|
149
|
+
const colInd = getColorIndex(meanHeight);
|
|
112
150
|
color1.v[n1] = colInd;
|
|
113
151
|
n1 = n1 + 1;
|
|
114
152
|
}
|
|
@@ -125,7 +163,8 @@
|
|
|
125
163
|
z2e.v[n2] = uz.v[z + 1];
|
|
126
164
|
y2e.v[n2] = Uy.v[(z + 1) * nx + x];
|
|
127
165
|
|
|
128
|
-
const
|
|
166
|
+
const meanHeight = (Uy.v[z * nx + x] + Uy.v[(z + 1) * nx + x]) / 2;
|
|
167
|
+
const colInd = getColorIndex(meanHeight);
|
|
129
168
|
color2.v[n2] = colInd;
|
|
130
169
|
n2 = n2 + 1;
|
|
131
170
|
}
|
|
@@ -147,17 +186,16 @@
|
|
|
147
186
|
const s2s = $derived(mesh ? transform3D(mesh.M2s, axes.tM()) : null);
|
|
148
187
|
const s2e = $derived(mesh ? transform3D(mesh.M2e, axes.tM()) : null);
|
|
149
188
|
|
|
150
|
-
const lineStyleStr = $derived(`stroke-width: ${lineWidth}px;stroke-dasharray:${LINE_STYLES[axes.scale()][
|
|
189
|
+
const lineStyleStr = $derived(`stroke-width: ${lineWidth}px;stroke-dasharray:${LINE_STYLES[axes.scale()][validLineType - 1]};`);
|
|
151
190
|
</script>
|
|
152
191
|
|
|
153
|
-
<g class="series series_mesh" style={lineStyleStr} data-title={title}>
|
|
154
192
|
{#if s1s && s1e && s2s && s2e }
|
|
193
|
+
<g class="series series_mesh" style={lineStyleStr} data-title={title}>
|
|
155
194
|
{#each s1s.x as v, i}
|
|
156
|
-
<line x1={s1s.x[i]} x2={s1e.x[i]} y1={s1s.y[i]} y2={s1e.y[i]} style={`stroke:${
|
|
195
|
+
<line x1={s1s.x[i]} x2={s1e.x[i]} y1={s1s.y[i]} y2={s1e.y[i]} style={`stroke:${colors[mesh.color1.v[i]]};`} />
|
|
157
196
|
{/each}
|
|
158
197
|
{#each s2s.x as v, i}
|
|
159
|
-
<line x1={s2s.x[i]} x2={s2e.x[i]} y1={s2s.y[i]} y2={s2e.y[i]} style={`stroke:${
|
|
198
|
+
<line x1={s2s.x[i]} x2={s2e.x[i]} y1={s2s.y[i]} y2={s2e.y[i]} style={`stroke:${colors[mesh.color2.v[i]]};`} />
|
|
160
199
|
{/each}
|
|
161
|
-
{/if}
|
|
162
200
|
</g>
|
|
163
|
-
|
|
201
|
+
{/if}
|