svelte-plots-basic 1.1.3 → 1.1.4
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 +2 -3
- package/package.json +22 -33
- package/src/Axes.svelte +23 -11
- package/src/BarSeries.svelte +0 -10
- package/src/Rectangles.svelte +1 -1
- package/src/ScatterSeries.svelte +0 -16
- package/src/Segments.svelte +5 -1
- package/src/TextLabels.svelte +32 -22
- package/dist/index.js +0 -4209
- package/dist/index.mjs +0 -4187
package/README.md
CHANGED
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
## Description
|
|
4
4
|
|
|
5
|
-
`svelte-plots-basic` is a [Svelte](https://svelte.dev) component library for creating very simple 2D plots/charts. It is currently under active development and breaking changes may occur. In its current state (v. 1.1.
|
|
5
|
+
`svelte-plots-basic` is a [Svelte](https://svelte.dev) component library for creating very simple 2D plots/charts. It is currently under active development and breaking changes may occur. In its current state (v. 1.1.4) it allows to create bar, scatter, area and line charts as well as any combination of the four. The charts are created using [SVG](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics) and are re-scalable.
|
|
6
6
|
|
|
7
|
-
The description is not complete yet and will be updated during February-March of 2022.
|
|
8
7
|
|
|
9
8
|
## Installation
|
|
10
9
|
|
|
@@ -52,7 +51,7 @@ Just create a new Svelte app following the [quick start guide](https://svelte.de
|
|
|
52
51
|
</div>
|
|
53
52
|
```
|
|
54
53
|
|
|
55
|
-
Then run `npm run dev` in terminal and open
|
|
54
|
+
Then run `npm run dev` in terminal and open the URL provided by npm in browser. That is it.
|
|
56
55
|
|
|
57
56
|
Example below shows how to create a plot with axis, box and grid as well as how to combine several plot series together.
|
|
58
57
|
|
package/package.json
CHANGED
|
@@ -1,35 +1,24 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"src",
|
|
25
|
-
"dist"
|
|
26
|
-
],
|
|
27
|
-
"description": "Svelte components for creating simple plots",
|
|
28
|
-
"version": "1.1.3",
|
|
29
|
-
"repository": {
|
|
30
|
-
"type": "git",
|
|
31
|
-
"url": "svkucheryavski/svelte-plots-basic"
|
|
32
|
-
},
|
|
33
|
-
"author": "Sergey Kucheryavskiy",
|
|
34
|
-
"license": "MIT"
|
|
2
|
+
"name": "svelte-plots-basic",
|
|
3
|
+
"description": "Svelte components for creating simple plots",
|
|
4
|
+
"version": "1.1.4",
|
|
5
|
+
"author": "Sergey Kucheryavskiy",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"private": false,
|
|
8
|
+
"main": "src/index.js",
|
|
9
|
+
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"mdatools": "^0.6.1"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"svelte",
|
|
15
|
+
"svelte plots"
|
|
16
|
+
],
|
|
17
|
+
"files": [
|
|
18
|
+
"src"
|
|
19
|
+
],
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "svkucheryavski/svelte-plots-basic"
|
|
23
|
+
}
|
|
35
24
|
}
|
package/src/Axes.svelte
CHANGED
|
@@ -118,6 +118,13 @@
|
|
|
118
118
|
return adjustedLim;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Rounds coordinate values to single decimal
|
|
123
|
+
*/
|
|
124
|
+
const roundCoords = function(x) {
|
|
125
|
+
return Math.round(x * 10) / 10
|
|
126
|
+
}
|
|
127
|
+
|
|
121
128
|
/** Rescales x-values from plot coordinates to screen (SVG) coordinates
|
|
122
129
|
* @param {Array} x - vector with coordinates (or objects size) in original plot coordinates
|
|
123
130
|
* @param {Array} xLim - vector with current limits for x-axis in original plot coordinates
|
|
@@ -126,14 +133,16 @@
|
|
|
126
133
|
* @returns {Array} vector with rescaled values
|
|
127
134
|
*/
|
|
128
135
|
const scaleX = function(x, xLim, width, doSizeScale = false) {
|
|
129
|
-
if (!$isOk || x === undefined ||
|
|
136
|
+
if (!$isOk || x === undefined || !Array.isArray(x)) return undefined;
|
|
130
137
|
|
|
131
138
|
if (doSizeScale) {
|
|
132
139
|
// scale size of objects instead of coordinates
|
|
133
|
-
return x.map(v => v / (xLim[1] - xLim[0]) * (width - margins[1] - margins[3]));
|
|
140
|
+
return x.map(v => roundCoords(v / (xLim[1] - xLim[0]) * (width - margins[1] - margins[3])));
|
|
134
141
|
}
|
|
135
142
|
|
|
136
|
-
return x.map(v =>
|
|
143
|
+
return x.map(v =>
|
|
144
|
+
roundCoords((v - xLim[0]) / (xLim[1] - xLim[0]) * (width - margins[1] - margins[3]) + margins[1])
|
|
145
|
+
);
|
|
137
146
|
}
|
|
138
147
|
|
|
139
148
|
/** Rescales x-values from plot coordinates to screen (SVG) coordinates
|
|
@@ -144,15 +153,17 @@
|
|
|
144
153
|
* @returns {Array} vector with rescaled values
|
|
145
154
|
*/
|
|
146
155
|
const scaleY = function(y, yLim, height, doSizeScale = false) {
|
|
147
|
-
if (!$isOk || y === undefined ||
|
|
156
|
+
if (!$isOk || y === undefined || !Array.isArray(y)) return undefined;
|
|
148
157
|
|
|
149
158
|
if (doSizeScale) {
|
|
150
159
|
// scale size of objects instead of coordinates
|
|
151
|
-
return y.map(v => v / (yLim[1] - yLim[0]) * (height - margins[0] - margins[2]));
|
|
160
|
+
return y.map(v => roundCoords(v / (yLim[1] - yLim[0]) * (height - margins[0] - margins[2])));
|
|
152
161
|
}
|
|
153
162
|
|
|
154
163
|
// for coordinates we also need to invert (flip) the y-axis
|
|
155
|
-
return y.map(v =>
|
|
164
|
+
return y.map(v =>
|
|
165
|
+
roundCoords((yLim[1] - v) / (yLim[1] - yLim[0]) * (height - margins[0] - margins[2]) + margins[2])
|
|
166
|
+
);
|
|
156
167
|
}
|
|
157
168
|
|
|
158
169
|
/** Computes nice tick values for axis
|
|
@@ -355,7 +366,7 @@
|
|
|
355
366
|
|
|
356
367
|
<!-- axes (coordinate system) -->
|
|
357
368
|
<div class="axes-wrapper" bind:this={axesElement} >
|
|
358
|
-
<svg on:click={handleClick} preserveAspectRatio="none" class="axes">
|
|
369
|
+
<svg on:click={handleClick} on:keydown={handleClick} preserveAspectRatio="none" class="axes">
|
|
359
370
|
|
|
360
371
|
<!-- define clipping path -->
|
|
361
372
|
<defs>
|
|
@@ -377,21 +388,21 @@
|
|
|
377
388
|
<slot name="box"></slot>
|
|
378
389
|
|
|
379
390
|
</svg>
|
|
380
|
-
</div>
|
|
381
|
-
|
|
382
391
|
{#if !$isOk}
|
|
383
392
|
<p class="message_error">
|
|
384
393
|
Axes component was not properly initialized. <br />
|
|
385
394
|
Add plot series (check that coordinates are numeric) or define axes limits manually.
|
|
386
395
|
</p>
|
|
387
396
|
{/if}
|
|
397
|
+
</div>
|
|
398
|
+
|
|
388
399
|
|
|
389
400
|
</div>
|
|
390
401
|
|
|
391
402
|
<style>
|
|
392
403
|
|
|
393
404
|
/* Plot (main container) */
|
|
394
|
-
|
|
405
|
+
.plot {
|
|
395
406
|
font-family: Arial, Helvetica, sans-serif;
|
|
396
407
|
|
|
397
408
|
display: grid;
|
|
@@ -435,7 +446,8 @@
|
|
|
435
446
|
:global(.message_error) {
|
|
436
447
|
font-size: 1.2em;
|
|
437
448
|
color: crimson;
|
|
438
|
-
padding:
|
|
449
|
+
padding: 1em;
|
|
450
|
+
box-sizing: border-box;
|
|
439
451
|
text-align: center;
|
|
440
452
|
}
|
|
441
453
|
|
package/src/BarSeries.svelte
CHANGED
|
@@ -66,13 +66,3 @@
|
|
|
66
66
|
</script>
|
|
67
67
|
|
|
68
68
|
<Rectangles style="series_bar" {left} {top} {width} {height} {borderColor} {faceColor} {title} />
|
|
69
|
-
|
|
70
|
-
<style>
|
|
71
|
-
:global(.series_bar > rect){
|
|
72
|
-
stroke-width: 1px;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
:global(.series_bar > rect:hover){
|
|
76
|
-
opacity: 90%;
|
|
77
|
-
}
|
|
78
|
-
</style>
|
package/src/Rectangles.svelte
CHANGED
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
$: rh = axes.scaleY(height, $yLim, $axesHeight, true);
|
|
49
49
|
|
|
50
50
|
// styles for bars and labels
|
|
51
|
-
$: barsStyleStr = `fill:${faceColor};stroke:${borderColor};stroke-width
|
|
51
|
+
$: barsStyleStr = `fill:${faceColor};stroke:${borderColor};stroke-width:${lineWidth}px;`;
|
|
52
52
|
|
|
53
53
|
</script>
|
|
54
54
|
|
package/src/ScatterSeries.svelte
CHANGED
|
@@ -67,19 +67,3 @@
|
|
|
67
67
|
textSize={markerSize}
|
|
68
68
|
/>
|
|
69
69
|
|
|
70
|
-
<style>
|
|
71
|
-
|
|
72
|
-
:global(.series_scatter > text) {
|
|
73
|
-
font-size: 1em;
|
|
74
|
-
cursor: default;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
:global(.series_scatter > text:hover) {
|
|
78
|
-
opacity: 90%;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
:global(.series_scatter > *:hover + .labels_hover) {
|
|
82
|
-
visibility: visible;
|
|
83
|
-
opacity: 1;
|
|
84
|
-
}
|
|
85
|
-
</style>
|
package/src/Segments.svelte
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
export let lineColor = Colors.PRIMARY;
|
|
11
11
|
export let lineType = 1;
|
|
12
12
|
export let lineWidth = 1;
|
|
13
|
+
export let title = "";
|
|
14
|
+
export let style = "series_seg";
|
|
13
15
|
|
|
14
16
|
/* sanity check for input parameters */
|
|
15
17
|
if (!Array.isArray(xStart) || !Array.isArray(xEnd) || !Array.isArray(yStart) || !Array.isArray(yEnd)) {
|
|
@@ -39,9 +41,11 @@
|
|
|
39
41
|
</script>
|
|
40
42
|
|
|
41
43
|
{#if x1 !== undefined && y1 !== undefined}
|
|
44
|
+
<g class="series {style}" title={title} style={lineStyleStr}>
|
|
42
45
|
{#each x1 as v, i}
|
|
43
|
-
<line x1=
|
|
46
|
+
<line x1={x1[i]} x2={x2[i]} y1={y1[i]} y2={y2[i]}></line>
|
|
44
47
|
{/each}
|
|
48
|
+
</g>
|
|
45
49
|
{/if}
|
|
46
50
|
|
|
47
51
|
<style>
|
package/src/TextLabels.svelte
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
import { getContext } from 'svelte';
|
|
10
10
|
import { Colors } from './Colors';
|
|
11
|
+
import { subset } from 'mdatools/stat';
|
|
12
|
+
import { vmult } from 'mdatools/matrix';
|
|
11
13
|
|
|
12
14
|
// input parameters
|
|
13
15
|
export let xValues;
|
|
@@ -21,28 +23,24 @@
|
|
|
21
23
|
export let style = "";
|
|
22
24
|
export let title = "series_text";
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
|
|
27
|
+
// function to prepare values for variables 'label' and 'pos'
|
|
28
|
+
function processValues(v, n, name) {
|
|
29
|
+
if (!Array.isArray(v)) return Array(n).fill(v);
|
|
30
|
+
if (v.length !== n) {
|
|
31
|
+
throw(`TextLabels: parameter ${name} must be a single number or a vector of the same size as 'x' and 'y'.`)
|
|
32
|
+
}
|
|
33
|
+
return v;
|
|
34
|
+
}
|
|
26
35
|
|
|
27
36
|
// sanity check for input parameters
|
|
28
|
-
if (!Array.isArray(xValues) ||
|
|
37
|
+
if (!Array.isArray(xValues) || !Array.isArray(yValues) || xValues.length !== yValues.length) {
|
|
29
38
|
throw("TextLabels: parameters 'xValues' and 'yValues' must be vectors of the same length.")
|
|
30
39
|
}
|
|
31
40
|
|
|
32
|
-
//
|
|
33
|
-
$:
|
|
34
|
-
|
|
35
|
-
if (!Array.isArray(labels)) labels = Array(n).fill(labels);
|
|
36
|
-
|
|
37
|
-
// workaround for an issue when xValues and yValues are changed in parent app
|
|
38
|
-
// but array of labels is still the same as in the
|
|
39
|
-
if (labels.length != n) labels = Array(n).fill(labels[0]);
|
|
40
|
-
|
|
41
|
-
// check that the length of labels vector is correct
|
|
42
|
-
if (labels.length !== n) {
|
|
43
|
-
throw("TextLabels: parameter 'labels' must be a single text value or a vector of the same size as 'x' and 'y'.")
|
|
44
|
-
}
|
|
45
|
-
}
|
|
41
|
+
// prepare values for labels and positions
|
|
42
|
+
$: labelsLocal = processValues(labels, xValues.length);
|
|
43
|
+
$: posLocal = pos === 0 ? pos : processValues(pos, xValues.length);
|
|
46
44
|
|
|
47
45
|
// get axes context and reactive variables needed to compute coordinates
|
|
48
46
|
const axes = getContext('axes');
|
|
@@ -55,24 +53,36 @@
|
|
|
55
53
|
// reactive variables for coordinates of data points in pixels
|
|
56
54
|
$: x = axes.scaleX(xValues, $xLim, $axesWidth);
|
|
57
55
|
$: y = axes.scaleY(yValues, $yLim, $axesHeight);
|
|
58
|
-
$: dx =
|
|
59
|
-
$: dy =
|
|
56
|
+
$: dx = pos === 0 ? 0 : subset(vmult([0, 1, 0, -1], axes.LABELS_MARGIN[$scale]), posLocal)
|
|
57
|
+
$: dy = pos === 0 ? 0 : subset(vmult([1, 0, -1, 0], axes.LABELS_MARGIN[$scale]), posLocal)
|
|
58
|
+
$: textAnchors = pos === 0 ? "middle" : subset([ "middle", "start", "middle", "end"], posLocal);
|
|
60
59
|
|
|
61
60
|
// styles for the elements
|
|
62
|
-
$: textStyleStr = `fill:${faceColor};stroke-width:${borderWidth}px;stroke:${borderColor};
|
|
63
|
-
font-size:${textSize}em; text-anchor
|
|
61
|
+
$: textStyleStr = `dominant-baseline:middle;fill:${faceColor};stroke-width:${borderWidth}px;stroke:${borderColor};
|
|
62
|
+
font-size:${textSize}em; text-anchor:middle;`;
|
|
64
63
|
</script>
|
|
65
64
|
|
|
66
65
|
{#if x !== undefined && y !== undefined}
|
|
67
66
|
<g class="series {style}" title={title} style={textStyleStr} >
|
|
67
|
+
|
|
68
|
+
{#if pos === 0}
|
|
69
|
+
{#each x as v, i}
|
|
70
|
+
<text data-id={i} x={x[i]} y={y[i]}>{@html labelsLocal[i]}</text>
|
|
71
|
+
{/each}
|
|
72
|
+
|
|
73
|
+
{:else}
|
|
68
74
|
{#each x as v, i}
|
|
69
|
-
<text data-id={i} x={x[i]} y={y[i]} dx={dx} dy={dy}>{@html
|
|
75
|
+
<text data-id={i} x={x[i]} y={y[i]} dx={dx[i]} dy={dy[i]} text-anchor={textAnchors[i]}>{@html labelsLocal[i]}</text>
|
|
70
76
|
{/each}
|
|
77
|
+
{/if}
|
|
78
|
+
|
|
71
79
|
</g>
|
|
72
80
|
{/if}
|
|
73
81
|
|
|
74
82
|
<style>
|
|
75
83
|
text, text > :global(tspan) {
|
|
84
|
+
cursor: default;
|
|
85
|
+
user-select: none;
|
|
76
86
|
dominant-baseline: middle;
|
|
77
87
|
}
|
|
78
88
|
</style>
|