Skip to content

Commit 62048e3

Browse files
committed
Add legend download buttons, refactor download code
1 parent e1840cf commit 62048e3

File tree

6 files changed

+247
-94
lines changed

6 files changed

+247
-94
lines changed

examples-src/App.vue

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@
1515
<PlotContainer
1616
:pWidth="500"
1717
:pHeight="dynamicPlotHeight"
18-
:pMarginTop="120"
19-
:pMarginLeft="120"
20-
:pMarginRight="120"
18+
:pMarginTop="100"
19+
:pMarginLeft="100"
20+
:pMarginRight="100"
2121
:pMarginBottom="100"
2222
:showDownloadButton="true"
2323
:downloadButtonOffsetX="0"
24-
:downloadButtonOffsetY="0"
24+
:downloadButtonOffsetY="60"
2525
:showResizeButton="true"
2626
>
2727
<Axis
@@ -119,6 +119,7 @@
119119
:lWidth="250"
120120
:getScale="getScale"
121121
:getStack="getStack"
122+
:showDownloadButton="true"
122123
/>
123124

124125
<h3>&lt;ContinuousLegend/&gt;</h3>
@@ -127,6 +128,7 @@
127128
:lWidth="250"
128129
:getScale="getScale"
129130
:getStack="getStack"
131+
:showDownloadButton="true"
130132
/>
131133

132134
<h3>&lt;BarPlot/&gt;</h3>
@@ -1664,5 +1666,4 @@ a {
16641666
margin-bottom: 5px;
16651667
}
16661668
1667-
16681669
</style>

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "vueplotlib",
3-
"version": "1.11.4",
3+
"version": "1.11.5",
44
"private": false,
55
"scripts": {
66
"serve": "vue-cli-service serve --open ./examples-src/index.js",

src/components/PlotContainer.vue

Lines changed: 8 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
<script>
2-
import { DOWNLOAD_PATH } from './../icons.js';
32
import { create as d3_create, event as d3_event } from 'd3';
43
import { select as d3_select } from 'd3-selection';
54
import { drag as d3_drag } from 'd3-drag';
65
6+
import { DOWNLOAD_PATH } from './../icons.js';
7+
import { downloadSvg } from './../helpers.js';
8+
79
810
/**
911
* Function that takes in array of VNodes and adds props from a provided props object.
@@ -29,33 +31,6 @@ const addProp = function(slotArray, newProps) {
2931
return [];
3032
}
3133
32-
/**
33-
* Given an SVG DOM node, return the SVG contents as a data URI that can be saved to a file.
34-
* @private
35-
* @param {any} svg The SVG node.
36-
* @returns {string}
37-
*/
38-
const svgToUri = function(svg) {
39-
// Reference: https://stackoverflow.com/a/23218877
40-
const serializer = new XMLSerializer();
41-
var source = serializer.serializeToString(svg);
42-
43-
// Add namespace.
44-
if(!source.match(/^<svg[^>]+xmlns="http\:\/\/www\.w3\.org\/2000\/svg"/)){
45-
source = source.replace(/^<svg/, '<svg xmlns="http://www.w3.org/2000/svg"');
46-
}
47-
if(!source.match(/^<svg[^>]+"http\:\/\/www\.w3\.org\/1999\/xlink"/)){
48-
source = source.replace(/^<svg/, '<svg xmlns:xlink="http://www.w3.org/1999/xlink"');
49-
}
50-
51-
// Add xml declaration.
52-
source = '<?xml version="1.0" standalone="no"?>\r\n' + source;
53-
54-
// Convert svg source to URI.
55-
//return "data:image/svg+xml;charset=utf-8," + encodeURIComponent(source);
56-
return source;
57-
};
58-
5934
6035
/**
6136
* This component is a container for axis and plot components,
@@ -310,14 +285,8 @@ export default {
310285
},
311286
methods: {
312287
downloadViaButton() {
313-
const blob = this.download();
314-
const url = URL.createObjectURL(blob);
315-
const downloadAnchorNode = document.createElement('a');
316-
downloadAnchorNode.setAttribute("href", url);
317-
downloadAnchorNode.setAttribute("download", this.downloadName + ".svg");
318-
document.body.appendChild(downloadAnchorNode); // required for firefox
319-
downloadAnchorNode.click();
320-
downloadAnchorNode.remove();
288+
const svg = this.download();
289+
downloadSvg(svg, this.downloadName);
321290
},
322291
initResizeButton() {
323292
if(this.showResizeButton) {
@@ -347,7 +316,8 @@ export default {
347316
348317
const svg = d3_create("svg")
349318
.attr("width", this.fullWidth)
350-
.attr("height", this.fullHeight);
319+
.attr("height", this.fullHeight)
320+
.attr("viewBox", `0 0 ${this.fullWidth} ${this.fullHeight}`);
351321
352322
const defs = svg
353323
.append("defs");
@@ -443,11 +413,7 @@ export default {
443413
renderAxisToContext("axisRight");
444414
renderAxisToContext("axisBottom");
445415
446-
const svgContent = svgToUri(svg.node());
447-
448-
const blob = new Blob([svgContent], {'type': 'image/svg+xml'});
449-
450-
return blob;
416+
return svg;
451417
}
452418
}
453419
}

src/components/legends/CategoricalLegend.vue

Lines changed: 86 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
<script>
2929
import { scaleBand as d3_scaleBand } from 'd3-scale';
3030
import { select as d3_select } from 'd3-selection';
31+
import { create as d3_create } from 'd3';
3132
3233
3334
import CategoricalScale from './../../scales/CategoricalScale.js';
@@ -37,9 +38,11 @@ import HistoryStack from './../../history/HistoryStack.js';
3738
import ColorScalePicker from './../modals/ColorScalePicker.vue';
3839
import ColorPicker from './../modals/ColorPicker.vue';
3940
40-
import { COLOR_PICKER_PATH, EYE_PATH, EYE_DISABLED_PATH, PAINT_BUCKET_PATH } from './../../icons.js';
41+
import { COLOR_PICKER_PATH, EYE_PATH, EYE_DISABLED_PATH, PAINT_BUCKET_PATH, DOWNLOAD_PATH } from './../../icons.js';
4142
import { EVENT_TYPES, EVENT_SUBTYPES } from '../../history/base-events.js';
4243
44+
import { downloadSvg } from './../../helpers.js';
45+
4346
const STYLES = Object.freeze({ "BAR": 1, "DOT": 2, "LINE": 3, "SHAPE": 4 });
4447
4548
let uuid = 0;
@@ -89,6 +92,14 @@ export default {
8992
},
9093
'clickHandler': {
9194
type: Function
95+
},
96+
'showDownloadButton': {
97+
type: Boolean,
98+
default: false
99+
},
100+
'downloadName': {
101+
type: String,
102+
default: 'legend'
92103
}
93104
},
94105
data() {
@@ -193,7 +204,7 @@ export default {
193204
[scaleKey]
194205
));
195206
},
196-
drawLegend() {
207+
drawLegend(d3Node) {
197208
const vm = this;
198209
vm.removeLegend();
199210
@@ -204,43 +215,38 @@ export default {
204215
const textOffset = 30;
205216
const marginX = 4;
206217
const marginY = 2;
218+
const buttonWidth = 16;
207219
208220
vm.lHeight = vm.lItemHeight * varScale.domain.length + titleHeight;
209221
210222
/*
211223
* Create the SVG elements
212224
*/
213-
214-
const container = d3_select(vm.legendSelector)
215-
.append("svg")
216-
.attr("width", vm.computedWidth)
217-
.attr("height", vm.computedHeight);
225+
let container;
226+
if(d3Node) {
227+
container = d3Node;
228+
} else {
229+
container = d3_select(vm.legendSelector)
230+
.append("svg")
231+
.attr("width", vm.computedWidth)
232+
.attr("height", vm.computedHeight);
233+
}
218234
219235
const legend = container.append("g")
220236
.attr("class", "legend")
221237
.attr("transform", "translate(" + vm.computedTranslateX + "," + vm.computedTranslateY + ")");
222-
223-
224238
225239
const title = legend.append("g")
226240
.attr("width", vm.lWidth);
227241
228242
const titleText = title.append("text")
229243
.style("text-anchor", "start")
244+
.style("font-family", "Avenir")
230245
.text(varScale.name);
231246
const titleTextBbox = titleText.node().getBBox();
232247
titleText.attr("transform", "translate(" + 0 + "," + titleTextBbox.height + ")");
233248
234-
title.append("path")
235-
.attr("d", PAINT_BUCKET_PATH)
236-
.attr("width", 20)
237-
.attr("height", 20)
238-
.attr("transform", "translate(" + (vm.lWidth - 1.5*marginX) + "," + (titleTextBbox.height/2) + ") scale(-0.7 0.7)")
239-
.style("cursor", "pointer")
240-
.attr("fill", "silver")
241-
.on("click", () => {
242-
vm.showColorScalePicker = true;
243-
});
249+
244250
245251
const legendInner = legend.append("g")
246252
.attr("class", "legend-inner");
@@ -254,7 +260,8 @@ export default {
254260
.attr("width", vm.lWidth)
255261
.attr("height", "1px")
256262
.attr("fill", "black")
257-
.attr("fill-opacity", 0);
263+
.attr("fill-opacity", 0)
264+
.style("user-select", "none");
258265
259266
highlight.append("rect")
260267
.attr("x", 0)
@@ -263,7 +270,8 @@ export default {
263270
.attr("height", 1)
264271
.attr("fill", "black")
265272
.attr("fill-opacity", 0)
266-
.attr("transform", "translate(0," + (vm.lItemHeight) + ")");
273+
.attr("transform", "translate(0," + (vm.lItemHeight) + ")")
274+
.style("user-select", "none");
267275
268276
269277
@@ -299,6 +307,7 @@ export default {
299307
300308
const itemText = items.append("text")
301309
.style("text-anchor", "start")
310+
.style("font-family", "Avenir")
302311
.attr("y", scale.bandwidth() - 5)
303312
.attr("x", (textOffset + marginX) + "px")
304313
.style("font-size", "13px")
@@ -323,10 +332,49 @@ export default {
323332
.attr("fill", (d) => varScale.color(d))
324333
.attr("fill-opacity", (d) => varScale.domainFiltered.includes(d) ? 1 : 0);
325334
}
326-
335+
336+
if(d3Node) {
337+
return; /* SVG passed in to function, so not interactive */
338+
}
339+
327340
328341
// Action buttons
329-
const buttonWidth = 16;
342+
343+
const colorScaleButtonG = title
344+
.append("g")
345+
.attr("width", 20)
346+
.attr("height", 20)
347+
.attr("transform", "translate(" + (vm.lWidth - 1.5*marginX) + "," + (titleTextBbox.height/2) + ") scale(-0.7 0.7)")
348+
.style("cursor", "pointer")
349+
.on("click", () => {
350+
vm.showColorScalePicker = true;
351+
});
352+
colorScaleButtonG.append("rect")
353+
.attr("width", 20)
354+
.attr("height", 20)
355+
.attr("fill", "transparent");
356+
colorScaleButtonG.append("path")
357+
.attr("d", PAINT_BUCKET_PATH)
358+
.attr("fill", "silver");
359+
360+
if(vm.showDownloadButton) {
361+
const downloadButtonG = title
362+
.append("g")
363+
.attr("width", 20)
364+
.attr("height", 20)
365+
.attr("transform", "translate(" + (vm.lWidth - 2*(buttonWidth) + marginX/2) + "," + (titleTextBbox.height/2) + ") scale(-0.7 0.7)")
366+
.style("cursor", "pointer")
367+
.on("click", vm.downloadViaButton);
368+
369+
downloadButtonG.append("rect")
370+
.attr("width", 20)
371+
.attr("height", 20)
372+
.attr("fill", "transparent");
373+
downloadButtonG.append("path")
374+
.attr("d", DOWNLOAD_PATH)
375+
.attr("fill", "silver");
376+
377+
}
330378
331379
const filterButtons = items.append("g")
332380
.attr("transform", "translate(" + (vm.lWidth - 2*(buttonWidth + 2*marginX)) + ",0)")
@@ -409,6 +457,21 @@ export default {
409457
.attr("transform", "scale(0.7 0.7)")
410458
.attr("fill", "silver");
411459
460+
},
461+
download() {
462+
const svg = d3_create("svg")
463+
.attr("width", this.computedWidth)
464+
.attr("height", this.computedHeight)
465+
.attr("viewBox", `0 0 ${this.computedWidth} ${this.computedHeight}`);
466+
467+
this.drawLegend(svg);
468+
this.drawLegend();
469+
470+
return svg;
471+
},
472+
downloadViaButton() {
473+
const svg = this.download();
474+
downloadSvg(svg, this.downloadName);
412475
}
413476
}
414477
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy