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.
@@ -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][lineType-1]}`);
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
-
@@ -1,18 +1,20 @@
1
1
  <!--
2
- @component Adds a series with line segments.
2
+ @component Adds text labels at specified plot coordinates.
3
3
 
4
4
  Main properties:
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 or array with labels (strings).
8
- - `pos`- position of labels related to coordinates (`0` - middle - default, `1` - bottom, `2` - left, `3` - top, `4` - right).
9
- - `faceColor`- face color of label symbols, default: `Colors.PRIMARY_TEXT`.
10
- - `lineColor`- border colors of label symbols, default: `'transparent'`.
11
- - `lineWidth`- border width of label symbols in pixels, default: `0`.
12
- - `textSize`- size of label symbols in em, default: `1`.
13
- - `rotateAngle`- angle to rotate labels, default: `0`.
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
- if (typeof labels === 'string' && typeof pos !== 'number' ) {
75
- console.error('TextLabels: parameter "labels" is provided as single string, so "pos" must be a single number.');
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[i]]}>{@html labels}</text>
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
 
@@ -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 x = $derived(left ? transformCoords([left], axes.tX()) : null);
20
- const y = $derived(top ? transformCoords([top], axes.tY()) : null );
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 && elements.length > 0}
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 elements as el, i}
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}
@@ -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>
@@ -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 `limX`).
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>
@@ -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 y-axis (in plot units), default: `[0, 1]`.
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
- ```jsx
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 { checkArray, getScale, getXAxisCoords3D, getYAxisCoords3D, getZAxisCoords3D,
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
- checkArray(limX, 2) &&
109
- checkArray(limY, 2) &&
110
- checkArray(limZ, 2) &&
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>
@@ -1,5 +1,5 @@
1
1
  <!--
2
- @component draws line segments for axis elements (ticks. etc), not for manual use.
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 and compute screen coordinates
16
+ // get axes context and compute screen coordinates
17
17
  const axes = getContext('axes');
18
- const s1 = $derived(lineCoords.length == 2 ? transform3D(lineCoords[0], axes.tM()) : null);
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
-
@@ -1,5 +1,5 @@
1
1
  <!--
2
- @component shows tick labels.
2
+ @component Shows tick labels.
3
3
  -->
4
4
  <script>
5
5
 
@@ -34,4 +34,4 @@
34
34
  user-select: none;
35
35
  dominant-baseline: middle;
36
36
  }
37
- </style>
37
+ </style>
@@ -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 of vector with x-coordinates of the points.
6
- - `yValues` - array of vector with y-coordinates of the points.
7
- - `zValues` - array of vector with z-coordinates of the points.
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, defailt: `1`.
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
- ```jsx
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()][lineType-1]}`);
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
-
@@ -1,20 +1,20 @@
1
1
  <!--
2
- @component Adds a mesh plot as a set of grid points and line segments connected them.
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 of vector with x-coordinates of the mesh points.
6
- - `zValues` - array of vector with z-coordinates of the mesh points.
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 height.
9
- - `lineWidth` - width (thickness) of the lines in pixels, defailt: `1`.
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 line series group.
11
+ - `title` - title of the mesh series group.
12
12
 
13
13
  Example:
14
- ```jsx
14
+ ```svelte
15
15
  <script>
16
- import { Axes, Mesh } from 'svelte-plots-basic/3d';
17
- import { gecolmap } from 'svelte-plots-basic/utils';
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)', ux.length) : null);
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 = colmap.length;
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((nx - 1) * nz);
76
- const z1s = Vector.zeros((nx - 1) * nz);
77
- const y1s = Vector.zeros((nx - 1) * nz);
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((nx - 1) * nz);
80
- const z2s = Vector.zeros((nx - 1) * nz);
81
- const y2s = Vector.zeros((nx - 1) * nz);
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((nx - 1) * nz);
120
+ const color1 = Vector.zeros(nsegmentsX);
84
121
 
85
- const x1e = Vector.zeros((nx - 1) * nz);
86
- const z1e = Vector.zeros((nx - 1) * nz);
87
- const y1e = Vector.zeros((nx - 1) * nz);
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((nx - 1) * nz);
90
- const z2e = Vector.zeros((nx - 1) * nz);
91
- const y2e = Vector.zeros((nx - 1) * nz);
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((nx - 1) * nz);
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 colInd = Math.round(((Uy.v[z * nx + x] + Uy.v[z * nx + x + 1]) / 2 - left) / span * (nbins - 1));
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 colInd = Math.round(((Uy.v[z * nx + x] + Uy.v[(z + 1) * nx + x]) / 2 - left) / span * (nbins - 1));
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()][lineType-1]};`);
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:${colmap[mesh.color1.v[i]]};`} />
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:${colmap[mesh.color2.v[i]]};`} />
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}