|
| 1 | +'use strict'; |
| 2 | + |
| 3 | +var Plotly = require('../plotly'), |
| 4 | + d3 = require('d3'); |
| 5 | + |
| 6 | +module.exports = function toSVG(gd, format) { |
| 7 | + |
| 8 | + // make background color a rect in the svg, then revert after scraping |
| 9 | + // all other alterations have been dealt with by properly preparing the svg |
| 10 | + // in the first place... like setting cursors with css classes so we don't |
| 11 | + // have to remove them, and providing the right namespaces in the svg to |
| 12 | + // begin with |
| 13 | + var fullLayout = gd._fullLayout, |
| 14 | + svg = fullLayout._paper, |
| 15 | + size = fullLayout._size, |
| 16 | + domain, |
| 17 | + i; |
| 18 | + |
| 19 | + svg.insert('rect', ':first-child') |
| 20 | + .call(Plotly.Drawing.setRect, 0, 0, fullLayout.width, fullLayout.height) |
| 21 | + .call(Plotly.Color.fill, fullLayout.paper_bgcolor); |
| 22 | + |
| 23 | + /* Grab the 3d scenes and rasterize em. Calculate their positions, |
| 24 | + * then insert them into the SVG element as images */ |
| 25 | + var sceneIds = Plotly.Plots.getSubplotIds(fullLayout, 'gl3d'), |
| 26 | + scene; |
| 27 | + |
| 28 | + for(i = 0; i < sceneIds.length; i++) { |
| 29 | + scene = fullLayout[sceneIds[i]]; |
| 30 | + domain = scene.domain; |
| 31 | + insertGlImage(fullLayout, scene._scene, { |
| 32 | + x: size.l + size.w * domain.x[0], |
| 33 | + y: size.t + size.h * (1 - domain.y[1]), |
| 34 | + width: size.w * (domain.x[1] - domain.x[0]), |
| 35 | + height: size.h * (domain.y[1] - domain.y[0]) |
| 36 | + }); |
| 37 | + } |
| 38 | + |
| 39 | + // similarly for 2d scenes |
| 40 | + var subplotIds = Plotly.Plots.getSubplotIds(fullLayout, 'gl2d'), |
| 41 | + subplot; |
| 42 | + |
| 43 | + for(i = 0; i < subplotIds.length; i++) { |
| 44 | + subplot = fullLayout._plots[subplotIds[i]]; |
| 45 | + insertGlImage(fullLayout, subplot._scene2d, { |
| 46 | + x: size.l, |
| 47 | + y: size.t, |
| 48 | + width: size.w, |
| 49 | + height: size.h |
| 50 | + }); |
| 51 | + } |
| 52 | + |
| 53 | + // Grab the geos off the geo-container and place them in geoimages |
| 54 | + var geoIds = Plotly.Plots.getSubplotIds(fullLayout, 'geo'), |
| 55 | + geoLayout, |
| 56 | + geoFramework; |
| 57 | + |
| 58 | + for(i = 0; i < geoIds.length; i++) { |
| 59 | + geoLayout = fullLayout[geoIds[i]]; |
| 60 | + domain = geoLayout.domain; |
| 61 | + geoFramework = geoLayout._geo.framework; |
| 62 | + |
| 63 | + geoFramework.attr('style', null); |
| 64 | + geoFramework |
| 65 | + .attr({ |
| 66 | + x: size.l + size.w * domain.x[0] + geoLayout._marginX, |
| 67 | + y: size.t + size.h * (1 - domain.y[1]) + geoLayout._marginY, |
| 68 | + width: geoLayout._width, |
| 69 | + height: geoLayout._height |
| 70 | + }); |
| 71 | + |
| 72 | + fullLayout._geoimages.node() |
| 73 | + .appendChild(geoFramework.node()); |
| 74 | + } |
| 75 | + |
| 76 | + // now that we've got the 3d images in the right layer, add top items above them |
| 77 | + // assumes everything in toppaper is a group, and if it's empty (like hoverlayer) |
| 78 | + // we can ignore it |
| 79 | + if(fullLayout._toppaper) { |
| 80 | + var topGroups = fullLayout._toppaper.node().childNodes, |
| 81 | + topGroup; |
| 82 | + for(i = 0; i < topGroups.length; i++) { |
| 83 | + topGroup = topGroups[i]; |
| 84 | + if(topGroup.childNodes.length) svg.node().appendChild(topGroup); |
| 85 | + } |
| 86 | + } |
| 87 | + |
| 88 | + // in case the svg element had an explicit background color, remove this |
| 89 | + // we want the rect to get the color so it's the right size; svg bg will |
| 90 | + // fill whatever container it's displayed in regardless of plot size. |
| 91 | + svg.node().style.background = ''; |
| 92 | + |
| 93 | + svg.selectAll('text') |
| 94 | + .attr({'data-unformatted': null}) |
| 95 | + .each(function() { |
| 96 | + // hidden text is pre-formatting mathjax, the browser ignores it but it can still confuse batik |
| 97 | + var txt = d3.select(this); |
| 98 | + if(txt.style('visibility') === 'hidden') { |
| 99 | + txt.remove(); |
| 100 | + return; |
| 101 | + } |
| 102 | + |
| 103 | + // I've seen font-family styles with non-escaped double quotes in them - breaks the |
| 104 | + // serialized svg because the style attribute itself is double-quoted! |
| 105 | + // Is this an IE thing? Any other attributes or style elements that can have quotes in them? |
| 106 | + // TODO: this looks like a noop right now - what happened to it? |
| 107 | + var ff = txt.style('font-family'); |
| 108 | + if(ff && ff.indexOf('"') !== -1) txt.style('font-family', ff.replace(/"/g, '"')); |
| 109 | + }); |
| 110 | + |
| 111 | + if(format === 'pdf' || format === 'eps') { |
| 112 | + // these formats make the extra line MathJax adds around symbols look super thick in some cases |
| 113 | + // it looks better if this is removed entirely. |
| 114 | + svg.selectAll('#MathJax_SVG_glyphs path') |
| 115 | + .attr('stroke-width', 0); |
| 116 | + } |
| 117 | + |
| 118 | + // fix for IE namespacing quirk? |
| 119 | + // http://stackoverflow.com/questions/19610089/unwanted-namespaces-on-svg-markup-when-using-xmlserializer-in-javascript-with-ie |
| 120 | + svg.node().setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns', 'http://www.w3.org/2000/svg'); |
| 121 | + svg.node().setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xlink', 'http://www.w3.org/1999/xlink'); |
| 122 | + |
| 123 | + var s = new window.XMLSerializer().serializeToString(svg.node()); |
| 124 | + s = Plotly.util.html_entity_decode(s); |
| 125 | + s = Plotly.util.xml_entity_encode(s); |
| 126 | + |
| 127 | + return s; |
| 128 | +}; |
| 129 | + |
| 130 | +function insertGlImage(fullLayout, scene, opts) { |
| 131 | + var imageData = scene.toImage('png'); |
| 132 | + |
| 133 | + fullLayout._glimages.append('svg:image') |
| 134 | + .attr({ |
| 135 | + xmlns:'http://www.w3.org/2000/svg', |
| 136 | + 'xlink:xlink:href': imageData, // odd d3 quirk, need namespace twice |
| 137 | + x: opts.x, |
| 138 | + y: opts.y, |
| 139 | + width: opts.width, |
| 140 | + height: opts.height, |
| 141 | + preserveAspectRatio: 'none' |
| 142 | + }); |
| 143 | + |
| 144 | + scene.destroy(); |
| 145 | +} |
0 commit comments