svelte-plots-basic 1.1.4 → 2.1.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 +59 -67
- package/package.json +10 -4
- package/src/2d/Area.svelte +61 -0
- package/src/2d/Axes.svelte +500 -0
- package/src/2d/Axis.svelte +67 -0
- package/src/2d/AxisLines.svelte +54 -0
- package/src/2d/AxisTickLabels.svelte +82 -0
- package/src/2d/Bars.svelte +65 -0
- package/src/2d/Box.svelte +50 -0
- package/src/2d/ColorLegend.svelte +36 -0
- package/src/2d/ColormapLegend.svelte +100 -0
- package/src/2d/Heatmap.svelte +109 -0
- package/src/2d/Legend.svelte +148 -0
- package/src/2d/Lines.svelte +49 -0
- package/src/2d/Multilines.svelte +67 -0
- package/src/2d/Points.svelte +79 -0
- package/src/2d/Rectangles.svelte +74 -0
- package/src/2d/Segments.svelte +65 -0
- package/src/2d/TextLabels.svelte +107 -0
- package/src/2d/TextLegend.svelte +53 -0
- package/src/2d/XAxis.svelte +109 -0
- package/src/2d/YAxis.svelte +109 -0
- package/src/2d/index.js +21 -0
- package/src/3d/Axes.svelte +445 -0
- package/src/3d/Axis.svelte +64 -0
- package/src/3d/AxisLines.svelte +50 -0
- package/src/3d/AxisTickLabels.svelte +54 -0
- package/src/3d/Lines.svelte +49 -0
- package/src/3d/Mesh.svelte +185 -0
- package/src/3d/Points.svelte +61 -0
- package/src/3d/Segments.svelte +69 -0
- package/src/3d/TextLabels.svelte +79 -0
- package/src/3d/XAxis.svelte +123 -0
- package/src/3d/YAxis.svelte +123 -0
- package/src/3d/ZAxis.svelte +123 -0
- package/src/3d/index.js +13 -0
- package/src/Colors.js +36 -1
- package/src/Utils.js +243 -0
- package/src/AreaSeries.svelte +0 -53
- package/src/Axes.svelte +0 -550
- package/src/BarSeries.svelte +0 -68
- package/src/Box.svelte +0 -35
- package/src/LineSeries.svelte +0 -50
- package/src/Rectangles.svelte +0 -64
- package/src/ScatterSeries.svelte +0 -69
- package/src/Segments.svelte +0 -52
- package/src/TextLabels.svelte +0 -88
- package/src/TextLegend.svelte +0 -38
- package/src/XAxis.svelte +0 -69
- package/src/YAxis.svelte +0 -72
- package/src/index.js +0 -16
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
/****************************************************
|
|
3
|
+
* Bars *
|
|
4
|
+
* -------------------- *
|
|
5
|
+
* shows series of bars on a plot *
|
|
6
|
+
*****************************************************/
|
|
7
|
+
|
|
8
|
+
import { max, diff } from 'mdatools/stat';
|
|
9
|
+
import { Vector } from 'mdatools/arrays';
|
|
10
|
+
import { Colors } from '../Colors';
|
|
11
|
+
import { checkCoords } from '../Utils';
|
|
12
|
+
|
|
13
|
+
import Rectangles from './Rectangles.svelte';
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
/*****************************************/
|
|
17
|
+
/* Input parameters */
|
|
18
|
+
/*****************************************/
|
|
19
|
+
|
|
20
|
+
export let xValues; // vector with x-coordinates of middle points of the bars
|
|
21
|
+
export let yValues; // vector with y-coordinates of top points of the bars
|
|
22
|
+
export let barWidth = 0.8; // width of bars as per cent of maximum width
|
|
23
|
+
export let faceColor = Colors.PRIMARY; // face color of the bars
|
|
24
|
+
export let borderColor = Colors.PRIMARY; // border color of the bars
|
|
25
|
+
export let title = ''; // title of the bar series (reserved for future use)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
/*****************************************/
|
|
29
|
+
/* Component code */
|
|
30
|
+
/*****************************************/
|
|
31
|
+
|
|
32
|
+
let width, left, top, height;
|
|
33
|
+
|
|
34
|
+
// reactive code for computing position of left sides and bar width
|
|
35
|
+
$: {
|
|
36
|
+
|
|
37
|
+
if (!barWidth) {
|
|
38
|
+
barWidth = 0.8;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (barWidth <= 0 || barWidth > 1) {
|
|
42
|
+
throw Error('BarSeries: parameters "barWidth" should be between 0 and 1.');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// compute maximum bar width and position of left side
|
|
46
|
+
const xv = checkCoords(xValues, 'BarSeries');
|
|
47
|
+
const w = max(diff(xv.v)) * barWidth;
|
|
48
|
+
left = xv.subtract(w/2);
|
|
49
|
+
width = Vector.fill(w, xv.length);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// reactive code for computing position of top sides and bar height
|
|
53
|
+
$: {
|
|
54
|
+
|
|
55
|
+
const yv = checkCoords(yValues, 'BarSeries');
|
|
56
|
+
if (yv.length !== left.length) {
|
|
57
|
+
throw Error('BarSeries: parameters "yValues" must be vector of the same length as "xValues".');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
top = yv.apply(v => v > 0 ? v : 0);
|
|
61
|
+
height = yv.apply(v => Math.abs(v));
|
|
62
|
+
}
|
|
63
|
+
</script>
|
|
64
|
+
|
|
65
|
+
<Rectangles className="series_bar" {left} {top} {width} {height} {borderColor} {faceColor} {title} />
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
/****************************************************
|
|
3
|
+
* Box *
|
|
4
|
+
* -------------------- *
|
|
5
|
+
* shows border box around axes *
|
|
6
|
+
*****************************************************/
|
|
7
|
+
|
|
8
|
+
import { getContext } from 'svelte';
|
|
9
|
+
import { Colors } from '../Colors';
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
/*****************************************/
|
|
13
|
+
/* Input parameters */
|
|
14
|
+
/*****************************************/
|
|
15
|
+
|
|
16
|
+
export let slot; // slot name, must be "box"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
/*****************************************/
|
|
20
|
+
/* Component code */
|
|
21
|
+
/*****************************************/
|
|
22
|
+
|
|
23
|
+
// check that the box is located in a correct slot
|
|
24
|
+
if (slot !== 'box') {
|
|
25
|
+
throw('Component Box must have \'slot="box"\' attribute.')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// box style
|
|
29
|
+
const rectStyleStr = `fill:transparent;stroke:${Colors.DARKGRAY};stroke-width:1px;`;
|
|
30
|
+
|
|
31
|
+
// get axes context and reactive variables needed to compute coordinates
|
|
32
|
+
const axes = getContext('axes');
|
|
33
|
+
const xLim = axes.xLim;
|
|
34
|
+
const yLim = axes.yLim;
|
|
35
|
+
const isOk = axes.isOk;
|
|
36
|
+
const tX = axes.tX;
|
|
37
|
+
const tY = axes.tY;
|
|
38
|
+
|
|
39
|
+
// reactive variables for coordinates of box points in pixels
|
|
40
|
+
$: xx = $isOk ? axes.transform($xLim, $tX.coords) : undefined;
|
|
41
|
+
$: yy = $isOk ? axes.transform($yLim, $tY.coords) : undefined;
|
|
42
|
+
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
{#if $isOk}
|
|
46
|
+
<g style="pointer-events:none" class="mdaplot__axes-box">
|
|
47
|
+
<rect x={xx[0]} y={yy[1]} width={xx[1] - xx[0]} height={yy[0] - yy[1]} style={rectStyleStr} />
|
|
48
|
+
</g>
|
|
49
|
+
{/if}
|
|
50
|
+
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
/****************************************************
|
|
3
|
+
* ColorLegend *
|
|
4
|
+
* -------------------- *
|
|
5
|
+
* shows color legend on top part of the plot *
|
|
6
|
+
*****************************************************/
|
|
7
|
+
|
|
8
|
+
import { getContext } from 'svelte';
|
|
9
|
+
import { Colors } from '../Colors';
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
/*****************************************/
|
|
13
|
+
/* Input parameters */
|
|
14
|
+
/*****************************************/
|
|
15
|
+
|
|
16
|
+
export let colormap; // colormap returned by method getcolormap() from colors
|
|
17
|
+
|
|
18
|
+
/*****************************************/
|
|
19
|
+
/* Component code */
|
|
20
|
+
/*****************************************/
|
|
21
|
+
|
|
22
|
+
const axes = getContext('axes');
|
|
23
|
+
const xLim = axes.xLim;
|
|
24
|
+
const yLim = axes.yLim;
|
|
25
|
+
const tX = axes.tX;
|
|
26
|
+
const tY = axes.tY;
|
|
27
|
+
const isOk = axes.isOk;
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
// TODO: will be implemented as a sequence of rectangles and text labels beneath them
|
|
31
|
+
|
|
32
|
+
// compute coordinates of rectangles and their colors
|
|
33
|
+
|
|
34
|
+
// compute coordinates of text labels
|
|
35
|
+
</script>
|
|
36
|
+
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
/*********************************************************
|
|
3
|
+
* ColormapLegend *
|
|
4
|
+
* -------------------- *
|
|
5
|
+
* shows color map legend on a plot *
|
|
6
|
+
**********************************************************/
|
|
7
|
+
|
|
8
|
+
import { getContext } from 'svelte';
|
|
9
|
+
import { checkCoords } from '../Utils';
|
|
10
|
+
import { Vector, isvector } from 'mdatools/arrays';
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
/*****************************************/
|
|
14
|
+
/* Input parameters */
|
|
15
|
+
/*****************************************/
|
|
16
|
+
|
|
17
|
+
export let breaks; // vector with breaks for color intervals
|
|
18
|
+
export let colmap; // array with colors for each interval
|
|
19
|
+
export let decNum = 1; // number of decimals to show the interval boundaries with
|
|
20
|
+
export let labels = null; // optional vector with labels for interval boundaries
|
|
21
|
+
export let labelColor = '#909090'; // color of labels
|
|
22
|
+
export let fontSize = 0.85; // font size for labels in em
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
/*****************************************/
|
|
26
|
+
/* Component code */
|
|
27
|
+
/*****************************************/
|
|
28
|
+
|
|
29
|
+
// constant parameters
|
|
30
|
+
const WIDTH_RATIO = 0.80; // legend will span 80% of plot width
|
|
31
|
+
const HEIGHT_RATIO = 0.02; // legend will span 2% of plot height
|
|
32
|
+
const TOP_MARGIN = 0.25; // legend top margin is 25% of its height
|
|
33
|
+
|
|
34
|
+
// height of the legend elements in pixels
|
|
35
|
+
const EL_HEIGHTS_PX = {'small': 5, 'medium': 10, 'large': 15, 'xlarge': 20};
|
|
36
|
+
|
|
37
|
+
// check breaks values
|
|
38
|
+
$: if (!breaks || breaks.length < 2) {
|
|
39
|
+
throw new Error('ColormapLegend: breaks values must be provided.');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// check colormap values
|
|
43
|
+
$: if (!colmap || colmap.length !== breaks.length - 1) {
|
|
44
|
+
throw new Error('ColormapLegend: number of color values in colormap does not match number of intervals defined by breaks.');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// define local variables for breaks and prepare breaks labels
|
|
48
|
+
$: lBreaks = isvector(breaks) ? breaks.v : breaks;
|
|
49
|
+
$: lLabels = labels && labels.length === lBreaks.length ?
|
|
50
|
+
labels :
|
|
51
|
+
Array.from(lBreaks).map(v => v.toFixed(decNum).toString());
|
|
52
|
+
|
|
53
|
+
// get axes context and reactive variables needed to compute coordinates
|
|
54
|
+
const axes = getContext('axes');
|
|
55
|
+
const tX = axes.tX;
|
|
56
|
+
const tY = axes.tY;
|
|
57
|
+
const xLim = axes.xLim;
|
|
58
|
+
const yLim = axes.yLim;
|
|
59
|
+
const scale = axes.scale;
|
|
60
|
+
const isOk = axes.isOk;
|
|
61
|
+
|
|
62
|
+
// number of elements
|
|
63
|
+
$: n = colmap.length;
|
|
64
|
+
|
|
65
|
+
// compute screen coordinates of the elements
|
|
66
|
+
let rx, ry, rw, rh = undefined;
|
|
67
|
+
$: {
|
|
68
|
+
if ($isOk) {
|
|
69
|
+
// compute size of the whole block in plot coordinates
|
|
70
|
+
const axesWidth = $xLim[1] - $xLim[0];
|
|
71
|
+
const axesHeight = $yLim[1] - $yLim[0];
|
|
72
|
+
const width = axesWidth * WIDTH_RATIO;
|
|
73
|
+
const height = axesHeight * HEIGHT_RATIO;
|
|
74
|
+
|
|
75
|
+
// compute elements size and position
|
|
76
|
+
const elWidth = width / n;
|
|
77
|
+
const left = $xLim[0] + axesWidth * (1 - WIDTH_RATIO) * 0.5 ;
|
|
78
|
+
const elTop = $yLim[1] - height * TOP_MARGIN;
|
|
79
|
+
|
|
80
|
+
// compute screen coordinates and sized
|
|
81
|
+
ry = axes.transform(checkCoords([elTop], 'Rectangles'), $tY.coords);
|
|
82
|
+
rx = axes.transform(Vector.seq(0, n).apply(v => left + v * elWidth), $tX.coords);
|
|
83
|
+
rw = axes.transform([elWidth], $tX.objects);
|
|
84
|
+
rh = EL_HEIGHTS_PX[$scale];
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
</script>
|
|
88
|
+
|
|
89
|
+
{#if $isOk && rx.length > 0}
|
|
90
|
+
<g class="series colormap_legend" style="stroke:0;stroke-width:0px;">
|
|
91
|
+
{#each colmap as col, i}
|
|
92
|
+
<rect x={rx[i]} y={ry} width={rw} height={rh} fill="{col}"/>
|
|
93
|
+
<text x={rx[i]} y={ry} dx={0} dy={rh * 1.25} alignment-baseline="hanging" fill={labelColor} font-size="{fontSize}em" text-anchor="middle">{@html lLabels[i]}</text>
|
|
94
|
+
{/each}
|
|
95
|
+
<text x={rx[n]} y={ry} dx={0} dy={rh * 1.25} alignment-baseline="hanging" fill={labelColor} font-size="{fontSize}em" text-anchor="middle">{@html lLabels[n]}</text>
|
|
96
|
+
</g>
|
|
97
|
+
{/if}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
/*********************************************************
|
|
3
|
+
* Heatmap *
|
|
4
|
+
* -------------------- *
|
|
5
|
+
* shows matrix values as a heatmap *
|
|
6
|
+
**********************************************************/
|
|
7
|
+
|
|
8
|
+
import { getContext } from 'svelte';
|
|
9
|
+
import { Vector, ismatrix } from 'mdatools/arrays';
|
|
10
|
+
import { expandgrid } from 'mdatools/misc';
|
|
11
|
+
import { split } from 'mdatools/stat';
|
|
12
|
+
|
|
13
|
+
import { getcolmap } from '../Colors';
|
|
14
|
+
import { checkCoords } from '../Utils';
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
/*****************************************/
|
|
18
|
+
/* Input parameters */
|
|
19
|
+
/*****************************************/
|
|
20
|
+
|
|
21
|
+
export let values; // Matrix with values to show heatmap for
|
|
22
|
+
export let breaks = null; // vector with breaks to distribute the values in
|
|
23
|
+
export let colmap = null; // array with colors for each interval
|
|
24
|
+
|
|
25
|
+
export let className = 'series_heatmap'; // CSS class name of the SVG group
|
|
26
|
+
export let title = ''; // title of the rectangle series (reserved for future use)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
/*****************************************/
|
|
30
|
+
/* Component code */
|
|
31
|
+
/*****************************************/
|
|
32
|
+
|
|
33
|
+
// check values
|
|
34
|
+
$: if (!ismatrix(values) || values.v.length === 0) {
|
|
35
|
+
throw new Error('Heatmap: parameter "values" must be a matrix.');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// check breaks values
|
|
39
|
+
$: if (!lBreaks || lBreaks.length < 2) {
|
|
40
|
+
throw new Error('Heatmap: breaks values are neither defined nor can not be computed.');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// check colormap values
|
|
44
|
+
$: if (!lColmap || lColmap.length !== lBreaks.length - 1) {
|
|
45
|
+
throw new Error('Heatmap: number of color values in colormap does not match number of intervals defined by breaks.');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// define local variables for breaks and colormap
|
|
49
|
+
$: lBreaks = breaks ? breaks.v : split(values.v, 13).v;
|
|
50
|
+
$: lColmap = colmap ? colmap : getcolmap(lBreaks.length - 1);
|
|
51
|
+
|
|
52
|
+
// get axes context and reactive variables needed to compute coordinates
|
|
53
|
+
const axes = getContext('axes');
|
|
54
|
+
const tX = axes.tX;
|
|
55
|
+
const tY = axes.tY;
|
|
56
|
+
const isOk = axes.isOk;
|
|
57
|
+
|
|
58
|
+
// compute combinations of heatmap element positions
|
|
59
|
+
let top, left;
|
|
60
|
+
$: {
|
|
61
|
+
const x = Vector.seq(1, values.ncols);
|
|
62
|
+
const y = Vector.seq(1, values.nrows);
|
|
63
|
+
[top, left] = expandgrid(y.v, x.v);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// compute screen coordinates of the elements for each interval
|
|
67
|
+
let rx, ry, rw, rh = undefined;
|
|
68
|
+
$: {
|
|
69
|
+
if ($isOk) {
|
|
70
|
+
rx = [];
|
|
71
|
+
ry = [];
|
|
72
|
+
for (let i = 1; i < lBreaks.length; i++) {
|
|
73
|
+
let lleft = [];
|
|
74
|
+
let ltop = [];
|
|
75
|
+
for (let j = 0; j < values.v.length; j++) {
|
|
76
|
+
const v = values.v[j];
|
|
77
|
+
if (v > lBreaks[i - 1] && v <= lBreaks[i]) {
|
|
78
|
+
lleft.push(left.v[j] - 0.5);
|
|
79
|
+
ltop.push(top.v[j] + 0.5);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
rx.push(lleft.length === 0 ? [] : axes.transform(checkCoords(lleft, 'Rectangles'), $tX.coords));
|
|
83
|
+
ry.push(ltop.length === 0 ? [] : axes.transform(checkCoords(ltop, 'Rectangles'), $tY.coords));
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
rw = axes.transform([1], $tX.objects);
|
|
87
|
+
rh = axes.transform([1], $tY.objects);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
</script>
|
|
92
|
+
|
|
93
|
+
{#if $isOk && rx.length > 0}
|
|
94
|
+
<g class="series {className}" title={title} style="stroke:0;stroke-width:0px;">
|
|
95
|
+
<!-- loop over colors/intervals -->
|
|
96
|
+
{#each lColmap as col, i}
|
|
97
|
+
<g title="heatmap_group" style="fill:{col};">
|
|
98
|
+
{#if rx[i].length > 0}
|
|
99
|
+
<!-- loop over elements which fall into the interval -->
|
|
100
|
+
{#each rx[i] as v, j}
|
|
101
|
+
<rect x={rx[i][j]} y={ry[i][j]} width={rw} height={rh} />
|
|
102
|
+
{/each}
|
|
103
|
+
{/if}
|
|
104
|
+
</g>
|
|
105
|
+
{/each}
|
|
106
|
+
</g>
|
|
107
|
+
{/if}
|
|
108
|
+
|
|
109
|
+
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
/****************************************************
|
|
3
|
+
* Legend *
|
|
4
|
+
* -------------------- *
|
|
5
|
+
* shows legend for plot components *
|
|
6
|
+
*****************************************************/
|
|
7
|
+
|
|
8
|
+
import { mean } from 'mdatools/stat';
|
|
9
|
+
import { getContext } from 'svelte';
|
|
10
|
+
import { Colors } from '../Colors';
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
/*****************************************/
|
|
14
|
+
/* Input parameters */
|
|
15
|
+
/*****************************************/
|
|
16
|
+
|
|
17
|
+
export let items; // array with text labels for each legend element
|
|
18
|
+
export let position = "topleft"; // position of the legend ("topleft", "top", "topright", "right", "bottomright", etc).
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
/*****************************************/
|
|
22
|
+
/* Component code */
|
|
23
|
+
/*****************************************/
|
|
24
|
+
|
|
25
|
+
const axes = getContext('axes');
|
|
26
|
+
const scale = axes.scale;
|
|
27
|
+
const xLim = axes.xLim;
|
|
28
|
+
const yLim = axes.yLim;
|
|
29
|
+
const tX = axes.tX;
|
|
30
|
+
const tY = axes.tY;
|
|
31
|
+
const isOk = axes.isOk;
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Computes width of text element for given font.
|
|
36
|
+
*
|
|
37
|
+
* @param {string} text - string with text
|
|
38
|
+
* @param {string} font - font name and size, e.g. '12px Arial'
|
|
39
|
+
*
|
|
40
|
+
* @return {number} size of element in pixels
|
|
41
|
+
*/
|
|
42
|
+
function getTextWidth(text, font) {
|
|
43
|
+
const element = document.createElement('canvas');
|
|
44
|
+
const context = element.getContext('2d');
|
|
45
|
+
context.font = font;
|
|
46
|
+
return context.measureText(text).width;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Compute size of main legend elements.
|
|
51
|
+
*
|
|
52
|
+
* @param {Array} items - array of legend items and their properties.
|
|
53
|
+
* @param {number} fontSize - font size of a legend element.
|
|
54
|
+
* @param {string} position - position of the legend.
|
|
55
|
+
*
|
|
56
|
+
* @return {Array} array with height and width og graphical part of legend item, padding size,
|
|
57
|
+
* height and width of text labels (all in pixels).
|
|
58
|
+
*/
|
|
59
|
+
function getLegendSize(items, fontSize, position) {
|
|
60
|
+
|
|
61
|
+
// compute size of graphical elements
|
|
62
|
+
const elHeight = fontSize; // height of legend element without padding
|
|
63
|
+
const elPadding = elHeight / 4; // top and bottom padding
|
|
64
|
+
const elWidth = elHeight * 1.5; // width of legend element without padding
|
|
65
|
+
|
|
66
|
+
// compute size of text label elements
|
|
67
|
+
// TODO: implement one row legend if position is "top" or "bottom"
|
|
68
|
+
const lbHeight = elHeight;
|
|
69
|
+
let lbWidth = 0;
|
|
70
|
+
for (let i = 0; i < items.length; i++) {
|
|
71
|
+
const w = getTextWidth(' ' + items[i].label + ' ', fontSize + 'px Arial');
|
|
72
|
+
lbWidth = w > lbWidth ? w : lbWidth;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return [elHeight, elWidth, elPadding, lbHeight, lbWidth];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// reactive expression to compute all sizes and coordinates
|
|
79
|
+
let left, top, elHeight, elWidth, elPadding, lbHeight, lbWidth, legendHeight, legendWidth, fontSize;
|
|
80
|
+
$: if ($isOk) {
|
|
81
|
+
|
|
82
|
+
fontSize = axes.LEGEND_FONT_SIZE[$scale];
|
|
83
|
+
|
|
84
|
+
// compute size of elements
|
|
85
|
+
[elHeight, elWidth, elPadding, lbHeight, lbWidth] = getLegendSize(items, fontSize, position);
|
|
86
|
+
|
|
87
|
+
// compute size of whole legend box
|
|
88
|
+
legendHeight = (elHeight + 2 * elPadding) * items.length;
|
|
89
|
+
legendWidth = (elWidth + 2 * elPadding + lbWidth);
|
|
90
|
+
|
|
91
|
+
// compute coordinates of top left corner of the legend box
|
|
92
|
+
const xLimPx = axes.transform($xLim, $tX.coords);
|
|
93
|
+
left = position.includes("left") ? xLimPx[0] + axes.TICK_SIZE[$scale] :
|
|
94
|
+
position.includes("right") ? xLimPx[1] - legendWidth - axes.TICK_SIZE[$scale] :
|
|
95
|
+
mean(xLimPx) - legendWidth/2;
|
|
96
|
+
|
|
97
|
+
const yLimPx = axes.transform($yLim, $tY.coords);
|
|
98
|
+
top = position.includes("top") ? yLimPx[1] + axes.TICK_SIZE[$scale] :
|
|
99
|
+
position.includes("bottom") ? yLimPx[0] - legendHeight - axes.TICK_SIZE[$scale] :
|
|
100
|
+
mean(yLimPx) - legendHeight/2;
|
|
101
|
+
}
|
|
102
|
+
</script>
|
|
103
|
+
|
|
104
|
+
{#if $isOk }
|
|
105
|
+
|
|
106
|
+
<!-- outer legend box, background and frame -->
|
|
107
|
+
<svg x="{left}px" y="{top}px" height="{legendHeight}px" width="{legendWidth}">
|
|
108
|
+
<rect height="100%" width="100%" style="fill:white;stroke:{Colors.MIDDLEGRAY};"></rect>
|
|
109
|
+
|
|
110
|
+
{#each items as item, i}
|
|
111
|
+
|
|
112
|
+
<!-- individual legend item -->
|
|
113
|
+
<svg x="{0}px" y="{i * (elHeight + 2 * elPadding)}px" width="{legendWidth}px" height="{elHeight + 2 * elPadding}px">
|
|
114
|
+
|
|
115
|
+
<!-- line -->
|
|
116
|
+
{#if item.lineType && item.lineType > 0 && item.lineType <= 4}
|
|
117
|
+
<line x1={elPadding} x2={elPadding + elWidth} y1={(elHeight + elPadding * 2)/2} y2={(elHeight + elPadding * 2)/2} style={`
|
|
118
|
+
stroke:${item.lineColor ? item.lineColor : Colors.PRIMARY};
|
|
119
|
+
stroke-width: ${item.lineWidth ? item.lineWidth : 1}px;
|
|
120
|
+
stroke-dasharray:${axes.LINE_STYLES[$scale][item.lineType - 1]}
|
|
121
|
+
`}/>
|
|
122
|
+
{/if}
|
|
123
|
+
|
|
124
|
+
<!-- marker -->
|
|
125
|
+
{#if item.marker && item.marker > 0 && item.marker < axes.MARKER_SYMBOLS.length}
|
|
126
|
+
<text x="{(elWidth + 2 * elPadding)/2}px" y="{(elHeight + 2 * elPadding)/2}px"
|
|
127
|
+
dominant-baseline="middle"
|
|
128
|
+
style={`
|
|
129
|
+
fill:${item.faceColor ? item.faceColor : "transparent"};
|
|
130
|
+
stroke-width:${item.borderWidth ? item.borderWidth : 1}px;
|
|
131
|
+
stroke:${item.borderColor ? item.borderColor : Colors.PRIMARY};
|
|
132
|
+
font-size:${fontSize}px;
|
|
133
|
+
text-anchor:middle;`}
|
|
134
|
+
>{axes.MARKER_SYMBOLS[item.marker - 1]}</text>
|
|
135
|
+
{/if}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
<!-- text -->
|
|
140
|
+
<text x="{elWidth + elPadding * 3}px" y="{elPadding}px"
|
|
141
|
+
dominant-baseline="hanging"
|
|
142
|
+
style={`text-anchor:start;fill:${Colors.LEGEND};font-size:${fontSize}px`};
|
|
143
|
+
>{@html ' ' + item.label}</text>
|
|
144
|
+
|
|
145
|
+
</svg>
|
|
146
|
+
{/each}
|
|
147
|
+
</svg>
|
|
148
|
+
{/if}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
/****************************************************
|
|
3
|
+
* Lines *
|
|
4
|
+
* -------------------- *
|
|
5
|
+
* shows lines connected sequence of points *
|
|
6
|
+
*****************************************************/
|
|
7
|
+
|
|
8
|
+
import { getContext } from 'svelte';
|
|
9
|
+
import { Colors } from '../Colors';
|
|
10
|
+
import { val2p } from '../Utils';
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
/*****************************************/
|
|
14
|
+
/* Input parameters */
|
|
15
|
+
/*****************************************/
|
|
16
|
+
|
|
17
|
+
export let xValues; // vector or array with x-coordinates of the points
|
|
18
|
+
export let yValues; // vector or array with y-coordinates of the points
|
|
19
|
+
export let lineWidth = 1; // width (thickness) of the lines
|
|
20
|
+
export let lineColor = Colors.PRIMARY; // color of the lines
|
|
21
|
+
export let lineType = 1; // type of the lines (1 - solid, 2 - dashed, 3 - dotted, 4 - dashdot)
|
|
22
|
+
export let title = ''; // title of the bar series (reserved for future use)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
/*****************************************/
|
|
26
|
+
/* Component code */
|
|
27
|
+
/*****************************************/
|
|
28
|
+
|
|
29
|
+
// get axes context
|
|
30
|
+
const axes = getContext('axes');
|
|
31
|
+
|
|
32
|
+
const scale = axes.scale;
|
|
33
|
+
const isOk = axes.isOk;
|
|
34
|
+
const tX = axes.tX;
|
|
35
|
+
const tY = axes.tY;
|
|
36
|
+
|
|
37
|
+
// compute polynomial coordinates as string
|
|
38
|
+
$: p = $isOk ? val2p(xValues, yValues, $tX, $tY, axes) : undefined;
|
|
39
|
+
|
|
40
|
+
// reactive variables for coordinates of data points in pixels
|
|
41
|
+
$: lineStyleStr = `fill:transparent;stroke:${lineColor};stroke-width: ${lineWidth}px; stroke-dasharray:${axes.LINE_STYLES[$scale][lineType-1]}`;
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
{#if $isOk && p !== undefined}
|
|
45
|
+
<g class="series series_line" style={lineStyleStr} title={title}>
|
|
46
|
+
<polyline class="line" points={p}/>
|
|
47
|
+
</g>
|
|
48
|
+
{/if}
|
|
49
|
+
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
/****************************************************
|
|
3
|
+
* Multilines *
|
|
4
|
+
* -------------------- *
|
|
5
|
+
* shows series of lines components *
|
|
6
|
+
*****************************************************/
|
|
7
|
+
|
|
8
|
+
import { getContext } from 'svelte';
|
|
9
|
+
import { ismatrix } from 'mdatools/arrays';
|
|
10
|
+
import { Colors } from '../Colors';
|
|
11
|
+
import { val2p } from '../Utils';
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
/*****************************************/
|
|
15
|
+
/* Input parameters */
|
|
16
|
+
/*****************************************/
|
|
17
|
+
|
|
18
|
+
export let xValues; // vector or array with x-coordinates of the points
|
|
19
|
+
export let yValues; // matrix (!) with y-coordinates of the points - every column is separate line
|
|
20
|
+
export let lineWidth = 1; // width (thickness) of the lines
|
|
21
|
+
export let lineColor = Colors.PRIMARY; // color of the lines
|
|
22
|
+
export let lineType = 1; // type of the lines (1 - solid, 2 - dashed, 3 - dotted, 4 - dashdot)
|
|
23
|
+
export let title = ''; // title of the bar series (reserved for future use)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
/*****************************************/
|
|
27
|
+
/* Component code */
|
|
28
|
+
/*****************************************/
|
|
29
|
+
|
|
30
|
+
// get axes context
|
|
31
|
+
const axes = getContext('axes');
|
|
32
|
+
|
|
33
|
+
const scale = axes.scale;
|
|
34
|
+
const isOk = axes.isOk;
|
|
35
|
+
const tX = axes.tX;
|
|
36
|
+
const tY = axes.tY;
|
|
37
|
+
|
|
38
|
+
// compute polynomial coordinates as string
|
|
39
|
+
let pp;
|
|
40
|
+
$: if ($isOk) {
|
|
41
|
+
|
|
42
|
+
if (!ismatrix(yValues)) {
|
|
43
|
+
throw Error('Multilines: parameter "yValues" must be a matrix.');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (xValues.length !== yValues.nrows) {
|
|
47
|
+
throw Error('Multilines: parameter "xValues" must have the same number of values as number of rows in "yValues".');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
pp = new Array(yValues.ncols);
|
|
51
|
+
for (let i = 0; i < pp.length; i++) {
|
|
52
|
+
pp[i] = val2p(xValues, yValues.getcolumn(i + 1), $tX, $tY, axes);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// reactive variables for coordinates of data points in pixels
|
|
57
|
+
$: lineStyleStr = `fill:transparent;stroke:${lineColor};stroke-width: ${lineWidth}px; stroke-dasharray:${axes.LINE_STYLES[$scale][lineType-1]}`;
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
{#if $isOk && pp !== undefined && pp.length > 0}
|
|
61
|
+
<g class="series series_multiline" style={lineStyleStr} title={title}>
|
|
62
|
+
{#each pp as p}
|
|
63
|
+
<polyline class="line" points={p}/>
|
|
64
|
+
{/each}
|
|
65
|
+
</g>
|
|
66
|
+
{/if}
|
|
67
|
+
|