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/Heatmap.svelte
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
@component Adds a heatmap
|
|
2
|
+
@component Adds a heatmap visualizing values of a matrix.
|
|
3
3
|
|
|
4
4
|
Main properties:
|
|
5
5
|
- `values` - matrix (object of class `Matrix` from `mdatools` package) with values to visualize.
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import { Matrix } from 'mdatools/arrays';
|
|
14
14
|
import { Axes, Heatmap } from 'svelte-plots-basic/2d';
|
|
15
15
|
|
|
16
|
-
// create matrix with 5 rows and 10 columns filled with
|
|
16
|
+
// create matrix with 5 rows and 10 columns filled with random values.
|
|
17
17
|
const x = Matrix.rand(5, 10);
|
|
18
18
|
</script>
|
|
19
19
|
|
|
@@ -27,13 +27,12 @@
|
|
|
27
27
|
import { getContext } from 'svelte';
|
|
28
28
|
import { ismatrix, isvector } from 'mdatools/arrays';
|
|
29
29
|
import { expandgrid } from 'mdatools/misc';
|
|
30
|
-
import { split } from 'mdatools/stat';
|
|
30
|
+
import { range, split } from 'mdatools/stat';
|
|
31
31
|
|
|
32
32
|
import { getcolmap, transformObjects, transformCoords, seq } from '../methods';
|
|
33
33
|
import { HEATMAP_NUM_SPLITS } from '../constants';
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
/** @type {Props} */
|
|
37
36
|
let {
|
|
38
37
|
values, // Matrix with values to show heatmap for
|
|
39
38
|
breaks, // vector with breaks to distribute the values in
|
|
@@ -42,26 +41,101 @@
|
|
|
42
41
|
} = $props();
|
|
43
42
|
|
|
44
43
|
|
|
44
|
+
function validateBreaks(values) {
|
|
45
|
+
if (!values || values.length < 2) {
|
|
46
|
+
console.error('Heatmap: "breaks" must contain at least two values.');
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const items = Array.from(values);
|
|
51
|
+
|
|
52
|
+
if (items.some(value =>
|
|
53
|
+
typeof value !== 'number' || Number.isNaN(value)
|
|
54
|
+
)) {
|
|
55
|
+
console.error('Heatmap: values in "breaks" must be numeric.');
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (items.some((value, index) =>
|
|
60
|
+
index > 0 && value <= items[index - 1]
|
|
61
|
+
)) {
|
|
62
|
+
console.error('Heatmap: values in "breaks" must be strictly increasing.');
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return values;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
function findInterval(value, breaks) {
|
|
71
|
+
let low = 1;
|
|
72
|
+
let high = breaks.length - 1;
|
|
73
|
+
|
|
74
|
+
while (low < high) {
|
|
75
|
+
const middle = Math.floor((low + high) / 2);
|
|
76
|
+
|
|
77
|
+
if (value <= breaks[middle]) {
|
|
78
|
+
high = middle;
|
|
79
|
+
} else {
|
|
80
|
+
low = middle + 1;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const index = low - 1;
|
|
85
|
+
|
|
86
|
+
return value > breaks[index] && value <= breaks[index + 1]
|
|
87
|
+
? index
|
|
88
|
+
: -1;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
45
92
|
|
|
46
93
|
// check that values are provided as a matrix
|
|
47
|
-
|
|
94
|
+
const v = $derived.by(() => {
|
|
95
|
+
if (!ismatrix(values) || values.nrows < 1 || values.ncols < 1) {
|
|
96
|
+
console.error('Heatmap: parameter "values" must be a non-empty Matrix.');
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return values;
|
|
101
|
+
});
|
|
48
102
|
|
|
49
103
|
// check and process left values for breaks
|
|
50
104
|
let lb = $derived.by(() => {
|
|
51
105
|
if (!v) return null;
|
|
52
|
-
|
|
53
|
-
if (breaks &&
|
|
54
|
-
|
|
106
|
+
|
|
107
|
+
if (breaks !== undefined && breaks !== null) {
|
|
108
|
+
if (Array.isArray(breaks)) return validateBreaks(breaks);
|
|
109
|
+
if (isvector(breaks)) return validateBreaks(breaks.v);
|
|
110
|
+
|
|
111
|
+
console.error('Heatmap: "breaks" must be an array or Vector.');
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const valueRange = range(v.v);
|
|
116
|
+
if (valueRange[0] === valueRange[1]) {
|
|
117
|
+
const value = valueRange[0];
|
|
118
|
+
const delta = Math.abs(value) > 0 ? Math.abs(value) * 0.01 : 1;
|
|
119
|
+
const lower = Number.isFinite(value - delta) ? value - delta : value;
|
|
120
|
+
const upper = Number.isFinite(value + delta) ? value + delta : value;
|
|
121
|
+
return validateBreaks([lower, upper]);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return validateBreaks(split(v.v, HEATMAP_NUM_SPLITS).v);
|
|
55
125
|
});
|
|
56
126
|
|
|
57
127
|
// check and process colormap values
|
|
58
128
|
let lc = $derived.by(() => {
|
|
59
|
-
if (
|
|
60
|
-
|
|
61
|
-
|
|
129
|
+
if (!lb) return null;
|
|
130
|
+
|
|
131
|
+
if (colmap !== null && colmap !== undefined) {
|
|
132
|
+
if (!Array.isArray(colmap) || colmap.some(color =>
|
|
133
|
+
typeof color !== 'string' || color.trim() === ''
|
|
134
|
+
)) {
|
|
135
|
+
console.error('Heatmap: parameter "colmap" must be an array of non-empty color strings.');
|
|
62
136
|
return null;
|
|
63
137
|
}
|
|
64
|
-
if (colmap.length !==
|
|
138
|
+
if (colmap.length !== lb.length - 1) {
|
|
65
139
|
console.error('Heatmap: number of color values in colormap does not match number of intervals defined by breaks.');
|
|
66
140
|
return null;
|
|
67
141
|
}
|
|
@@ -81,10 +155,11 @@
|
|
|
81
155
|
const [top, left] = lt;
|
|
82
156
|
const nv = left.length; // number of values in matrix
|
|
83
157
|
const nb = lb.length; // number of breaks
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
const
|
|
158
|
+
const intervalCount = nb - 1;
|
|
159
|
+
const rl = Array.from({length: intervalCount}, () => []); // coordinates of left for every interval
|
|
160
|
+
const rt = Array.from({length: intervalCount}, () => []); // coordinates of top for every interval
|
|
161
|
+
const rr = Array.from({length: intervalCount}, () => []); // row indices for every interval
|
|
162
|
+
const rc = Array.from({length: intervalCount}, () => []); // column indices for every interval
|
|
88
163
|
|
|
89
164
|
// adjust left side for the first break and right side for the last
|
|
90
165
|
const llb = lb.slice();
|
|
@@ -95,15 +170,13 @@
|
|
|
95
170
|
// loop over all breaks
|
|
96
171
|
for (let j = 0; j < nv; j++) {
|
|
97
172
|
const vj = v.v[j];
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
}
|
|
173
|
+
const i = findInterval(vj, llb);
|
|
174
|
+
if (i === -1) continue;
|
|
175
|
+
|
|
176
|
+
rl[i].push(left.v[j] - 0.5);
|
|
177
|
+
rt[i].push(v.nrows - top.v[j] + 1.5);
|
|
178
|
+
rr[i].push(top.v[j]);
|
|
179
|
+
rc[i].push(left.v[j]);
|
|
107
180
|
};
|
|
108
181
|
return {rl, rt, rr, rc};
|
|
109
182
|
});
|
package/src/2d/Legend.svelte
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
Main properties:
|
|
5
5
|
- `items` - array with text labels and their visual properties for each legend element.
|
|
6
6
|
- `position` - position of the legend ("topleft", "top", "topright", "right", etc), default: `"topleft"`.
|
|
7
|
+
- `orientation` - arrangement of legend items (`"vertical"` or `"horizontal"`), default: `"vertical"`.
|
|
7
8
|
- `lineColor` - color of the legend box line, default: `Colors.LEGEND`.
|
|
8
9
|
- `lineWidth` - width (thickness) of the legend box line in pixels: `1`.
|
|
9
10
|
- `faceColor` - color of the legend box background: `'#fff'`.
|
|
@@ -13,41 +14,47 @@
|
|
|
13
14
|
|
|
14
15
|
```svelte
|
|
15
16
|
<script>
|
|
16
|
-
import {Vector} from 'mdatools/
|
|
17
|
+
import { Vector } from 'mdatools/arrays';
|
|
18
|
+
import { Axes, Box, Legend, Lines, Points, XAxis, YAxis } from 'svelte-plots-basic/2d';
|
|
17
19
|
|
|
18
20
|
const x = Vector.seq(-10, 10, 0.1);
|
|
19
21
|
const y1 = x.apply(v => Math.cos(v));
|
|
20
22
|
const y2 = x.apply(v => Math.sin(v));
|
|
23
|
+
|
|
24
|
+
const line1 = { lineType: 3, lineColor: 'red' };
|
|
25
|
+
const line2 = { lineType: 1, lineColor: 'blue' };
|
|
26
|
+
const point2 = { marker: 1, faceColor: 'white', lineColor: 'blue' };
|
|
27
|
+
|
|
28
|
+
const items = [
|
|
29
|
+
{ label: 'cos(x)', line: line1 },
|
|
30
|
+
{ label: 'sin(x)', line: line2, point: point2 }
|
|
31
|
+
];
|
|
21
32
|
</script>
|
|
22
33
|
|
|
23
34
|
<Axes limX={[-10, 10]} limY={[-1.1, 1.1]} >
|
|
24
35
|
|
|
25
|
-
<Lines xValues={x} yValues={y1}
|
|
26
|
-
<Lines xValues={x} yValues={y2}
|
|
27
|
-
<Points xValues={x} yValues={y2}
|
|
36
|
+
<Lines xValues={x} yValues={y1} {...line1} />
|
|
37
|
+
<Lines xValues={x} yValues={y2} {...line2} />
|
|
38
|
+
<Points xValues={x} yValues={y2} {...point2} />
|
|
28
39
|
|
|
29
|
-
<Legend
|
|
30
|
-
position="topright"
|
|
31
|
-
items = {[
|
|
32
|
-
{"label": "cos(x)", "lineType": 1, "lineColor": "red"},
|
|
33
|
-
{"label": "sin(x)", "lineType": 1, "lineColor": "blue", "marker": 1, "faceColor": "white", "borderColor": "blue"},
|
|
34
|
-
]}
|
|
35
|
-
/>
|
|
40
|
+
<Legend position="top" orientation="horizontal" {items} />
|
|
36
41
|
|
|
37
42
|
<Box />
|
|
38
43
|
<XAxis label="x" showGrid={true} />
|
|
39
44
|
<YAxis label="f(x)" showGrid={true} />
|
|
40
45
|
</Axes>
|
|
46
|
+
```
|
|
41
47
|
-->
|
|
42
48
|
<script>
|
|
43
|
-
import { getContext } from 'svelte';
|
|
49
|
+
import { getContext, onDestroy } from 'svelte';
|
|
44
50
|
import { Colors, MARKER_SYMBOLS } from '../constants';
|
|
45
|
-
import { text2svg } from '../methods';
|
|
51
|
+
import { text2svg, normalizeLineType } from '../methods';
|
|
46
52
|
|
|
47
53
|
let {
|
|
48
54
|
items, // array with text labels and their visual properties for each legend element.
|
|
49
55
|
show = true, // logical, use to hide the element
|
|
50
|
-
|
|
56
|
+
position = 'topleft', // position of the legend ("topleft", "top", "topright", "right", "bottomright", etc).
|
|
57
|
+
orientation = 'vertical', // arrangement of legend items ("vertical" or "horizontal")
|
|
51
58
|
lineColor = Colors.LEGEND, // color of the legend box line
|
|
52
59
|
lineWidth = 1, // width (thickness) of the legend box line
|
|
53
60
|
faceColor = '#fff', // background color of the legend box
|
|
@@ -70,6 +77,21 @@
|
|
|
70
77
|
return position;
|
|
71
78
|
}
|
|
72
79
|
|
|
80
|
+
/* check if user provided orientation is correct */
|
|
81
|
+
function checkOrientation(orientation) {
|
|
82
|
+
|
|
83
|
+
if (typeof orientation !== 'string') {
|
|
84
|
+
console.error('Legend: value of "orientation" must be a string.');
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!['vertical', 'horizontal'].includes(orientation)) {
|
|
89
|
+
console.error('Legend: wrong value of "orientation" property');
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
return orientation;
|
|
93
|
+
}
|
|
94
|
+
|
|
73
95
|
/* check if user provided items are correct, transform text labels and set default values if they are absent */
|
|
74
96
|
function processItems(items) {
|
|
75
97
|
|
|
@@ -96,25 +118,35 @@
|
|
|
96
118
|
// set default values for lines
|
|
97
119
|
if (item.line) {
|
|
98
120
|
item.line = {...item.line};
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
121
|
+
item.line.lineType = normalizeLineType(
|
|
122
|
+
item.line.lineType ?? 1,
|
|
123
|
+
`Legend item ${i + 1}`
|
|
124
|
+
);
|
|
103
125
|
item.line.lineColor = item.line.lineColor ? item.line.lineColor : Colors.PRIMARY;
|
|
104
|
-
item.line.lineWidth = item.line.lineWidth
|
|
126
|
+
item.line.lineWidth = item.line.lineWidth ?? 1;
|
|
105
127
|
}
|
|
106
128
|
|
|
107
129
|
// set default values for markers
|
|
108
130
|
if (item.point) {
|
|
109
131
|
item.point = {...item.point};
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
132
|
+
const canConvertMarker =
|
|
133
|
+
typeof item.point.marker === 'number' ||
|
|
134
|
+
(typeof item.point.marker === 'string' && item.point.marker.trim() !== '');
|
|
135
|
+
const marker = item.point.marker == null ? 1 :
|
|
136
|
+
canConvertMarker ? Number(item.point.marker) : NaN;
|
|
137
|
+
|
|
138
|
+
if (!Number.isInteger(marker) || marker < 1 || marker > MARKER_SYMBOLS.length) {
|
|
139
|
+
console.error(
|
|
140
|
+
`Legend item ${i + 1}: parameter "marker" must be a whole number from 1 to ${MARKER_SYMBOLS.length}.`
|
|
141
|
+
);
|
|
142
|
+
item.point = null;
|
|
143
|
+
} else {
|
|
144
|
+
item.point.marker = marker;
|
|
145
|
+
item.point.faceColor = item.point.faceColor ? item.point.faceColor : 'transparent';
|
|
146
|
+
item.point.lineWidth = item.point.lineWidth ?? 1;
|
|
147
|
+
item.point.markerSize = item.point.markerSize ?? 1;
|
|
148
|
+
item.point.lineColor = item.point.lineColor ? item.point.lineColor : Colors.PRIMARY;
|
|
113
149
|
}
|
|
114
|
-
item.point.faceColor = item.point.faceColor ? item.point.faceColor : 'transparent';
|
|
115
|
-
item.point.lineWidth = item.point.lineWidth ? item.point.lineWidth : 1;
|
|
116
|
-
item.point.markerSize = item.point.markerSize ? item.point.markerSize : 1;
|
|
117
|
-
item.point.lineColor = item.point.lineColor ? item.point.lineColor : Colors.PRIMARY;
|
|
118
150
|
}
|
|
119
151
|
|
|
120
152
|
result.push(item);
|
|
@@ -122,15 +154,35 @@
|
|
|
122
154
|
return result;
|
|
123
155
|
}
|
|
124
156
|
|
|
125
|
-
//
|
|
157
|
+
// Validate and transform only when the corresponding input changes.
|
|
158
|
+
const processedItems = $derived(processItems(items));
|
|
159
|
+
const validPosition = $derived(checkPosition(position));
|
|
160
|
+
const validOrientation = $derived(checkOrientation(orientation));
|
|
161
|
+
|
|
162
|
+
// get context and update group legend parameters reactively
|
|
126
163
|
const axes = getContext('axes');
|
|
127
164
|
$effect(() => {
|
|
128
|
-
const newItems = processItems(items);
|
|
129
|
-
const newPosition = checkPosition(position);
|
|
130
165
|
axes.setGroupLegend(
|
|
131
|
-
|
|
132
|
-
{
|
|
166
|
+
processedItems && validPosition && validOrientation ?
|
|
167
|
+
{
|
|
168
|
+
show,
|
|
169
|
+
position: validPosition,
|
|
170
|
+
orientation: validOrientation,
|
|
171
|
+
items: processedItems,
|
|
172
|
+
lineColor,
|
|
173
|
+
faceColor,
|
|
174
|
+
lineWidth,
|
|
175
|
+
fontSize
|
|
176
|
+
} :
|
|
133
177
|
{show: false, position: null, items: null}
|
|
134
178
|
);
|
|
135
179
|
});
|
|
180
|
+
|
|
181
|
+
onDestroy(() => {
|
|
182
|
+
axes.setGroupLegend({
|
|
183
|
+
show: false,
|
|
184
|
+
position: null,
|
|
185
|
+
items: null
|
|
186
|
+
});
|
|
187
|
+
});
|
|
136
188
|
</script>
|
package/src/2d/Lines.svelte
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
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
|
|
5
|
+
- `xValues` - array or vector with x-coordinates of the points.
|
|
6
|
+
- `yValues` - array or vector with y-coordinates of the points.
|
|
7
7
|
- `lineColor` - lines color, default: `Colors.PRIMARY`.
|
|
8
|
-
- `lineWidth` - width (thickness) of the lines in pixels,
|
|
8
|
+
- `lineWidth` - width (thickness) of the lines in pixels, default: `1`.
|
|
9
9
|
- `lineType` - type of lines (`1` - solid, `2` - dashed, `3` - dotted, `4` - dashdot).
|
|
10
10
|
- `onclick` - function (callback) to be called when user clicks on a polyline.
|
|
11
11
|
|
|
@@ -27,10 +27,9 @@
|
|
|
27
27
|
<script>
|
|
28
28
|
import { getContext } from 'svelte';
|
|
29
29
|
import { Colors } from '../constants';
|
|
30
|
-
import { val2p, checkCoords, handleClick } from '../methods';
|
|
30
|
+
import { val2p, checkCoords, handleClick, normalizeLineType } from '../methods';
|
|
31
31
|
import { LINE_STYLES } from '../constants';
|
|
32
32
|
|
|
33
|
-
/** @type {Props} */
|
|
34
33
|
let {
|
|
35
34
|
xValues, // vector or array with x-coordinates of the points
|
|
36
35
|
yValues, // vector or array with y-coordinates of the points
|
|
@@ -44,13 +43,14 @@
|
|
|
44
43
|
// check user provided coordinates
|
|
45
44
|
const xv = $derived(checkCoords(xValues, 'Lines'));
|
|
46
45
|
const yv = $derived(xv ? checkCoords(yValues, 'Lines', xv.length) : null);
|
|
46
|
+
const validLineType = $derived(normalizeLineType(lineType, 'Lines'));
|
|
47
47
|
|
|
48
48
|
// get axes context and compute coordinates of polyline
|
|
49
49
|
const axes = getContext('axes');
|
|
50
50
|
const p = $derived(xv && yv ? val2p(xv, yv, axes.tX(), axes.tY()) : null);
|
|
51
51
|
|
|
52
52
|
// reactive variables for coordinates of data points in pixels
|
|
53
|
-
const lineStyleStr = $derived(`fill:transparent;stroke:${lineColor};stroke-width: ${lineWidth}px; stroke-dasharray:${LINE_STYLES[axes.scales().plot][
|
|
53
|
+
const lineStyleStr = $derived(`fill:transparent;stroke:${lineColor};stroke-width: ${lineWidth}px; stroke-dasharray:${LINE_STYLES[axes.scales().plot][validLineType - 1]}`);
|
|
54
54
|
</script>
|
|
55
55
|
|
|
56
56
|
{#if p}
|
|
@@ -60,4 +60,3 @@
|
|
|
60
60
|
<polyline class="line" points={p}/>
|
|
61
61
|
</g>
|
|
62
62
|
{/if}
|
|
63
|
-
|
package/src/2d/Multilines.svelte
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
@component Adds a series of polylines (like `<Lines>` but with several of those).
|
|
3
3
|
|
|
4
4
|
Main properties:
|
|
5
|
-
- `xValues` - array
|
|
6
|
-
- `yValues` - Matrix (object of class `Matrix` from `mdatools/
|
|
5
|
+
- `xValues` - array or vector with x-coordinates of the points.
|
|
6
|
+
- `yValues` - Matrix (object of class `Matrix` from `mdatools/arrays`) with y-coordinates of the points.
|
|
7
7
|
- `lineColor` - lines color, default: `Colors.PRIMARY`.
|
|
8
|
-
- `lineWidth` - width (thickness) of the lines in pixels,
|
|
8
|
+
- `lineWidth` - width (thickness) of the lines in pixels, default: `1`.
|
|
9
9
|
- `lineType` - type of segment lines (`1` - solid, `2` - dashed, `3` - dotted, `4` - dashdot).
|
|
10
10
|
- `onclick` - function (callback) to be called when user clicks on a polyline.
|
|
11
11
|
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
import { Vector, cbind } from 'mdatools/arrays';
|
|
17
17
|
import { Axes, Multilines } from 'svelte-plots-basic/2d';
|
|
18
18
|
|
|
19
|
-
const xValues = Vector.seq(0,
|
|
20
|
-
const
|
|
19
|
+
const xValues = Vector.seq(0, 10, 0.1);
|
|
20
|
+
const yValues = cbind(
|
|
21
21
|
xValues.apply(v => Math.sin(v)),
|
|
22
22
|
xValues.apply(v => Math.sin(v + 0.5) + 0.1),
|
|
23
23
|
xValues.apply(v => Math.sin(v - 0.5) - 0.1),
|
|
@@ -35,13 +35,12 @@
|
|
|
35
35
|
import { getContext } from 'svelte';
|
|
36
36
|
import { ismatrix } from 'mdatools/arrays';
|
|
37
37
|
import { Colors } from '../constants';
|
|
38
|
-
import { val2p, checkCoords, handleClick } from '../methods';
|
|
38
|
+
import { val2p, checkCoords, handleClick, normalizeLineType } from '../methods';
|
|
39
39
|
import { LINE_STYLES } from '../constants';
|
|
40
40
|
|
|
41
|
-
/** @type {Props} */
|
|
42
41
|
let {
|
|
43
42
|
xValues, // vector or array with x-coordinates of the points
|
|
44
|
-
yValues, //
|
|
43
|
+
yValues, // matrix with y-coordinates of the points
|
|
45
44
|
lineColor = Colors.PRIMARY, // color of segment lines
|
|
46
45
|
lineType = 1, // type of segment lines (1 - solid, 2 - dashed, 3 - dotted, 4 - dashdot)
|
|
47
46
|
lineWidth = 1, // width (thickness) of segment lines
|
|
@@ -50,6 +49,7 @@
|
|
|
50
49
|
|
|
51
50
|
// check user provided coordinates
|
|
52
51
|
const xv = $derived(checkCoords(xValues, 'Multilines'));
|
|
52
|
+
const validLineType = $derived(normalizeLineType(lineType, 'Multilines'));
|
|
53
53
|
const yv = $derived.by(() => {
|
|
54
54
|
|
|
55
55
|
if (!xv) return null;
|
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
|
|
62
62
|
if (xv.length !== yValues.nrows) {
|
|
63
63
|
console.error('Multilines: parameter "xValues" must have the same number of values as number of rows in "yValues".');
|
|
64
|
+
return null;
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
return yValues;
|
|
@@ -79,7 +80,7 @@
|
|
|
79
80
|
});
|
|
80
81
|
|
|
81
82
|
// reactive variables for coordinates of data points in pixels
|
|
82
|
-
const lineStyleStr = $derived(`fill:transparent;stroke:${lineColor};stroke-width: ${lineWidth}px; stroke-dasharray:${LINE_STYLES[axes.scales().plot][
|
|
83
|
+
const lineStyleStr = $derived(`fill:transparent;stroke:${lineColor};stroke-width: ${lineWidth}px; stroke-dasharray:${LINE_STYLES[axes.scales().plot][validLineType - 1]}`);
|
|
83
84
|
</script>
|
|
84
85
|
|
|
85
86
|
{#if pp && pp.length > 0}
|
|
@@ -91,4 +92,3 @@
|
|
|
91
92
|
{/each}
|
|
92
93
|
</g>
|
|
93
94
|
{/if}
|
|
94
|
-
|
package/src/2d/Points.svelte
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
@component Adds a series of points.
|
|
3
3
|
|
|
4
4
|
Main properties:
|
|
5
|
-
- `xValues` - array
|
|
6
|
-
- `yValues` - array
|
|
5
|
+
- `xValues` - array or vector with x-coordinates of the points.
|
|
6
|
+
- `yValues` - array or vector with y-coordinates of the points.
|
|
7
7
|
- `marker` - index for a point symbol (from 1 - default - to 8): `"●", "◼", "▲", "▼", "⬥", "+", "*", "✕"`.
|
|
8
8
|
- `faceColor` - face (fill) color of the markers, default: `'transparent'`.
|
|
9
9
|
- `lineColor` - border color of the markers, default: `Colors.PRIMARY`.
|
|
10
|
-
- `lineWidth` - width (thickness) of the markers' border in pixels,
|
|
11
|
-
- `markerSize` - size of the markers in em,
|
|
10
|
+
- `lineWidth` - width (thickness) of the markers' border in pixels, default: `1`.
|
|
11
|
+
- `markerSize` - size of the markers in em, default: `1`.
|
|
12
12
|
- `onclick` - function (callback) to be called when user clicks on a marker.
|
|
13
13
|
|
|
14
14
|
Example:
|
|
@@ -31,10 +31,9 @@
|
|
|
31
31
|
import { Colors, MARKER_SYMBOLS } from '../constants';
|
|
32
32
|
import { checkCoords, transformCoords, handleClick } from '../methods';
|
|
33
33
|
|
|
34
|
-
/** @type {Props} */
|
|
35
34
|
let {
|
|
36
|
-
xValues, // array
|
|
37
|
-
yValues, // array
|
|
35
|
+
xValues, // array or vector with x-coordinates of points
|
|
36
|
+
yValues, // array or vector with y-coordinates of points
|
|
38
37
|
marker = 1, // index for point symbol (from 1 to 8): "●", "◼", "▲", "▼", "⬥", "+", "*", "✕"
|
|
39
38
|
faceColor = 'transparent', // face (fill) color of the points
|
|
40
39
|
lineColor = Colors.PRIMARY, // border color of the points
|
|
@@ -46,8 +45,8 @@
|
|
|
46
45
|
|
|
47
46
|
// select which symbol to use as a marker
|
|
48
47
|
const markerSymbol = $derived.by(() => {
|
|
49
|
-
if (
|
|
50
|
-
console.error('Points: parameter "marker" must be a number from 1 to ' + MARKER_SYMBOLS.length + '.');
|
|
48
|
+
if (!Number.isInteger(marker) || marker < 1 || marker > MARKER_SYMBOLS.length) {
|
|
49
|
+
console.error('Points: parameter "marker" must be a whole number from 1 to ' + MARKER_SYMBOLS.length + '.');
|
|
51
50
|
return null;
|
|
52
51
|
}
|
|
53
52
|
return MARKER_SYMBOLS[marker - 1]
|
|
@@ -71,7 +70,7 @@
|
|
|
71
70
|
const className = $derived(['◼', '⬥', '+', '✳', '✕'].includes(markerSymbol) ? 'series-points2' : 'series-points1');
|
|
72
71
|
|
|
73
72
|
// check if all coordinates are correct
|
|
74
|
-
const isOk = $derived(x && y && x.length === y.length);
|
|
73
|
+
const isOk = $derived(markerSymbol && x && y && x.length === y.length);
|
|
75
74
|
</script>
|
|
76
75
|
|
|
77
76
|
{#if isOk}
|
package/src/2d/Rectangles.svelte
CHANGED
|
@@ -3,20 +3,19 @@
|
|
|
3
3
|
|
|
4
4
|
Main properties:
|
|
5
5
|
- `left` - array or vector with world coordinates of left side of the rectangles.
|
|
6
|
-
- `top` - array or vector with world coordinates of
|
|
6
|
+
- `top` - array or vector with world coordinates of top side of the rectangles.
|
|
7
7
|
- `width` - width of the rectangles (one value or array/vector with individual values).
|
|
8
8
|
- `height` - height of the rectangles (one value or array/vector with individual values).
|
|
9
|
-
- `
|
|
9
|
+
- `faceColor` - face color of the rectangles (same for all).
|
|
10
10
|
- `lineColor` - border color of each rectangle.
|
|
11
11
|
- `lineWidth` - width (thickness) of border lines in pixels.
|
|
12
|
-
- `className
|
|
12
|
+
- `className` - CSS class name for the rectangles group, default: `'series-rect'`.
|
|
13
13
|
- `onclick` - function (callback) to be called when user clicks on any rectangle.
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
Example:
|
|
17
|
-
```
|
|
17
|
+
```svelte
|
|
18
18
|
<script>
|
|
19
|
-
import { Matrix } from 'mdatools/arrays';
|
|
20
19
|
import { Axes, Rectangles } from 'svelte-plots-basic/2d';
|
|
21
20
|
|
|
22
21
|
const left = [0, 10, 20, 30];
|
|
@@ -32,28 +31,58 @@
|
|
|
32
31
|
-->
|
|
33
32
|
<script>
|
|
34
33
|
import { getContext } from 'svelte';
|
|
35
|
-
import { Vector } from 'mdatools/arrays';
|
|
34
|
+
import { Vector, isvector } from 'mdatools/arrays';
|
|
36
35
|
import { Colors } from '../constants';
|
|
37
36
|
import { checkCoords, transformCoords, transformObjects, handleClick } from '../methods';
|
|
38
37
|
|
|
39
|
-
/** @type {Props} */
|
|
40
38
|
let {
|
|
41
|
-
left, // array
|
|
42
|
-
top, // array
|
|
43
|
-
width, // single value (same for all) or vector/array with
|
|
44
|
-
height, // single value (same for all) or vector/array with
|
|
45
|
-
faceColor = Colors.PRIMARY, // color of
|
|
46
|
-
lineColor = faceColor,
|
|
47
|
-
lineWidth = 1, // width (thickness) of
|
|
39
|
+
left, // array or vector with coordinates of left sides of the rectangles
|
|
40
|
+
top, // array or vector with coordinates of top sides of the rectangles
|
|
41
|
+
width, // single value (same for all) or vector/array with rectangle width
|
|
42
|
+
height, // single value (same for all) or vector/array with rectangle height
|
|
43
|
+
faceColor = Colors.PRIMARY, // color of rectangle faces (fill)
|
|
44
|
+
lineColor = faceColor, // color of rectangle borders
|
|
45
|
+
lineWidth = 1, // width (thickness) of rectangle border lines
|
|
48
46
|
className = 'series-rect', // CSS class name of the SVG group
|
|
49
47
|
onclick, // function to be called if onclick event fires
|
|
50
48
|
} = $props();
|
|
51
49
|
|
|
50
|
+
function checkDimensions(value, name, len) {
|
|
51
|
+
const canConvert =
|
|
52
|
+
typeof value === 'number' ||
|
|
53
|
+
(typeof value === 'string' && value.trim() !== '');
|
|
54
|
+
let dimensions;
|
|
55
|
+
|
|
56
|
+
if (canConvert) {
|
|
57
|
+
const scalar = Number(value);
|
|
58
|
+
if (!Number.isFinite(scalar) || scalar < 0) {
|
|
59
|
+
console.error(`Rectangles: parameter "${name}" must contain only finite non-negative values.`);
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
dimensions = Vector.fill(scalar, len);
|
|
63
|
+
} else if (Array.isArray(value) || isvector(value)) {
|
|
64
|
+
dimensions = checkCoords(value, `Rectangles (${name})`, len);
|
|
65
|
+
if (!dimensions) return null;
|
|
66
|
+
} else {
|
|
67
|
+
console.error(`Rectangles: parameter "${name}" must be a number, array, or Vector.`);
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
for (let i = 0; i < dimensions.length; i++) {
|
|
72
|
+
if (dimensions.v[i] < 0) {
|
|
73
|
+
console.error(`Rectangles: parameter "${name}" must contain only finite non-negative values.`);
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return dimensions;
|
|
79
|
+
}
|
|
80
|
+
|
|
52
81
|
// process provided values and compute world coordinates of rectangles
|
|
53
82
|
const l = $derived(checkCoords(left, 'Rectangles (left)'));
|
|
54
83
|
const t = $derived(l ? checkCoords(top, 'Rectangles (top)', l.length) : null);
|
|
55
|
-
const w = $derived(t ? (
|
|
56
|
-
const h = $derived(t ? (
|
|
84
|
+
const w = $derived(t ? checkDimensions(width, 'width', l.length) : null);
|
|
85
|
+
const h = $derived(t ? checkDimensions(height, 'height', l.length) : null);
|
|
57
86
|
|
|
58
87
|
// get axes context and compute screen coordinates
|
|
59
88
|
const axes = getContext('axes');
|
|
@@ -63,11 +92,11 @@
|
|
|
63
92
|
const rw = $derived(w ? transformObjects(w, axes.tX()) : null);
|
|
64
93
|
const rh = $derived(h ? transformObjects(h, axes.tY()) : null);
|
|
65
94
|
|
|
66
|
-
// styles for
|
|
95
|
+
// styles for rectangles
|
|
67
96
|
const barsStyleStr = $derived(`fill:${faceColor};stroke:${lineColor};stroke-width:${lineWidth}px;`);
|
|
68
97
|
|
|
69
98
|
// check if all coordinates are correct
|
|
70
|
-
const isOk = $derived(rx && ry && rw && rh
|
|
99
|
+
const isOk = $derived(rx && ry && rw && rh);
|
|
71
100
|
</script>
|
|
72
101
|
|
|
73
102
|
{#if isOk}
|
|
@@ -79,5 +108,3 @@
|
|
|
79
108
|
{/each}
|
|
80
109
|
</g>
|
|
81
110
|
{/if}
|
|
82
|
-
|
|
83
|
-
|