D3 森伯斯特图深度
D3 Sunburst diagram depth
我正在处理 this example 旭日图。我想要得到的是删除外层,即显示类别 SKU 信息的层,并且只有 Group 和 Category 层。我从未使用过 D3,所以我猜测树深度是个问题,但我无法弄清楚要编辑代码的哪一部分,所以我尝试了其他方法。
我希望解决问题的方法是删除外层中使用的所有 JSON 数据。这 fiddle 显示结果。不幸的是,代码似乎 "smart" 不足以填充空白/调整切片宽度。因为我也不聪明,所以我来找你帮忙。
由于字符数限制,我只发布了我认为相关的部分代码。
原始数据结构:
var data1 = JSON.parse('[
{
"group":["Books","Arts","ZD111111"],"current":{"count":37}
},{
"group":["Electronics","Audio","ZD111288"],"current":{"count":36}
},{
"group":["Electronics","Camcorders","ZD111301"],"current":{"count":35}
}, ... ]);
编辑后的数据结构:
var data1 = JSON.parse('[
{
"group":["Books","Arts"],"current":{"count":37}
},{
"group":["Electronics","Audio"],"current":{"count":36}
},{
"group":["Electronics","Camcorders"],"current":{"count":35}
}, ... ]);
旭日图:
treePath = ["group","category"],
var controller = function(data, progress) {
if(progress === 100) {
data2 = $.extend(true, [], data2);
var flatData = [];
data.map(function(d) {
var item = {};
for(var i = 0; i < treePath.length; i++) {
item[treePath[i]] = d.group[i];
}
//item.size = d3.selectAll("input").filter(function (d) { return this.checked; }).attr("value") === "count" ? d.current.count : d.current.metrics.price.sum;
item.size = d.current.count; // always show count data
item.model = d;
return flatData.push(item);
});
flatData.forEach(function(d) {
d.model.group = d.model.group[d.model.group.length - 1];
});
var treeData = genJSON(flatData, treePath.slice(0, treePath.length - 1));
d3.select("#vis")
.datum(treeData)
.call(chart);
}
};
function genJSON(csvData, groups) {
var genGroups = function(data) {
return _.map(data, function(element, index) {
return { name : index, children : element };
});
};
var nest = function(node, curIndex) {
if (curIndex === 0) {
node.children = genGroups(_.groupBy(csvData, groups[0]));
_.each(node.children, function (child) {
nest(child, curIndex + 1);
});
}
else {
if (curIndex < groups.length) {
node.children = genGroups(
_.groupBy(node.children, groups[curIndex])
);
_.each(node.children, function (child) {
nest(child, curIndex + 1);
});
}
}
return node;
};
return nest({}, 0);
}
function isInt(n) {
return n % 1 === 0;
}
function sunburst() {
var instance = this,
svg = null,
timestamp = new Date().getTime(),
widgetHeight = 600,
widgetWidth = 600,
widgetSize = 'large',
margin = {top: 0, right: 0, bottom: 0, left: 10},
width = widgetWidth - margin.left - margin.right,
height = widgetHeight - margin.top - margin.bottom,
radius = Math.min(width, height) / 2,
x = d3.scale.linear().range([0, 2 * Math.PI]),
y = d3.scale.pow().exponent(1),
pgColor = d3.scale.ordinal().range([
{"family": "Blue", 1: "#0000CC", 2: "#0099FF", 3: "#CCFFFF"},
{"family": "Orange", 1: "#FF6600", 2: "#FFCC00", 3: "#FFFFCC"},
{"family": "Green", 1: "#009900", 2: "#99CC33", 3: "#CCFF99"},
{"family": "Red", 1: "#FF3333", 2: "#FF9999", 3: "#FFCCCC"},
{"family": "Purple", 1: "#CC0099", 2: "#FF66CC", 3: "#FFCCFF"},
{"family": "Grey", 1: "#7b7b7b", 2: "#999999", 3: "#eeeeee"}]),
luminance = d3.scale.sqrt()
.domain([0, 1e6])
.clamp(true)
.range([90, 20]),
i = 0,
partition = d3.layout.partition().sort(function(a, b) { return d3.ascending(a.name || a[treePath[treePath.length - 1]], b.name || b[treePath[treePath.length - 1]])}),
arc = d3.svg.arc()
.startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
.endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); })
.innerRadius(function(d) { return Math.max(0, d.y ? y(d.y) : d.y); })
.outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); });
function chart(selection) {
selection.each(function(data) {
instance.data = data;
width = widgetWidth - margin.left - margin.right;
height = widgetHeight - margin.top - margin.bottom;
radius = Math.min(width, height) / 2;
y.range([0, radius]);
// Select the svg element, if it exists.
svg = d3.select(this).selectAll("svg").data([data]);
var gEnter = svg.enter()
.append("svg")
.attr("width", "600")
.attr("height", "600")
.append("g")
.attr("class", "main-group");
gEnter.append("defs")
.append("clipPath")
.attr("id", "clip-" + timestamp)
.append("rect")
.attr("x", 0)
.attr("y", 0);
var sunburstGroup = gEnter.append("g")
.attr("class", "sunburst-area")
.append("g")
.attr("class", "sunburst-group");
sunburstGroup.append("rect")
.attr("class", "sunburst-background")
.attr("x", 0)
.attr("y", 0)
.style("fill", "white");
// Update the inner group dimensions.
var g = svg.select("g.main-group")
.attr("transform", "translate(" + (width / 2 + margin.left) + "," + (height / 2 + margin.top) + ")");
g.select(".sunburst-background")
.attr("width", width)
.attr("height", height);
partition.value(function(d) { return d.size; })
.nodes(data)
.forEach(function(d) {
d.key = key(d);
});
var path = g.select(".sunburst-group").selectAll(".sunArcs")
.data(partition.nodes(data), function(d) { return d.key; });
path.enter().append("path")
.attr("class", "sunArcs")
.attr("d", arc)
.style("fill", function(d) {
if(d.depth === 0) return "#fff";
var color = pgColor(d.key.split(".")[0]);
return color[d.depth];
})
.style("fill-opacity", 0)
.on("click", click)
.on("mouseover", mouseover)
.on("mouseleave", mouseleave)
.each(function(d) {
this.x0 = d.x;
this.dx0 = d.dx;
});
path.transition()
.duration(duration)
.style("fill-opacity", 1)
.attrTween("d", arcTweenUpdate);
path.exit()
.transition()
.duration(duration)
.attrTween("d", arcTweenUpdate)
.style("fill-opacity", 0)
.remove();
function key(d) {
var k = [], p = d;
while (p.depth) k.push(p.name || p[treePath[treePath.length - 1]]), p = p.parent;
return k.reverse().join(".");
}
function click(d) {
path.transition()
.duration(duration)
.attrTween("d", arcTween(d));
}
function getParents(d) {
var parents = [], p = d;
while (p.depth >= 1) {
parents.push(p);
p = p.parent;
}
return parents;
}
function mouseover(d) {
if(d.depth === 0) return;
var parentNodes = getParents(d);
// Fade all the arcs.
d3.selectAll(".sunArcs")
.style("opacity", 0.3);
// Highlight all arcs in path
d3.selectAll(".sunArcs").filter(function(d){
return (parentNodes.indexOf(d) >= 0);
})
.style("opacity", 1);
// Initialize variables for tooltip
var group = d.name || d[treePath[treePath.length - 1]],
valueFormat = d3.format(",.0f"),
textMargin = 5,
popupMargin = 10,
opacity = 1,
fill = d3.select(this).style("fill"),
hoveredPoint = d3.select(this),
pathEl = hoveredPoint.node(),
// Fade the popup stroke mixing the shape fill with 60% white
popupStrokeColor = d3.rgb(
d3.rgb(fill).r + 0.6 * (255 - d3.rgb(fill).r),
d3.rgb(fill).g + 0.6 * (255 - d3.rgb(fill).g),
d3.rgb(fill).b + 0.6 * (255 - d3.rgb(fill).b)
),
// Fade the popup fill mixing the shape fill with 80% white
popupFillColor = d3.rgb(
d3.rgb(fill).r + 0.8 * (255 - d3.rgb(fill).r),
d3.rgb(fill).g + 0.8 * (255 - d3.rgb(fill).g),
d3.rgb(fill).b + 0.8 * (255 - d3.rgb(fill).b)
),
// The running y value for the text elements
y = 0,
// The maximum bounds of the text elements
w = 0,
h = 0,
t,
box,
rows = [], p = d,
overlap;
var hoverGroup = d3.select(this.parentNode.parentNode.parentNode.parentNode).append("g").attr("class", "hoverGroup");
// Add a group for text
t = hoverGroup.append("g");
// Create a box for the popup in the text group
box = t.append("rect")
.attr("class", "tooltip");
if(!isInt(d.value)) {
valueFormat = d3.format(",.2f");
}
while (p.depth >= 1) {
rows.push(treePath[p.depth - 1] + ": " + (p.name || p[treePath[treePath.length - 1]]));
p = p.parent;
}
rows.reverse();
rows.push("Volume: " + valueFormat(d.value));
t.selectAll(".textHoverShapes").data(rows).enter()
.append("text")
.attr("class", "textHoverShapes")
.text(function (d) { return d; })
.style("font-size", 14);
// Get the max height and width of the text items
t.each(function () {
w = (this.getBBox().width > w ? this.getBBox().width : w);
h = (this.getBBox().width > h ? this.getBBox().height : h);
});
// Position the text relatve to the bubble, the absolute positioning
// will be done by translating the group
t.selectAll("text")
.attr("x", 0)
.attr("y", function () {
// Increment the y position
y += this.getBBox().height;
// Position the text at the centre point
return y - (this.getBBox().height / 2);
});
// Draw the box with a margin around the text
box.attr("x", -textMargin)
.attr("y", -textMargin)
.attr("height", Math.floor(y + textMargin) - 0.5)
.attr("width", w + 2 * textMargin)
.attr("rx", 5)
.attr("ry", 5)
.style("fill", popupFillColor)
.style("stroke", popupStrokeColor)
.style("stroke-width", 2)
.style("opacity", 0.95);
// Move the tooltip box next to the line point
t.attr("transform", "translate(" + margin.left + " , " + 10 + ")");
}
// Mouseleave Handler
function mouseleave(d) {
d3.selectAll(".sunArcs")
.style("opacity", 1);
d3.selectAll(".hoverGroup")
.remove();
}
// Interpolate the scales!
function arcTween(d) {
xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
yd = d3.interpolate(y.domain(), [d.y, 1]),
yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
return function(d, i) {
return i
? function(t) { return arc(d); }
: function(t) { x.domain(xd(t)); y.domain(yd(t)).range(yr(t)); return arc(d); };
};
}
function arcTweenUpdate(a) {
var updateArc = this;
var i = d3.interpolate({x: updateArc.x0, dx: updateArc.dx0}, a);
return function(t) {
var b = i(t);
updateArc.x0 = b.x;
updateArc.dx0 = b.dx;
return arc(i(t));
};
}
});
}
我会接受任何一种解决方案。
谢谢。
删除 SKU 组后,有多个对象具有相同的组。
示例:
{
"group": ["Jewelry", "Diamonds"],
"current": {
"count": 26
}
}
{
"group": ["Jewelry", "Diamonds"],
"current": {
"count": 28
}
}
您应该更正数据或对这些条目求和。我已经更新了您的 fiddle 以在具有相同组的聚合条目后进行测试,现在看起来不错 (https://jsfiddle.net/zwg31n7h/):
var oldData = _.find(flatData, function(f){
var found = true;
_.each(treePath, function(t){
if(f[t] !== item[t]) {
found = false;
}
});
return found;
});
if(oldData) {
oldData.size += item.size;
oldData.model.current.count += item.size;
} else {
flatData.push(item);
}
我正在处理 this example 旭日图。我想要得到的是删除外层,即显示类别 SKU 信息的层,并且只有 Group 和 Category 层。我从未使用过 D3,所以我猜测树深度是个问题,但我无法弄清楚要编辑代码的哪一部分,所以我尝试了其他方法。
我希望解决问题的方法是删除外层中使用的所有 JSON 数据。这 fiddle 显示结果。不幸的是,代码似乎 "smart" 不足以填充空白/调整切片宽度。因为我也不聪明,所以我来找你帮忙。
由于字符数限制,我只发布了我认为相关的部分代码。
原始数据结构:
var data1 = JSON.parse('[
{
"group":["Books","Arts","ZD111111"],"current":{"count":37}
},{
"group":["Electronics","Audio","ZD111288"],"current":{"count":36}
},{
"group":["Electronics","Camcorders","ZD111301"],"current":{"count":35}
}, ... ]);
编辑后的数据结构:
var data1 = JSON.parse('[
{
"group":["Books","Arts"],"current":{"count":37}
},{
"group":["Electronics","Audio"],"current":{"count":36}
},{
"group":["Electronics","Camcorders"],"current":{"count":35}
}, ... ]);
旭日图:
treePath = ["group","category"],
var controller = function(data, progress) {
if(progress === 100) {
data2 = $.extend(true, [], data2);
var flatData = [];
data.map(function(d) {
var item = {};
for(var i = 0; i < treePath.length; i++) {
item[treePath[i]] = d.group[i];
}
//item.size = d3.selectAll("input").filter(function (d) { return this.checked; }).attr("value") === "count" ? d.current.count : d.current.metrics.price.sum;
item.size = d.current.count; // always show count data
item.model = d;
return flatData.push(item);
});
flatData.forEach(function(d) {
d.model.group = d.model.group[d.model.group.length - 1];
});
var treeData = genJSON(flatData, treePath.slice(0, treePath.length - 1));
d3.select("#vis")
.datum(treeData)
.call(chart);
}
};
function genJSON(csvData, groups) {
var genGroups = function(data) {
return _.map(data, function(element, index) {
return { name : index, children : element };
});
};
var nest = function(node, curIndex) {
if (curIndex === 0) {
node.children = genGroups(_.groupBy(csvData, groups[0]));
_.each(node.children, function (child) {
nest(child, curIndex + 1);
});
}
else {
if (curIndex < groups.length) {
node.children = genGroups(
_.groupBy(node.children, groups[curIndex])
);
_.each(node.children, function (child) {
nest(child, curIndex + 1);
});
}
}
return node;
};
return nest({}, 0);
}
function isInt(n) {
return n % 1 === 0;
}
function sunburst() {
var instance = this,
svg = null,
timestamp = new Date().getTime(),
widgetHeight = 600,
widgetWidth = 600,
widgetSize = 'large',
margin = {top: 0, right: 0, bottom: 0, left: 10},
width = widgetWidth - margin.left - margin.right,
height = widgetHeight - margin.top - margin.bottom,
radius = Math.min(width, height) / 2,
x = d3.scale.linear().range([0, 2 * Math.PI]),
y = d3.scale.pow().exponent(1),
pgColor = d3.scale.ordinal().range([
{"family": "Blue", 1: "#0000CC", 2: "#0099FF", 3: "#CCFFFF"},
{"family": "Orange", 1: "#FF6600", 2: "#FFCC00", 3: "#FFFFCC"},
{"family": "Green", 1: "#009900", 2: "#99CC33", 3: "#CCFF99"},
{"family": "Red", 1: "#FF3333", 2: "#FF9999", 3: "#FFCCCC"},
{"family": "Purple", 1: "#CC0099", 2: "#FF66CC", 3: "#FFCCFF"},
{"family": "Grey", 1: "#7b7b7b", 2: "#999999", 3: "#eeeeee"}]),
luminance = d3.scale.sqrt()
.domain([0, 1e6])
.clamp(true)
.range([90, 20]),
i = 0,
partition = d3.layout.partition().sort(function(a, b) { return d3.ascending(a.name || a[treePath[treePath.length - 1]], b.name || b[treePath[treePath.length - 1]])}),
arc = d3.svg.arc()
.startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
.endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); })
.innerRadius(function(d) { return Math.max(0, d.y ? y(d.y) : d.y); })
.outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); });
function chart(selection) {
selection.each(function(data) {
instance.data = data;
width = widgetWidth - margin.left - margin.right;
height = widgetHeight - margin.top - margin.bottom;
radius = Math.min(width, height) / 2;
y.range([0, radius]);
// Select the svg element, if it exists.
svg = d3.select(this).selectAll("svg").data([data]);
var gEnter = svg.enter()
.append("svg")
.attr("width", "600")
.attr("height", "600")
.append("g")
.attr("class", "main-group");
gEnter.append("defs")
.append("clipPath")
.attr("id", "clip-" + timestamp)
.append("rect")
.attr("x", 0)
.attr("y", 0);
var sunburstGroup = gEnter.append("g")
.attr("class", "sunburst-area")
.append("g")
.attr("class", "sunburst-group");
sunburstGroup.append("rect")
.attr("class", "sunburst-background")
.attr("x", 0)
.attr("y", 0)
.style("fill", "white");
// Update the inner group dimensions.
var g = svg.select("g.main-group")
.attr("transform", "translate(" + (width / 2 + margin.left) + "," + (height / 2 + margin.top) + ")");
g.select(".sunburst-background")
.attr("width", width)
.attr("height", height);
partition.value(function(d) { return d.size; })
.nodes(data)
.forEach(function(d) {
d.key = key(d);
});
var path = g.select(".sunburst-group").selectAll(".sunArcs")
.data(partition.nodes(data), function(d) { return d.key; });
path.enter().append("path")
.attr("class", "sunArcs")
.attr("d", arc)
.style("fill", function(d) {
if(d.depth === 0) return "#fff";
var color = pgColor(d.key.split(".")[0]);
return color[d.depth];
})
.style("fill-opacity", 0)
.on("click", click)
.on("mouseover", mouseover)
.on("mouseleave", mouseleave)
.each(function(d) {
this.x0 = d.x;
this.dx0 = d.dx;
});
path.transition()
.duration(duration)
.style("fill-opacity", 1)
.attrTween("d", arcTweenUpdate);
path.exit()
.transition()
.duration(duration)
.attrTween("d", arcTweenUpdate)
.style("fill-opacity", 0)
.remove();
function key(d) {
var k = [], p = d;
while (p.depth) k.push(p.name || p[treePath[treePath.length - 1]]), p = p.parent;
return k.reverse().join(".");
}
function click(d) {
path.transition()
.duration(duration)
.attrTween("d", arcTween(d));
}
function getParents(d) {
var parents = [], p = d;
while (p.depth >= 1) {
parents.push(p);
p = p.parent;
}
return parents;
}
function mouseover(d) {
if(d.depth === 0) return;
var parentNodes = getParents(d);
// Fade all the arcs.
d3.selectAll(".sunArcs")
.style("opacity", 0.3);
// Highlight all arcs in path
d3.selectAll(".sunArcs").filter(function(d){
return (parentNodes.indexOf(d) >= 0);
})
.style("opacity", 1);
// Initialize variables for tooltip
var group = d.name || d[treePath[treePath.length - 1]],
valueFormat = d3.format(",.0f"),
textMargin = 5,
popupMargin = 10,
opacity = 1,
fill = d3.select(this).style("fill"),
hoveredPoint = d3.select(this),
pathEl = hoveredPoint.node(),
// Fade the popup stroke mixing the shape fill with 60% white
popupStrokeColor = d3.rgb(
d3.rgb(fill).r + 0.6 * (255 - d3.rgb(fill).r),
d3.rgb(fill).g + 0.6 * (255 - d3.rgb(fill).g),
d3.rgb(fill).b + 0.6 * (255 - d3.rgb(fill).b)
),
// Fade the popup fill mixing the shape fill with 80% white
popupFillColor = d3.rgb(
d3.rgb(fill).r + 0.8 * (255 - d3.rgb(fill).r),
d3.rgb(fill).g + 0.8 * (255 - d3.rgb(fill).g),
d3.rgb(fill).b + 0.8 * (255 - d3.rgb(fill).b)
),
// The running y value for the text elements
y = 0,
// The maximum bounds of the text elements
w = 0,
h = 0,
t,
box,
rows = [], p = d,
overlap;
var hoverGroup = d3.select(this.parentNode.parentNode.parentNode.parentNode).append("g").attr("class", "hoverGroup");
// Add a group for text
t = hoverGroup.append("g");
// Create a box for the popup in the text group
box = t.append("rect")
.attr("class", "tooltip");
if(!isInt(d.value)) {
valueFormat = d3.format(",.2f");
}
while (p.depth >= 1) {
rows.push(treePath[p.depth - 1] + ": " + (p.name || p[treePath[treePath.length - 1]]));
p = p.parent;
}
rows.reverse();
rows.push("Volume: " + valueFormat(d.value));
t.selectAll(".textHoverShapes").data(rows).enter()
.append("text")
.attr("class", "textHoverShapes")
.text(function (d) { return d; })
.style("font-size", 14);
// Get the max height and width of the text items
t.each(function () {
w = (this.getBBox().width > w ? this.getBBox().width : w);
h = (this.getBBox().width > h ? this.getBBox().height : h);
});
// Position the text relatve to the bubble, the absolute positioning
// will be done by translating the group
t.selectAll("text")
.attr("x", 0)
.attr("y", function () {
// Increment the y position
y += this.getBBox().height;
// Position the text at the centre point
return y - (this.getBBox().height / 2);
});
// Draw the box with a margin around the text
box.attr("x", -textMargin)
.attr("y", -textMargin)
.attr("height", Math.floor(y + textMargin) - 0.5)
.attr("width", w + 2 * textMargin)
.attr("rx", 5)
.attr("ry", 5)
.style("fill", popupFillColor)
.style("stroke", popupStrokeColor)
.style("stroke-width", 2)
.style("opacity", 0.95);
// Move the tooltip box next to the line point
t.attr("transform", "translate(" + margin.left + " , " + 10 + ")");
}
// Mouseleave Handler
function mouseleave(d) {
d3.selectAll(".sunArcs")
.style("opacity", 1);
d3.selectAll(".hoverGroup")
.remove();
}
// Interpolate the scales!
function arcTween(d) {
xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
yd = d3.interpolate(y.domain(), [d.y, 1]),
yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
return function(d, i) {
return i
? function(t) { return arc(d); }
: function(t) { x.domain(xd(t)); y.domain(yd(t)).range(yr(t)); return arc(d); };
};
}
function arcTweenUpdate(a) {
var updateArc = this;
var i = d3.interpolate({x: updateArc.x0, dx: updateArc.dx0}, a);
return function(t) {
var b = i(t);
updateArc.x0 = b.x;
updateArc.dx0 = b.dx;
return arc(i(t));
};
}
});
}
我会接受任何一种解决方案。
谢谢。
删除 SKU 组后,有多个对象具有相同的组。 示例:
{
"group": ["Jewelry", "Diamonds"],
"current": {
"count": 26
}
}
{
"group": ["Jewelry", "Diamonds"],
"current": {
"count": 28
}
}
您应该更正数据或对这些条目求和。我已经更新了您的 fiddle 以在具有相同组的聚合条目后进行测试,现在看起来不错 (https://jsfiddle.net/zwg31n7h/):
var oldData = _.find(flatData, function(f){
var found = true;
_.each(treePath, function(t){
if(f[t] !== item[t]) {
found = false;
}
});
return found;
});
if(oldData) {
oldData.size += item.size;
oldData.model.current.count += item.size;
} else {
flatData.push(item);
}