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/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
-