svelte-plots-basic 2.5.8 → 3.0.1

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.
@@ -1,333 +1,324 @@
1
+ <!--
2
+ @component Parent component for any plot, all other components should be nested inside `<Axes></Axes>`.
3
+
4
+ Main properties:
5
+ - `limX` - array with limits for x-axis (in plot units), default: `[0, 1]`.
6
+ - `limY` - array with limits for y-axis (in plot units), default: `[0, 1]`.
7
+ - `limZ` - array with limits for y-axis (in plot units), default: `[0, 1]`.
8
+ - `theta` - angle (in degrees) for orientation of projection plane, default: `-10`.
9
+ - `phi` - angle (in degrees) for orientation of projection plane, default: `-10`.
10
+ - `zoom` - zooming factor, default: `0.5`.
11
+
12
+ Properties for downloading the plot as PNG or SVG file or copying it to clipboard:
13
+ - `downloadLinks` - how to show download links panel (`'none'` - default, `'hover'`, `'fixed'`).
14
+ - `fileName` - file name for download (without extension), default: `'plot'`.
15
+ - `pngWidth` - width of PNG image in cm, default `8`
16
+ - `pngHeight` - height of PNG image in cm, default `8`.
17
+ - `pngRes` - resolution of PNG image (pixels per inch), default `300`.
18
+ - `clipboardWidth` - width of plot image to copy to clipboard in pixels.
19
+ - `clipboardHeight` - height of plot image to copy to clipboard in pixels.
20
+
21
+ Example:
22
+ ```jsx
23
+ <script>
24
+ import {Axes} from 'svelte-plots-basic/3d';
25
+ </script>
26
+
27
+ <Axes limX={[-10, 10]} limY={[-10, 10]} limZ={[-10, 10]}>
28
+ //all other plotting components are here
29
+ </Axes>
30
+ ```
31
+ -->
1
32
  <script>
2
- /****************************************************
3
- * Axes for 3D plots *
4
- * -------------------- *
5
- * root item for any plot *
6
- *****************************************************/
7
-
8
- import { setContext, onMount, onDestroy } from 'svelte';
9
- import { writable } from 'svelte/store';
10
- import { cbind, matrix, Matrix, Vector } from 'mdatools/arrays';
11
- import { getScale, roundCoords } from '../Utils.js';
12
-
13
-
14
- /*****************************************/
15
- /* Input parameters */
16
- /*****************************************/
17
-
18
- export let limX; // limits for x-axis (in plot units) [min, max]
19
- export let limY; // limits for y-axis (in plot units) [min, max]
20
- export let limZ; // limits for z-axis (in plot units) [min, max]
21
- export let theta = -10;
22
- export let phi = -10;
23
- export let zoom = 0.5;
24
-
25
-
26
- /*****************************************/
27
- /* Constants */
28
- /*****************************************/
29
-
30
- // number of ticks along each axis
31
- const TICK_NUM = {
32
- "small": 5,
33
- "medium": 8,
34
- "large": 12,
35
- "xlarge": 15
36
- };
37
-
38
- // margin between plot series elements and data labels
39
- const LABELS_MARGIN = {
40
- "small": 5,
41
- "medium": 10,
42
- "large": 15,
43
- "xlarge": 20
44
- };
45
-
46
- // line styles for different scales and types
47
- const LINE_STYLES = {
48
- small: ["0", "3,3", "1,1", "3,1"],
49
- medium: ["0", "5,5", "2,2", "5,2"],
50
- large: ["0", "7,7", "3,3", "7,3"],
51
- xlarge: ["0", "9,9", "5,5", "9,5"],
33
+ import { matrix, Matrix } from 'mdatools/arrays';
34
+ import { setContext } from 'svelte';
35
+ import { PLOT_FONT_SIZE } from '../constants.js';
36
+ import { checkArray, getScale, getXAxisCoords3D, getYAxisCoords3D, getZAxisCoords3D,
37
+ downloadPNG, downloadSVG, copyToClipboard} from '../methods.js';
38
+
39
+ import AxisLines from './AxisLines.svelte';
40
+ import AxisTickLabels from './AxisTickLabels.svelte';
41
+ import TextLabels from './TextLabels.svelte';
42
+
43
+ /** @type {Props} */
44
+ let {
45
+ limX = [0, 1],
46
+ limY = [0, 1],
47
+ limZ = [0, 1],
48
+ theta = -10,
49
+ phi = -10,
50
+ zoom = 0.5,
51
+ downloadLinks = 'hover',
52
+ fileName = 'plot',
53
+ pngWidth = 8,
54
+ pngHeight = 8,
55
+ pngRes = 300,
56
+ clipboardWidth = 1200,
57
+ clipboardHeight = 800,
58
+ children
59
+ } = $props();
60
+
61
+
62
+
63
+ /* Copy plot to clipboard as PNG image. */
64
+ async function handleClickCopy(e) {
65
+ copyToClipboard(e.target, plotElement, clipboardWidth, clipboardHeight);
52
66
  }
53
67
 
68
+ /* Download plot as SVG image when user clicks on corresponding button. */
69
+ function handleClickSVG() {
70
+ downloadSVG(plotElement, fileName);
71
+ }
72
+
73
+ /* Download plot as PNG image when user clicks on corresponding button. */
74
+ function handleClickPNG() {
75
+ downloadPNG(plotElement, fileName, pngWidth, pngHeight, pngRes);
76
+ }
77
+
78
+
54
79
  // translation matrix to move points to center of a unit cube
55
80
  const T1 = matrix([
56
- 1, 0, 0, 0,
57
- 0, 1, 0, 0,
58
- 0, 0, 1, 0,
59
- 0.5, 0.5, 0.5, 1
81
+ 1.0, 0.0, 0.0, 0.0,
82
+ 0.0, 1.0, 0.0, 0.0,
83
+ 0.0, 0.0, 1.0, 0.0,
84
+ 0.5, 0.5, 0.5, 1.0
60
85
  ], 4, 4);
61
86
 
62
87
  // inverse translation matrix for T1
63
88
  const T2 = matrix([
64
- 1, 0, 0, 0,
65
- 0, 1, 0, 0,
66
- 0, 0, 1, 0,
67
- -0.5, -0.5, -0.5, 1
89
+ 1.0, 0.0, 0.0, 0.0,
90
+ 0.0, 1.0, 0.0, 0.0,
91
+ 0.0, 0.0, 1.0, 0.0,
92
+ -0.5, -0.5, -0.5, 1.0
68
93
  ], 4, 4);
69
94
 
70
- // constant to make clip path ID unique
71
- const clipPathID = "plottingArea" + Math.round(Math.random() * 10000);
72
-
73
-
74
- /*****************************************/
75
- /* Variable parameters for internal use */
76
- /*****************************************/
77
-
78
- // bindings to plot DOM elements
79
- let plotElement;
80
- let axesElement;
81
- let width, height = 100;
82
-
83
-
84
- /*****************************************/
85
- /* Helper functions */
86
- /*****************************************/
87
-
88
- /**
89
- * Transforms world coordinates to 2D scene pixels by applying the transformation matrix 'tM'.
90
- *
91
- * @param {Matrix} coords - matrix with coordinates [X, Y, Z]
92
- * @param {Matrix} tM - transformation matrix
93
- *
94
- * @returns {Array} array with transformed coordinates as two vectors [x, y].
95
- *
96
- */
97
- const transform = function(coords, tM) {
98
- // const coords2D = cbind(coords, Vector.ones(coords.nrows)).dot(tM);
99
- // const coords2D = tcrossprod(tM, cbind(coords, Vector.ones(coords.nrows))).t();
100
- const coords2D = cbind(coords, Vector.ones(coords.nrows)).dot(tM);
101
- return [coords2D.getcolref(1).map(v => roundCoords(v)), coords2D.getcolref(2).map(v => roundCoords(v))];
102
- }
103
-
104
-
105
- /*****************************************/
106
- /* Storage to share with children */
107
- /*****************************************/
108
-
109
- const tM = writable(Matrix.eye(4)); // transformation matrix
110
- const scale = writable('medium'); // plot scale (small/medium/large)
111
- const xLim = writable(limX); // x-axis limits in 3D (before projection)
112
- const yLim = writable(limY); // y-axis limits in 3D (before projection)
113
- const zLim = writable(limZ); // z-axis limits in 3D (before projection)
114
- const isOk = writable(false); // indicator that axes works fine
115
-
116
95
 
117
- /*****************************************/
118
- /* Axes context */
119
- /*****************************************/
120
-
121
- let context = {
122
-
123
- // methods
124
- transform: transform,
125
-
126
- // state proporties
127
- scale: scale,
128
- isOk: isOk,
129
- xLim: xLim,
130
- yLim: yLim,
131
- zLim: zLim,
132
- tM: tM,
133
-
134
- // constants
135
- LINE_STYLES: LINE_STYLES,
136
- LABELS_MARGIN: LABELS_MARGIN,
137
- TICK_NUM: TICK_NUM
138
- }
139
96
 
140
- setContext('axes', context);
97
+ // pointers to plot DOM element and its width and height
98
+ let plotElement = $state(0);
99
+ let width = $state(100);
100
+ let height = $state(100);
141
101
 
102
+ // scale factor and dependent font size
103
+ const scale = $derived(getScale(width, height));
104
+ const fontSize = $derived(PLOT_FONT_SIZE[scale]);
142
105
 
143
- /*****************************************/
144
- /* Reactive updates of the parameters */
145
- /*****************************************/
146
-
147
- // update limits if necessary
148
- $: xLim.update(v => limX);
149
- $: yLim.update(v => limY);
150
- $: zLim.update(v => limZ);
106
+ // check main plot parameters and set status
107
+ const isOk = $derived(
108
+ checkArray(limX, 2) &&
109
+ checkArray(limY, 2) &&
110
+ checkArray(limZ, 2) &&
111
+ limX[0] < limX[1] &&
112
+ limY[0] < limY[1] &&
113
+ limZ[0] < limZ[1] &&
114
+ width > 50 &&
115
+ height > 50
116
+ );
151
117
 
152
118
  // projection matrix (step 1)
153
- $: P2 = matrix([
119
+ const P2 = $derived(matrix([
154
120
  1, 0, 0, 0,
155
121
  0, Math.cos(theta), Math.sin(theta), 0,
156
122
  0, -Math.sin(theta), Math.cos(theta), 0,
157
123
  0, 0, 0, 1,
158
- ], 4, 4);
124
+ ], 4, 4));
159
125
 
160
126
  // projection matrix (step 2)
161
- $: P1 = matrix([
127
+ const P1 = $derived(matrix([
162
128
  Math.cos(phi), 0, -Math.sin(phi), 0,
163
129
  0, 1, 0, 0,
164
130
  Math.sin(phi), 0, Math.cos(phi), 0,
165
131
  0, 0, 0, 1
166
- ], 4, 4);
132
+ ], 4, 4));
167
133
 
168
134
  // zoom matrix
169
- $: Z = matrix([
135
+ const Z = $derived(matrix([
170
136
  zoom, 0, 0, 0,
171
137
  0, zoom, 0, 0,
172
138
  0, 0, zoom, 0,
173
139
  0, 0, 0, 1
174
- ], 4, 4);
140
+ ], 4, 4));
175
141
 
176
- // matrix for projection and zooming
142
+ // matrix for projection and zooming inside unit cube
177
143
  // we shift [0, 1] cube to center, project, zoom, and then shift back
178
- $: P = T1.dot(Z.dot(P2.dot(P1.dot(T2))))
179
-
180
- // update transformation matrix if angles of the norm vectors are changed
181
- $: {
182
- if ($isOk) {
183
- // translate
184
- const TT1 = matrix([
185
- 1, 0, 0, 0,
186
- 0, 1, 0, 0,
187
- 0, 0, 1, 0,
188
- -$xLim[0], -$yLim[0], -$zLim[0], 1
189
- ], 4, 4);
190
-
191
- // scale the whole cube to [0, 1] limits
192
- const SS1 = matrix([
193
- 1 / ($xLim[1] - $xLim[0]) , 0, 0, 0,
194
- 0, 1 / ($yLim[1] - $yLim[0]), 0, 0,
195
- 0, 0, 1 / ($zLim[1] - $zLim[0]), 0,
196
- 0, 0, 0, 1
197
- ], 4, 4);
198
-
199
- // scale to screen coordinates
200
- const SS2 = matrix([
201
- width, 0, 0, 0,
202
- 0, height, 0, 0,
203
- 0, 0, 1, 0,
204
- 0, 0, 0, 1
205
- ], 4, 4);
206
-
207
- tM.update(x => SS2.dot(P.dot(SS1.dot(TT1))).t());
208
- } else {
209
- tM.update(x => Matrix.eye(4));
210
- }
211
- }
212
-
144
+ const P = $derived(T1.dot(Z.dot(P2.dot(P1.dot(T2)))));
213
145
 
214
- /*****************************************/
215
- /* Events observers */
216
- /*****************************************/
217
-
218
- // observer for the plot area size — to update scale
219
- const ro1 = new ResizeObserver(entries => {
220
- for (let entry of entries) {
221
- const pcr = plotElement.getBoundingClientRect();
222
- scale.update(x => getScale(pcr.width, pcr.height));
223
- }
224
- });
225
-
226
- // observer for the axes area size - to update size of axes
227
- const ro2 = new ResizeObserver(entries => {
228
- for (let entry of entries) {
229
- const acr = axesElement.getBoundingClientRect();
230
- width = acr.width;
231
- height = acr.height;
232
- }
233
- });
146
+ // translation in world coordinates to origin (0, 0, 0)
147
+ const TT1 = $derived(isOk ? matrix([
148
+ 1, 0, 0, 0,
149
+ 0, 1, 0, 0,
150
+ 0, 0, 1, 0,
151
+ -limX[0], -limY[0], -limZ[0], 1
152
+ ], 4, 4) : null);
153
+
154
+ // scale world coordinates to unit cube
155
+ const SS1 = $derived(isOk ? matrix([
156
+ 1 / (limX[1] - limX[0]) , 0, 0, 0,
157
+ 0, 1 / (limY[1] - limY[0]), 0, 0,
158
+ 0, 0, 1 / (limZ[1] - limZ[0]), 0,
159
+ 0, 0, 0, 1
160
+ ], 4, 4) : null);
234
161
 
235
- onMount(() => {
236
- ro1.observe(plotElement);
237
- ro2.observe(plotElement);
162
+ // scale unit cube to screen coordinates
163
+ const SS2 = $derived(isOk ? matrix([
164
+ width, 0, 0, 0,
165
+ 0, height, 0, 0,
166
+ 0, 0, 1, 0,
167
+ 0, 0, 0, 1
168
+ ], 4, 4) : null);
169
+
170
+ // main transformation matrix
171
+ const tM = $derived(SS2 && P && SS1 && TT1 ?
172
+ SS2.dot(P.dot(SS1.dot(TT1))).t() :
173
+ Matrix.eye(4)
174
+ );
175
+
176
+ // containers for axis properties
177
+ let xaxis = $state({show: false, label: ""});
178
+ let yaxis = $state({show: false, label: ""});
179
+ let zaxis = $state({show: false, label: ""});
180
+
181
+ // axes context to share with children
182
+ setContext('axes', {
183
+ scale: () => scale,
184
+ tM: () => tM,
185
+ setXAxis: (v) => xaxis = v,
186
+ setYAxis: (v) => yaxis = v,
187
+ setZAxis: (v) => zaxis = v,
238
188
  });
239
189
 
240
- onDestroy(() => {
241
- ro1.unobserve(plotElement);
242
- ro2.unobserve(plotElement);
243
- })
244
-
245
- // check if everything is ok regarding the axis limits
246
- $: isOk.update(v =>
247
- !$yLim.some(v => v === undefined) &&
248
- !$xLim.some(v => v === undefined) &&
249
- !$zLim.some(v => v === undefined) &&
250
- !$yLim.some(v => isNaN(v)) &&
251
- !$xLim.some(v => isNaN(v)) &&
252
- !$zLim.some(v => isNaN(v))
253
- )
190
+ // compute screen coordinates for the axis
191
+ const xaxisCoords = $derived(xaxis && xaxis.show ? getXAxisCoords3D(xaxis, limX, limY, limZ, scale) : null);
192
+ const yaxisCoords = $derived(yaxis && yaxis.show ? getYAxisCoords3D(yaxis, limX, limY, limZ, scale) : null);
193
+ const zaxisCoords = $derived(zaxis && zaxis.show ? getZAxisCoords3D(zaxis, limX, limY, limZ, scale) : null);
254
194
  </script>
255
195
 
256
-
257
- <div class="plot {'plot_' + $scale}" bind:this={plotElement} class:plot_error={!$isOk}>
258
-
259
- <!-- axes (coordinate system) -->
260
- <div class="axes-wrapper" bind:this={axesElement} >
261
- <slot name="title"></slot>
262
-
263
- <svg vector-effect="non-scaling-stroke" preserveAspectRatio="none" class="axes">
264
-
265
- <!-- axis and box -->
266
- <slot name="xaxis"></slot>
267
- <slot name="yaxis"></slot>
268
- <slot name="zaxis"></slot>
269
-
270
- <!-- main plot content -->
271
- <g class="axes-content" >
272
- <slot></slot>
273
- </g>
274
-
275
- <!-- axis and box -->
276
- <slot name="box"></slot>
277
- </svg>
278
-
279
- {#if !$isOk}
280
- <p class="message_error">
281
- Axes component was not properly initialized. <br />
282
- Add plot series (check that coordinates are numeric) or define axes limits manually.
283
- </p>
284
- {/if}
196
+ <!-- snipped for axis -->
197
+ {#snippet axis(axis, coords)}
198
+
199
+ <g class="axis" >
200
+
201
+ <!-- grid -->
202
+ {#if axis.showGrid }
203
+ <AxisLines lineCoords={coords.grid1} lineColor={axis.gridColor} lineType={3} />
204
+ <AxisLines lineCoords={coords.grid2} lineColor={axis.gridColor} lineType={3} />
205
+ {/if}
206
+
207
+ <!-- main axis line -->
208
+ <AxisLines lineCoords={coords.axisLine} lineColor={axis.lineColor} lineType={1} />
209
+
210
+ <!-- ticks-->
211
+ <AxisLines lineCoords={[coords.tickCoords[0], coords.tickCoords[1]]} lineColor={axis.lineColor} lineType={1} />
212
+ <AxisLines lineCoords={[coords.tickCoords[0], coords.tickCoords[2]]} lineColor={axis.lineColor} lineType={1} />
213
+
214
+ <!-- labels -->
215
+ {#if coords.tickCoords }
216
+ <AxisTickLabels tickCoords={coords.tickCoords[4]} textColor={axis.textColor} tickLabels={coords.tickLabels} />
217
+ {/if}
218
+
219
+ <!-- title -->
220
+ {#if coords.titleCoords && axis.label !== ''}
221
+ <TextLabels
222
+ xValues={coords.titleCoords[0]} yValues={coords.titleCoords[1]} zValues={coords.titleCoords[2]}
223
+ faceColor={axis.textColor}
224
+ labels={axis.label}
225
+ textSize={1.0}
226
+ fontWeight="bold"
227
+ />
228
+ {/if}
229
+ </g>
230
+ {/snippet}
231
+
232
+ <div class="plot-container show-download-links-{downloadLinks}" class:plot_error={!isOk}>
233
+ <svg class="plot" bind:this={plotElement} bind:clientWidth={width} bind:clientHeight={height}
234
+ xmlns="http://www.w3.org/2000/svg" style="font-size:{fontSize}px">
235
+ <style>
236
+ .plot {
237
+ font-family: Arial, Helvetica, sans-serif;
238
+ }
239
+
240
+ polyline, line {
241
+ vector-effect: "non-scaling-stroke";
242
+ }
243
+
244
+ .plot-labels text {
245
+ font-weight:bold;
246
+ text-anchor:middle;
247
+ }
248
+
249
+ .tick-labels text,
250
+ .tick-labels tspan,
251
+ .series-text text,
252
+ .series-text tspan,
253
+ .series-points text,
254
+ .series-points tspan {
255
+ dominant-baseline: middle;
256
+ cursor: default;
257
+ }
258
+
259
+ .series-text text,
260
+ .series-text tspan {
261
+ text-anchor: middle;
262
+ }
263
+
264
+ .tick-labels text,
265
+ .tick-labels tspan {
266
+ font-size:1em;
267
+ }
268
+
269
+ text, tspan {
270
+ user-select: none;
271
+ -webkit-user-select: none;
272
+ }
273
+ </style>
274
+
275
+ <!-- axis -->
276
+ {#if xaxis && xaxis.show} {@render axis(xaxis, xaxisCoords)} {/if}
277
+ {#if yaxis && yaxis.show} {@render axis(yaxis, yaxisCoords)} {/if}
278
+ {#if zaxis && zaxis.show} {@render axis(zaxis, zaxisCoords)} {/if}
279
+
280
+ <!-- main plot content -->
281
+ <g class="axes-content" >
282
+ {@render children?.()}
283
+ </g>
284
+ </svg>
285
+
286
+ {#if !isOk}
287
+ <p class="message_error">
288
+ Axes component was not properly initialized. <br />
289
+ Add plot series (check that coordinates are numeric) or define axes limits manually.
290
+ </p>
291
+ {:else}
292
+ <div class="download-links">
293
+ <button onclick={handleClickSVG} onkeydown={handleClickSVG}>⇩ svg</button>
294
+ <button onclick={handleClickPNG} onkeydown={handleClickPNG}>⇩ png</button>
295
+ <button onclick={handleClickCopy} onkeydown={handleClickCopy}>⧉ copy</button>
285
296
  </div>
286
-
287
-
297
+ {/if}
288
298
  </div>
289
299
 
290
300
  <style>
291
301
 
292
- /* Plot (main container) */
293
- .plot {
302
+ /* Plot container */
303
+ .plot-container {
294
304
  font-family: Arial, Helvetica, sans-serif;
295
-
296
- display: grid;
297
- grid-template-columns: min-content auto;
298
- grid-template-rows: min-content auto min-content;
299
- grid-template-areas:
300
- ". title"
301
- "ylab axes"
302
- ". xlab";
303
-
304
305
  box-sizing: border-box;
306
+ position: relative;
305
307
  min-width: 100px;
306
308
  min-height: 50px;
307
309
  width: 100%;
308
310
  height: 100%;
309
311
  padding: 0;
310
312
  margin: 0;
311
- background-color: white;
312
- }
313
-
314
- :global(.plot_small) {
315
- font-size: 11px;
316
- }
317
-
318
- :global(.plot_medium) {
319
- font-size: 13px;
313
+ overflow: hidden;
314
+ background: #fff;
320
315
  }
321
316
 
322
- :global(.plot_large) {
323
- font-size: 16px;
324
- }
325
-
326
- :global(.plot_error) {
317
+ .plot_error {
327
318
  display: flex;
328
319
  }
329
320
 
330
- :global(.plot_error > .axes-wrapper) {
321
+ .plot_error > .plot {
331
322
  display: none;
332
323
  }
333
324
 
@@ -339,107 +330,74 @@
339
330
  width: 100%;
340
331
  }
341
332
 
342
- /* Axes (coordinate system) */
343
- .axes-wrapper {
344
- grid-area: axes;
345
- position:relative;
346
- box-sizing: border-box;
347
- display: flex;
348
- width: 100%;
349
- height: 100%;
350
- padding: 0;
351
- margin: 0;
352
- }
353
-
354
- .axes {
333
+ .plot {
355
334
  display: block;
356
335
  box-sizing: border-box;
357
336
  position:absolute;
358
- user-select: none;
359
- cursor: default;
360
337
  padding: 0;
361
338
  margin: 0;
362
- height: 100%;
339
+ left: 0;
340
+ top: 0;
341
+ bottom: 0;
342
+ right: 0;
363
343
  width: 100%;
364
- max-height: 100%;
365
- max-width: 100%;
366
- min-height: 100%;
367
- min-width: 100%;
344
+ height: 100%;
368
345
  }
369
346
 
370
- .axes :global(text) {
371
- -webkit-user-select: none;
372
- user-select: none;
373
- cursor: default;
374
- }
375
347
 
376
- :global(.axes-content) {
377
- padding: 0;
378
- margin: 0;
379
- height: 100%;
380
- width: 100%;
381
- max-height: 100%;
382
- max-width: 100%;
383
- min-height: 100%;
384
- min-width: 100%;
348
+ .show-download-links-none > .download-links{
349
+ display: none;
350
+ }
385
351
 
352
+ .show-download-links-hover:hover > .download-links {
353
+ bottom: 0;
386
354
  }
387
355
 
388
- :global(.axes__xlabel) {
389
- grid-area: xlab;
390
- font-size: 1.0em;
391
- font-weight: 600;
392
- padding: 0.25em;
393
- text-align: center;
356
+ .show-download-links-fixed > .download-links{
357
+ bottom: 0;
394
358
  }
395
359
 
360
+ .download-links {
361
+ box-sizing: border-box;
362
+ position: absolute;
363
+ font-size: 0.8em;
364
+ bottom: -50%;
365
+ right: 0;
366
+ z-index: 10;
367
+ padding: 0.5em 1em 0.5em 1em;
368
+ background: #fefefe;
369
+ border-radius: 0.5em;
370
+ transition: bottom 0.35s ease;
371
+ box-shadow: 0 0 0.75em #00000060;
372
+ margin: 0.6em;
373
+ box-sizing: border-box;
396
374
 
397
- :global(.axes__ylabel) {
398
- grid-area: ylab;
399
- font-size: 1.0em;
400
- font-weight: 600;
401
- padding: 0.25em;
402
- text-align: center;
403
- vertical-align: middle;
404
375
  display: flex;
376
+ align-items: center;
377
+ justify-content: center;
405
378
  }
406
379
 
407
- :global(.axes__ylabel > span) {
408
- width: 1.5em;
409
- line-height: 1.5em;
410
- display: inline-block;
411
- writing-mode: vertical-rl;
412
- transform: rotate(180deg);
413
- }
414
-
415
-
416
- :global(.axes__title) {
417
- background: transparent;
418
- grid-area: title;
419
-
420
- font-size: 1.3em;
421
- font-weight: bold;
422
- line-height: 1.2em;
423
- padding: 0.5em 0;
424
- text-align: center;
380
+ .download-links > button{
381
+ box-sizing: border-box;
382
+ color: #606060;
383
+ border: none;
384
+ box-shadow: none;
385
+ background: #fafafa;
386
+ border-radius: 0.35em;
387
+ padding: 0.25em 0.5em;
425
388
  }
426
389
 
427
- /* Axis */
428
- :global(.axis-labels) {
429
- fill: #303030;
430
- font-size: 0.95em;
390
+ .download-links > button:hover{
391
+ background: #443333;
392
+ color: #fafafa;
431
393
  }
432
394
 
433
- /* Data labels */
434
- :global(.labels) {
435
- fill: #606060;
436
- font-size: 0.90em;
395
+ .download-links > :global(button.error){
396
+ background: #ee4433;
437
397
  }
438
398
 
439
- :global(.labels) {
440
- visibility: hidden;
441
- transition:visibility 0.25s linear, opacity 0.25s linear;
442
- opacity: 0;
399
+ .download-links > :global(button.copied){
400
+ background: #4488ee;
443
401
  }
444
402
 
445
403
  </style>