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/README.md +633 -20
- package/package.json +1 -1
- package/src/2d/Axes.svelte +114 -15
- package/src/2d/AxisTickLabels.svelte +2 -10
- package/src/2d/Bars.svelte +1 -1
- package/src/2d/Box.svelte +1 -3
- package/src/2d/Heatmap.svelte +2 -2
- package/src/2d/Legend.svelte +12 -20
- package/src/2d/Lines.svelte +1 -1
- package/src/2d/Multilines.svelte +1 -1
- package/src/2d/Points.svelte +5 -14
- package/src/2d/Rectangles.svelte +1 -1
- package/src/2d/Segments.svelte +1 -1
- package/src/2d/TextLabels.svelte +3 -13
- package/src/Utils.js +245 -1
- package/src/2d/ColorLegend.svelte +0 -36
package/src/Utils.js
CHANGED
|
@@ -240,4 +240,248 @@ export function getScale(width, height) {
|
|
|
240
240
|
if (height < 600.2 || width < 600.2) return "medium";
|
|
241
241
|
if (height < 850.2 || width < 850.2) return "large";
|
|
242
242
|
return "xlarge";
|
|
243
|
-
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
/*************************************************************/
|
|
247
|
+
/* Functions needed to adjust DPI of resulting PNG image */
|
|
248
|
+
/* taken from: https://github.com/shutterstock/changeDPI */
|
|
249
|
+
/* and modified */
|
|
250
|
+
/*************************************************************/
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Create table for PNG CRC calculation
|
|
254
|
+
*/
|
|
255
|
+
function createPngDataTable() {
|
|
256
|
+
const crcTable = new Int32Array(256);
|
|
257
|
+
for (let n = 0; n < 256; n++) {
|
|
258
|
+
let c = n;
|
|
259
|
+
for (let k = 0; k < 8; k++) {
|
|
260
|
+
c = (c & 1) ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
|
261
|
+
}
|
|
262
|
+
crcTable[n] = c;
|
|
263
|
+
}
|
|
264
|
+
return crcTable;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Calculate CRC for PNG image
|
|
269
|
+
* @param buf image data
|
|
270
|
+
*/
|
|
271
|
+
function calcCrc(buf) {
|
|
272
|
+
let c = -1;
|
|
273
|
+
let pngDataTable = createPngDataTable();
|
|
274
|
+
for (let n = 0; n < buf.length; n++) {
|
|
275
|
+
c = pngDataTable[(c ^ buf[n]) & 0xFF] ^ (c >>> 8);
|
|
276
|
+
}
|
|
277
|
+
return c ^ -1;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Change DPI of PNG image
|
|
282
|
+
* @param dataArray image data
|
|
283
|
+
* @param dpi new DPI
|
|
284
|
+
*/
|
|
285
|
+
function changeDpiOnArray(dataArray, dpi) {
|
|
286
|
+
|
|
287
|
+
const physChunk = new Uint8Array(13);
|
|
288
|
+
// chunk header pHYs
|
|
289
|
+
// 9 bytes of data
|
|
290
|
+
// 4 bytes of crc
|
|
291
|
+
// this multiplication is because the standard is dpi per meter.
|
|
292
|
+
dpi *= 39.3701;
|
|
293
|
+
physChunk[0] = 'p'.charCodeAt(0);
|
|
294
|
+
physChunk[1] = 'H'.charCodeAt(0);
|
|
295
|
+
physChunk[2] = 'Y'.charCodeAt(0);
|
|
296
|
+
physChunk[3] = 's'.charCodeAt(0);
|
|
297
|
+
physChunk[4] = dpi >>> 24; // dpiX byte 3
|
|
298
|
+
physChunk[5] = dpi >>> 16; // dpiX byte 2
|
|
299
|
+
physChunk[6] = dpi >>> 8; // dpiX byte 1
|
|
300
|
+
physChunk[7] = dpi & 0xff; // dpiX byte 0
|
|
301
|
+
physChunk[8] = physChunk[4]; // dpiY byte 3
|
|
302
|
+
physChunk[9] = physChunk[5]; // dpiY byte 2
|
|
303
|
+
physChunk[10] = physChunk[6]; // dpiY byte 1
|
|
304
|
+
physChunk[11] = physChunk[7]; // dpiY byte 0
|
|
305
|
+
physChunk[12] = 1; // dot per meter....
|
|
306
|
+
|
|
307
|
+
// compute and add CRC for physChunk
|
|
308
|
+
const crc = calcCrc(physChunk);
|
|
309
|
+
const crcChunk = new Uint8Array(4);
|
|
310
|
+
crcChunk[0] = crc >>> 24;
|
|
311
|
+
crcChunk[1] = crc >>> 16;
|
|
312
|
+
crcChunk[2] = crc >>> 8;
|
|
313
|
+
crcChunk[3] = crc & 0xff;
|
|
314
|
+
|
|
315
|
+
// chunk structur 4 bytes for length is 9
|
|
316
|
+
const chunkLength = new Uint8Array(4);
|
|
317
|
+
chunkLength[0] = 0;
|
|
318
|
+
chunkLength[1] = 0;
|
|
319
|
+
chunkLength[2] = 0;
|
|
320
|
+
chunkLength[3] = 9;
|
|
321
|
+
|
|
322
|
+
const finalHeader = new Uint8Array(54);
|
|
323
|
+
finalHeader.set(dataArray, 0);
|
|
324
|
+
finalHeader.set(chunkLength, 33);
|
|
325
|
+
finalHeader.set(physChunk, 37);
|
|
326
|
+
finalHeader.set(crcChunk, 50);
|
|
327
|
+
return finalHeader;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Change DPI of PNG image
|
|
332
|
+
* @param base64Image image data
|
|
333
|
+
* @param dpi new DPI
|
|
334
|
+
*/
|
|
335
|
+
function changeDPI(base64Image, dpi) {
|
|
336
|
+
|
|
337
|
+
const dataSplitted = base64Image.split(',');
|
|
338
|
+
const format = dataSplitted[0];
|
|
339
|
+
const body = dataSplitted[1];
|
|
340
|
+
|
|
341
|
+
// here we assume there is pHYS chunk in the header
|
|
342
|
+
const headerLength = 33 / 3 * 4;
|
|
343
|
+
|
|
344
|
+
const stringHeader = body.substring(0, headerLength);
|
|
345
|
+
const restOfData = body.substring(headerLength);
|
|
346
|
+
const headerBytes = atob(stringHeader);
|
|
347
|
+
const dataArray = new Uint8Array(headerBytes.length);
|
|
348
|
+
for (let i = 0; i < dataArray.length; i++) {
|
|
349
|
+
dataArray[i] = headerBytes.charCodeAt(i);
|
|
350
|
+
}
|
|
351
|
+
const finalArray = changeDpiOnArray(dataArray, dpi, 'image/png');
|
|
352
|
+
const base64Header = btoa(String.fromCharCode(...finalArray));
|
|
353
|
+
return [format, ',', base64Header, restOfData].join('');
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Downloads plot (or any other SVG image) as a PNG file.
|
|
359
|
+
*
|
|
360
|
+
* @param {SVGElement} svg - root SVG element of a plot
|
|
361
|
+
* @param {string} fileName - filename to save with (without extension)
|
|
362
|
+
* @param {number} width - desired image width in cm
|
|
363
|
+
* @param {number} height - desired image height in cm
|
|
364
|
+
* @param {number} res - resolution (pixels per inch)
|
|
365
|
+
*
|
|
366
|
+
*/
|
|
367
|
+
export function downloadPNG (svg, fileName, width, height, res) {
|
|
368
|
+
|
|
369
|
+
if (!width) {
|
|
370
|
+
width = 10;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (!height) {
|
|
374
|
+
height = 10;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (!res) {
|
|
378
|
+
res = 300;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (width < 1 || width > 30) {
|
|
382
|
+
throw Error('Parameter "width" must be between 1 and 30 (cm).');
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (height < 1 || height > 30) {
|
|
386
|
+
throw Error('Parameter "height" must be between 1 and 30 (cm).');
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (res < 50 || res > 1200) {
|
|
390
|
+
throw Error('Parameter "res" must be between 50 and 1200 (ppi).');
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (!svg || !(svg instanceof SVGElement)) {
|
|
394
|
+
throw Error('Parameter "svg" is not an instance of SVGElement.');
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (!fileName || fileName.trim() === '') {
|
|
398
|
+
fileName = 'plot';
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// recalculate width and height to pixels
|
|
402
|
+
width = width / 2.54 * res;
|
|
403
|
+
height = height / 2.54 * res;
|
|
404
|
+
|
|
405
|
+
setTimeout(() => {
|
|
406
|
+
|
|
407
|
+
// compute canvas size based on SVG size and desired PNG WIDTH in pixels
|
|
408
|
+
const svgHeight = svg.clientHeight;
|
|
409
|
+
const svgWidth = svg.clientWidth;
|
|
410
|
+
const scaleFactor = Math.max(width / svgWidth, height / svgHeight);
|
|
411
|
+
|
|
412
|
+
// create a new canvas element
|
|
413
|
+
const canvas = document.createElement('canvas');
|
|
414
|
+
const context = canvas.getContext('2d');
|
|
415
|
+
|
|
416
|
+
// set the canvas size to match the SVG element
|
|
417
|
+
canvas.width = svgWidth * scaleFactor;
|
|
418
|
+
canvas.height = svgHeight * scaleFactor;
|
|
419
|
+
|
|
420
|
+
console.log([svgHeight, svgWidth, height, width])
|
|
421
|
+
svg.setAttribute('width', canvas.width);
|
|
422
|
+
svg.setAttribute('height', canvas.height);
|
|
423
|
+
|
|
424
|
+
// draw a white background
|
|
425
|
+
context.fillStyle = 'white';
|
|
426
|
+
context.scale(scaleFactor, scaleFactor)
|
|
427
|
+
context.fillRect(0, 0, canvas.width, canvas.height);
|
|
428
|
+
|
|
429
|
+
// draw the SVG onto the canvas
|
|
430
|
+
const svgXml = new XMLSerializer().serializeToString(svg);
|
|
431
|
+
const blob = new Blob([svgXml], { type: 'image/svg+xml;charset=utf-8' });
|
|
432
|
+
const url = URL.createObjectURL(blob);
|
|
433
|
+
|
|
434
|
+
const img = new Image();
|
|
435
|
+
|
|
436
|
+
img.onload = function () {
|
|
437
|
+
|
|
438
|
+
// draw the image
|
|
439
|
+
context.drawImage(img, 0, 0, canvas.width, canvas.height);
|
|
440
|
+
|
|
441
|
+
// create a data URL for the canvas and adjust its DPI
|
|
442
|
+
const dataURL = canvas.toDataURL('image/png');
|
|
443
|
+
const dataURL2 = changeDPI(dataURL, res);
|
|
444
|
+
|
|
445
|
+
// create a temporary link and trigger the download
|
|
446
|
+
const downloadLink = document.createElement('a');
|
|
447
|
+
downloadLink.href = dataURL2;
|
|
448
|
+
downloadLink.download = `${fileName}.png`;
|
|
449
|
+
downloadLink.click();
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
// set the image source
|
|
453
|
+
img.src = url;
|
|
454
|
+
}, 20);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Downloads plot (or any other SVG image) as an SVG file.
|
|
460
|
+
*
|
|
461
|
+
* @param {SVGElement} svg - root SVG element of a plot
|
|
462
|
+
* @param {string} fileName - filename to save with (without extension)
|
|
463
|
+
*/
|
|
464
|
+
export function downloadSVG(svg, fileName) {
|
|
465
|
+
|
|
466
|
+
if (!svg || !(svg instanceof SVGElement)) {
|
|
467
|
+
throw Error('Variable "svg" is not an instance of SVGElement.');
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (!fileName || fileName.trim() === "") {
|
|
471
|
+
fileName = "plot";
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const svgHeight = svg.clientHeight;
|
|
475
|
+
const svgWidth = svg.clientWidth;
|
|
476
|
+
svg.setAttribute('width', svgWidth + 'px');
|
|
477
|
+
svg.setAttribute('height', svgHeight + 'px');
|
|
478
|
+
svg.setAttribute('viewBox', `0 0 ${svgWidth} ${svgHeight}`);
|
|
479
|
+
|
|
480
|
+
const blob = new Blob([svg.outerHTML], { type: 'image/svg+xml' });
|
|
481
|
+
const url = window.URL.createObjectURL(blob)
|
|
482
|
+
const a = document.createElement('a')
|
|
483
|
+
a.setAttribute('href', url)
|
|
484
|
+
a.setAttribute('download', `${fileName}.svg`);
|
|
485
|
+
a.click()
|
|
486
|
+
}
|
|
487
|
+
|
|
@@ -1,36 +0,0 @@
|
|
|
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
|
-
|