C3/D3饼图图例格式/标签重叠
C3/D3 pie legend format / label overlap
我有一个使用 C3 的饼图。
我现在通过添加值和百分比更改了默认图例名称。
- 我正在寻找一种很好地格式化此图例的方法,以便值和百分比的位置类似于列。
- 有没有办法防止标签重叠?
这是我到目前为止的进展:
var columns = ['data11', 'data2', 'data347', 'data40098'];
var data = [150, 250, 300, 50];
var colors = ['#0065A3', '#767670', '#D73648', '#7FB2CE', '#00345B'];
var padding = 5;
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);
});
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: redrawLabelBackgrounds
});
function addLabelBackground(index) {
//get label text element
var textLabel = d3.select(".c3-target-" + columns[index] + " > text");
//add rect to parent
var labelNode = textLabel.node();
if (labelNode /*&& labelNode.innerHTML.length > 0*/ ) {
var p = d3.select(labelNode.parentNode).insert("rect", "text")
.style("fill", colors[index]);
}
}
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 redrawLabelBackgrounds() {
//for all label texts drawn yet
//for all label texts drawn yet
d3.select('#chart').selectAll(".c3-chart-arc > 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);
}
}
}
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.9/c3.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.9/c3.min.js"></script>
<div id="chart"></div>
JSfiddle:
https://jsfiddle.net/gothmogg/m59poqcd/34/
[编辑]
我找到了 flot 修复饼图标签重叠的错误修正:
var label_w = label.width(); //4.3.2014 - from flot user fix #609 on github
var label_h = label.height(); //
var labelTop = (y - label_h / 2); //
var labelLeft = (x - label_w / 2); //
//label.css("top", labelTop); //
//label.css("left", labelLeft); //
// check to make sure that the label doesn't overlap one of the other labels - 4.3.2014 - from flot user fix #609 on github
var label_pos = [ [ labelLeft, labelLeft + label_w ], [ labelTop, labelTop + label_h ] ];
var newradius = radius;
var bCollision = false;
var yix = 10; //max label reiterations with collisions
do{
for(var j=(labels.length-1); j>=0; j--)
{
while(comparePositions(label_pos[0], labels[j][0]) && comparePositions(label_pos[1], labels[j][1]))
{
newradius -= 2;
if(newradius < 0.00) {
break;
}
x = centerLeft + Math.round(Math.cos(halfAngle) * newradius);
y = centerTop + Math.round(Math.sin(halfAngle) * newradius) * options.series.pie.tilt;
labelTop = (y - label_h / 2);
labelLeft = (x - label_w / 2);
label_pos[0][0] = labelLeft;
label_pos[0][1] = labelLeft + label_w;
label_pos[1][0] = labelTop;
label_pos[1][1] = labelTop + label_h;
bCollision = true;
}
if(bCollision) break;
}
label.css("top", labelTop);
label.css("left", labelLeft);
if(bCollision) bCollision = false;
else break;
yix--;
}while(yix>0);
function comparePositions(p1, p2) {
var x1 = p1[0] < p2[0] ? p1 : p2;
var x2 = p1[0] < p2[0] ? p2 : p1;
return x1[1] > x2[0] || x1[0] === x2[0] ? true : false;
}
labels.push(label_pos);
//end of added code - 4.3.2014 - from flot user fix #609 on github
不幸的是,我不知道如何将此修复程序转移到 C3/D3...
可以创建 custom legend。然后您可以在 SVG 或 HTML.
中根据需要创建和设计样式
var columns = ['data11', 'data2', 'data347', 'data40098'];
var data = [150, 250, 300, 50];
var colors = ['#0065A3', '#767670', '#D73648', '#7FB2CE', '#00345B'];
var padding = 5;
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);
});
var chart = c3.generate({
bindto: d3.select('#chart'),
data: {
columns: [
[columns[0]].concat(data[0])
],
names: columnNames,
type: 'pie',
},
legend: {
position: 'right',
show: false
},
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: redrawLabelBackgrounds
});
function addLabelBackground(index) {
//get label text element
var textLabel = d3.select(".c3-target-" + columns[index] + " > text");
//add rect to parent
var labelNode = textLabel.node();
if (labelNode /*&& labelNode.innerHTML.length > 0*/ ) {
var p = d3.select(labelNode.parentNode).insert("rect", "text")
.style("fill", colors[index]);
}
}
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 redrawLabelBackgrounds() {
//for all label texts drawn yet
//for all label texts drawn yet
d3.select('#chart').selectAll(".c3-chart-arc > 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);
}
}
}
});
}
//console.log(columnData)
function toggle(id) {
chart.toggle(id);
}
var table = d3.select('#chart')
.append('table').attr('class', 'legend')
var row = table.selectAll('row').data(columnData)
var rowEnter = row.enter().append('tr').attr('class', 'legend-row')
rowEnter.append('td').attr('style', (d, i) => `width: 20px;background-color:${colors[i]}`)
rowEnter.append('td').text(d => d[0])
rowEnter.append('td').text(d => d[1])
rowEnter.on('mouseover', function(id) {
if (!this.classList.contains('legend-row-disabled')) {
rowEnter.classed('legend-row-inactive', true)
chart.focus(id);
}
});
rowEnter.on('mouseout', function(id) {
rowEnter.classed('legend-row-inactive', false)
if (!this.classList.contains('legend-row-disabled')) {
chart.focus()
}
})
rowEnter.on('click', function(id) {
chart.toggle(id);
this.classList.toggle('legend-row-disabled');
chart.focus()
});
.legend {
font: 12px sans-serif;
border-collapse: collapse;
}
.legend .legend-row {
cursor: pointer;
}
.legend .legend-row td:first-child {
width: 14px !important;
}
.legend .legend-row.legend-row-inactive {
opacity: 0.3;
}
.legend .legend-row.legend-row-inactive:hover {
opacity: 1.0;
}
.legend .legend-row.legend-row-disabled,
.legend .legend-row.legend-row-disabled:hover {
opacity: 0.2;
}
.legend td {
padding: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.9/c3.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.9/c3.min.js"></script>
<div id="chart">
</div>
我知道我可以添加自定义图例,但我想使用原始 SVG 图例,我现在添加了 tspans:
var columns = ['data11', 'data2', 'data347', 'data40091'];
var data = [150, 250, 300, 50];
var colors = ['#0065A3', '#767670', '#D73648', '#7FB2CE', '#00345B'];
var padding = 5;
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) {
//get label text element
var textLabel = d3.select(".c3-target-" + columns[index] + " > text");
//add rect to parent
var labelNode = textLabel.node();
if (labelNode /*&& labelNode.innerHTML.length > 0*/ ) {
var p = d3.select(labelNode.parentNode).insert("rect", "text")
.style("fill", colors[index]);
}
}
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(v) {
// get d3 node
var legendItem = d3.select(this);
legendItem.attr("style", "font-size: 8pt;");
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("");
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", 170)
.attr("text-anchor", "end");
legendItem.append("tspan")
.text(data[2])
.attr("class", "ratio-row")
.attr("x", 200)
.attr("text-anchor", "end");
//.attr("transform", (n === 0) ? "translate(0,0)" : "translate(200,0)")
}
}
});
d3.select('#chart').selectAll(".c3-legend-item > rect").each(function(v) {
var legendItem = d3.select(this);
legendItem.attr("width", 170);
});
}
function redrawLabelBackgrounds() {
//for all label texts drawn yet
d3.select('#chart').selectAll(".c3-chart-arc > 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);
}
}
}
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.9/c3.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.9/c3.min.js"></script>
<div id="chart">
</div>
这解决了问题 #1 到 98% ;)
现在我只需要弄清楚如何以最优雅的方式根据内容动态计算 span 的宽度。
(当然还有#2)
我有一个使用 C3 的饼图。 我现在通过添加值和百分比更改了默认图例名称。
- 我正在寻找一种很好地格式化此图例的方法,以便值和百分比的位置类似于列。
- 有没有办法防止标签重叠?
这是我到目前为止的进展:
var columns = ['data11', 'data2', 'data347', 'data40098'];
var data = [150, 250, 300, 50];
var colors = ['#0065A3', '#767670', '#D73648', '#7FB2CE', '#00345B'];
var padding = 5;
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);
});
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: redrawLabelBackgrounds
});
function addLabelBackground(index) {
//get label text element
var textLabel = d3.select(".c3-target-" + columns[index] + " > text");
//add rect to parent
var labelNode = textLabel.node();
if (labelNode /*&& labelNode.innerHTML.length > 0*/ ) {
var p = d3.select(labelNode.parentNode).insert("rect", "text")
.style("fill", colors[index]);
}
}
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 redrawLabelBackgrounds() {
//for all label texts drawn yet
//for all label texts drawn yet
d3.select('#chart').selectAll(".c3-chart-arc > 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);
}
}
}
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.9/c3.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.9/c3.min.js"></script>
<div id="chart"></div>
JSfiddle: https://jsfiddle.net/gothmogg/m59poqcd/34/
[编辑]
我找到了 flot 修复饼图标签重叠的错误修正:
var label_w = label.width(); //4.3.2014 - from flot user fix #609 on github
var label_h = label.height(); //
var labelTop = (y - label_h / 2); //
var labelLeft = (x - label_w / 2); //
//label.css("top", labelTop); //
//label.css("left", labelLeft); //
// check to make sure that the label doesn't overlap one of the other labels - 4.3.2014 - from flot user fix #609 on github
var label_pos = [ [ labelLeft, labelLeft + label_w ], [ labelTop, labelTop + label_h ] ];
var newradius = radius;
var bCollision = false;
var yix = 10; //max label reiterations with collisions
do{
for(var j=(labels.length-1); j>=0; j--)
{
while(comparePositions(label_pos[0], labels[j][0]) && comparePositions(label_pos[1], labels[j][1]))
{
newradius -= 2;
if(newradius < 0.00) {
break;
}
x = centerLeft + Math.round(Math.cos(halfAngle) * newradius);
y = centerTop + Math.round(Math.sin(halfAngle) * newradius) * options.series.pie.tilt;
labelTop = (y - label_h / 2);
labelLeft = (x - label_w / 2);
label_pos[0][0] = labelLeft;
label_pos[0][1] = labelLeft + label_w;
label_pos[1][0] = labelTop;
label_pos[1][1] = labelTop + label_h;
bCollision = true;
}
if(bCollision) break;
}
label.css("top", labelTop);
label.css("left", labelLeft);
if(bCollision) bCollision = false;
else break;
yix--;
}while(yix>0);
function comparePositions(p1, p2) {
var x1 = p1[0] < p2[0] ? p1 : p2;
var x2 = p1[0] < p2[0] ? p2 : p1;
return x1[1] > x2[0] || x1[0] === x2[0] ? true : false;
}
labels.push(label_pos);
//end of added code - 4.3.2014 - from flot user fix #609 on github
不幸的是,我不知道如何将此修复程序转移到 C3/D3...
可以创建 custom legend。然后您可以在 SVG 或 HTML.
中根据需要创建和设计样式var columns = ['data11', 'data2', 'data347', 'data40098'];
var data = [150, 250, 300, 50];
var colors = ['#0065A3', '#767670', '#D73648', '#7FB2CE', '#00345B'];
var padding = 5;
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);
});
var chart = c3.generate({
bindto: d3.select('#chart'),
data: {
columns: [
[columns[0]].concat(data[0])
],
names: columnNames,
type: 'pie',
},
legend: {
position: 'right',
show: false
},
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: redrawLabelBackgrounds
});
function addLabelBackground(index) {
//get label text element
var textLabel = d3.select(".c3-target-" + columns[index] + " > text");
//add rect to parent
var labelNode = textLabel.node();
if (labelNode /*&& labelNode.innerHTML.length > 0*/ ) {
var p = d3.select(labelNode.parentNode).insert("rect", "text")
.style("fill", colors[index]);
}
}
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 redrawLabelBackgrounds() {
//for all label texts drawn yet
//for all label texts drawn yet
d3.select('#chart').selectAll(".c3-chart-arc > 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);
}
}
}
});
}
//console.log(columnData)
function toggle(id) {
chart.toggle(id);
}
var table = d3.select('#chart')
.append('table').attr('class', 'legend')
var row = table.selectAll('row').data(columnData)
var rowEnter = row.enter().append('tr').attr('class', 'legend-row')
rowEnter.append('td').attr('style', (d, i) => `width: 20px;background-color:${colors[i]}`)
rowEnter.append('td').text(d => d[0])
rowEnter.append('td').text(d => d[1])
rowEnter.on('mouseover', function(id) {
if (!this.classList.contains('legend-row-disabled')) {
rowEnter.classed('legend-row-inactive', true)
chart.focus(id);
}
});
rowEnter.on('mouseout', function(id) {
rowEnter.classed('legend-row-inactive', false)
if (!this.classList.contains('legend-row-disabled')) {
chart.focus()
}
})
rowEnter.on('click', function(id) {
chart.toggle(id);
this.classList.toggle('legend-row-disabled');
chart.focus()
});
.legend {
font: 12px sans-serif;
border-collapse: collapse;
}
.legend .legend-row {
cursor: pointer;
}
.legend .legend-row td:first-child {
width: 14px !important;
}
.legend .legend-row.legend-row-inactive {
opacity: 0.3;
}
.legend .legend-row.legend-row-inactive:hover {
opacity: 1.0;
}
.legend .legend-row.legend-row-disabled,
.legend .legend-row.legend-row-disabled:hover {
opacity: 0.2;
}
.legend td {
padding: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.9/c3.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.9/c3.min.js"></script>
<div id="chart">
</div>
我知道我可以添加自定义图例,但我想使用原始 SVG 图例,我现在添加了 tspans:
var columns = ['data11', 'data2', 'data347', 'data40091'];
var data = [150, 250, 300, 50];
var colors = ['#0065A3', '#767670', '#D73648', '#7FB2CE', '#00345B'];
var padding = 5;
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) {
//get label text element
var textLabel = d3.select(".c3-target-" + columns[index] + " > text");
//add rect to parent
var labelNode = textLabel.node();
if (labelNode /*&& labelNode.innerHTML.length > 0*/ ) {
var p = d3.select(labelNode.parentNode).insert("rect", "text")
.style("fill", colors[index]);
}
}
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(v) {
// get d3 node
var legendItem = d3.select(this);
legendItem.attr("style", "font-size: 8pt;");
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("");
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", 170)
.attr("text-anchor", "end");
legendItem.append("tspan")
.text(data[2])
.attr("class", "ratio-row")
.attr("x", 200)
.attr("text-anchor", "end");
//.attr("transform", (n === 0) ? "translate(0,0)" : "translate(200,0)")
}
}
});
d3.select('#chart').selectAll(".c3-legend-item > rect").each(function(v) {
var legendItem = d3.select(this);
legendItem.attr("width", 170);
});
}
function redrawLabelBackgrounds() {
//for all label texts drawn yet
d3.select('#chart').selectAll(".c3-chart-arc > 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);
}
}
}
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.9/c3.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.9/c3.min.js"></script>
<div id="chart">
</div>
这解决了问题 #1 到 98% ;)
现在我只需要弄清楚如何以最优雅的方式根据内容动态计算 span 的宽度。
(当然还有#2)