svelte-plots-basic 1.1.2 → 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 +42 -20
- 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 -4194
- package/dist/index.mjs +0 -4172
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.2",
|
|
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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script>
|
|
2
|
-
import { setContext, createEventDispatcher, onMount } from 'svelte';
|
|
2
|
+
import { setContext, createEventDispatcher, onMount, onDestroy } from 'svelte';
|
|
3
3
|
import { writable } from 'svelte/store';
|
|
4
4
|
|
|
5
5
|
/* input parameters */
|
|
@@ -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
|
|
@@ -317,22 +328,32 @@
|
|
|
317
328
|
$: cpx = $isOk ? scaleX($xLim, $xLim, $width) : [0, 1];
|
|
318
329
|
$: cpy = $isOk ? scaleY($yLim, $yLim, $height) : [1, 0];
|
|
319
330
|
|
|
320
|
-
/* observer for the
|
|
321
|
-
|
|
331
|
+
/* observer for the axes area size - to update size of axes */
|
|
332
|
+
const ro2 = new ResizeObserver(entries => {
|
|
322
333
|
for (let entry of entries) {
|
|
323
|
-
|
|
324
|
-
const pcr = plotElement.getBoundingClientRect();
|
|
325
334
|
const acr = axesElement.getBoundingClientRect();
|
|
326
|
-
|
|
327
335
|
width.update(x => acr.width);
|
|
328
336
|
height.update(x => acr.height);
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
/* observer for the plot area size — to update scale */
|
|
341
|
+
const ro1 = new ResizeObserver(entries => {
|
|
342
|
+
for (let entry of entries) {
|
|
343
|
+
const pcr = plotElement.getBoundingClientRect();
|
|
329
344
|
scale.update(x => getScale(pcr.width, pcr.height));
|
|
330
345
|
}
|
|
331
346
|
});
|
|
332
347
|
|
|
333
348
|
onMount(() => {
|
|
334
|
-
|
|
349
|
+
ro1.observe(plotElement);
|
|
350
|
+
ro2.observe(axesElement);
|
|
335
351
|
});
|
|
352
|
+
|
|
353
|
+
onDestroy(() => {
|
|
354
|
+
ro1.unobserve(plotElement);
|
|
355
|
+
ro2.unobserve(axesElement);
|
|
356
|
+
})
|
|
336
357
|
</script>
|
|
337
358
|
|
|
338
359
|
|
|
@@ -345,7 +366,7 @@
|
|
|
345
366
|
|
|
346
367
|
<!-- axes (coordinate system) -->
|
|
347
368
|
<div class="axes-wrapper" bind:this={axesElement} >
|
|
348
|
-
<svg on:click={handleClick} preserveAspectRatio="none" class="axes">
|
|
369
|
+
<svg on:click={handleClick} on:keydown={handleClick} preserveAspectRatio="none" class="axes">
|
|
349
370
|
|
|
350
371
|
<!-- define clipping path -->
|
|
351
372
|
<defs>
|
|
@@ -367,26 +388,26 @@
|
|
|
367
388
|
<slot name="box"></slot>
|
|
368
389
|
|
|
369
390
|
</svg>
|
|
370
|
-
</div>
|
|
371
|
-
|
|
372
391
|
{#if !$isOk}
|
|
373
392
|
<p class="message_error">
|
|
374
393
|
Axes component was not properly initialized. <br />
|
|
375
394
|
Add plot series (check that coordinates are numeric) or define axes limits manually.
|
|
376
395
|
</p>
|
|
377
396
|
{/if}
|
|
397
|
+
</div>
|
|
398
|
+
|
|
378
399
|
|
|
379
400
|
</div>
|
|
380
401
|
|
|
381
402
|
<style>
|
|
382
403
|
|
|
383
404
|
/* Plot (main container) */
|
|
384
|
-
|
|
405
|
+
.plot {
|
|
385
406
|
font-family: Arial, Helvetica, sans-serif;
|
|
386
407
|
|
|
387
408
|
display: grid;
|
|
388
|
-
grid-template-columns: min-content
|
|
389
|
-
grid-template-rows: auto
|
|
409
|
+
grid-template-columns: min-content auto;
|
|
410
|
+
grid-template-rows: min-content auto min-content;
|
|
390
411
|
grid-template-areas:
|
|
391
412
|
". title"
|
|
392
413
|
"ylab axes"
|
|
@@ -425,7 +446,8 @@
|
|
|
425
446
|
:global(.message_error) {
|
|
426
447
|
font-size: 1.2em;
|
|
427
448
|
color: crimson;
|
|
428
|
-
padding:
|
|
449
|
+
padding: 1em;
|
|
450
|
+
box-sizing: border-box;
|
|
429
451
|
text-align: center;
|
|
430
452
|
}
|
|
431
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>
|