svelte-plots-basic 3.4.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/README.md +31 -14
- package/package.json +4 -5
- package/src/2d/Area.svelte +8 -7
- package/src/2d/Axes.svelte +10 -8
- package/src/2d/AxisLines.svelte +4 -6
- package/src/2d/AxisTickLabels.svelte +1 -3
- package/src/2d/Bars.svelte +30 -9
- package/src/2d/Heatmap.svelte +15 -6
- package/src/2d/Legend.svelte +74 -30
- package/src/2d/Lines.svelte +6 -5
- package/src/2d/Multilines.svelte +9 -8
- package/src/2d/Points.svelte +9 -9
- package/src/2d/Rectangles.svelte +47 -18
- package/src/2d/Segments.svelte +3 -3
- package/src/2d/TextLabels.svelte +12 -10
- package/src/2d/TextLegend.svelte +49 -11
- package/src/2d/YAxis.svelte +1 -1
- package/src/3d/Axes.svelte +5 -7
- 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 +15 -13
- 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 +2 -4
- package/src/3d/YAxis.svelte +4 -6
- package/src/3d/ZAxis.svelte +4 -6
- package/src/ExportDialog.svelte +236 -66
- package/src/methods.js +241 -127
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
`svelte-plots-basic` is a [Svelte](https://svelte.dev) component library for making simple responsive 2D and 3D plots/charts. The plots are created by generating [SVG](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics) inside HTML document.
|
|
4
4
|
|
|
5
|
-
One can think of this library as "Lego" bricks for plots. It has two groups of components: for 2D and for 3D plots. The 2D components are more developed while 3D components remain quite basic. In addition to `svelte`, the package
|
|
5
|
+
One can think of this library as "Lego" bricks for plots. It has two groups of components: for 2D and for 3D plots. The 2D components are more developed while 3D components remain quite basic. In addition to `svelte`, the package requires the [mdatools-js](https://github.com/svkucheryavski/mdatools-js) library as a peer dependency. It is used for vector/matrix operations, statistics, and other manipulations with data values.
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
## Showcase
|
|
@@ -15,6 +15,14 @@ These websites and web-applications use `svelte-plots-basic` library:
|
|
|
15
15
|
|
|
16
16
|
## News
|
|
17
17
|
|
|
18
|
+
### 4.0.0
|
|
19
|
+
|
|
20
|
+
* Added horizontal legend layout and improved legend sizing and alignment.
|
|
21
|
+
* Improved PNG/SVG export reliability, export-dialog accessibility, and clipboard handling.
|
|
22
|
+
* Strengthened validation, automatic axis ticks, marker rendering, and component cleanup.
|
|
23
|
+
* Made subscripts, superscripts, and tick-factor labels consistent across modern browsers.
|
|
24
|
+
* Updated to `mdatools ^1.5.0`, now declared as a peer dependency to prevent incompatible duplicate instances.
|
|
25
|
+
|
|
18
26
|
### 3.4.0
|
|
19
27
|
|
|
20
28
|
* Added the `plotActions` property to `Axes` for adding custom buttons to the download panel.
|
|
@@ -220,7 +228,7 @@ You can also define tick positions and corresponding labels manually. Tick posit
|
|
|
220
228
|
|
|
221
229
|
```svelte
|
|
222
230
|
<Axes limX={[2019, 2026]} limY={[0, 60000]}>
|
|
223
|
-
<
|
|
231
|
+
<XAxis ticks={[2023, 2024, 2025]} tickLabels={['Past', 'Present', 'Future']} />
|
|
224
232
|
</Axes>
|
|
225
233
|
```
|
|
226
234
|
|
|
@@ -620,7 +628,7 @@ Here is an example:
|
|
|
620
628
|
|
|
621
629
|
```svelte
|
|
622
630
|
<script>
|
|
623
|
-
import { Axes,
|
|
631
|
+
import { Axes, XAxis, YAxis, Box, Area } from 'svelte-plots-basic/2d';
|
|
624
632
|
|
|
625
633
|
const xValues = [3, 2, 2.5, 4, 5.5, 6, 5];
|
|
626
634
|
const yValues = [1, 3, 4, 5, 4, 3, 1];
|
|
@@ -677,7 +685,7 @@ Here is an example:
|
|
|
677
685
|
|
|
678
686
|
```svelte
|
|
679
687
|
<script>
|
|
680
|
-
import { Axes,
|
|
688
|
+
import { Axes, XAxis, YAxis, Box, Heatmap } from 'svelte-plots-basic/2d';
|
|
681
689
|
import { Matrix } from 'mdatools/arrays';
|
|
682
690
|
|
|
683
691
|
// create 5 x 10 matrix filled with normally distributed random values
|
|
@@ -729,7 +737,7 @@ Colormap legend can be used together with `<Heatmap>` or any other color groupin
|
|
|
729
737
|
|
|
730
738
|
```svelte
|
|
731
739
|
<script>
|
|
732
|
-
import { Axes,
|
|
740
|
+
import { Axes, XAxis, YAxis, Box, Heatmap, ColormapLegend } from 'svelte-plots-basic/2d';
|
|
733
741
|
import { Matrix } from 'mdatools/arrays';
|
|
734
742
|
|
|
735
743
|
// create 5 x 10 matrix filled with normally distributed random values
|
|
@@ -789,16 +797,16 @@ Here is an example:
|
|
|
789
797
|
const y3 = x.apply(v => Math.pow(v, 4));
|
|
790
798
|
|
|
791
799
|
// define properties of each series
|
|
792
|
-
line1Props = {lineType: 3, lineColor: 'red'};
|
|
793
|
-
line2Props = {lineColor: 'blue', lineType: 1};
|
|
794
|
-
point2Props = {lineColor: 'blue', faceColor: 'white'};
|
|
795
|
-
point3Props = {marker: 5, lineColor: 'green', faceColor: 'yellow'};
|
|
800
|
+
const line1Props = {lineType: 3, lineColor: 'red'};
|
|
801
|
+
const line2Props = {lineColor: 'blue', lineType: 1};
|
|
802
|
+
const point2Props = {lineColor: 'blue', faceColor: 'white'};
|
|
803
|
+
const point3Props = {marker: 5, lineColor: 'green', faceColor: 'yellow'};
|
|
796
804
|
|
|
797
805
|
// legend items
|
|
798
|
-
items = [
|
|
799
|
-
{label: 'y=x^2', line:
|
|
800
|
-
{label: "y=x^3", line:
|
|
801
|
-
{label: "y=x^4", point:
|
|
806
|
+
const items = [
|
|
807
|
+
{label: 'y=x^2', line: line1Props },
|
|
808
|
+
{label: "y=x^3", line: line2Props, point: point2Props },
|
|
809
|
+
{label: "y=x^4", point: point3Props },
|
|
802
810
|
];
|
|
803
811
|
</script>
|
|
804
812
|
|
|
@@ -812,7 +820,7 @@ Here is an example:
|
|
|
812
820
|
<Points xValues={x} yValues={y2} {...point2Props} />
|
|
813
821
|
|
|
814
822
|
<!-- series 3: markers in form of diamonds with green stroke and yellow fill -->
|
|
815
|
-
<Points xValues={x} yValues={y3} {...
|
|
823
|
+
<Points xValues={x} yValues={y3} {...point3Props} />
|
|
816
824
|
|
|
817
825
|
<!-- legend with one JSON for each series -->
|
|
818
826
|
<Legend position="right" {items} />
|
|
@@ -826,12 +834,21 @@ Check this example in Svelte REPL: [plots-legend](https://svelte.dev/playground/
|
|
|
826
834
|
|
|
827
835
|
The `position` parameter can be one of the follows: `'topleft'`, `'top'`, `'topright'`, `'right'`, `'bottomright'` and so on.
|
|
828
836
|
|
|
837
|
+
By default, legend items are arranged vertically. Set `orientation="horizontal"` to arrange them in a single row, which is especially useful with the `top` and `bottom` positions:
|
|
838
|
+
|
|
839
|
+
```svelte
|
|
840
|
+
<Legend position="top" orientation="horizontal" {items} />
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
The horizontal layout does not wrap items onto additional rows, so a large number of items or long labels may require more plot width.
|
|
844
|
+
|
|
829
845
|
The component has also properties which changes it apearance, here is the full list:
|
|
830
846
|
|
|
831
847
|
Property name | Default value | Description
|
|
832
848
|
--|--|--
|
|
833
849
|
`items` | | array with JSON properties of legend items
|
|
834
850
|
`position` | `'topleft'` | position of the legend element inside plotting area
|
|
851
|
+
`orientation` | `'vertical'` | arrangement of legend items: `'vertical'` or `'horizontal'`
|
|
835
852
|
`lineColor` | `'#303030'` | color of the legend box line
|
|
836
853
|
`lineWidth` | `1` | width (thickness) of the legend box line
|
|
837
854
|
`faceColor` | `'#fff'` | background color of the legend box
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelte-plots-basic",
|
|
3
3
|
"description": "Svelte components for creating simple plots",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "4.0.0",
|
|
5
5
|
"author": "Sergey Kucheryavskiy",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"private": false,
|
|
@@ -20,14 +20,13 @@
|
|
|
20
20
|
"./3d/*.svelte": "./src/3d/*.svelte"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"
|
|
23
|
+
"mdatools": "^1.5.0",
|
|
24
|
+
"svelte": "^5.56.8"
|
|
24
25
|
},
|
|
25
26
|
"peerDependencies": {
|
|
27
|
+
"mdatools": "^1.5.0",
|
|
26
28
|
"svelte": "^5.0.0"
|
|
27
29
|
},
|
|
28
|
-
"dependencies": {
|
|
29
|
-
"mdatools": "^1.x.x"
|
|
30
|
-
},
|
|
31
30
|
"keywords": [
|
|
32
31
|
"svelte",
|
|
33
32
|
"plots",
|
package/src/2d/Area.svelte
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
@component Adds a closed polygon (area).
|
|
3
3
|
|
|
4
4
|
Main properties:
|
|
5
|
-
- `xValues` - array
|
|
6
|
-
- `yValues` - array
|
|
7
|
-
- `lineWidth` - width (thickness) of the polygon's line in pixels,
|
|
5
|
+
- `xValues` - array or vector with x-coordinates of the polygon's points.
|
|
6
|
+
- `yValues` - array or vector with y-coordinates of the polygon's points.
|
|
7
|
+
- `lineWidth` - width (thickness) of the polygon's line in pixels, default: `1`.
|
|
8
8
|
- `lineColor` - line color, default: `Colors.PRIMARY`.
|
|
9
9
|
- `lineType` - type of line (`1` - solid, `2` - dashed, `3` - dotted, `4` - dashdot).
|
|
10
|
-
- `
|
|
10
|
+
- `faceColor` - face (fill) color of the polygon, default: `'transparent'`.
|
|
11
11
|
- `opacity` - opacity of the face color, default: `1`.
|
|
12
12
|
- `onclick` - function (callback) to be called when user clicks on a polygon.
|
|
13
13
|
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
<script>
|
|
30
30
|
import { getContext } from 'svelte';
|
|
31
31
|
import { Colors } from '../constants';
|
|
32
|
-
import { val2p, checkCoords, handleClick, transformCoords } from '../methods';
|
|
32
|
+
import { val2p, checkCoords, handleClick, transformCoords, normalizeLineType } from '../methods';
|
|
33
33
|
import { LINE_STYLES } from '../constants';
|
|
34
34
|
|
|
35
35
|
let {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
lineColor = Colors.PRIMARY, // color of segment lines
|
|
39
39
|
lineType = 1, // type of segment lines (1 - solid, 2 - dashed, 3 - dotted, 4 - dashdot)
|
|
40
40
|
lineWidth = 1, // width (thickness) of segment lines
|
|
41
|
-
faceColor = 'transparent', // color of
|
|
41
|
+
faceColor = 'transparent', // face (fill) color of the polygon
|
|
42
42
|
opacity = 1, // opacity of the area color
|
|
43
43
|
onclick, // function to be called if onclick event fires
|
|
44
44
|
} = $props();
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
// check user provided coordinates
|
|
47
47
|
const xv = $derived(checkCoords(xValues, 'Area'));
|
|
48
48
|
const yv = $derived(xv ? checkCoords(yValues, 'Area', xv.length) : null);
|
|
49
|
+
const validLineType = $derived(normalizeLineType(lineType, 'Area'));
|
|
49
50
|
|
|
50
51
|
// get axes context and compute coordinates of polyline
|
|
51
52
|
const axes = getContext('axes');
|
|
@@ -55,7 +56,7 @@
|
|
|
55
56
|
const xs = $derived(xv ? transformCoords(xv.subset(1), axes.tX()) : null);
|
|
56
57
|
const pa = $derived(p && xs && ys ? xs + ',' + ys + ' ' + p + ' ' + xs + ',' + ys : null);
|
|
57
58
|
|
|
58
|
-
const areaStyleStr = $derived(`opacity:${opacity};fill:${faceColor};stroke:${lineColor};stroke-width: ${lineWidth}px;stroke-dasharray:${LINE_STYLES[axes.scales().plot][
|
|
59
|
+
const areaStyleStr = $derived(`opacity:${opacity};fill:${faceColor};stroke:${lineColor};stroke-width: ${lineWidth}px;stroke-dasharray:${LINE_STYLES[axes.scales().plot][validLineType - 1]}`);
|
|
59
60
|
</script>
|
|
60
61
|
|
|
61
62
|
{#if pa}
|
package/src/2d/Axes.svelte
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
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
7
|
- `title` - title of the plot (string).
|
|
8
|
-
- `margins` - array with relative margins (bottom, left, top, right), default `[1.0,
|
|
8
|
+
- `margins` - array with relative margins (bottom, left, top, right), default `[1.0, 1.1, 0.55, 0.60]`.
|
|
9
9
|
|
|
10
10
|
Properties for downloading the plot as PNG or SVG file or copying it to clipboard:
|
|
11
11
|
- `downloadLinks` - how to show download links panel (`'none'` - default, `'hover'`, `'fixed'`).
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
- `pngWidth` - width of PNG image in cm, default `8`
|
|
14
14
|
- `pngHeight` - height of PNG image in cm, default `8`.
|
|
15
15
|
- `pngRes` - resolution of PNG image (pixels per inch), default `300`.
|
|
16
|
-
- `clipboardWidth` - width of plot image to copy to clipboard in pixels
|
|
17
|
-
- `clipboardHeight` - height of plot image to copy to clipboard in pixels
|
|
16
|
+
- `clipboardWidth` - width of plot image to copy to clipboard in pixels, default `1200`.
|
|
17
|
+
- `clipboardHeight` - height of plot image to copy to clipboard in pixels, default `800`.
|
|
18
18
|
|
|
19
19
|
Example:
|
|
20
20
|
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
import {Axes} from 'svelte-plots-basic/2d';
|
|
24
24
|
</script>
|
|
25
25
|
|
|
26
|
-
<Axes limX={[2000, 2050]} limY={[0, 100]} title="GDP of
|
|
26
|
+
<Axes limX={[2000, 2050]} limY={[0, 100]} title="GDP of countries" >
|
|
27
27
|
</Axes>
|
|
28
28
|
```
|
|
29
29
|
-->
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
|
|
102
102
|
/* handler for mouse events */
|
|
103
103
|
function handleMouse(e, f) {
|
|
104
|
-
if (f && e.target.id
|
|
104
|
+
if (f && e.target.id === "axes-box") {
|
|
105
105
|
const x = invTransformCoords([e.offsetX - left], tX)[0];
|
|
106
106
|
const y = invTransformCoords([e.offsetY - top], tY)[0];
|
|
107
107
|
f(x, y);
|
|
@@ -307,7 +307,7 @@
|
|
|
307
307
|
<text {x} y={coords.ltop} dx="1.2em" dy="-0.25em" dominant-baseline="hanging"
|
|
308
308
|
fill={params.labelColor}
|
|
309
309
|
font-size="{params.fontSize}em"
|
|
310
|
-
text-anchor="
|
|
310
|
+
text-anchor="start"
|
|
311
311
|
>{@html getTickFactorLabel(params.labelsFactor)}</text>
|
|
312
312
|
{/if}
|
|
313
313
|
{/snippet}
|
|
@@ -316,14 +316,16 @@
|
|
|
316
316
|
{#snippet glgSnippet(params, coords)}
|
|
317
317
|
{@const elw = coords.elw + 2 * coords.elp}
|
|
318
318
|
{@const elp = coords.elp}
|
|
319
|
+
{@const elx = coords.elx}
|
|
319
320
|
{@const elh = coords.elh}
|
|
320
321
|
{@const ely = coords.ely}
|
|
322
|
+
{@const eliw = coords.eliw}
|
|
321
323
|
|
|
322
324
|
<svg x={coords.lgl} y={coords.lgt} height={coords.lgh} width={coords.lgw}>
|
|
323
325
|
<rect height="100%" width="100%" fill={params.faceColor} stroke={params.lineColor} stroke-width={params.lineWidth} />
|
|
324
326
|
|
|
325
327
|
{#each params.items as item, i}
|
|
326
|
-
<svg x={
|
|
328
|
+
<svg x={elx[i]} y={ely[i]} width={eliw[i]} height={elh[i]}>
|
|
327
329
|
|
|
328
330
|
<!-- line -->
|
|
329
331
|
{#if item.line}
|
|
@@ -532,7 +534,7 @@
|
|
|
532
534
|
</p>
|
|
533
535
|
{:else}
|
|
534
536
|
<div class="download-links">
|
|
535
|
-
<button type="button" onclick={handleClickSVG}>↓
|
|
537
|
+
<button type="button" onclick={handleClickSVG}>↓ svg</button>
|
|
536
538
|
<button type="button" onclick={handleClickPNG}>↓ png</button>
|
|
537
539
|
<button type="button" onclick={handleClickPNGAdvanced}>↓ png+</button>
|
|
538
540
|
<button type="button" onclick={handleClickCopy}>⧉ copy</button>
|
package/src/2d/AxisLines.svelte
CHANGED
|
@@ -3,21 +3,19 @@
|
|
|
3
3
|
|
|
4
4
|
Main properties:
|
|
5
5
|
- `lineCoords` - coordinates of the line segments as 2D array.
|
|
6
|
-
- `lineColor` - color of the
|
|
7
|
-
- `lineWidth` - width of
|
|
6
|
+
- `lineColor` - color of the line segments, default: `Colors.DARKGRAY`.
|
|
7
|
+
- `lineWidth` - width of the line segments in pixels, default: `1`.
|
|
8
8
|
- `lineType` - line type (`1` - solid (default), `2` - dashed, `3` - dotted, `4` - dashdot).
|
|
9
9
|
- `className` - string with classname to be added to `<g></g>` tag wrapping the lines.
|
|
10
10
|
|
|
11
11
|
**Description:**
|
|
12
12
|
|
|
13
|
-
The coordinates
|
|
13
|
+
The coordinates must be provided in form of nested arrays. Each element of this array contains
|
|
14
14
|
`Vector` or `Array` with the coordinates in world (not screen) coordinate system:
|
|
15
15
|
- `lineCoords[0][0]` - x-coordinates of start points of segments.
|
|
16
16
|
- `lineCoords[1][0]` - x-coordinates of end points of segments.
|
|
17
17
|
- `lineCoords[0][1]` - y-coordinates of start points of segments.
|
|
18
18
|
- `lineCoords[1][1]` - y-coordinates of end points of segments.
|
|
19
|
-
|
|
20
|
-
```
|
|
21
19
|
-->
|
|
22
20
|
<script>
|
|
23
21
|
import { getContext } from 'svelte';
|
|
@@ -33,7 +31,7 @@
|
|
|
33
31
|
className = '' // CSS class name for the component
|
|
34
32
|
} = $props();
|
|
35
33
|
|
|
36
|
-
// get axes context
|
|
34
|
+
// get axes context for coordinate transforms
|
|
37
35
|
const axes = getContext('axes');
|
|
38
36
|
|
|
39
37
|
// reactive variables for coordinates of axis lines
|
|
@@ -16,8 +16,6 @@
|
|
|
16
16
|
- `tickCoords[1][0]` - x-coordinates of end points of tick segments.
|
|
17
17
|
- `tickCoords[0][1]` - y-coordinates of start points of tick segments.
|
|
18
18
|
- `tickCoords[1][1]` - y-coordinates of end points of tick segments.
|
|
19
|
-
|
|
20
|
-
```
|
|
21
19
|
-->
|
|
22
20
|
<script>
|
|
23
21
|
|
|
@@ -57,7 +55,7 @@
|
|
|
57
55
|
{/each}
|
|
58
56
|
{:else if las === 2 && pos === 1}
|
|
59
57
|
{#each x as v, i}
|
|
60
|
-
<text data-id={i} x={x[i]} y={y[i]} dx={-dy/2} dy={0} transform={`rotate(-90, ${x[i]}, ${y[i]})`} text-anchor=
|
|
58
|
+
<text data-id={i} x={x[i]} y={y[i]} dx={-dy/2} dy={0} transform={`rotate(-90, ${x[i]}, ${y[i]})`} text-anchor="end">{@html tickLabels[i]}</text>
|
|
61
59
|
{/each}
|
|
62
60
|
{:else}
|
|
63
61
|
{#each x as v, i}
|
package/src/2d/Bars.svelte
CHANGED
|
@@ -45,25 +45,46 @@
|
|
|
45
45
|
onclick
|
|
46
46
|
} = $props();
|
|
47
47
|
|
|
48
|
+
function toNumber(value) {
|
|
49
|
+
const canConvert =
|
|
50
|
+
typeof value === 'number' ||
|
|
51
|
+
(typeof value === 'string' && value.trim() !== '');
|
|
52
|
+
|
|
53
|
+
return canConvert ? Number(value) : NaN;
|
|
54
|
+
}
|
|
55
|
+
|
|
48
56
|
const bw = $derived.by(() => {
|
|
49
|
-
|
|
50
|
-
|
|
57
|
+
const value = toNumber(barWidth);
|
|
58
|
+
if (!Number.isFinite(value) || value <= 0 || value > 1) {
|
|
59
|
+
console.error('Bars: parameter "barWidth" must be a finite number greater than 0 and no greater than 1.');
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
return value;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const exactWidth = $derived.by(() => {
|
|
66
|
+
if (barWidthExact == null) return {provided: false, value: null};
|
|
67
|
+
|
|
68
|
+
const value = toNumber(barWidthExact);
|
|
69
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
70
|
+
console.error('Bars: parameter "barWidthExact" must be a finite positive number.');
|
|
51
71
|
return null;
|
|
52
72
|
}
|
|
53
|
-
|
|
73
|
+
|
|
74
|
+
return {provided: true, value};
|
|
54
75
|
});
|
|
55
76
|
|
|
56
77
|
const x = $derived.by(() => {
|
|
57
|
-
if (
|
|
78
|
+
if (bw === null || exactWidth === null) return null;
|
|
58
79
|
const xv = checkCoords(xValues, 'BarSeries');
|
|
59
80
|
if (!xv) return null;
|
|
60
81
|
const n = xv.length;
|
|
61
82
|
if (n < 1) return null;
|
|
62
83
|
|
|
63
84
|
let w;
|
|
64
|
-
if (
|
|
65
|
-
w =
|
|
66
|
-
} else if (n
|
|
85
|
+
if (exactWidth.provided) {
|
|
86
|
+
w = exactWidth.value;
|
|
87
|
+
} else if (n === 1) {
|
|
67
88
|
console.error('Bars: if only one bar must be shown, value for property "barWidthExact" should be provided.');
|
|
68
89
|
return null;
|
|
69
90
|
} else {
|
|
@@ -74,7 +95,7 @@
|
|
|
74
95
|
}
|
|
75
96
|
}
|
|
76
97
|
|
|
77
|
-
w = w *
|
|
98
|
+
w = w * bw;
|
|
78
99
|
const whalf = w / 2;
|
|
79
100
|
const left = Vector.zeros(n);
|
|
80
101
|
const width = Vector.zeros(n);
|
|
@@ -113,6 +134,6 @@
|
|
|
113
134
|
</script>
|
|
114
135
|
|
|
115
136
|
{#if isOk}
|
|
116
|
-
<Rectangles className="series
|
|
137
|
+
<Rectangles className="series-bar" left={x.left} width={x.width} top={y.top} height={y.height}
|
|
117
138
|
lineWidth={lineWidth} {lineColor} {faceColor} {onclick} />
|
|
118
139
|
{/if}
|
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
|
|
|
@@ -91,7 +91,14 @@
|
|
|
91
91
|
|
|
92
92
|
|
|
93
93
|
// check that values are provided as a matrix
|
|
94
|
-
|
|
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
|
+
});
|
|
95
102
|
|
|
96
103
|
// check and process left values for breaks
|
|
97
104
|
let lb = $derived.by(() => {
|
|
@@ -121,9 +128,11 @@
|
|
|
121
128
|
let lc = $derived.by(() => {
|
|
122
129
|
if (!lb) return null;
|
|
123
130
|
|
|
124
|
-
if (colmap) {
|
|
125
|
-
if (!Array.isArray(colmap)
|
|
126
|
-
|
|
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.');
|
|
127
136
|
return null;
|
|
128
137
|
}
|
|
129
138
|
if (colmap.length !== lb.length - 1) {
|
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
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,14 +154,26 @@
|
|
|
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
|
});
|
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,7 +27,7 @@
|
|
|
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
33
|
let {
|
|
@@ -43,13 +43,14 @@
|
|
|
43
43
|
// check user provided coordinates
|
|
44
44
|
const xv = $derived(checkCoords(xValues, 'Lines'));
|
|
45
45
|
const yv = $derived(xv ? checkCoords(yValues, 'Lines', xv.length) : null);
|
|
46
|
+
const validLineType = $derived(normalizeLineType(lineType, 'Lines'));
|
|
46
47
|
|
|
47
48
|
// get axes context and compute coordinates of polyline
|
|
48
49
|
const axes = getContext('axes');
|
|
49
50
|
const p = $derived(xv && yv ? val2p(xv, yv, axes.tX(), axes.tY()) : null);
|
|
50
51
|
|
|
51
52
|
// reactive variables for coordinates of data points in pixels
|
|
52
|
-
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]}`);
|
|
53
54
|
</script>
|
|
54
55
|
|
|
55
56
|
{#if p}
|