svelte-plots-basic 2.0.1 → 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 CHANGED
@@ -4,7 +4,13 @@ The library is under development and breaking changes may occur in the coming ve
4
4
 
5
5
  ## News
6
6
 
7
- New release (v. 2.0.0) introduces many breaking changes as the library was completely re-written. If you use previous versions of `svelte-plots-basic` in your projects, and do not want to change anything, stick to the latest 1.x.x version (v. 1.1.4).
7
+ New major release (v. 2.0.0) introduces many breaking changes as the library was completely re-written. If you use previous versions of `svelte-plots-basic` in your projects, and do not want to change anything, stick to the latest 1.x.x version (v. 1.1.4).
8
+
9
+ ### 2.1.0
10
+
11
+ * Added new elements, `Heatmap` and `ColormapLegend`.
12
+ * Improvements to tick labels, in particular if values are too small (< 0.01) or too large (>99) the values are adjusted and a common factor is shown at the end of axis. This is applied only to automatic ticks, manually provided ticks and tick labels are shown as is.
13
+ * Small improvements and bug fixes.
8
14
 
9
15
  ## Description
10
16
 
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": "2.0.1",
4
+ "version": "2.1.0",
5
5
  "author": "Sergey Kucheryavskiy",
6
6
  "license": "MIT",
7
7
  "private": false,
@@ -24,10 +24,22 @@
24
24
  export let grid = []; // array with grid coordinates
25
25
  export let axisLine = []; // array with coordinates of main axis line
26
26
  export let tickCoords = []; // array with axis tick coordinates
27
+ export let tfCoords = []; // array with tick factor coordinates
28
+ export let tickFactor = 0; // tick factor (as power of 10)
27
29
 
28
30
  export let lineColor = Colors.DARKGRAY; // color of main axis line
29
31
  export let gridColor = Colors.MIDDLEGRAY; // color of grid lines
30
32
  export let textColor = Colors.DARKGRAY; // color of tick labels
33
+
34
+ // generate text for tick factor label
35
+ function getTickFactorLabel(tf) {
36
+ if (tf === 0) return '';
37
+ if (tf < 0) {
38
+ return `&times;10<tspan baseline-shift = "super">${(tf).toString()}</tspan>`;
39
+ } else {
40
+ return `&times;10<tspan baseline-shift = "super">${(tf).toString()}</tspan>`;
41
+ }
42
+ }
31
43
  </script>
32
44
 
33
45
  <g class="mdaplot__axis {className}">
@@ -44,7 +56,12 @@
44
56
 
45
57
  <!-- labels -->
46
58
  {#if tickCoords.length === 2 && tickLabels.length === tickCoords[1][0].length}
47
- <AxisTickLabels {las} {pos} {tickCoords} {tickLabels} {textColor}/>
59
+ <AxisTickLabels {las} {pos} {tickCoords} {tickLabels} {textColor} />
60
+ {/if}
61
+
62
+ <!-- tick factor -->
63
+ {#if tickFactor !== 0}
64
+ <AxisTickLabels {pos} tickCoords={tfCoords} tickLabels={[getTickFactorLabel(tickFactor)]} {textColor} />
48
65
  {/if}
49
66
  </g>
50
67
 
@@ -44,7 +44,7 @@
44
44
 
45
45
  // compute maximum bar width and position of left side
46
46
  const xv = checkCoords(xValues, 'BarSeries');
47
- const w = max(diff(xv)) * barWidth;
47
+ const w = max(diff(xv.v)) * barWidth;
48
48
  left = xv.subtract(w/2);
49
49
  width = Vector.fill(w, xv.length);
50
50
  }
@@ -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
+
@@ -40,7 +40,7 @@
40
40
  $: if ($isOk) {
41
41
 
42
42
  if (typeof(marker) !== "number" || marker < 1 || marker > axes.MARKER_SYMBOLS.length) {
43
- throw Error(`ScatterSeries: parameter "marker" must be a number from 1 to ${axes.MARKER_SYMBOLS.length}.`);
43
+ throw Error(`Points: parameter "marker" must be a number from 1 to ${axes.MARKER_SYMBOLS.length}.`);
44
44
  }
45
45
 
46
46
  markerSymbol = axes.MARKER_SYMBOLS[marker - 1];
@@ -8,7 +8,7 @@
8
8
  import { getContext } from 'svelte';
9
9
  import { Vector, vector } from 'mdatools/arrays';
10
10
  import { Colors } from '../Colors.js';
11
- import { getAxisTicks } from '../Utils.js';
11
+ import { getAxisTicks, getTickLabels } from '../Utils.js';
12
12
  import Axis from './Axis.svelte';
13
13
 
14
14
 
@@ -50,6 +50,8 @@
50
50
  let grid = [];
51
51
  let axisLine = [];
52
52
  let tickCoords = [];
53
+ let tfCoords = [];
54
+ let tickFactor = 0;
53
55
 
54
56
  // compute coordinates for ticks, grid and axis line
55
57
  $: if ($isOk) {
@@ -66,8 +68,11 @@
66
68
  // compute coordinates for the ends of grid
67
69
  const gridYEnd = Vector.fill($yLim[1], tickNum);
68
70
 
69
- // tick labels
70
- tickLabels = (ticks === undefined || tickLabels === undefined) ? ticksX.v : tickLabels;
71
+ // tick labels and tick factor
72
+ if (ticks === undefined || tickLabels === undefined) {
73
+ [tickFactor, tickLabels] = getTickLabels(ticksX.v);
74
+ }
75
+
71
76
  if (tickLabels.length !== ticksX.length) {
72
77
  throw('XAxis: "tickLabels" must be a array of the same size as ticks.')
73
78
  }
@@ -87,6 +92,11 @@
87
92
  [ticksX, ticksY2],
88
93
  [ticksX, ticksY1]
89
94
  ];
95
+
96
+ tfCoords = [
97
+ [null, null],
98
+ [[$xLim[1]], [ticksY1.v[tickNum - 1]]],
99
+ ];
90
100
  }
91
101
  </script>
92
102
 
@@ -94,6 +104,6 @@
94
104
  <Axis
95
105
  className="mdaplot__xaxis" pos={1}
96
106
  {lineColor} {gridColor} {textColor}
97
- {showGrid} {grid} {axisLine} {tickCoords} {tickLabels} {las}
107
+ {showGrid} {grid} {axisLine} {tickCoords} {tickLabels} {tfCoords} {tickFactor} {las}
98
108
  />
99
109
  {/if}
@@ -8,7 +8,7 @@
8
8
  import { getContext } from 'svelte';
9
9
  import { Vector, vector } from 'mdatools/arrays';
10
10
  import { Colors } from '../Colors.js';
11
- import { getAxisTicks } from '../Utils.js';
11
+ import { getAxisTicks, getTickLabels } from '../Utils.js';
12
12
  import Axis from './Axis.svelte';
13
13
 
14
14
 
@@ -50,6 +50,8 @@
50
50
  let grid = [];
51
51
  let axisLine = [];
52
52
  let tickCoords = [];
53
+ let tfCoords = [];
54
+ let tickFactor = 0;
53
55
 
54
56
  // compute tick x-coordinates
55
57
  $: if ($isOk) {
@@ -66,8 +68,11 @@
66
68
  // coordinates for the ends of grid
67
69
  const gridXEnd = Vector.fill($xLim[1], tickNum);
68
70
 
69
- // tick labels
70
- tickLabels = (ticks === undefined || tickLabels === undefined) ? ticksY.v : tickLabels;
71
+ // tick labels and tick factor
72
+ if (ticks === undefined || tickLabels === undefined) {
73
+ [tickFactor, tickLabels] = getTickLabels(ticksY.v)
74
+ }
75
+
71
76
  if (tickLabels.length !== ticksY.length) {
72
77
  throw('YAxis: "tickLabels" must be a array of the same size as ticks.')
73
78
  }
@@ -87,6 +92,11 @@
87
92
  [ticksX1, ticksY],
88
93
  [ticksX2, ticksY]
89
94
  ];
95
+
96
+ tfCoords = [
97
+ [[], []],
98
+ [[ticksX1.v[tickNum - 1]], [$yLim[1]]],
99
+ ];
90
100
  }
91
101
  </script>
92
102
 
@@ -94,6 +104,6 @@
94
104
  <Axis
95
105
  className="mdaplot__yaxis" pos={4}
96
106
  {lineColor} {gridColor} {textColor}
97
- {showGrid} {grid} {axisLine} {tickCoords} {tickLabels} {las}
107
+ {showGrid} {grid} {axisLine} {tickCoords} {tickLabels} {tfCoords} {tickFactor} {las}
98
108
  />
99
109
  {/if}
package/src/2d/index.js CHANGED
@@ -12,9 +12,10 @@ export { default as Bars } from './Bars.svelte';
12
12
  export { default as Points } from './Points.svelte';
13
13
  export { default as Lines } from './Lines.svelte';
14
14
  export { default as Multilines } from './Multilines.svelte';
15
+ export { default as Heatmap } from './Heatmap.svelte';
15
16
 
16
- export { default as ColorLegend } from './ColorLegend.svelte';
17
17
  export { default as Legend } from './Legend.svelte';
18
18
  export { default as TextLegend } from './TextLegend.svelte';
19
+ export { default as ColormapLegend } from './ColormapLegend.svelte';
19
20
 
20
21
  export { getcolmap, Colors } from '../Colors.js';
@@ -60,8 +60,6 @@
60
60
  const left = nr[0] - dnr;
61
61
  const right = nr[1] + dnr;
62
62
  const span = right - left;
63
- console.log(nr)
64
- console.log([left, right, span])
65
63
 
66
64
  // compute coordinates for mesh segments lines
67
65
  xMesh1Start = Vector.zeros((nx - 1) * nz);
@@ -171,7 +169,6 @@
171
169
  }
172
170
  };
173
171
 
174
- $: console.log(meshColor1)
175
172
  $: lineStyleStr = `stroke-width: ${lineWidth}px;stroke-dasharray:${axes.LINE_STYLES[$scale][lineType-1]};`;
176
173
  </script>
177
174
 
package/src/Utils.js CHANGED
@@ -1,4 +1,40 @@
1
1
  import { cbind, vector, isvector, Vector } from 'mdatools/arrays';
2
+ import { min, max, diff } from 'mdatools/stat';
3
+
4
+
5
+ /**
6
+ * Generate vector with axis tick labels and tick factor based on numeric tick values.
7
+ *
8
+ * @param {Vector} ticks - vector with numeric tick values.
9
+ *
10
+ * @returns {Array} an array with tick factor and array with tick labels.
11
+ */
12
+ export function getTickLabels(ticks) {
13
+
14
+ if (ticks === undefined) return undefined;
15
+
16
+ const step = Math.abs(min(diff(ticks)));
17
+ let tickFactor = 0;
18
+ if (step < 1 && Math.abs(max(ticks)) < 1) {
19
+ // values between 0 and 1 (absolute)
20
+ let decNum = Math.ceil(-Math.log10(step));
21
+ if (decNum <= 2) {
22
+ return [0, Array.from(ticks).map(v => v.toFixed(decNum).toString())];
23
+ }
24
+ tickFactor = decNum - 2;
25
+ decNum = 2;
26
+ return [-tickFactor, Array.from(ticks).map(v => (v * Math.pow(10, tickFactor)).toFixed(decNum).toString())];
27
+ } else {
28
+ let decNum = Math.ceil(Math.log10(step));
29
+ if (decNum <= 2) {
30
+ return [0, Array.from(ticks).map(v => v.toString())];
31
+ }
32
+ tickFactor = decNum - 2;
33
+ decNum = 2;
34
+ return [tickFactor, Array.from(ticks).map(v => (v / Math.pow(10, tickFactor)).toString())];
35
+ }
36
+ }
37
+
2
38
 
3
39
  /**
4
40
  * Check coordinates and convert them to vector if necessary.
@@ -26,6 +62,7 @@ export function checkCoords(x, source) {
26
62
  return x;
27
63
  }
28
64
 
65
+
29
66
  /**
30
67
  * Create string with coordinates of SVG polygon in 3D.
31
68
  *
@@ -130,10 +167,11 @@ export function getAxisTicks(ticks, lim, maxTickNum, round = true) {
130
167
  // create a sequence of ticks
131
168
  ticks = Vector.seq(tickMin, tickMax, tickSpacing);
132
169
 
133
- // if step is smaller than 1 round values to remove small decimals accidentiall added by JS
170
+ // if step is smaller than 1 round values to remove small decimals accidentially added by JS
134
171
  if (Math.abs(tickSpacing) < 1) {
172
+ const decNum = Math.round(-Math.log10(tickSpacing));
135
173
  const r = Math.pow(10, 1 + Math.round(-Math.log10(tickSpacing)));
136
- ticks = ticks.apply(v => Math.round((v + Number.EPSILON) * r) / r)
174
+ ticks = ticks.apply(v => (Math.round((v + Number.EPSILON) * r) / r));
137
175
  }
138
176
 
139
177
  // make sure the ticks are not aligned with axes limits