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/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021-2026 Sergey Kucheryavskiy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
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,32 @@ 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
|
+
|
|
26
|
+
### 3.4.0
|
|
27
|
+
|
|
28
|
+
* Added the `plotActions` property to `Axes` for adding custom buttons to the download panel.
|
|
29
|
+
* Added advanced validation for axis limits, text-label positions, heatmap breaks, and custom plot actions.
|
|
30
|
+
* Fixed text-label positioning and rotation when individual positions are provided.
|
|
31
|
+
* Fixed heatmaps with automatic, custom, or constant-value breaks.
|
|
32
|
+
* Added support for rectangular and constant-height 3D meshes.
|
|
33
|
+
* Improved PNG, SVG, and clipboard export by serializing a clone instead of modifying the visible plot.
|
|
34
|
+
* Fixed the advanced PNG export dialog so a default resolution is always selected, falling back to 300 dpi when necessary.
|
|
35
|
+
* Prevented plot controls from submitting an enclosing HTML form.
|
|
36
|
+
* Added package validation before publishing and synchronized Svelte 5 peer-dependency metadata.
|
|
37
|
+
* Fixed conditionally rendered axes, boxes, and legends so their parent state is cleared when they are removed.
|
|
38
|
+
* Improved heatmap interval processing and added automatically generated colormaps with more than 16 colors.
|
|
39
|
+
* Fixed `TextLegend` positioning at zero and strengthened finite-range validation for 3D axes.
|
|
40
|
+
* Reduced reactive and repeated allocation overhead for axis configuration, text measurement, PNG export, and colormap generation.
|
|
41
|
+
* Added dependency-free regression tests and made them run automatically before packaging.
|
|
42
|
+
* Documented the trust requirements for labels rendered as SVG/HTML markup.
|
|
43
|
+
|
|
18
44
|
### 3.3.0
|
|
19
45
|
|
|
20
46
|
* Added advanced "save as PNG" option where user can select size and resolution of the image as well as change the filename.
|
|
@@ -57,6 +83,13 @@ In addition to Svelte 5 syntax and functionality, this release also introduces a
|
|
|
57
83
|
|
|
58
84
|
* **Properites** — from 3.x.x the naming of the properties is more consistent. For example, before the library used properties `borderColor` and `borderWidth` for areas, rectangels, bars and markers, while for lines, segments and multilines the similar properties were named as `lineColor` and `lineWidth`. Now they all have prefix `line*` if it is something about lines or segments and `face*` if it is somthing inside a closed contour. So no more `border*` properties.
|
|
59
85
|
|
|
86
|
+
|
|
87
|
+
## Security and label markup
|
|
88
|
+
|
|
89
|
+
Several title and label properties support SVG/HTML markup and are rendered as raw markup using Svelte's `{@html}` mechanism. This enables formatted labels, HTML entities, subscripts, and superscripts.
|
|
90
|
+
|
|
91
|
+
Only pass trusted content to these properties. Do not pass unsanitized user input or external data directly as a plot title, axis label, tick label, legend label, or text-label value. Escape untrusted text, or sanitize it with an allow-list appropriate for SVG markup, before passing it to a component.
|
|
92
|
+
|
|
60
93
|
## Installation
|
|
61
94
|
|
|
62
95
|
The set up process is similar to any other Svelte component library. Just use:
|
|
@@ -140,6 +173,7 @@ Here is a table with properties of the component related to saving plot to a fil
|
|
|
140
173
|
`pngRes` | `300` | resolution of PNG image (pixels per inch)
|
|
141
174
|
`clipboardWidth` | `1200` | width of image in clipboard in pixels
|
|
142
175
|
`clipboardHeight` | `800` | height of image in clipboard in pixels
|
|
176
|
+
`plotActions` | `[]` | array of `{ text, callback }` objects for adding custom buttons to the download panel.
|
|
143
177
|
|
|
144
178
|
Please pay attention that most of the browsers enable clipboard functionality only if website is available via HTTPS.
|
|
145
179
|
|
|
@@ -194,7 +228,7 @@ You can also define tick positions and corresponding labels manually. Tick posit
|
|
|
194
228
|
|
|
195
229
|
```svelte
|
|
196
230
|
<Axes limX={[2019, 2026]} limY={[0, 60000]}>
|
|
197
|
-
<
|
|
231
|
+
<XAxis ticks={[2023, 2024, 2025]} tickLabels={['Past', 'Present', 'Future']} />
|
|
198
232
|
</Axes>
|
|
199
233
|
```
|
|
200
234
|
|
|
@@ -594,7 +628,7 @@ Here is an example:
|
|
|
594
628
|
|
|
595
629
|
```svelte
|
|
596
630
|
<script>
|
|
597
|
-
import { Axes,
|
|
631
|
+
import { Axes, XAxis, YAxis, Box, Area } from 'svelte-plots-basic/2d';
|
|
598
632
|
|
|
599
633
|
const xValues = [3, 2, 2.5, 4, 5.5, 6, 5];
|
|
600
634
|
const yValues = [1, 3, 4, 5, 4, 3, 1];
|
|
@@ -651,7 +685,7 @@ Here is an example:
|
|
|
651
685
|
|
|
652
686
|
```svelte
|
|
653
687
|
<script>
|
|
654
|
-
import { Axes,
|
|
688
|
+
import { Axes, XAxis, YAxis, Box, Heatmap } from 'svelte-plots-basic/2d';
|
|
655
689
|
import { Matrix } from 'mdatools/arrays';
|
|
656
690
|
|
|
657
691
|
// create 5 x 10 matrix filled with normally distributed random values
|
|
@@ -703,7 +737,7 @@ Colormap legend can be used together with `<Heatmap>` or any other color groupin
|
|
|
703
737
|
|
|
704
738
|
```svelte
|
|
705
739
|
<script>
|
|
706
|
-
import { Axes,
|
|
740
|
+
import { Axes, XAxis, YAxis, Box, Heatmap, ColormapLegend } from 'svelte-plots-basic/2d';
|
|
707
741
|
import { Matrix } from 'mdatools/arrays';
|
|
708
742
|
|
|
709
743
|
// create 5 x 10 matrix filled with normally distributed random values
|
|
@@ -763,16 +797,16 @@ Here is an example:
|
|
|
763
797
|
const y3 = x.apply(v => Math.pow(v, 4));
|
|
764
798
|
|
|
765
799
|
// define properties of each series
|
|
766
|
-
line1Props = {lineType: 3, lineColor: 'red'};
|
|
767
|
-
line2Props = {lineColor: 'blue', lineType: 1};
|
|
768
|
-
point2Props = {lineColor: 'blue', faceColor: 'white'};
|
|
769
|
-
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'};
|
|
770
804
|
|
|
771
805
|
// legend items
|
|
772
|
-
items = [
|
|
773
|
-
{label: 'y=x^2', line:
|
|
774
|
-
{label: "y=x^3", line:
|
|
775
|
-
{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 },
|
|
776
810
|
];
|
|
777
811
|
</script>
|
|
778
812
|
|
|
@@ -786,7 +820,7 @@ Here is an example:
|
|
|
786
820
|
<Points xValues={x} yValues={y2} {...point2Props} />
|
|
787
821
|
|
|
788
822
|
<!-- series 3: markers in form of diamonds with green stroke and yellow fill -->
|
|
789
|
-
<Points xValues={x} yValues={y3} {...
|
|
823
|
+
<Points xValues={x} yValues={y3} {...point3Props} />
|
|
790
824
|
|
|
791
825
|
<!-- legend with one JSON for each series -->
|
|
792
826
|
<Legend position="right" {items} />
|
|
@@ -800,12 +834,21 @@ Check this example in Svelte REPL: [plots-legend](https://svelte.dev/playground/
|
|
|
800
834
|
|
|
801
835
|
The `position` parameter can be one of the follows: `'topleft'`, `'top'`, `'topright'`, `'right'`, `'bottomright'` and so on.
|
|
802
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
|
+
|
|
803
845
|
The component has also properties which changes it apearance, here is the full list:
|
|
804
846
|
|
|
805
847
|
Property name | Default value | Description
|
|
806
848
|
--|--|--
|
|
807
849
|
`items` | | array with JSON properties of legend items
|
|
808
850
|
`position` | `'topleft'` | position of the legend element inside plotting area
|
|
851
|
+
`orientation` | `'vertical'` | arrangement of legend items: `'vertical'` or `'horizontal'`
|
|
809
852
|
`lineColor` | `'#303030'` | color of the legend box line
|
|
810
853
|
`lineWidth` | `1` | width (thickness) of the legend box line
|
|
811
854
|
`faceColor` | `'#fff'` | background color of the legend box
|
|
@@ -841,15 +884,12 @@ Here is an example of simple 3D scatter plot:
|
|
|
841
884
|
<Axes limX={[-10, 10]} limY={[-10, 10]} limZ={[-10, 10]} {zoom} {phi} {theta}>
|
|
842
885
|
<Points {xValues} {yValues} {zValues} />
|
|
843
886
|
|
|
844
|
-
<XAxis showGrid={true}
|
|
845
|
-
<YAxis showGrid={true}
|
|
846
|
-
<ZAxis showGrid={true}
|
|
887
|
+
<XAxis showGrid={true} label="X" />
|
|
888
|
+
<YAxis showGrid={true} label="Y" />
|
|
889
|
+
<ZAxis showGrid={true} label="Z" />
|
|
847
890
|
</Axes>
|
|
848
891
|
```
|
|
849
892
|
|
|
850
893
|
You can also add `Lines`, `Segments` and `Mesh` series to 3D plots.
|
|
851
894
|
|
|
852
895
|
Check more advanced example in Svelte REPL with mouse and keyboard support, and more: [plots-3d](https://svelte.dev/playground/2294eecba7d7477ab9a09c1734d32ac2)
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
package/package.json
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
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,
|
|
8
|
+
"type": "module",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"check": "node scripts/check.cjs",
|
|
11
|
+
"test": "node --test",
|
|
12
|
+
"prepack": "npm run check && npm test"
|
|
13
|
+
},
|
|
8
14
|
"exports": {
|
|
9
15
|
"./constants": "./src/constants.js",
|
|
10
16
|
"./utils": "./src/methods.js",
|
|
@@ -14,10 +20,12 @@
|
|
|
14
20
|
"./3d/*.svelte": "./src/3d/*.svelte"
|
|
15
21
|
},
|
|
16
22
|
"devDependencies": {
|
|
17
|
-
"
|
|
23
|
+
"mdatools": "^1.5.0",
|
|
24
|
+
"svelte": "^5.56.8"
|
|
18
25
|
},
|
|
19
|
-
"
|
|
20
|
-
"mdatools": "^1.
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"mdatools": "^1.5.0",
|
|
28
|
+
"svelte": "^5.0.0"
|
|
21
29
|
},
|
|
22
30
|
"keywords": [
|
|
23
31
|
"svelte",
|
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,17 +29,16 @@
|
|
|
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
|
-
/** @type {Props} */
|
|
36
35
|
let {
|
|
37
36
|
xValues, // vector or array with x-coordinates of the points
|
|
38
37
|
yValues, // vector or array with y-coordinates of the points
|
|
39
38
|
lineColor = Colors.PRIMARY, // color of segment lines
|
|
40
39
|
lineType = 1, // type of segment lines (1 - solid, 2 - dashed, 3 - dotted, 4 - dashdot)
|
|
41
40
|
lineWidth = 1, // width (thickness) of segment lines
|
|
42
|
-
faceColor = 'transparent', // color of
|
|
41
|
+
faceColor = 'transparent', // face (fill) color of the polygon
|
|
43
42
|
opacity = 1, // opacity of the area color
|
|
44
43
|
onclick, // function to be called if onclick event fires
|
|
45
44
|
} = $props();
|
|
@@ -47,6 +46,7 @@
|
|
|
47
46
|
// check user provided coordinates
|
|
48
47
|
const xv = $derived(checkCoords(xValues, 'Area'));
|
|
49
48
|
const yv = $derived(xv ? checkCoords(yValues, 'Area', xv.length) : null);
|
|
49
|
+
const validLineType = $derived(normalizeLineType(lineType, 'Area'));
|
|
50
50
|
|
|
51
51
|
// get axes context and compute coordinates of polyline
|
|
52
52
|
const axes = getContext('axes');
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
const xs = $derived(xv ? transformCoords(xv.subset(1), axes.tX()) : null);
|
|
57
57
|
const pa = $derived(p && xs && ys ? xs + ',' + ys + ' ' + p + ' ' + xs + ',' + ys : null);
|
|
58
58
|
|
|
59
|
-
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]}`);
|
|
60
60
|
</script>
|
|
61
61
|
|
|
62
62
|
{#if pa}
|
|
@@ -66,4 +66,3 @@
|
|
|
66
66
|
<polygon points={pa}/>
|
|
67
67
|
</g>
|
|
68
68
|
{/if}
|
|
69
|
-
|
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,14 +23,14 @@
|
|
|
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
|
-->
|
|
30
30
|
<script>
|
|
31
31
|
import { setContext } from 'svelte';
|
|
32
32
|
import { Colors, PLOT_FONT_SIZE, AXES_MARGIN_FACTORS, TICK_SIZE, LINE_STYLES, MARKER_SYMBOLS } from '../constants.js';
|
|
33
|
-
import { downloadSVG, downloadPNG, copyToClipboard,
|
|
33
|
+
import { downloadSVG, downloadPNG, copyToClipboard, getScale, getAxisScale, getXAxisParams, getYAxisParams,
|
|
34
34
|
transformCoords, getColormapLegendParams, getColormapLegendCoords, getTickFactorLabel,
|
|
35
35
|
getGroupLegendCoords, text2svg,
|
|
36
36
|
invTransformCoords } from '../methods.js';
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
limY = [0, 1],
|
|
47
47
|
margins = [1.0, 1.1, 0.55, 0.60],
|
|
48
48
|
downloadLinks = 'none',
|
|
49
|
+
plotActions = [],
|
|
49
50
|
fileName = 'plot',
|
|
50
51
|
pngWidth = 8,
|
|
51
52
|
pngHeight = 8,
|
|
@@ -60,9 +61,47 @@
|
|
|
60
61
|
} = $props();
|
|
61
62
|
|
|
62
63
|
|
|
64
|
+
const actions = $derived.by(() => {
|
|
65
|
+
if (plotActions == null) return [];
|
|
66
|
+
|
|
67
|
+
if (!Array.isArray(plotActions)) {
|
|
68
|
+
console.error('Axes: "plotActions" must be an array.');
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return plotActions.filter((action, index) => {
|
|
73
|
+
const isValid =
|
|
74
|
+
action &&
|
|
75
|
+
typeof action.text === 'string' &&
|
|
76
|
+
typeof action.callback === 'function';
|
|
77
|
+
|
|
78
|
+
if (!isValid) {
|
|
79
|
+
console.error(
|
|
80
|
+
`Axes: plot action ${index + 1} must contain a text string and callback function.`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return isValid;
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
// Check that axis limits contain a finite, meaningful numeric range.
|
|
90
|
+
function hasUsableRange(lim) {
|
|
91
|
+
if (!Array.isArray(lim) || lim.length !== 2) return false;
|
|
92
|
+
if (!lim.every(v => typeof v === 'number' && Number.isFinite(v))) return false;
|
|
93
|
+
|
|
94
|
+
const span = Math.abs(lim[1] - lim[0]);
|
|
95
|
+
const scale = Math.max(Math.abs(lim[0]), Math.abs(lim[1]));
|
|
96
|
+
|
|
97
|
+
return Number.isFinite(1 / span) &&
|
|
98
|
+
span > Number.EPSILON * scale * 16;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
|
|
63
102
|
/* handler for mouse events */
|
|
64
103
|
function handleMouse(e, f) {
|
|
65
|
-
if (f && e.target.id
|
|
104
|
+
if (f && e.target.id === "axes-box") {
|
|
66
105
|
const x = invTransformCoords([e.offsetX - left], tX)[0];
|
|
67
106
|
const y = invTransformCoords([e.offsetY - top], tY)[0];
|
|
68
107
|
f(x, y);
|
|
@@ -138,8 +177,8 @@
|
|
|
138
177
|
|
|
139
178
|
// plot status
|
|
140
179
|
const isOk = $derived(
|
|
141
|
-
|
|
142
|
-
|
|
180
|
+
hasUsableRange(limX) &&
|
|
181
|
+
hasUsableRange(limY) &&
|
|
143
182
|
width > (pxMargins[1] + pxMargins[3]) &&
|
|
144
183
|
height > (pxMargins[0] + pxMargins[2])
|
|
145
184
|
);
|
|
@@ -175,11 +214,11 @@
|
|
|
175
214
|
const titleHeight = $derived(plotTitle ? (plotTitle.length === title.length ? 1.0 : 1.4) : 0);
|
|
176
215
|
|
|
177
216
|
// default parameters of box, axis elements, colomap legend and group legend
|
|
178
|
-
let box = $state({show: false});
|
|
179
|
-
let xaxis = $state({show: false, error: ''});
|
|
180
|
-
let yaxis = $state({show: false, error: ''});
|
|
181
|
-
let clg = $state({show: false, colmap: null, breaks: null});
|
|
182
|
-
let glg = $state({show: false, items: null, position: null});
|
|
217
|
+
let box = $state.raw({show: false});
|
|
218
|
+
let xaxis = $state.raw({show: false, error: ''});
|
|
219
|
+
let yaxis = $state.raw({show: false, error: ''});
|
|
220
|
+
let clg = $state.raw({show: false, colmap: null, breaks: null});
|
|
221
|
+
let glg = $state.raw({show: false, items: null, position: null});
|
|
183
222
|
|
|
184
223
|
// set colormap legend parameters if requested
|
|
185
224
|
const clgParams = $derived(isOk && clg && clg.show ? getColormapLegendParams(clg) : null);
|
|
@@ -268,7 +307,7 @@
|
|
|
268
307
|
<text {x} y={coords.ltop} dx="1.2em" dy="-0.25em" dominant-baseline="hanging"
|
|
269
308
|
fill={params.labelColor}
|
|
270
309
|
font-size="{params.fontSize}em"
|
|
271
|
-
text-anchor="
|
|
310
|
+
text-anchor="start"
|
|
272
311
|
>{@html getTickFactorLabel(params.labelsFactor)}</text>
|
|
273
312
|
{/if}
|
|
274
313
|
{/snippet}
|
|
@@ -277,14 +316,16 @@
|
|
|
277
316
|
{#snippet glgSnippet(params, coords)}
|
|
278
317
|
{@const elw = coords.elw + 2 * coords.elp}
|
|
279
318
|
{@const elp = coords.elp}
|
|
319
|
+
{@const elx = coords.elx}
|
|
280
320
|
{@const elh = coords.elh}
|
|
281
321
|
{@const ely = coords.ely}
|
|
322
|
+
{@const eliw = coords.eliw}
|
|
282
323
|
|
|
283
324
|
<svg x={coords.lgl} y={coords.lgt} height={coords.lgh} width={coords.lgw}>
|
|
284
325
|
<rect height="100%" width="100%" fill={params.faceColor} stroke={params.lineColor} stroke-width={params.lineWidth} />
|
|
285
326
|
|
|
286
327
|
{#each params.items as item, i}
|
|
287
|
-
<svg x={
|
|
328
|
+
<svg x={elx[i]} y={ely[i]} width={eliw[i]} height={elh[i]}>
|
|
288
329
|
|
|
289
330
|
<!-- line -->
|
|
290
331
|
{#if item.line}
|
|
@@ -445,6 +486,8 @@
|
|
|
445
486
|
|
|
446
487
|
<!-- box -->
|
|
447
488
|
<g class="axes-box">
|
|
489
|
+
<!-- The plot callback requires real pointer coordinates; a keyboard-generated coordinate would be arbitrary. -->
|
|
490
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
448
491
|
<rect stroke={box.show ? box.lineColor : 'transparent'}
|
|
449
492
|
stroke-width={box.show ? box.lineWidth + 'px' : '0'}
|
|
450
493
|
fill="transparent"
|
|
@@ -491,10 +534,16 @@
|
|
|
491
534
|
</p>
|
|
492
535
|
{:else}
|
|
493
536
|
<div class="download-links">
|
|
494
|
-
<button onclick={handleClickSVG}
|
|
495
|
-
<button onclick={handleClickPNG}
|
|
496
|
-
<button onclick={handleClickPNGAdvanced}
|
|
497
|
-
<button onclick={handleClickCopy}>⧉ copy</button>
|
|
537
|
+
<button type="button" onclick={handleClickSVG}>↓ svg</button>
|
|
538
|
+
<button type="button" onclick={handleClickPNG}>↓ png</button>
|
|
539
|
+
<button type="button" onclick={handleClickPNGAdvanced}>↓ png+</button>
|
|
540
|
+
<button type="button" onclick={handleClickCopy}>⧉ copy</button>
|
|
541
|
+
{#if actions.length > 0}
|
|
542
|
+
<span class="divider">⋮</span>
|
|
543
|
+
{#each actions as ac}
|
|
544
|
+
<button type="button" onclick={ac.callback}>{ac.text}</button>
|
|
545
|
+
{/each}
|
|
546
|
+
{/if}
|
|
498
547
|
</div>
|
|
499
548
|
{/if}
|
|
500
549
|
|
|
@@ -545,6 +594,9 @@
|
|
|
545
594
|
background: #fafafa;
|
|
546
595
|
border-radius: 0.35em;
|
|
547
596
|
padding: 0.25em 0.5em;
|
|
597
|
+
margin: 0 1px;
|
|
598
|
+
|
|
599
|
+
line-height: 1.25;
|
|
548
600
|
}
|
|
549
601
|
|
|
550
602
|
.download-links > button:hover{
|
|
@@ -560,6 +612,12 @@
|
|
|
560
612
|
background: #4488ee;
|
|
561
613
|
}
|
|
562
614
|
|
|
615
|
+
.download-links > .divider {
|
|
616
|
+
display: inline-block;
|
|
617
|
+
width: 1em;
|
|
618
|
+
text-align: center;
|
|
619
|
+
color: #909090;
|
|
620
|
+
}
|
|
563
621
|
|
|
564
622
|
/* Plot container */
|
|
565
623
|
.plot-container {
|
|
@@ -617,4 +675,4 @@
|
|
|
617
675
|
min-width: 100%;
|
|
618
676
|
}
|
|
619
677
|
|
|
620
|
-
</style>
|
|
678
|
+
</style>
|
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
|
@@ -34,7 +34,6 @@
|
|
|
34
34
|
import Rectangles from './Rectangles.svelte';
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
/** @type {Props} */
|
|
38
37
|
let {
|
|
39
38
|
xValues, // vector with x-coordinates of middle points of the bars.
|
|
40
39
|
yValues, // vector with y-coordinates of top points of the bars.
|
|
@@ -46,25 +45,46 @@
|
|
|
46
45
|
onclick
|
|
47
46
|
} = $props();
|
|
48
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
|
+
|
|
49
56
|
const bw = $derived.by(() => {
|
|
50
|
-
|
|
51
|
-
|
|
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.');
|
|
52
71
|
return null;
|
|
53
72
|
}
|
|
54
|
-
|
|
73
|
+
|
|
74
|
+
return {provided: true, value};
|
|
55
75
|
});
|
|
56
76
|
|
|
57
77
|
const x = $derived.by(() => {
|
|
58
|
-
if (
|
|
78
|
+
if (bw === null || exactWidth === null) return null;
|
|
59
79
|
const xv = checkCoords(xValues, 'BarSeries');
|
|
60
80
|
if (!xv) return null;
|
|
61
81
|
const n = xv.length;
|
|
62
82
|
if (n < 1) return null;
|
|
63
83
|
|
|
64
84
|
let w;
|
|
65
|
-
if (
|
|
66
|
-
w =
|
|
67
|
-
} else if (n
|
|
85
|
+
if (exactWidth.provided) {
|
|
86
|
+
w = exactWidth.value;
|
|
87
|
+
} else if (n === 1) {
|
|
68
88
|
console.error('Bars: if only one bar must be shown, value for property "barWidthExact" should be provided.');
|
|
69
89
|
return null;
|
|
70
90
|
} else {
|
|
@@ -75,7 +95,7 @@
|
|
|
75
95
|
}
|
|
76
96
|
}
|
|
77
97
|
|
|
78
|
-
w = w *
|
|
98
|
+
w = w * bw;
|
|
79
99
|
const whalf = w / 2;
|
|
80
100
|
const left = Vector.zeros(n);
|
|
81
101
|
const width = Vector.zeros(n);
|
|
@@ -114,6 +134,6 @@
|
|
|
114
134
|
</script>
|
|
115
135
|
|
|
116
136
|
{#if isOk}
|
|
117
|
-
<Rectangles className="series
|
|
137
|
+
<Rectangles className="series-bar" left={x.left} width={x.width} top={y.top} height={y.height}
|
|
118
138
|
lineWidth={lineWidth} {lineColor} {faceColor} {onclick} />
|
|
119
|
-
{/if}
|
|
139
|
+
{/if}
|
package/src/2d/Box.svelte
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
```
|
|
17
17
|
-->
|
|
18
18
|
<script>
|
|
19
|
-
import { getContext } from 'svelte';
|
|
19
|
+
import { getContext, onDestroy } from 'svelte';
|
|
20
20
|
|
|
21
21
|
let {lineWidth = 1, lineColor = '#606060'} = $props();
|
|
22
22
|
const axes = getContext('axes');
|
|
@@ -24,5 +24,8 @@
|
|
|
24
24
|
$effect(() => {
|
|
25
25
|
axes.setBox({show:true, lineWidth, lineColor});
|
|
26
26
|
});
|
|
27
|
-
</script>
|
|
28
27
|
|
|
28
|
+
onDestroy(() => {
|
|
29
|
+
axes.setBox({show: false});
|
|
30
|
+
});
|
|
31
|
+
</script>
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
```
|
|
35
35
|
-->
|
|
36
36
|
<script>
|
|
37
|
-
import { getContext } from 'svelte';
|
|
37
|
+
import { getContext, onDestroy } from 'svelte';
|
|
38
38
|
import { Colors } from '../constants';
|
|
39
39
|
|
|
40
40
|
|
|
@@ -52,7 +52,10 @@
|
|
|
52
52
|
$effect(() => {
|
|
53
53
|
axes.setColmapLegend({show, breaks, colmap, labels, labelColor, fontSize})
|
|
54
54
|
});
|
|
55
|
-
</script>
|
|
56
55
|
|
|
56
|
+
onDestroy(() => {
|
|
57
|
+
axes.setColmapLegend({show: false});
|
|
58
|
+
});
|
|
59
|
+
</script>
|
|
57
60
|
|
|
58
61
|
|