Skip to content

Commit 0cd945c

Browse files
authored
Merge pull request #25 from keller-mark/downloading
Downloading working, fixes #24
2 parents b141085 + f7212fe commit 0cd945c

File tree

7 files changed

+169
-27
lines changed

7 files changed

+169
-27
lines changed

examples-src/App.vue

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,20 @@
1010
<PlotContainer
1111
:pWidth="500"
1212
:pHeight="300"
13-
:pMarginTop="10"
13+
:pMarginTop="120"
1414
:pMarginLeft="120"
15-
:pMarginRight="10"
15+
:pMarginRight="120"
1616
:pMarginBottom="100"
17+
ref="ScatterPlotContainer"
1718
>
19+
<Axis
20+
slot="axisTop"
21+
variable="vue_x"
22+
side="top"
23+
:tickRotation="0"
24+
:getScale="getScale"
25+
:getStack="getStack"
26+
/>
1827
<Axis
1928
slot="axisLeft"
2029
variable="vue_y"
@@ -35,6 +44,14 @@
3544
:getScale="getScale"
3645
:clickHandler="exampleClickHandler"
3746
/>
47+
<Axis
48+
slot="axisRight"
49+
variable="vue_y"
50+
side="right"
51+
:tickRotation="-35"
52+
:getScale="getScale"
53+
:getStack="getStack"
54+
/>
3855
<Axis
3956
slot="axisBottom"
4057
variable="vue_x"
@@ -45,6 +62,8 @@
4562
/>
4663
</PlotContainer>
4764

65+
<button @click="exampleDownload">Download Plot</button>
66+
4867
<h3>&lt;StackedBarPlot/&gt;</h3>
4968
<PlotContainer
5069
:pWidth="800"
@@ -850,10 +869,8 @@
850869
:getStack="getStack"
851870
/>
852871
</PlotContainer>
853-
854872

855873

856-
857874
<div class="stack-wrapper" v-show="showStack">
858875
<h3>&lt;Stack/&gt;</h3>
859876
<Stack :getStack="getStack" />
@@ -1299,6 +1316,17 @@ export default {
12991316
},
13001317
countBarPlotFilterFunction(val) {
13011318
return (val > 1);
1319+
},
1320+
exampleDownload() {
1321+
this.$refs.ScatterPlotContainer.download("test")
1322+
.then((uri) => {
1323+
const downloadAnchorNode = document.createElement('a');
1324+
downloadAnchorNode.setAttribute("href", uri);
1325+
downloadAnchorNode.setAttribute("download", "vueplotlib_download_demo.png");
1326+
document.body.appendChild(downloadAnchorNode); // required for firefox
1327+
downloadAnchorNode.click();
1328+
downloadAnchorNode.remove();
1329+
});
13021330
}
13031331
}
13041332
}

package.json

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

src/components/PlotContainer.vue

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
<script>
2+
import { getRetinaRatio } from './../helpers.js';
3+
24
/**
35
* Function that takes in array of VNodes and adds props from a provided props object.
46
* @private
@@ -22,6 +24,27 @@ const addProp = function(slotArray, newProps) {
2224
}
2325
return [];
2426
}
27+
28+
/**
29+
* Given a canvas context, x and y offsets, and an image URI, render the image to the context.
30+
* @private
31+
* @param {Context} ctx The canvas context.
32+
* @param {string} uri The image data URI.
33+
* @param {int} x The x offset.
34+
* @param {int} y The y offset.
35+
*/
36+
const renderToContext = function(ctx, uri, x, y, width, height) {
37+
return new Promise((resolve, reject) => {
38+
var img = new Image;
39+
img.onload = () => {
40+
ctx.drawImage(img, x, y, width, height);
41+
resolve();
42+
};
43+
img.src = uri;
44+
});
45+
};
46+
47+
2548
/**
2649
* This component is a container for axis and plot components,
2750
* which passes its props to its children and imposes styles.
@@ -109,10 +132,83 @@ export default {
109132
);
110133
let classes = ['vdp-plot-container'];
111134
let styles = {
112-
width: (this.pMarginLeft + this.pWidth + this.pMarginRight) + 'px',
113-
height: (this.pMarginTop + this.pHeight + this.pMarginBottom) + 'px'
135+
width: this.fullWidth + 'px',
136+
height: this.fullHeight + 'px'
114137
};
115138
return h('div', { class: classes, style: styles }, children);
139+
},
140+
computed: {
141+
fullWidth() {
142+
return this.pMarginLeft + this.pWidth + this.pMarginRight;
143+
},
144+
fullHeight() {
145+
return this.pMarginTop + this.pHeight + this.pMarginBottom;
146+
}
147+
},
148+
methods: {
149+
renderToContext(ctx, x, y, uri) {
150+
var img = new Image;
151+
img.onload = () => {
152+
ctx.drawImage(img, x, y); // Or at whatever offset you like
153+
};
154+
img.src = uri;
155+
},
156+
download() {
157+
const canvas = document.createElement('canvas');
158+
const ctx = canvas.getContext('2d');
159+
160+
const ratio = getRetinaRatio(ctx);
161+
const scaledWidth = this.fullWidth * ratio;
162+
const scaledHeight = this.fullHeight * ratio;
163+
164+
canvas.width = scaledWidth;
165+
canvas.height = scaledHeight;
166+
ctx.scale(ratio, ratio);
167+
168+
const renderAxisToContext = (axisType) => {
169+
if(this.$slots[axisType].length > 0) {
170+
return this.$slots[axisType][0].componentInstance.downloadAxis()
171+
.then((uri) => {
172+
console.log(uri);
173+
const x = this.$slots[axisType][0].componentInstance.computedLeft;
174+
const y = this.$slots[axisType][0].componentInstance.computedTop;
175+
const width = this.$slots[axisType][0].componentInstance.computedWidth;
176+
const height = this.$slots[axisType][0].componentInstance.computedHeight;
177+
return renderToContext(ctx, uri, x, y, width, height);
178+
});
179+
}
180+
return Promise.resolve();
181+
};
182+
183+
const renderPlotToContext = () => {
184+
if(this.$slots.plot.length > 0) {
185+
return this.$slots.plot[0].componentInstance.downloadPlot()
186+
.then((uri) => {
187+
console.log(uri);
188+
const x = this.pMarginLeft;
189+
const y = this.pMarginTop;
190+
const width = this.pWidth;
191+
const height = this.pHeight;
192+
return renderToContext(ctx, uri, x, y, width, height);
193+
});
194+
}
195+
return Promise.resolve();
196+
};
197+
198+
const renderPromises = [];
199+
renderPromises.push(renderAxisToContext("axisTop"));
200+
renderPromises.push(renderAxisToContext("axisLeft"));
201+
renderPromises.push(renderPlotToContext());
202+
renderPromises.push(renderAxisToContext("axisRight"));
203+
renderPromises.push(renderAxisToContext("axisBottom"));
204+
205+
return new Promise((resolve, reject) => {
206+
Promise.all(renderPromises).then(() => {
207+
const uri = canvas.toDataURL("image/png");
208+
resolve(uri);
209+
});
210+
});
211+
}
116212
}
117213
}
118214
</script>

src/components/axes/Axis.vue

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { axisTop as d3_axisTop, axisLeft as d3_axisLeft, axisRight as d3_axisRig
1717
import { brushX as d3_brushX, brushY as d3_brushY } from 'd3-brush';
1818
import { event as d3_event } from 'd3';
1919
20-
import { saveSvgAsPng } from 'save-svg-as-png';
20+
import { svgAsPngUri } from 'save-svg-as-png';
2121
2222
import AbstractScale from './../../scales/AbstractScale.js';
2323
import HistoryEvent from './../../history/HistoryEvent.js';
@@ -133,7 +133,7 @@ export default {
133133
if(this._side === SIDES.BOTTOM || this._side === SIDES.TOP) {
134134
return this.pMarginLeft + this.pWidth + this.pMarginRight;
135135
} else if(this._side === SIDES.LEFT) {
136-
return this.pMarginLeft;
136+
return this.pMarginLeft + 1;
137137
} else if(this._side === SIDES.RIGHT) {
138138
return this.pMarginRight;
139139
}
@@ -142,7 +142,7 @@ export default {
142142
if(this._side === SIDES.LEFT || this._side === SIDES.RIGHT) {
143143
return this.pMarginTop + this.pHeight + this.pMarginBottom;
144144
} else if(this._side === SIDES.TOP) {
145-
return this.pMarginTop;
145+
return this.pMarginTop + 1;
146146
} else if(this._side === SIDES.BOTTOM) {
147147
return this.pMarginBottom;
148148
}
@@ -161,7 +161,7 @@ export default {
161161
},
162162
computedTranslateX: function() {
163163
if(this._side === SIDES.LEFT) {
164-
return this.pMarginLeft - 1;
164+
return this.pMarginLeft;
165165
} else if(this._side === SIDES.BOTTOM || this._side === SIDES.TOP) {
166166
return this.pMarginLeft;
167167
}
@@ -173,9 +173,11 @@ export default {
173173
} else if(this._side === SIDES.RIGHT) {
174174
return this.pMarginTop;
175175
} else if(this._side === SIDES.TOP) {
176-
return this.pMarginTop - 1;
176+
return this.pMarginTop;
177+
} else if(this._side === SIDES.BOTTOM) {
178+
return 0;
177179
}
178-
return 0;
180+
179181
}
180182
},
181183
watch: {
@@ -684,6 +686,7 @@ export default {
684686
685687
const labelText = containerLabel.append("text")
686688
.style("text-anchor", "middle")
689+
.style("font-family", "Avenir")
687690
.text(varScale.name);
688691
689692
const labelTextBbox = labelText.node().getBBox();
@@ -722,8 +725,13 @@ export default {
722725
723726
},
724727
downloadAxis() {
725-
let node = d3_select(this.axisSelector).select("svg").node();
726-
saveSvgAsPng(node, this.axisElemID + ".png");
728+
const node = d3_select(this.axisSelector).select("svg").node();
729+
return new Promise((resolve, reject) => {
730+
svgAsPngUri(node, {}, (uri) => {
731+
resolve(uri);
732+
});
733+
});
734+
727735
}
728736
}
729737
}

src/components/axes/DendrogramAxis.vue

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import { select as d3_select } from 'd3-selection';
1515
import { cluster as d3_cluster, hierarchy as d3_hierarchy } from 'd3-hierarchy';
1616
17-
import { saveSvgAsPng } from 'save-svg-as-png';
17+
import { svgAsPngUri } from 'save-svg-as-png';
1818
1919
import AbstractScale from './../../scales/AbstractScale.js';
2020
import DataContainer from './../../data/DataContainer.js';
@@ -338,8 +338,12 @@ export default {
338338
339339
},
340340
downloadAxis() {
341-
let node = d3_select(this.axisSelector).select("svg").node();
342-
saveSvgAsPng(node, this.axisElemID + ".png");
341+
const node = d3_select(this.axisSelector).select("svg").node();
342+
return new Promise((resolve, reject) => {
343+
svgAsPngUri(node, {}, (uri) => {
344+
resolve(uri);
345+
});
346+
});
343347
}
344348
}
345349
}

src/components/axes/GenomeAxis.vue

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import { select as d3_select } from 'd3-selection';
4040
import { axisTop as d3_axisTop, axisLeft as d3_axisLeft, axisRight as d3_axisRight, axisBottom as d3_axisBottom } from 'd3-axis';
4141
import { zip as d3_zip } from 'd3-array';
4242
43-
import { saveSvgAsPng } from 'save-svg-as-png';
43+
import { svgAsPngUri } from 'save-svg-as-png';
4444
4545
import GenomeScale from './../../scales/GenomeScale.js';
4646
import HistoryEvent from './../../history/HistoryEvent.js';
@@ -132,7 +132,7 @@ export default {
132132
if(this._side === SIDES.BOTTOM || this._side === SIDES.TOP) {
133133
return this.pMarginLeft + this.pWidth + this.pMarginRight;
134134
} else if(this._side === SIDES.LEFT) {
135-
return this.pMarginLeft;
135+
return this.pMarginLeft + 1;
136136
} else if(this._side === SIDES.RIGHT) {
137137
return this.pMarginRight;
138138
}
@@ -141,7 +141,7 @@ export default {
141141
if(this._side === SIDES.LEFT || this._side === SIDES.RIGHT) {
142142
return this.pMarginTop + this.pHeight + this.pMarginBottom;
143143
} else if(this._side === SIDES.TOP) {
144-
return this.pMarginTop;
144+
return this.pMarginTop + 1;
145145
} else if(this._side === SIDES.BOTTOM) {
146146
return this.pMarginBottom;
147147
}
@@ -160,7 +160,7 @@ export default {
160160
},
161161
computedTranslateX: function() {
162162
if(this._side === SIDES.LEFT) {
163-
return this.pMarginLeft - 1;
163+
return this.pMarginLeft;
164164
} else if(this._side === SIDES.BOTTOM || this._side === SIDES.TOP) {
165165
return this.pMarginLeft;
166166
}
@@ -172,7 +172,7 @@ export default {
172172
} else if(this._side === SIDES.RIGHT) {
173173
return this.pMarginTop;
174174
} else if(this._side === SIDES.TOP) {
175-
return this.pMarginTop - 1;
175+
return this.pMarginTop;
176176
}
177177
return 0;
178178
},
@@ -504,8 +504,12 @@ export default {
504504
505505
},
506506
downloadAxis() {
507-
let node = d3_select(this.axisSelector).select("svg").node();
508-
saveSvgAsPng(node, this.axisElemID + ".png");
507+
const node = d3_select(this.axisSelector).select("svg").node();
508+
return new Promise((resolve, reject) => {
509+
svgAsPngUri(node, {}, (uri) => {
510+
resolve(uri);
511+
});
512+
});
509513
}
510514
}
511515
}

src/components/plots/mixin.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,10 @@ export default {
109109
// stub
110110
},
111111
downloadPlot: function() {
112-
let image = document.getElementById(this.plotElemID).toDataURL("image/png");
113-
document.write('<img src="'+image+'"/>');
112+
const uri = document.getElementById(this.plotElemID).toDataURL("image/png");
113+
return new Promise((resolve, reject) => {
114+
resolve(uri);
115+
});
114116
}
115117
}
116118
}

0 commit comments

Comments
 (0)