我有一个带有标签和图例的饼图,在SVG中看起来不错,但是一旦我使用canvg将其转换为画布,某些格式就会丢失/出错。 SVG:
画布:
我已经内联CSS以应用所有CSS设置,但是格式仍然不匹配。
有什么想法吗? 这是(canvg)错误还是我在做某事?错误吗?
var columns = ['data11', 'data2', 'data347', 'data40098'];
var data = [150, 250, 300, 50];
var colors = ['#0065A3', '#767670', '#D73648', '#7FB2CE', '#00345B'];
var padding = 5;
/**
* global C3Styles object
*/
var C3Styles = null;
var legendData = [];
var sumTotal = 0
//prepare pie data
var columnData = [];
var columnNames = {};
for (i = 0; i < columns.length; i++) {
columnData.push([columns[i]].concat(data[i]));
var val = (Array.isArray(data[i])) ? data[i].reduce(function(pv, cv) {
return pv + cv;
}, 0) : data[i];
sumTotal += val;
legendData.push({
id: columns[i],
value: val,
ratio: 0.0
});
}
legendData.forEach(function(el, i) {
el.ratio = el.value / sumTotal
columnNames[el.id] = [el.id, d3.format(",.0f")(el.value), d3.format(",.1%")(el.ratio)].join(';');
});
var chart = c3.generate({
bindto: d3.select('#chart'),
data: {
columns: [
[columns[0]].concat(data[0])
],
names: columnNames,
type: 'pie',
},
legend: {
position: 'right',
show: true
},
pie: {
label: {
threshold: 0.001,
format: function(value, ratio, id) {
return [id, d3.format(",.0f")(value), "[" + d3.format(",.1%")(ratio) + "]"].join(';');
}
}
},
color: {
pattern: colors
},
onrendered: function() {
redrawLabelBackgrounds();
redrawLegend();
}
});
function addLabelBackground(index) {
/*d3.select('#chart').select("g.c3-target-" + columns[index].replace(/\W+/g, '-')+".c3-chart-arc")
.insert("rect", "text")
.style("fill", colors[index]);*/
var p = d3.select('#chart').select("g.c3-target-" + columns[index].replace(/\W+/g, '-') + ".c3-chart-arc");
var g = p.append("g");
g.append("rect")
.style("fill", colors[index]);
g.append(function() {
return p.select("text").remove().node();
});
}
for (var i = 0; i < columns.length; i++) {
if (i > 0) {
setTimeout(function(column) {
chart.load({
columns: [
columnData[column],
]
});
//chart.data.names(columnNames[column])
addLabelBackground(column);
}, (i * 5000 / columnData.length), i);
} else {
addLabelBackground(i);
}
}
function redrawLegend() {
d3.select('#chart').selectAll(".c3-legend-item > text").each(function() {
// get d3 node
var legendItem = d3.select(this);
var legendItemNode = legendItem.node();
//check if label is drawn
if (legendItemNode) {
if (legendItemNode.childElementCount === 0 && legendItemNode.innerHTML.length > 0) {
//build data
var data = legendItemNode.innerHTML.split(';');
legendItem.text("");
//TODO format legend dynamically depending on text
legendItem.append("tspan")
.text(data[0] + ": ")
.attr("class", "id-row")
.attr("text-anchor", "start");
legendItem.append("tspan")
.text(data[1] + " = ")
.attr("class", "value-row")
.attr("x", 160)
.attr("text-anchor", "end");
legendItem.append("tspan")
.text(data[2])
.attr("class", "ratio-row")
.attr("x", 190)
.attr("text-anchor", "end");
}
}
});
d3.select('#chart').selectAll(".c3-legend-item > rect").each(function() {
var legendItem = d3.select(this);
legendItem.attr("width", 190);
});
}
function redrawLabelBackgrounds() {
//for all label texts drawn yet
//for all label texts drawn yet
d3.select('#chart').selectAll(".c3-chart-arc > g > text").each(function(v) {
// get d3 node
var label = d3.select(this);
var labelNode = label.node();
//check if label is drawn
if (labelNode) {
if (labelNode.childElementCount === 0 && labelNode.innerHTML.length > 0) {
//build data
var data = labelNode.innerHTML.split(';');
label.text("");
data.forEach(function(i, n) {
label.append("tspan")
.text(i)
.attr("dy", (n === 0) ? 0 : "1.2em")
.attr("x", 0)
.attr("text-anchor", "middle");
}, label);
}
//check if element is visible
if (d3.select(labelNode.parentNode).style("display") !== 'none') {
//get pos of the label text
var pos = label.attr("transform").match(/-?\d+(\.\d+)?/g);
if (pos) {
// TODO: mofify the pos of the text
// pos[0] = (pos[0]/h*90000);
// pos[1] = (pos[1]/h*90000);
// remove dy and move label
//d3.select(this).attr("dy", 0);
//d3.select(this).attr("transform", "translate(" + pos[0] + "," + pos[1] + ")");
//get surrounding box of the label
var bbox = labelNode.getBBox();
//now draw and move the rects
d3.select(labelNode.parentNode).select("rect")
.attr("transform", "translate(" + (pos[0] - (bbox.width + padding) / 2) +
"," + (pos[1] - bbox.height / labelNode.childElementCount) + ")")
.attr("width", bbox.width + padding)
.attr("height", bbox.height + padding);
}
}
}
});
}
document.getElementById("exportButton").onclick = function() {
exportChartToImage();
};
function exportChartToImage() {
var createImagePromise = new Promise(function(resolve, reject) {
var images = [];
d3.selectAll('svg').each(function() {
if (this.parentNode) {
images.push(getSvgImage(this.parentNode, true));
}
});
if (images.length > 0)
resolve(images);
else
reject(images);
});
createImagePromise.then(function(images) {
/*images.forEach(function(img, n) {
img.toBlob(function(blob) {
saveAs(blob, "image_" + (n + 1) + ".png");
});
});*/
})
.catch(function(error) {
throw error;
});
};
/**
* Converts a SVG-Chart to a canvas and returns it.
*/
function getSvgImage(svgContainer) {
var svgEl = d3.select(svgContainer).select('svg').node();
var svgCopyEl = svgEl.cloneNode(true);
if (!svgCopyEl)
return;
d3.select("#svgCopyEl").selectAll("*").remove();
d3.select("#svgCopyEl").node().append(svgCopyEl);
//apply all CSS styles to SVG
/* taken from https://gist.github.com/aendrew/1ad2eed6afa29e30d52e#file-exportchart-js
and changed from, angular to D3 functions
*/
/* Take styles from CSS and put as inline SVG attributes so that Canvg
can properly parse them. */
if (!C3Styles) {
var chartStyle;
// Get rules from c3.css
var styleSheets = document.styleSheets;
for (var i = 0; i <= styleSheets.length - 1; i++) {
if (styleSheets[i].href && (styleSheets[i].href.indexOf('c3.min.css') !== -1 || styleSheets[i].href.indexOf('c3.css') !== -1)) {
try {
if (styleSheets[i].rules !== undefined) {
chartStyle = styleSheets[i].rules;
} else {
chartStyle = styleSheets[i].cssRules;
}
break;
}
//Note that SecurityError exception is specific to Firefox.
catch (e) {
if (e.name == 'SecurityError') {
console.log("SecurityError. Cant read: " + styleSheets[i].href);
continue;
}
}
}
if (chartStyle !== null && chartStyle !== undefined) {
C3Styles = {};
var selector;
// Inline apply all the CSS rules as inline
for (i = 0; i < chartStyle.length; i++) {
if (chartStyle[i].type === 1) {
selector = chartStyle[i].selectorText;
var styleDec = chartStyle[i].style;
for (var s = 0; s < styleDec.length; s++) {
C3Styles[styleDec[s]] = styleDec[styleDec[s]];
}
}
}
}
}
}
if (C3Styles) {
d3.select(svgCopyEl).selectAll('.c3:not(.c3-chart):not(path)').style(C3Styles);
}
// SVG doesn't use CSS visibility and opacity is an attribute, not a style property. Change hidden stuff to "display: none"
d3.select(svgCopyEl).selectAll('*')
.filter(function(d) {
return d && d.style && (d.style('visibility') === 'hidden' || d.style('opacity') === '0');
})
.style('display', 'none');
//fix weird back fill
d3.select(svgCopyEl).selectAll("path").attr("fill", "none");
//fix no axes
d3.select(svgCopyEl).selectAll("path.domain").attr("stroke", "black");
//fix no tick
d3.select(svgCopyEl).selectAll(".tick line").attr("stroke", "black");
//apply svg text fill, set color
d3.select(svgCopyEl).selectAll("text:not(.c3-empty):not(.c3-axis)").attr("opacity", 1);
var canvasComputed = d3.select("#canvasComputed").node();
// transform SVG to canvas using external canvg
canvg(canvasComputed, new XMLSerializer().serializeToString(svgCopyEl));
return canvasComputed;
}
.c3-chart-arc.c3-target text {
color: white;
fill: white;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.12/c3.min.css" rel="stylesheet" />
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.12/c3.min.js"></script>
<!-- Required to convert named colors to RGB -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/canvg/1.4/rgbcolor.min.js"></script>
<!-- Optional if you want blur -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/stackblur-canvas/1.4.1/stackblur.min.js"></script>
<!-- Main canvg code -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/canvg/1.5/canvg.js"></script>
<script src="https://fastcdn.org/FileSaver.js/1.1.20151003/FileSaver.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h4>
SVG
</h4>
<div id="chart" class "c3">
</div>
<h4>
copy SVG
</h4>
<div id ="svgCopyEl">
</div>
<div>
<h4>
canvas
</h4>
<canvas id="canvasComputed"></canvas>
</div>
<button type="button" id="exportButton">
export to Canvas
</button>
答案 0 :(得分:0)
基本上,这是缺乏知识和错误的结合。
首先,在使用canvg时必须内联所有相关的CSS样式。其次,使用tspans和text-anchors的canvg中存在一个错误。
这是(或多或少)工作代码:
var columns = ['data11', 'data2', 'data347', 'data40098'];
var data = [150, 250, 300, 50];
var colors = ['#0065A3', '#767670', '#D73648', '#7FB2CE', '#00345B'];
var padding = 5;
/**
* global C3Styles object
*/
var C3Styles = null;
var legendData = [];
var sumTotal = 0
//prepare pie data
var columnData = [];
var columnNames = {};
for (i = 0; i < columns.length; i++) {
columnData.push([columns[i]].concat(data[i]));
var val = (Array.isArray(data[i])) ? data[i].reduce(function(pv, cv) {
return pv + cv;
}, 0) : data[i];
sumTotal += val;
legendData.push({
id: columns[i],
value: val,
ratio: 0.0
});
}
legendData.forEach(function(el, i) {
el.ratio = el.value / sumTotal
columnNames[el.id] = [el.id, d3.format(",.0f")(el.value), d3.format(",.1%")(el.ratio)].join(';');
});
var chart = c3.generate({
bindto: d3.select('#chart'),
data: {
columns: [
[columns[0]].concat(data[0])
],
names: columnNames,
type: 'pie',
},
legend: {
position: 'right',
show: true
},
pie: {
label: {
threshold: 0.001,
format: function(value, ratio, id) {
return [id, d3.format(",.0f")(value), "[" + d3.format(",.1%")(ratio) + "]"].join(';');
}
}
},
color: {
pattern: colors
},
onrendered: function() {
redrawLabelBackgrounds();
redrawLegend();
}
});
function addLabelBackground(index) {
/*d3.select('#chart').select("g.c3-target-" + columns[index].replace(/\W+/g, '-')+".c3-chart-arc")
.insert("rect", "text")
.style("fill", colors[index]);*/
var p = d3.select('#chart').select("g.c3-target-" + columns[index].replace(/\W+/g, '-') + ".c3-chart-arc");
var g = p.append("g");
g.append("rect")
.style("fill", colors[index]);
g.append(function() {
return p.select("text").remove().node();
});
}
for (var i = 0; i < columns.length; i++) {
if (i > 0) {
setTimeout(function(column) {
chart.load({
columns: [
columnData[column],
]
});
//chart.data.names(columnNames[column])
addLabelBackground(column);
}, (i * 5000 / columnData.length), i);
} else {
addLabelBackground(i);
}
}
function redrawLegend() {
d3.select('#chart').selectAll(".c3-legend-item > text").each(function() {
// get d3 node
var legendItem = d3.select(this);
var legendItemNode = legendItem.node();
//check if label is drawn
if (legendItemNode) {
if (legendItemNode.childElementCount === 0 && legendItemNode.innerHTML.length > 0) {
//build data
var data = legendItemNode.innerHTML.split(';');
legendItem.text("");
//TODO format legend dynamically depending on text
legendItem.append("tspan")
.text(data[0] + ": ")
.attr("class", "id-row")
.attr("text-anchor", "start");
legendItem.append("tspan")
.text(data[1] + " = ")
.attr("class", "value-row")
.attr("x", 160)
.attr("text-anchor", "end");
legendItem.append("tspan")
.text(data[2])
.attr("class", "ratio-row")
.attr("x", 190)
.attr("text-anchor", "end");
}
}
});
d3.select('#chart').selectAll(".c3-legend-item > rect").each(function() {
var legendItem = d3.select(this);
legendItem.attr("width", 190);
});
}
function redrawLabelBackgrounds() {
//for all label texts drawn yet
//for all label texts drawn yet
d3.select('#chart').selectAll(".c3-chart-arc > g > text").each(function(v) {
// get d3 node
var label = d3.select(this);
var labelNode = label.node();
//check if label is drawn
if (labelNode) {
var bbox = labelNode.getBBox();
var labelTextHeight = bbox.height;
if (labelNode.childElementCount === 0 && labelNode.innerHTML.length > 0) {
//build data
var data = labelNode.innerHTML.split(';');
label.html('')
.attr("dominant-baseline", "central")
.attr("text-anchor", "middle");
data.forEach(function(i, n) {
label.append("tspan")
.text(i)
.attr("dy", (n === 0) ? 0 : "1.2em")
.attr("x", 0);
}, label);
}
//check if element is visible
if (d3.select(labelNode.parentNode).style("display") !== 'none') {
//get pos of the label text
var pos = label.attr("transform").match(/-?\d+(\.\d+)?/g);
if (pos) {
// TODO: mofify the pos of the text
// pos[0] = (pos[0]/h*90000);
// pos[1] = (pos[1]/h*90000);
// remove dy and move label
//d3.select(this).attr("dy", 0);
//d3.select(this).attr("transform", "translate(" + pos[0] + "," + pos[1] + ")");
//get surrounding box of the label
bbox = labelNode.getBBox();
//now draw and move the rects
d3.select(labelNode.parentNode).select("rect")
.attr("transform", "translate(" + (pos[0] - bbox.width / 2 - padding) +
"," + (pos[1] - labelTextHeight/2 - padding)+")")
.attr("width", bbox.width + 2*padding)
.attr("height", bbox.height + 2*padding);
}
}
}
});
}
document.getElementById("exportButton").onclick = function() {
exportChartToImage();
};
function exportChartToImage() {
var createImagePromise = new Promise(function(resolve, reject) {
var images = [];
d3.selectAll('svg').each(function() {
if (this.parentNode) {
images.push(getSvgImage(this.parentNode, true));
}
});
if (images.length > 0)
resolve(images);
else
reject(images);
});
createImagePromise.then(function(images) {
/*images.forEach(function(img, n) {
img.toBlob(function(blob) {
saveAs(blob, "image_" + (n + 1) + ".png");
});
});*/
})
.catch(function(error) {
throw error;
});
};
/**
* Converts a SVG-Chart to a canvas and returns it.
*/
function getSvgImage(svgContainer) {
var svgEl = d3.select(svgContainer).select('svg').node();
var svgCopyEl = svgEl.cloneNode(true);
if (!svgCopyEl)
return;
d3.select("#svgCopyEl").selectAll("*").remove();
d3.select("#svgCopyEl").node().append(svgCopyEl); //.transition().duration(0);
//apply C3 CSS styles to SVG
// SVG doesn't use CSS visibility and opacity is an attribute, not a style property. Change hidden stuff to "display: none"
d3.select("#svgCopyEl").selectAll('*')
.filter(function(d) {
return d && d.style && (d.style('visibility') === 'hidden' || d.style('opacity') === '0');
})
.style('display', 'none');
d3.select("#svgCopyEl").selectAll('.c3-chart path')
.filter(function(d) {
return d && d.style('fill') === 'none';
})
.attr('fill', 'none');
d3.select("#svgCopyEl").selectAll('.c3-chart path')
.filter(function(d) {
return d && d.style('fill') !== 'none';
})
.attr('fill', function(d) {
return d.style('fill');
});
//set c3 default font
d3.select("#svgCopyEl").selectAll('.c3 svg')
.style('font', 'sans-serif')
.style('font-size', '10px');
//set c3 legend font
d3.select("#svgCopyEl").selectAll('.c3-legend-item > text')
.style('font', 'sans-serif')
.style('font-size', '12px');
d3.select("#svgCopyEl").selectAll('.c3-legend-item > text > tspan')
.style('font', 'sans-serif')
.style('font-size', '12px');
//set c3 arc shapes
d3.select("#svgCopyEl").selectAll('.c3-chart-arc path,rect')
.style('stroke', '#fff');
d3.select("#svgCopyEl").selectAll('.c3-chart-arc text')
.attr('fill', '#fff')
.style('font', 'sans-serif')
.style('font-size', '13px');
//fix weird back fill
d3.select("#svgCopyEl").selectAll("path").attr("fill", "none");
//fix no axes
d3.select("#svgCopyEl").selectAll("path.domain").attr("stroke", "black");
//fix no tick
d3.select("#svgCopyEl").selectAll(".tick line").attr("stroke", "black");
var canvasComputed = d3.select("#canvasComputed").node();
// transform SVG to canvas using external canvg
canvg(canvasComputed, new XMLSerializer().serializeToString(svgCopyEl));
return canvasComputed;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.12/c3.min.css" rel="stylesheet" />
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.12/c3.min.js"></script> -->
<!-- Required to convert named colors to RGB -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/canvg/1.4/rgbcolor.min.js"></script>
<!-- Optional if you want blur -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/stackblur-canvas/1.4.1/stackblur.min.js"></script>
<!-- Main canvg code -->
<script src="https://cdn.jsdelivr.net/npm/canvg@2.0.0-beta.1/dist/browser/canvg.min.js"></script>
<script src="https://fastcdn.org/FileSaver.js/1.1.20151003/FileSaver.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h4>
SVG
</h4>
<div id="chart" class "c3">
</div>
<h4>
copy SVG
</h4>
<div id="svgCopyEl">
</div>
<div>
<h4>
canvas
</h4>
<canvas id="canvasComputed"></canvas>
</div>
<button type="button" id="exportButton">
export to Canvas
</button>