svelte-plots-basic 2.5.7 → 3.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/README.md +530 -445
- package/package.json +5 -2
- package/src/2d/Area.svelte +56 -48
- package/src/2d/Axes.svelte +409 -373
- package/src/2d/AxisLines.svelte +37 -36
- package/src/2d/AxisTickLabels.svelte +41 -49
- package/src/2d/Bars.svelte +102 -47
- package/src/2d/Box.svelte +21 -41
- package/src/2d/ColormapLegend.svelte +47 -111
- package/src/2d/Heatmap.svelte +107 -85
- package/src/2d/Legend.svelte +115 -126
- package/src/2d/Lines.svelte +54 -40
- package/src/2d/Multilines.svelte +73 -46
- package/src/2d/Points.svelte +76 -58
- package/src/2d/Rectangles.svelte +67 -58
- package/src/2d/Segments.svelte +59 -46
- package/src/2d/TextLabels.svelte +102 -72
- package/src/2d/TextLegend.svelte +17 -39
- package/src/2d/XAxis.svelte +49 -98
- package/src/2d/YAxis.svelte +47 -93
- package/src/2d/index.js +2 -1
- package/src/3d/Axes.svelte +306 -348
- package/src/3d/AxisLines.svelte +19 -37
- package/src/3d/AxisTickLabels.svelte +15 -32
- package/src/3d/Lines.svelte +59 -32
- package/src/3d/Mesh.svelte +106 -128
- package/src/3d/Points.svelte +56 -36
- package/src/3d/Segments.svelte +68 -52
- package/src/3d/TextLabels.svelte +63 -49
- package/src/3d/XAxis.svelte +36 -113
- package/src/3d/YAxis.svelte +36 -113
- package/src/3d/ZAxis.svelte +37 -114
- package/src/3d/index.js +2 -1
- package/src/constants.js +103 -0
- package/src/methods.js +1484 -0
- package/src/2d/Axis.svelte +0 -60
- package/src/3d/Axis.svelte +0 -64
- package/src/Colors.js +0 -51
- package/src/Utils.js +0 -626
package/src/2d/Heatmap.svelte
CHANGED
|
@@ -1,99 +1,123 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component Adds a heatmap visializing values of a matrix.
|
|
3
|
+
|
|
4
|
+
Main properties:
|
|
5
|
+
- `values` - matrix (object of class `Matrix` from `mdatools` package) with values to visualize.
|
|
6
|
+
- `breaks` - array (or vector) with interval breaks boundaries (optional).
|
|
7
|
+
- `colmap` - array with colors for each break (optional).
|
|
8
|
+
|
|
9
|
+
Example:
|
|
10
|
+
|
|
11
|
+
```svelte
|
|
12
|
+
<script>
|
|
13
|
+
import { Matrix } from 'mdatools/arrays';
|
|
14
|
+
import { Axes, Heatmap } from 'svelte-plots-basic/2d';
|
|
15
|
+
|
|
16
|
+
// create matrix with 5 rows and 10 columns filled with 5random values.
|
|
17
|
+
const x = Matrix.rand(5, 10);
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<Axes>
|
|
21
|
+
<Heatmap values={x} />
|
|
22
|
+
</Axes>
|
|
23
|
+
```
|
|
24
|
+
-->
|
|
1
25
|
<script>
|
|
2
|
-
/*********************************************************
|
|
3
|
-
* Heatmap *
|
|
4
|
-
* -------------------- *
|
|
5
|
-
* shows matrix values as a heatmap *
|
|
6
|
-
**********************************************************/
|
|
7
26
|
|
|
8
27
|
import { getContext } from 'svelte';
|
|
9
|
-
import {
|
|
28
|
+
import { ismatrix, isvector } from 'mdatools/arrays';
|
|
10
29
|
import { expandgrid } from 'mdatools/misc';
|
|
11
30
|
import { split } from 'mdatools/stat';
|
|
12
31
|
|
|
13
|
-
import { getcolmap } from '../
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
32
|
+
import { getcolmap, transformObjects, transformCoords, seq } from '../methods';
|
|
33
|
+
import { HEATMAP_NUM_SPLITS } from '../constants';
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
/** @type {Props} */
|
|
37
|
+
let {
|
|
38
|
+
values, // Matrix with values to show heatmap for
|
|
39
|
+
breaks, // vector with breaks to distribute the values in
|
|
40
|
+
colmap = null, // array with colors for each interval
|
|
41
|
+
} = $props();
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
// check that values are provided as a matrix
|
|
45
|
+
let v = $derived(ismatrix(values) && values.nrows > 0 && values.ncols > 0 ? values : null);
|
|
46
|
+
|
|
47
|
+
// check and process left values for breaks
|
|
48
|
+
let lb = $derived.by(() => {
|
|
49
|
+
if (!v) return null;
|
|
50
|
+
if (breaks && Array.isArray(breaks)) return breaks;
|
|
51
|
+
if (breaks && isvector(breaks)) return breaks.v;
|
|
52
|
+
return split(v.v, HEATMAP_NUM_SPLITS).v;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// check and process colormap values
|
|
56
|
+
let lc = $derived.by(() => {
|
|
57
|
+
if (colmap) {
|
|
58
|
+
if (!Array.isArray(colmap)) {
|
|
59
|
+
console.error('Heatmap: parameter "colmap" must be array with colors.');
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
if (colmap.length !== breaks.length - 1) {
|
|
63
|
+
console.error('Heatmap: number of color values in colormap does not match number of intervals defined by breaks.');
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
return colmap;
|
|
67
|
+
}
|
|
68
|
+
return getcolmap(lb.length - 1);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// compute coordinates of heatmap elements (left top corners)
|
|
72
|
+
let l = $derived(v ? seq(v.ncols) : null);
|
|
73
|
+
let t = $derived(v ? seq(v.nrows) : null);
|
|
74
|
+
let lt = $derived(l && t ? expandgrid(t, l) : null);
|
|
75
|
+
|
|
76
|
+
// compute world coordinates of the elements for each interval in world coordinates
|
|
77
|
+
let wc = $derived.by( () => {
|
|
78
|
+
if (!lt || !lb || !lc) return null;
|
|
79
|
+
const [top, left] = lt;
|
|
80
|
+
const nv = left.length; // number of values in matrix
|
|
81
|
+
const nb = lb.length; // number of breaks
|
|
82
|
+
const rl = Array(nb).fill().map(() => []); // coordinates of left for every break
|
|
83
|
+
const rt = Array(nb).fill().map(() => []); // coordinates of top for every break
|
|
84
|
+
|
|
85
|
+
// adjust left side for the first break and right side for the last
|
|
86
|
+
const llb = lb.slice();
|
|
87
|
+
const w = llb[1] - llb[0];
|
|
88
|
+
llb[0] = llb[0] - 0.1 * w;
|
|
89
|
+
llb[nb - 1] = llb[nb - 1] + 0.1 * w;
|
|
90
|
+
|
|
91
|
+
// loop over all breaks
|
|
92
|
+
for (let j = 0; j < nv; j++) {
|
|
93
|
+
const vj = v.v[j];
|
|
94
|
+
for (let i = 0; i < nb - 1; i++) {
|
|
95
|
+
if (vj > llb[i] && vj <= llb[i + 1]) {
|
|
96
|
+
rl[i].push(left.v[j] - 0.5);
|
|
97
|
+
rt[i].push(v.nrows - top.v[j] + 1.5);
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
return {rl, rt};
|
|
103
|
+
});
|
|
47
104
|
|
|
48
|
-
// define local variables for breaks and colormap
|
|
49
|
-
$: lBreaks = breaks ? breaks.v : split(values.v, 13).v;
|
|
50
|
-
$: lColmap = colmap ? colmap : getcolmap(lBreaks.length - 1);
|
|
51
105
|
|
|
52
|
-
// get axes context and
|
|
106
|
+
// get axes context and compute screen coordinates
|
|
53
107
|
const axes = getContext('axes');
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
// compute combinations of heatmap element positions
|
|
59
|
-
let top, left;
|
|
60
|
-
$: {
|
|
61
|
-
const x = Vector.seq(1, values.ncols);
|
|
62
|
-
const y = Vector.seq(1, values.nrows);
|
|
63
|
-
[top, left] = expandgrid(y.v, x.v);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// compute screen coordinates of the elements for each interval
|
|
67
|
-
let rx, ry, rw, rh = undefined;
|
|
68
|
-
$: {
|
|
69
|
-
if ($isOk) {
|
|
70
|
-
rx = [];
|
|
71
|
-
ry = [];
|
|
72
|
-
for (let i = 1; i < lBreaks.length; i++) {
|
|
73
|
-
let lleft = [];
|
|
74
|
-
let ltop = [];
|
|
75
|
-
for (let j = 0; j < values.v.length; j++) {
|
|
76
|
-
const v = values.v[j];
|
|
77
|
-
if (v > lBreaks[i - 1] && v <= lBreaks[i]) {
|
|
78
|
-
lleft.push(left.v[j] - 0.5);
|
|
79
|
-
ltop.push(top.v[j] + 0.5);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
rx.push(lleft.length === 0 ? [] : axes.transform(checkCoords(lleft, 'Rectangles'), $tX.coords));
|
|
83
|
-
ry.push(ltop.length === 0 ? [] : axes.transform(checkCoords(ltop, 'Rectangles'), $tY.coords));
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
rw = axes.transform([1], $tX.objects);
|
|
87
|
-
rh = axes.transform([1], $tY.objects);
|
|
88
|
-
}
|
|
89
|
-
};
|
|
108
|
+
let rx = $derived(wc ? wc.rl.map(v => v.length > 0 ? transformCoords(v, axes.tX()) : []) : null);
|
|
109
|
+
let ry = $derived(wc ? wc.rt.map(v => v.length > 0 ? transformCoords(v, axes.tY()) : []) : null);
|
|
110
|
+
let rw = $derived(transformObjects([1], axes.tX()));
|
|
111
|
+
let rh = $derived(transformObjects([1], axes.tY()));
|
|
90
112
|
|
|
113
|
+
// check status
|
|
114
|
+
let isOk = $derived(rx && ry && rx.length === ry.length);
|
|
91
115
|
</script>
|
|
92
116
|
|
|
93
|
-
{#if
|
|
94
|
-
<g class="
|
|
117
|
+
{#if isOk}
|
|
118
|
+
<g class="heatmap" style="stroke:0;stroke-width:0px;">
|
|
95
119
|
<!-- loop over colors/intervals -->
|
|
96
|
-
{#each
|
|
120
|
+
{#each lc as col, i}
|
|
97
121
|
<g title="heatmap-group" style="fill:{col};">
|
|
98
122
|
{#if rx[i].length > 0}
|
|
99
123
|
<!-- loop over elements which fall into the interval -->
|
|
@@ -105,5 +129,3 @@
|
|
|
105
129
|
{/each}
|
|
106
130
|
</g>
|
|
107
131
|
{/if}
|
|
108
|
-
|
|
109
|
-
|
package/src/2d/Legend.svelte
CHANGED
|
@@ -1,140 +1,129 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component Adds legend for groups of series.
|
|
3
|
+
|
|
4
|
+
Main properties:
|
|
5
|
+
- `items` - array with text labels and their visual properties for each legend element.
|
|
6
|
+
- `position` - position of the legend ("topleft", "top", "topright", "right", etc), default: `"topleft"`.
|
|
7
|
+
- `lineColor` - color of the legend box line, default: `Colors.LEGEND`.
|
|
8
|
+
- `lineWidth` - width (thickness) of the legend box line in pixels: `1`.
|
|
9
|
+
- `faceColor` - color of the legend box background: `'#fff'`.
|
|
10
|
+
- `fontSize` - font size for the legend labels in em, default `0.85`.
|
|
11
|
+
|
|
12
|
+
Example:
|
|
13
|
+
|
|
14
|
+
```svelte
|
|
15
|
+
<script>
|
|
16
|
+
import {Vector} from 'mdatools/array';
|
|
17
|
+
|
|
18
|
+
const x = Vector.seq(-10, 10, 0.1);
|
|
19
|
+
const y1 = x.apply(v => Math.cos(v));
|
|
20
|
+
const y2 = x.apply(v => Math.sin(v));
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<Axes limX={[-10, 10]} limY={[-1.1, 1.1]} >
|
|
24
|
+
|
|
25
|
+
<Lines xValues={x} yValues={y1} lineColor="red" lineType="3" />
|
|
26
|
+
<Lines xValues={x} yValues={y2} lineColor="blue" lineType="1" />
|
|
27
|
+
<Points xValues={x} yValues={y2} faceColor="white" borderColor="blue" />
|
|
28
|
+
|
|
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
|
+
/>
|
|
36
|
+
|
|
37
|
+
<Box />
|
|
38
|
+
<XAxis label="x" showGrid={true} />
|
|
39
|
+
<YAxis label="f(x)" showGrid={true} />
|
|
40
|
+
</Axes>
|
|
41
|
+
-->
|
|
1
42
|
<script>
|
|
2
|
-
/****************************************************
|
|
3
|
-
* Legend *
|
|
4
|
-
* -------------------- *
|
|
5
|
-
* shows legend for plot components *
|
|
6
|
-
*****************************************************/
|
|
7
|
-
|
|
8
|
-
import { mean } from 'mdatools/stat';
|
|
9
43
|
import { getContext } from 'svelte';
|
|
10
|
-
import { Colors } from '../
|
|
11
|
-
|
|
44
|
+
import { Colors, MARKER_SYMBOLS } from '../constants';
|
|
45
|
+
import { text2svg } from '../methods';
|
|
12
46
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
47
|
+
let {
|
|
48
|
+
items, // array with text labels and their visual properties for each legend element.
|
|
49
|
+
position = 'topleft', // position of the legend ("topleft", "top", "topright", "right", "bottomright", etc).
|
|
50
|
+
lineColor = Colors.LEGEND, // color of the legend box line
|
|
51
|
+
lineWidth = 1, // width (thickness) of the legend box line
|
|
52
|
+
faceColor = '#fff', // background color of the legend box
|
|
53
|
+
fontSize = 0.85 // font size for labels in em.
|
|
54
|
+
} = $props();
|
|
16
55
|
|
|
17
|
-
export let items; // array with text labels for each legend element
|
|
18
|
-
export let position = "topleft"; // position of the legend ("topleft", "top", "topright", "right", "bottomright", etc).
|
|
19
56
|
|
|
57
|
+
/* check if user provided position is correct */
|
|
58
|
+
function checkPosition(position) {
|
|
20
59
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const axes = getContext('axes');
|
|
26
|
-
const scale = axes.scale;
|
|
27
|
-
const xLim = axes.xLim;
|
|
28
|
-
const yLim = axes.yLim;
|
|
29
|
-
const tX = axes.tX;
|
|
30
|
-
const tY = axes.tY;
|
|
31
|
-
const isOk = axes.isOk;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Computes width of text element for given font.
|
|
36
|
-
*
|
|
37
|
-
* @param {string} text - string with text
|
|
38
|
-
* @param {string} font - font name and size, e.g. '12px Arial'
|
|
39
|
-
*
|
|
40
|
-
* @return {number} size of element in pixels
|
|
41
|
-
*/
|
|
42
|
-
function getTextWidth(text, font) {
|
|
43
|
-
const element = document.createElement('canvas');
|
|
44
|
-
const context = element.getContext('2d');
|
|
45
|
-
context.font = font;
|
|
46
|
-
return context.measureText(text.replace(/<(.|\n)*?>/g, '')).width;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Compute size of main legend elements.
|
|
51
|
-
*
|
|
52
|
-
* @param {Array} items - array of legend items and their properties.
|
|
53
|
-
* @param {number} fontSize - font size of a legend element.
|
|
54
|
-
* @param {string} position - position of the legend.
|
|
55
|
-
*
|
|
56
|
-
* @return {Array} array with height and width og graphical part of legend item, padding size,
|
|
57
|
-
* height and width of text labels (all in pixels).
|
|
58
|
-
*/
|
|
59
|
-
function getLegendSize(items, fontSize, position) {
|
|
60
|
-
|
|
61
|
-
// compute size of graphical elements
|
|
62
|
-
const elHeight = fontSize; // height of legend element without padding
|
|
63
|
-
const elPadding = elHeight / 4; // top and bottom padding
|
|
64
|
-
const elWidth = elHeight * 1.5; // width of legend element without padding
|
|
65
|
-
|
|
66
|
-
// compute size of text label elements
|
|
67
|
-
// TODO: implement one row legend if position is "top" or "bottom"
|
|
68
|
-
const lbHeight = elHeight;
|
|
69
|
-
let lbWidth = 0;
|
|
70
|
-
for (let i = 0; i < items.length; i++) {
|
|
71
|
-
const w = getTextWidth(' ' + items[i].label + ' ', fontSize + 'px Arial');
|
|
72
|
-
lbWidth = w > lbWidth ? w : lbWidth;
|
|
60
|
+
if (typeof position !== 'string') {
|
|
61
|
+
console.error('Legend: value of "position" must be a string.');
|
|
62
|
+
return null;
|
|
73
63
|
}
|
|
74
64
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
$: if ($isOk) {
|
|
81
|
-
|
|
82
|
-
fontSize = axes.LEGEND_FONT_SIZE[$scale];
|
|
83
|
-
|
|
84
|
-
// compute size of elements
|
|
85
|
-
[elHeight, elWidth, elPadding, lbHeight, lbWidth] = getLegendSize(items, fontSize, position);
|
|
86
|
-
|
|
87
|
-
// compute size of whole legend box
|
|
88
|
-
legendHeight = (elHeight + 2 * elPadding) * items.length;
|
|
89
|
-
legendWidth = (elWidth + 2 * elPadding + lbWidth);
|
|
90
|
-
|
|
91
|
-
// compute coordinates of top left corner of the legend box
|
|
92
|
-
const xLimPx = axes.transform($xLim, $tX.coords);
|
|
93
|
-
left = position.includes("left") ? xLimPx[0] + axes.TICK_SIZE[$scale] :
|
|
94
|
-
position.includes("right") ? xLimPx[1] - legendWidth - axes.TICK_SIZE[$scale] :
|
|
95
|
-
mean(xLimPx) - legendWidth/2;
|
|
96
|
-
|
|
97
|
-
const yLimPx = axes.transform($yLim, $tY.coords);
|
|
98
|
-
top = position.includes("top") ? yLimPx[1] + axes.TICK_SIZE[$scale] :
|
|
99
|
-
position.includes("bottom") ? yLimPx[0] - legendHeight - axes.TICK_SIZE[$scale] :
|
|
100
|
-
mean(yLimPx) - legendHeight/2;
|
|
65
|
+
if (!['top', 'left', 'right', 'topleft', 'topright', 'bottom', 'bottomleft', 'bottomright'].includes(position)) {
|
|
66
|
+
console.error('Legend: wrong value of "position" property');
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
return position;
|
|
101
70
|
}
|
|
102
|
-
</script>
|
|
103
|
-
|
|
104
|
-
{#if $isOk }
|
|
105
|
-
|
|
106
|
-
<!-- outer legend box, background and frame -->
|
|
107
|
-
<svg x="{left}px" y="{top}px" height="{legendHeight}px" width="{legendWidth}">
|
|
108
|
-
<rect height="100%" width="100%" fill="white" stroke="{Colors.MIDDLEGRAY}"></rect>
|
|
109
71
|
|
|
110
|
-
|
|
72
|
+
/* check if user provided items are correct, transform text labels and set default values if they are absent */
|
|
73
|
+
function processItems(items) {
|
|
111
74
|
|
|
112
|
-
|
|
113
|
-
|
|
75
|
+
if (!items || !Array.isArray(items) || items.length < 1) {
|
|
76
|
+
console.error('Legend: property "items" must be provided as array of JSONs.');
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
114
79
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
80
|
+
for (let i = 0; i < items.length; i++) {
|
|
81
|
+
const item = items[i];
|
|
82
|
+
|
|
83
|
+
// check and transform label
|
|
84
|
+
if (!item.label) {
|
|
85
|
+
console.error('Legend: all elements of "items" must include at least one field: "label".');
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const newLabel = text2svg(item.label);
|
|
90
|
+
const labelHeight = newLabel.length === item.label ? 1 : 1.4;
|
|
91
|
+
item.label = newLabel;
|
|
92
|
+
item['labelHeight'] = labelHeight;
|
|
93
|
+
|
|
94
|
+
// set default values for lines
|
|
95
|
+
if (item.line) {
|
|
96
|
+
if (!item.line.lineType) item.line['lineType'] = 1
|
|
97
|
+
if (!(item.line.lineType > 0 && item.line.lineType <= 4)) {
|
|
98
|
+
console.error('Legend: parameter "lineType" for legend item ' + (i + 1) + ' is incorrect.');
|
|
99
|
+
}
|
|
100
|
+
item.line.lineColor = item.line.lineColor ? item.line.lineColor : Colors.PRIMARY;
|
|
101
|
+
item.line.lineWidth = item.line.lineWidth ? item.line.lineWidth : 1;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// set default values for markers
|
|
105
|
+
if (item.point) {
|
|
106
|
+
if (!item.point.marker) item.point['marker'] = 1
|
|
107
|
+
if (!(item.point.marker > 0 && item.point.marker <= MARKER_SYMBOLS.length)) {
|
|
108
|
+
console.error('Legend: parameter "marker" for legend item ' + (i + 1) + ' is incorrect.');
|
|
109
|
+
}
|
|
110
|
+
item.point.faceColor = item.point.faceColor ? item.point.faceColor : 'transparent';
|
|
111
|
+
item.point.lineWidth = item.point.lineWidth ? item.point.lineWidth : 1;
|
|
112
|
+
item.point.lineColor = item.point.lineColor ? item.point.lineColor : Colors.PRIMARY;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return items;
|
|
119
116
|
}
|
|
120
|
-
</style>
|
|
121
117
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
<!-- text -->
|
|
135
|
-
<text class="legend-text" xml:space="preserve" x="{elWidth + elPadding * 3}px" y="{elPadding}px" text-anchor="start" fill="{Colors.LEGEND}" font-size="{fontSize}px"> {@html item.label}</text>
|
|
136
|
-
|
|
137
|
-
</svg>
|
|
138
|
-
{/each}
|
|
139
|
-
</svg>
|
|
140
|
-
{/if}
|
|
118
|
+
// get context and update group legend parameters reactivey
|
|
119
|
+
const axes = getContext('axes');
|
|
120
|
+
$effect(() => {
|
|
121
|
+
const newItems = processItems(items);
|
|
122
|
+
const newPosition = checkPosition(position);
|
|
123
|
+
axes.setGroupLegend(
|
|
124
|
+
newItems && newPosition ?
|
|
125
|
+
{show: true, position, items, lineColor, faceColor, lineWidth, fontSize} :
|
|
126
|
+
{show: false, position: null, items: null}
|
|
127
|
+
);
|
|
128
|
+
});
|
|
129
|
+
</script>
|
package/src/2d/Lines.svelte
CHANGED
|
@@ -1,49 +1,63 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component Adds a series of lines connecting points with provided coordinates (polyline).
|
|
3
|
+
|
|
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
|
+
- `lineColor` - lines color, default: `Colors.PRIMARY`.
|
|
8
|
+
- `lineWidth` - width (thickness) of the lines in pixels, defailt: `1`.
|
|
9
|
+
- `lineType` - type of lines (`1` - solid, `2` - dashed, `3` - dotted, `4` - dashdot).
|
|
10
|
+
- `onclick` - function (callback) to be called when user clicks on a polyline.
|
|
11
|
+
|
|
12
|
+
Example:
|
|
13
|
+
|
|
14
|
+
```svelte
|
|
15
|
+
<script>
|
|
16
|
+
import { Axes, Lines } from 'svelte-plots-basic/2d';
|
|
17
|
+
|
|
18
|
+
const xValues = [-3, -2, -1, 0, 1, 2, 3];
|
|
19
|
+
const yValues = [9, 4, 1, 0, 1, 4, 9];
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<Axes limX={[-4, 4]} limY={[-2, 10]}>
|
|
23
|
+
<Lines {xValues} {yValues} />
|
|
24
|
+
</Axes>
|
|
25
|
+
```
|
|
26
|
+
-->
|
|
1
27
|
<script>
|
|
2
|
-
/****************************************************
|
|
3
|
-
* Lines *
|
|
4
|
-
* -------------------- *
|
|
5
|
-
* shows lines connected sequence of points *
|
|
6
|
-
*****************************************************/
|
|
7
|
-
|
|
8
28
|
import { getContext } from 'svelte';
|
|
9
|
-
import { Colors } from '../
|
|
10
|
-
import { val2p } from '../
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
// get axes context
|
|
29
|
+
import { Colors } from '../constants';
|
|
30
|
+
import { val2p, checkCoords, handleClick } from '../methods';
|
|
31
|
+
import { LINE_STYLES } from '../constants';
|
|
32
|
+
|
|
33
|
+
/** @type {Props} */
|
|
34
|
+
let {
|
|
35
|
+
xValues, // vector or array with x-coordinates of the points
|
|
36
|
+
yValues, // vector or array with y-coordinates of the points
|
|
37
|
+
lineColor = Colors.PRIMARY, // color of segment lines
|
|
38
|
+
lineType = 1, // type of segment lines (1 - solid, 2 - dashed, 3 - dotted, 4 - dashdot)
|
|
39
|
+
lineWidth = 1, // width (thickness) of segment lines
|
|
40
|
+
onclick, // function to be called if onclick event fires
|
|
41
|
+
} = $props();
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
// check user provided coordinates
|
|
45
|
+
const xv = $derived(checkCoords(xValues, 'Lines'));
|
|
46
|
+
const yv = $derived(xv ? checkCoords(yValues, 'Lines', xv.length) : null);
|
|
47
|
+
|
|
48
|
+
// get axes context and compute coordinates of polyline
|
|
30
49
|
const axes = getContext('axes');
|
|
31
|
-
|
|
32
|
-
const scale = axes.scale;
|
|
33
|
-
const isOk = axes.isOk;
|
|
34
|
-
const tX = axes.tX;
|
|
35
|
-
const tY = axes.tY;
|
|
36
|
-
|
|
37
|
-
// compute polynomial coordinates as string
|
|
38
|
-
$: p = $isOk ? val2p(xValues, yValues, $tX, $tY, axes) : undefined;
|
|
50
|
+
const p = $derived(xv && yv ? val2p(xv, yv, axes.tX(), axes.tY()) : null);
|
|
39
51
|
|
|
40
52
|
// reactive variables for coordinates of data points in pixels
|
|
41
|
-
|
|
53
|
+
const lineStyleStr = $derived(`fill:transparent;stroke:${lineColor};stroke-width: ${lineWidth}px; stroke-dasharray:${LINE_STYLES[axes.scales().plot][lineType-1]}`);
|
|
42
54
|
</script>
|
|
43
55
|
|
|
44
|
-
{#if
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
56
|
+
{#if p}
|
|
57
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
58
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
59
|
+
<g onclick={(e) => handleClick(e, 'polyline', onclick)} class="series series-line" style={lineStyleStr} >
|
|
60
|
+
<polyline class="line" points={p}/>
|
|
61
|
+
</g>
|
|
48
62
|
{/if}
|
|
49
63
|
|