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 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.3) 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.
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 `localhost:5000` in browser. That is it.
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
- "name": "svelte-plots-basic",
3
- "svelte": "src/index.js",
4
- "module": "dist/index.mjs",
5
- "main": "dist/index.js",
6
- "scripts": {
7
- "build": "rollup -c",
8
- "prepublishOnly": "npm run build"
9
- },
10
- "devDependencies": {
11
- "@rollup/plugin-node-resolve": "^9.0.0",
12
- "rollup": "^2.0.0",
13
- "rollup-plugin-svelte": "^6.0.0",
14
- "svelte": "^3.0.0"
15
- },
16
- "dependencies": {
17
- "mdatools": "^0.5.3"
18
- },
19
- "keywords": [
20
- "svelte",
21
- "svelte plots"
22
- ],
23
- "files": [
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 || !Array.isArray(x)) return 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 => (v - xLim[0]) / (xLim[1] - xLim[0]) * (width - margins[1] - margins[3]) + margins[1]);
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 || !Array.isArray(y)) return 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 => (yLim[1] - v) / (yLim[1] - yLim[0]) * (height - margins[0] - margins[2]) + margins[2]);
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
- :global(.plot) {
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: 20px;
449
+ padding: 1em;
450
+ box-sizing: border-box;
439
451
  text-align: center;
440
452
  }
441
453
 
@@ -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>
@@ -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: ${lineWidth}px;`;
51
+ $: barsStyleStr = `fill:${faceColor};stroke:${borderColor};stroke-width:${lineWidth}px;`;
52
52
 
53
53
  </script>
54
54
 
@@ -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>
@@ -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="{x1[i]}" x2="{x2[i]}" y1="{y1[i]}" y2="{y2[i]}" style="{lineStyleStr}"></line>
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>
@@ -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
- // text-anchor values depending on position
25
- const textAnchors = ["middle", "middle", "start", "middle", "end"];
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) || !Array.isArray(yValues) || xValues.length !== yValues.length) {
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
- // multiply label values if needed
33
- $: {
34
- const n = xValues.length;
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 = [0, 0, 1, 0, -1][pos] * axes.LABELS_MARGIN[$scale];
59
- $: dy = [0, 1, 0, -1, 0][pos] * axes.LABELS_MARGIN[$scale];
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:${textAnchors[pos]};`;
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 labels[i]}</text>
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>