如何在 dc.js 个饼图切片中添加图标而不是文本
How to add icons in dc.js piechart slices instead texts
我已经与 dc.js 合作一年了。最近我的任务是实现一个饼图如下:
我想用适当的图像替换饼图切片中的文本标签。
我看到这是在纯 d3.js 中实现的。有人可以帮我将实现翻译成 dc.js 吗?
pie
.width(600)
.height(500)
.radius(200)
.innerRadius(120)
.dimension(disastersDimension)
.group(disastersGroup)
.on("filtered", function (chart, filter) {
var sel = filter;
let percentage = 0,
value = 0;
let disastersBuffer = [];
totalAmount = 0;
pie.selectAll("text.pie-slice").text((d) => {
percentage = dc.utils.printSingleValue(
((d.endAngle - d.startAngle) / (2 * Math.PI)) * 100
);
disastersBuffer.push({ ...d.data, percentage });
totalAmount += parseFloat(d.data.value);
});
filterPiechart(sel, percentage, totalAmount, disastersBuffer, value);
})
.on("renderlet", (chart) => {
if (!chart.selectAll("g.selected")._groups[0].length) {
chart.filter(null);
filterPiechart("", 100, totalAmount, [], 0);
}
var arc = chart.radius(250).innerRadius(100);
console.log(arc);
var g = chart.selectAll(".pie-slice");
chart
.selectAll(".pie-slice")
.append("image")
.attr("xlink:href", "img/disasters/Floods.png")
.attr("transform", "translate(-10,10) rotate(315)")
.attr("width", "26px")
.attr("hight", "26px")
.style("background-color", "white")
.attr("x", function (d) {
var bbox = this.parentNode.getBBox();
return bbox.x;
})
.attr("y", function () {
var bbox = this.parentNode.getBBox();
return bbox.y;
});
g.append("g")
.append("svg:image")
.attr("xlink:href", function (d) {
let filteredImage = self.piedata.find(
(i) => i.label == d.data.key
);
let image = filteredImage ? filteredImage.image : "";
return image;
})
.attr("width", 30)
.attr("height", 40)
.attr("x", function (d) {
var bbox = this.parentNode.getBBox();
return bbox.x;
})
.attr("y", function (d) {
var bbox = this.parentNode.getBBox();
return bbox.y;
});
})
.addFilterHandler(function (filters, filter) {
filters.length = 0; // empty the array
filters.push(filter);
return filters;
});
我拍了fiddle and added a couple of 'Meteoicons' I found here.
(当然,这些图标只是举例,本人无权商用)
图标存储在单独的 <svg>
元素中。要呈现图标,只需 select 它的根 <g>
元素并将其内容复制到您在饼图中创建的另一个 <g>
:
g.append("g")
.attr("transform", d => `translate(${arc.centroid(d)}) scale(0.25)`)
.append('g')
.attr('transform', 'translate(-256,-256)') // The original icons are 256 x 256
.html(d => d3.select(`#meteo-icon-${... some attribute of d ...} > g`).html())
代码仅供演示,您需要根据自己的需要进行修改。
在下面的代码片段中查看结果:
var width = 550,
height = 550,
radius = 250,
colors = d3.scale.ordinal()
.range(['#336699 ','#336699 ','#ACD1E9','#ACD1E9','#ACD1E9']);
var image_width=40,
image_height=40;
var piedata = [
{
label: "test",
image: "http://placeimg.com/40/40/any",
value: 50
},
{
label: "",
image: "http://placeimg.com/42/42/any",
value: 50
},
{
label: "Jonathan",
image: "http://placeimg.com/44/44/any",
value: 50
},
{
label: "Lorenzo",
image: "http://placeimg.com/46/46/any",
value: 50
},
{
label: "Hillary",
image: "http://placeimg.com/38/38/any",
value: 50
}
]
var pie = d3.layout.pie()
.value(function(d) {
return d.value;
})
var arc = d3.svg.arc()
.outerRadius(250)
.innerRadius(100)
var svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate('+(width-radius)+','+(height-radius)+')');
var g = svg.selectAll(".arc")
.data(pie(piedata))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function(d,i) { return colors(i); });
g.append("g")
.attr("transform", d => `translate(${arc.centroid(d)}) scale(0.25)`)
.append('g')
.attr('transform', 'translate(-256,-256)')
.html(() => d3.select(`#meteo-icon-${Math.random() < 0.5 ? 1 : 2} > g`).html())
.selectAll('path')
.style('fill', 'orange');
path {
stroke: #fff;
fill-rule: evenodd;
}
text {
font-family: Arial, sans-serif;
font-size: 12px;
}
.meteo-icon {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<svg id="meteo-icon-1" class="meteo-icon" width="24" height="24" viewBox="0 0 512 512">
<g>
<path fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M177.615,288c7.438-36.521,39.688-64,78.396-64
c38.709,0,70.958,27.479,78.376,64h32c-7.834-54.125-54.084-96-110.376-96c-56.271,0-102.541,41.875-110.375,96H177.615z
M256.011,160c8.833,0,16-7.167,16-16v-32c0-8.833-7.167-16-16-16c-8.832,0-16,7.167-16,16v32
C240.011,152.833,247.179,160,256.011,160z M403.073,156.917c-6.249-6.25-16.374-6.25-22.625,0l-22.625,22.625
c-6.249,6.25-6.249,16.375,0,22.625c6.251,6.25,16.376,6.25,22.625,0l22.625-22.625
C409.323,173.292,409.323,163.167,403.073,156.917z M154.177,179.542l-22.625-22.625c-6.249-6.25-16.373-6.25-22.625,0
c-6.249,6.25-6.249,16.375,0,22.625l22.625,22.625c6.252,6.25,16.376,6.25,22.625,0
C160.429,195.917,160.429,185.792,154.177,179.542z M352.011,320h-192c-8.832,0-16,7.167-16,16s7.168,16,16,16h192
c8.833,0,16-7.167,16-16S360.844,320,352.011,320z M320.011,384h-128c-8.832,0-16,7.167-16,16s7.168,16,16,16h128
c8.833,0,16-7.167,16-16S328.844,384,320.011,384z"/>
</g>
</svg>
<svg id="meteo-icon-2" class="meteo-icon" width="24" height="24" viewBox="0 0 512 512">
<g>
<path fill="#1D1D1B" d="M349.852,343.15c-49.876,49.916-131.083,49.916-181,0c-49.916-49.917-49.916-131.125,0-181.021
c13.209-13.187,29.312-23.25,47.832-29.812c5.834-2.042,12.293-0.562,16.625,3.792c4.376,4.375,5.855,10.833,3.793,16.625
c-12.542,35.375-4,73.666,22.249,99.917c26.209,26.228,64.501,34.75,99.917,22.25c5.792-2.062,12.271-0.583,16.625,3.792
c4.376,4.333,5.834,10.812,3.771,16.625C373.143,313.838,363.06,329.941,349.852,343.15z M191.477,184.754
c-37.438,37.438-37.438,98.354,0,135.771c40,40.021,108.125,36.417,143-8.167c-35.959,2.25-71.375-10.729-97.75-37.084
c-26.375-26.354-39.333-61.771-37.084-97.729C196.769,179.796,194.039,182.192,191.477,184.754z"/>
</g>
</svg>
首先,对于一个不完整的示例表示歉意,但我 运行 没时间了,我认为这说明了原则。
我同意@MichaelRovinsky 的观点,SVG 图标比图像更好,但我找不到适合该示例的 SVG 图标的 CDN,我认为原则完全正确同样,因为您也可以将 SVG 嵌入 image
。
为此目的使用 placeimg.com 会导致奇怪的结果,因为相同的 URL 在读取两次时会产生不同的结果,例如两个切片可能会以相同的图像结束,并且当图表重绘时图像会发生变化。
幸运的是,这些都不是定制的重点 dc.js!
向 dc.js 个饼图添加东西
它would be nice如果dc.js使用svg g
组元素来放入文本。然后我们可以直接添加到它并且位置是正确的。
相反,我们必须添加我们的 image
元素并从饼图标签中读取相应的数据以获取位置:
chart.on('pretransition', chart => {
let labelg = chart.select('g.pie-label-group');
let data = labelg.selectAll('text.pie-label').data();
console.log('data', data);
然后我们可以在同一个layer/g:
中添加image
个元素
let pieImage = labelg.selectAll('image.pie-image');
let arcs = chart._buildArcs();
pieImage.data(data)
.join(
enter => enter.append('image')
.attr('class', 'pie-image')
.attr('x', -19)
.attr('y', -19))
.attr('href', d => images[d.data.key === 'Others' ? 'Others' : d.data.key.slice(4)])
.attr('transform', d => chart._labelPosition(d, arcs));
});
注意只需要设置一次(进入时)的属性在连接调用内,而每次重绘(更新时)需要设置的属性在连接调用外。
x
和 y
是图像大小的负二分之一以使图像居中。
我使用了一个对象来存储 URLs 但你可以使用任何东西。
限制
与饼图的任何自定义一样,这不能很好地解释动画。图像将在动画完成之前移动。如果您关心的话,我想我几年前写了一个正确处理这个问题的答案。我可能可以挖掘它,但它非常复杂,恕我直言,不值得。
我已经与 dc.js 合作一年了。最近我的任务是实现一个饼图如下:
我想用适当的图像替换饼图切片中的文本标签。
我看到这是在纯 d3.js 中实现的。有人可以帮我将实现翻译成 dc.js 吗?
pie
.width(600)
.height(500)
.radius(200)
.innerRadius(120)
.dimension(disastersDimension)
.group(disastersGroup)
.on("filtered", function (chart, filter) {
var sel = filter;
let percentage = 0,
value = 0;
let disastersBuffer = [];
totalAmount = 0;
pie.selectAll("text.pie-slice").text((d) => {
percentage = dc.utils.printSingleValue(
((d.endAngle - d.startAngle) / (2 * Math.PI)) * 100
);
disastersBuffer.push({ ...d.data, percentage });
totalAmount += parseFloat(d.data.value);
});
filterPiechart(sel, percentage, totalAmount, disastersBuffer, value);
})
.on("renderlet", (chart) => {
if (!chart.selectAll("g.selected")._groups[0].length) {
chart.filter(null);
filterPiechart("", 100, totalAmount, [], 0);
}
var arc = chart.radius(250).innerRadius(100);
console.log(arc);
var g = chart.selectAll(".pie-slice");
chart
.selectAll(".pie-slice")
.append("image")
.attr("xlink:href", "img/disasters/Floods.png")
.attr("transform", "translate(-10,10) rotate(315)")
.attr("width", "26px")
.attr("hight", "26px")
.style("background-color", "white")
.attr("x", function (d) {
var bbox = this.parentNode.getBBox();
return bbox.x;
})
.attr("y", function () {
var bbox = this.parentNode.getBBox();
return bbox.y;
});
g.append("g")
.append("svg:image")
.attr("xlink:href", function (d) {
let filteredImage = self.piedata.find(
(i) => i.label == d.data.key
);
let image = filteredImage ? filteredImage.image : "";
return image;
})
.attr("width", 30)
.attr("height", 40)
.attr("x", function (d) {
var bbox = this.parentNode.getBBox();
return bbox.x;
})
.attr("y", function (d) {
var bbox = this.parentNode.getBBox();
return bbox.y;
});
})
.addFilterHandler(function (filters, filter) {
filters.length = 0; // empty the array
filters.push(filter);
return filters;
});
我拍了fiddle and added a couple of 'Meteoicons' I found here.
(当然,这些图标只是举例,本人无权商用)
图标存储在单独的 <svg>
元素中。要呈现图标,只需 select 它的根 <g>
元素并将其内容复制到您在饼图中创建的另一个 <g>
:
g.append("g")
.attr("transform", d => `translate(${arc.centroid(d)}) scale(0.25)`)
.append('g')
.attr('transform', 'translate(-256,-256)') // The original icons are 256 x 256
.html(d => d3.select(`#meteo-icon-${... some attribute of d ...} > g`).html())
代码仅供演示,您需要根据自己的需要进行修改。
在下面的代码片段中查看结果:
var width = 550,
height = 550,
radius = 250,
colors = d3.scale.ordinal()
.range(['#336699 ','#336699 ','#ACD1E9','#ACD1E9','#ACD1E9']);
var image_width=40,
image_height=40;
var piedata = [
{
label: "test",
image: "http://placeimg.com/40/40/any",
value: 50
},
{
label: "",
image: "http://placeimg.com/42/42/any",
value: 50
},
{
label: "Jonathan",
image: "http://placeimg.com/44/44/any",
value: 50
},
{
label: "Lorenzo",
image: "http://placeimg.com/46/46/any",
value: 50
},
{
label: "Hillary",
image: "http://placeimg.com/38/38/any",
value: 50
}
]
var pie = d3.layout.pie()
.value(function(d) {
return d.value;
})
var arc = d3.svg.arc()
.outerRadius(250)
.innerRadius(100)
var svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate('+(width-radius)+','+(height-radius)+')');
var g = svg.selectAll(".arc")
.data(pie(piedata))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function(d,i) { return colors(i); });
g.append("g")
.attr("transform", d => `translate(${arc.centroid(d)}) scale(0.25)`)
.append('g')
.attr('transform', 'translate(-256,-256)')
.html(() => d3.select(`#meteo-icon-${Math.random() < 0.5 ? 1 : 2} > g`).html())
.selectAll('path')
.style('fill', 'orange');
path {
stroke: #fff;
fill-rule: evenodd;
}
text {
font-family: Arial, sans-serif;
font-size: 12px;
}
.meteo-icon {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<svg id="meteo-icon-1" class="meteo-icon" width="24" height="24" viewBox="0 0 512 512">
<g>
<path fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M177.615,288c7.438-36.521,39.688-64,78.396-64
c38.709,0,70.958,27.479,78.376,64h32c-7.834-54.125-54.084-96-110.376-96c-56.271,0-102.541,41.875-110.375,96H177.615z
M256.011,160c8.833,0,16-7.167,16-16v-32c0-8.833-7.167-16-16-16c-8.832,0-16,7.167-16,16v32
C240.011,152.833,247.179,160,256.011,160z M403.073,156.917c-6.249-6.25-16.374-6.25-22.625,0l-22.625,22.625
c-6.249,6.25-6.249,16.375,0,22.625c6.251,6.25,16.376,6.25,22.625,0l22.625-22.625
C409.323,173.292,409.323,163.167,403.073,156.917z M154.177,179.542l-22.625-22.625c-6.249-6.25-16.373-6.25-22.625,0
c-6.249,6.25-6.249,16.375,0,22.625l22.625,22.625c6.252,6.25,16.376,6.25,22.625,0
C160.429,195.917,160.429,185.792,154.177,179.542z M352.011,320h-192c-8.832,0-16,7.167-16,16s7.168,16,16,16h192
c8.833,0,16-7.167,16-16S360.844,320,352.011,320z M320.011,384h-128c-8.832,0-16,7.167-16,16s7.168,16,16,16h128
c8.833,0,16-7.167,16-16S328.844,384,320.011,384z"/>
</g>
</svg>
<svg id="meteo-icon-2" class="meteo-icon" width="24" height="24" viewBox="0 0 512 512">
<g>
<path fill="#1D1D1B" d="M349.852,343.15c-49.876,49.916-131.083,49.916-181,0c-49.916-49.917-49.916-131.125,0-181.021
c13.209-13.187,29.312-23.25,47.832-29.812c5.834-2.042,12.293-0.562,16.625,3.792c4.376,4.375,5.855,10.833,3.793,16.625
c-12.542,35.375-4,73.666,22.249,99.917c26.209,26.228,64.501,34.75,99.917,22.25c5.792-2.062,12.271-0.583,16.625,3.792
c4.376,4.333,5.834,10.812,3.771,16.625C373.143,313.838,363.06,329.941,349.852,343.15z M191.477,184.754
c-37.438,37.438-37.438,98.354,0,135.771c40,40.021,108.125,36.417,143-8.167c-35.959,2.25-71.375-10.729-97.75-37.084
c-26.375-26.354-39.333-61.771-37.084-97.729C196.769,179.796,194.039,182.192,191.477,184.754z"/>
</g>
</svg>
首先,对于一个不完整的示例表示歉意,但我 运行 没时间了,我认为这说明了原则。
我同意@MichaelRovinsky 的观点,SVG 图标比图像更好,但我找不到适合该示例的 SVG 图标的 CDN,我认为原则完全正确同样,因为您也可以将 SVG 嵌入
image
。为此目的使用 placeimg.com 会导致奇怪的结果,因为相同的 URL 在读取两次时会产生不同的结果,例如两个切片可能会以相同的图像结束,并且当图表重绘时图像会发生变化。
幸运的是,这些都不是定制的重点 dc.js!
向 dc.js 个饼图添加东西
它would be nice如果dc.js使用svg g
组元素来放入文本。然后我们可以直接添加到它并且位置是正确的。
相反,我们必须添加我们的 image
元素并从饼图标签中读取相应的数据以获取位置:
chart.on('pretransition', chart => {
let labelg = chart.select('g.pie-label-group');
let data = labelg.selectAll('text.pie-label').data();
console.log('data', data);
然后我们可以在同一个layer/g:
中添加image
个元素
let pieImage = labelg.selectAll('image.pie-image');
let arcs = chart._buildArcs();
pieImage.data(data)
.join(
enter => enter.append('image')
.attr('class', 'pie-image')
.attr('x', -19)
.attr('y', -19))
.attr('href', d => images[d.data.key === 'Others' ? 'Others' : d.data.key.slice(4)])
.attr('transform', d => chart._labelPosition(d, arcs));
});
注意只需要设置一次(进入时)的属性在连接调用内,而每次重绘(更新时)需要设置的属性在连接调用外。
x
和 y
是图像大小的负二分之一以使图像居中。
我使用了一个对象来存储 URLs 但你可以使用任何东西。
限制
与饼图的任何自定义一样,这不能很好地解释动画。图像将在动画完成之前移动。如果您关心的话,我想我几年前写了一个正确处理这个问题的答案。我可能可以挖掘它,但它非常复杂,恕我直言,不值得。