svelte-plots-basic 2.2.3 → 2.3.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/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.2.3",
4
+ "version": "2.3.0",
5
5
  "author": "Sergey Kucheryavskiy",
6
6
  "license": "MIT",
7
7
  "private": false,
@@ -8,7 +8,7 @@
8
8
  import { setContext, createEventDispatcher, onMount, onDestroy } from 'svelte';
9
9
  import { writable } from 'svelte/store';
10
10
  import { isvector, vector, Vector } from 'mdatools/arrays';
11
- import { roundCoords, getScale } from '../Utils.js';
11
+ import { roundCoords, getScale, downloadSVG, downloadPNG } from '../Utils.js';
12
12
 
13
13
 
14
14
  /*****************************************/
@@ -23,6 +23,16 @@
23
23
  export let margins = [1.0, 0.75, 0.5, 0.5]; // margins [bottom, left, top, right] )
24
24
 
25
25
 
26
+ /*****************************************/
27
+ /* Input parameters for downloading */
28
+ /*****************************************/
29
+ export let downloadLinks = 'hover'; // how to show download links panel ('none', 'hover', 'fixed')
30
+ export let fileName = 'plot'; // file name for download (without extension)
31
+ export let pngWidth = 8; // width of PNG image in cm
32
+ export let pngHeight = 8; // height of PNG image in cm
33
+ export let pngRes = 300; // resolution of PNG image (pixels per inch)
34
+
35
+
26
36
  /*****************************************/
27
37
  /* Constants */
28
38
  /*****************************************/
@@ -87,7 +97,7 @@
87
97
  }
88
98
 
89
99
  // marker symbols
90
- const MARKER_SYMBOLS = ["●", "◼", "▲", "▼", "⬥", "+", "*", "×"];
100
+ const MARKER_SYMBOLS = ["●", "◼", "▲", "▼", "⬥", "+", "✳", "✕"];
91
101
 
92
102
  // constant to make clip path ID unique
93
103
  const clipPathID = 'plottingArea' + Math.round(Math.random() * 10000);
@@ -130,13 +140,13 @@
130
140
  function handleClick(e) {
131
141
 
132
142
  // click on scatter plot markers
133
- if (e.target.tagName === 'text' && e.target.parentNode.classList.contains('series_points')) {
143
+ if (e.target.tagName === 'text' && e.target.parentNode.classList.contains('series-points')) {
134
144
  dispatchClickEvent('markerclick', e.target);
135
145
  return;
136
146
  }
137
147
 
138
148
  // click on bar plot bars
139
- if (e.target.tagName === 'rect' && e.target.parentNode.classList.contains('series_bar')) {
149
+ if (e.target.tagName === 'rect' && e.target.parentNode.classList.contains('series-bar')) {
140
150
  dispatchClickEvent('barclick', e.target);
141
151
  return;
142
152
  }
@@ -145,6 +155,14 @@
145
155
  dispatch('axesclick');
146
156
  }
147
157
 
158
+ function handleClickSVG() {
159
+ downloadSVG(plotElement, fileName)
160
+ }
161
+
162
+ function handleClickPNG() {
163
+ downloadPNG(plotElement, fileName, pngWidth, pngHeight, pngRes)
164
+ }
165
+
148
166
  /**
149
167
  * Generic function to transform x or y-values from plot coordinates to screen (SVG) coordinates.
150
168
  *
@@ -187,11 +205,11 @@
187
205
  /*****************************************/
188
206
 
189
207
  const scale = writable('medium'); // scale factor (how big the shown plot is)
208
+ const isOk = writable(false); // are axes ready for drawing
209
+ const xLim = writable(limX); // validated values for x-axis limits
210
+ const yLim = writable(limY); // validated values for y-axis limits
190
211
  const tX = writable({'coords': [1, 0, 0], 'objects': [1, 0, 0]}); // scaling and translation factors for x-dimension
191
212
  const tY = writable({'coords': [1, 0, 0], 'objects': [1, 0, 0]}); // scaling and translation factors for y-dimension
192
- const isOk = writable(false); // are axes ready for drawing
193
- const xLim = writable(limX);
194
- const yLim = writable(limY);
195
213
 
196
214
 
197
215
  /*****************************************/
@@ -293,8 +311,8 @@
293
311
  left = xLabel && xLabel !== '' ? m : 0;
294
312
  bottom = yLabel && yLabel !== '' ? m : 0;
295
313
  top = title && title !== '' ? m : 0;
296
- width = pcr.width - left;
297
- height = pcr.height - bottom - top;
314
+ width = pcr.width > left ? pcr.width - left : 0;
315
+ height = pcr.height > (bottom + top) ? pcr.height - bottom - top : 0;
298
316
  }
299
317
  });
300
318
 
@@ -308,16 +326,46 @@
308
326
  ro.unobserve(plotElement);
309
327
  })
310
328
 
311
- $: lblStyleStr = `font-weight:bold;text-anchor:middle;`;
312
-
313
329
  </script>
314
330
 
315
- <div class="plot-container" bind:this={plotElement} class:plot_error={!$isOk}>
331
+ <div class="plot-container show-download-links-{downloadLinks}" class:plot_error={!$isOk}>
316
332
 
317
333
  <svg class="plot" bind:this={plotElement} xmlns="http://www.w3.org/2000/svg"
318
- style={`font-family: Arial, Helvetica, sans-serif;font-size:${fontSize}px`}>
319
-
320
- <g style={lblStyleStr}>
334
+ style={`font-size:${fontSize}px`}>
335
+
336
+ <style>
337
+ .plot {
338
+ font-family: Arial, Helvetica, sans-serif;
339
+ }
340
+
341
+ .plot-labels text {
342
+ font-weight:bold;
343
+ text-anchor:middle;
344
+ }
345
+
346
+ .tick-labels text,
347
+ .tick-labels tspan,
348
+ .series-text text,
349
+ .series-text tspan,
350
+ .series-points text,
351
+ .series-points tspan {
352
+ dominant-baseline: middle;
353
+ cursor: default;
354
+ user-select: none;
355
+ }
356
+
357
+ .series-text text,
358
+ .series-text tspan {
359
+ text-anchor: middle;
360
+ }
361
+
362
+ .tick-labels text,
363
+ .tick-labels tspan {
364
+ font-size:1em;
365
+ }
366
+ </style>
367
+
368
+ <g class="plot-labels">
321
369
  <!-- y-axis label -->
322
370
  {#if yLabel && yLabel !== ''}
323
371
  <text x={0} y={(height + top) /2} dx={0} dy={0} dominant-baseline="top" transform={`rotate(-90, 10, ${height/2})`} style="font-size:1.1em;">{@html yLabel}</text>
@@ -361,10 +409,59 @@
361
409
  Axes component was not properly initialized. <br />
362
410
  Check that you defined axes limits and margins correctly.
363
411
  </p>
412
+ {:else}
413
+ <div class="download-links">
414
+ <button on:click={handleClickSVG} on:keydown={handleClickSVG}>⇩ svg</button>
415
+ <button on:click={handleClickPNG} on:keydown={handleClickPNG}>⇩ png</button>
416
+ </div>
364
417
  {/if}
418
+
365
419
  </div>
366
420
 
367
421
  <style>
422
+ .show-download-links-none > .download-links{
423
+ display: none;
424
+ }
425
+
426
+ .show-download-links-hover:hover > .download-links {
427
+ bottom: 0;
428
+ }
429
+
430
+ .show-download-links-fixed > .download-links{
431
+ bottom: 0;
432
+ }
433
+
434
+ .download-links {
435
+ bottom: right 0.5s ease;
436
+ position: absolute;
437
+ font-size: 0.8em;
438
+ bottom: -50%;
439
+ right: 0;
440
+ z-index: 10;
441
+ padding: 0.5em 1em 0.5em 1em;
442
+ background: #fefefe;
443
+ border-top-left-radius: 0.5em;
444
+ transition: bottom 0.35s ease;
445
+ box-shadow: 0 0 1em #00000040;
446
+
447
+ display: flex;
448
+ align-items: center;
449
+ justify-content: center;
450
+ }
451
+
452
+ .download-links > button{
453
+ color: #606060;
454
+ border: none;
455
+ box-shadow: none;
456
+ background: #fafafa;
457
+ border-radius: 0.35em;
458
+ padding: 0.25em 0.5em;
459
+ }
460
+
461
+ .download-links > button:hover{
462
+ background: #443333;
463
+ color: #fafafa;
464
+ }
368
465
 
369
466
  /* Plot container */
370
467
  .plot-container {
@@ -377,6 +474,8 @@
377
474
  height: 100%;
378
475
  padding: 0;
379
476
  margin: 0;
477
+ overflow: hidden;
478
+ background: #fff;
380
479
  }
381
480
 
382
481
  .plot_error {
@@ -53,11 +53,11 @@
53
53
  }
54
54
 
55
55
  // styles for the elements
56
- $: textStyleStr = `dominant-baseline:middle;fill:${textColor};font-size:1em; text-anchor:middle;`;
56
+ $: textStyleStr = `fill:${textColor};`;
57
57
  </script>
58
58
 
59
59
  {#if x !== undefined && y !== undefined}
60
- <g class="tick_labels" bind:this={el} style={textStyleStr} >
60
+ <g class="tick-labels" bind:this={el} style={textStyleStr} >
61
61
  {#if las === 2 && pos === 4}
62
62
  {#each x as v, i}
63
63
  <text data-id={i} x={x[i]} y={y[i]} dx={0} dy={dx*1.25} transform={`rotate(-90, ${x[i]}, ${y[i]})`} text-anchor={"middle"}>{@html tickLabels[i]}</text>
@@ -73,11 +73,3 @@
73
73
  {/if}
74
74
  </g>
75
75
  {/if}
76
-
77
- <style>
78
- text, text > :global(tspan) {
79
- cursor: default;
80
- user-select: none;
81
- dominant-baseline: middle;
82
- }
83
- </style>
@@ -62,4 +62,4 @@
62
62
  }
63
63
  </script>
64
64
 
65
- <Rectangles className="series_bar" {left} {top} {width} {height} {borderColor} {faceColor} {title} />
65
+ <Rectangles className="series-bar" {left} {top} {width} {height} {borderColor} {faceColor} {title} />
package/src/2d/Box.svelte CHANGED
@@ -25,8 +25,6 @@
25
25
  throw('Component Box must have \'slot="box"\' attribute.')
26
26
  }
27
27
 
28
- // box style
29
- const rectStyleStr = `fill:transparent;stroke:${Colors.DARKGRAY};stroke-width:1px;`;
30
28
 
31
29
  // get axes context and reactive variables needed to compute coordinates
32
30
  const axes = getContext('axes');
@@ -44,7 +42,7 @@
44
42
 
45
43
  {#if $isOk}
46
44
  <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} />
45
+ <rect stroke="{Colors.DARKGRAY}" stroke-width="0.1em" fill="transparent" x={xx[0]} y={yy[1]} width={xx[1] - xx[0]} height={yy[0] - yy[1]} />
48
46
  </g>
49
47
  {/if}
50
48
 
@@ -22,7 +22,7 @@
22
22
  export let breaks = null; // vector with breaks to distribute the values in
23
23
  export let colmap = null; // array with colors for each interval
24
24
 
25
- export let className = 'series_heatmap'; // CSS class name of the SVG group
25
+ export let className = 'series-heatmap'; // CSS class name of the SVG group
26
26
  export let title = ''; // title of the rectangle series (reserved for future use)
27
27
 
28
28
 
@@ -94,7 +94,7 @@
94
94
  <g class="series {className}" title={title} style="stroke:0;stroke-width:0px;">
95
95
  <!-- loop over colors/intervals -->
96
96
  {#each lColmap as col, i}
97
- <g title="heatmap_group" style="fill:{col};">
97
+ <g title="heatmap-group" style="fill:{col};">
98
98
  {#if rx[i].length > 0}
99
99
  <!-- loop over elements which fall into the interval -->
100
100
  {#each rx[i] as v, j}
@@ -43,7 +43,7 @@
43
43
  const element = document.createElement('canvas');
44
44
  const context = element.getContext('2d');
45
45
  context.font = font;
46
- return context.measureText(text).width;
46
+ return context.measureText(text.replace(/<(.|\n)*?>/g, '')).width;
47
47
  }
48
48
 
49
49
  /**
@@ -105,42 +105,34 @@
105
105
 
106
106
  <!-- outer legend box, background and frame -->
107
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>
108
+ <rect height="100%" width="100%" fill="white" stroke="{Colors.MIDDLEGRAY}"></rect>
109
109
 
110
110
  {#each items as item, i}
111
111
 
112
112
  <!-- individual legend item -->
113
113
  <svg x="{0}px" y="{i * (elHeight + 2 * elPadding)}px" width="{legendWidth}px" height="{elHeight + 2 * elPadding}px">
114
114
 
115
+ <style>
116
+ .legend-text,
117
+ .legend-text tspan {
118
+ dominant-baseline: hanging;
119
+ }
120
+ </style>
121
+
115
122
  <!-- line -->
116
123
  {#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
- `}/>
124
+ <line x1={elPadding} x2={elPadding + elWidth} y1={(elHeight + elPadding * 2)/2} y2={(elHeight + elPadding * 2)/2} stroke="{item.lineColor ? item.lineColor : Colors.PRIMARY}" stroke-width="{item.lineWidth ? item.lineWidth : 1}px" stroke-dasharray="{axes.LINE_STYLES[$scale][item.lineType - 1]}"/>
122
125
  {/if}
123
126
 
124
127
  <!-- marker -->
125
128
  {#if item.marker && item.marker > 0 && item.marker < axes.MARKER_SYMBOLS.length}
126
129
  <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>
130
+ dominant-baseline="middle" fill="{item.faceColor ? item.faceColor : 'transparent'}" stroke-width="{item.borderWidth ? item.borderWidth : 1}px" stroke="{item.borderColor ? item.borderColor : Colors.PRIMARY}" font-size="{fontSize}px" text-anchor="middle">{axes.MARKER_SYMBOLS[item.marker - 1]}</text>
135
131
  {/if}
136
132
 
137
133
 
138
-
139
134
  <!-- 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 '&nbsp;' + item.label}</text>
135
+ <text class="legend-text" xml:space="preserve" x="{elWidth + elPadding * 3}px" y="{elPadding}px" text-anchor="start" fill="{Colors.LEGEND}" font-size="{fontSize}px"> {@html item.label}</text>
144
136
 
145
137
  </svg>
146
138
  {/each}
@@ -42,7 +42,7 @@
42
42
  </script>
43
43
 
44
44
  {#if $isOk && p !== undefined}
45
- <g class="series series_line" style={lineStyleStr} title={title}>
45
+ <g class="series series-line" style={lineStyleStr} title={title}>
46
46
  <polyline class="line" points={p}/>
47
47
  </g>
48
48
  {/if}
@@ -58,7 +58,7 @@
58
58
  </script>
59
59
 
60
60
  {#if $isOk && pp !== undefined && pp.length > 0}
61
- <g class="series series_multiline" style={lineStyleStr} title={title}>
61
+ <g class="series series-multiline" style={lineStyleStr} title={title}>
62
62
  {#each pp as p}
63
63
  <polyline class="line" points={p}/>
64
64
  {/each}
@@ -45,7 +45,6 @@
45
45
 
46
46
  markerSymbol = axes.MARKER_SYMBOLS[marker - 1];
47
47
 
48
-
49
48
  x = axes.transform(checkCoords(xValues, 'Points'), $tX.coords);
50
49
  y = axes.transform(checkCoords(yValues, 'Points'), $tY.coords);
51
50
 
@@ -55,25 +54,17 @@
55
54
  }
56
55
  }
57
56
 
58
-
59
57
  // styles for the elements
60
- $: textStyleStr = `dominant-baseline:middle;fill:${faceColor};stroke-width:${borderWidth}px;stroke:${borderColor};
61
- font-size:${markerSize}em; text-anchor:middle;cursor:default;user-select:none;`;
58
+ $: textStyleStr = ['+', '✳', '✕'].includes(markerSymbol) ?
59
+ `fill:${borderColor && borderColor !== 'transparent' ? borderColor : faceColor};stroke-width:0px;font-size:${markerSize}em;` :
60
+ `fill:${faceColor};stroke-width:${borderWidth}px;stroke:${borderColor}; font-size:${markerSize}em;`;
61
+
62
62
  </script>
63
63
 
64
64
  {#if $isOk && x !== undefined && y !== undefined}
65
- <g class="series series_points" title={title} style={textStyleStr} >
65
+ <g class="series series-points" title={title} style={textStyleStr} dominant-baseline="middle" text-anchor="middle">
66
66
  {#each x as v, i}
67
67
  <text x={x[i]} y={y[i]}>{markerSymbol}</text>
68
68
  {/each}
69
69
  </g>
70
70
  {/if}
71
-
72
- <style>
73
- text, text > :global(tspan) {
74
- dominant-baseline: middle;
75
- text-anchor: middle;
76
- cursor: default;
77
- user-select: none;
78
- }
79
- </style>
@@ -22,7 +22,7 @@
22
22
  export let faceColor = Colors.PRIMARY; // color of bar faces (fill)
23
23
  export let borderColor = faceColor; // color of bar borders
24
24
  export let lineWidth = 1; // width (thickness) of bar border lines
25
- export let className = 'series_rect'; // CSS class name of the SVG group
25
+ export let className = 'series-rect'; // CSS class name of the SVG group
26
26
  export let title = ''; // title of the rectangle series (reserved for future use)
27
27
 
28
28
 
@@ -20,7 +20,7 @@
20
20
  export let lineColor = Colors.PRIMARY; // color of segment lines
21
21
  export let lineType = 1; // type of segment lines (1 - solid, 2 - dashed, 3 - dotted, 4 - dashdot)
22
22
  export let lineWidth = 1; // width (thickness) of segment lines
23
- export let className = 'series_seg'; // CSS class name of the SVG group
23
+ export let className = 'series-seg'; // CSS class name of the SVG group
24
24
  export let title = ''; // title of the segment series (reserved for future use)
25
25
 
26
26
 
@@ -22,8 +22,8 @@
22
22
  export let borderColor = 'transparent'; // border colors of label symbols
23
23
  export let borderWidth = 0; // border width of label symbols
24
24
  export let textSize = 1; // size of label symbols
25
- export let className = 'series_text'; // CSS class name for the labels group
26
- export let title = 'series_text'; // title of the labels SVG group
25
+ export let className = 'series-text'; // CSS class name for the labels group
26
+ export let title = 'series-text'; // title of the labels SVG group
27
27
  export let rotateAngle = 0; // angle to rotate labels
28
28
 
29
29
  /*****************************************/
@@ -70,8 +70,7 @@
70
70
  }
71
71
 
72
72
  // styles for the elements
73
- $: textStyleStr = `dominant-baseline:middle;fill:${faceColor};stroke-width:${borderWidth}px;stroke:${borderColor};
74
- font-size:${textSize}em;`;
73
+ $: textStyleStr = `fill:${faceColor};stroke-width:${borderWidth}px;stroke:${borderColor};font-size:${textSize}em;`;
75
74
  </script>
76
75
 
77
76
  {#if $isOk && x !== undefined && y !== undefined}
@@ -96,12 +95,3 @@
96
95
  {/if}
97
96
  </g>
98
97
  {/if}
99
-
100
- <style>
101
- text, text > :global(tspan) {
102
- dominant-baseline: middle;
103
- text-anchor: middle;
104
- cursor: default;
105
- user-select: none;
106
- }
107
- </style>