D3.js - как автоматически вычислять длины дуги в радиальной дендрограмме
Я создаю модифицированную версию иерархической диаграммы привязки к диаграмме Майка Бостока:
http://mbostock.github.com/d3/talk/20111116/bundle.html
но я хочу создать дуги, которые охватывают определенные группы данных, например:
![enter image description here]()
В настоящее время я просто кодирую длину дуги, но хочу сделать это динамически. Как я могу это сделать? Вот мой текущий код:
/* MH - USER DEFINED VARIABLES */
var chartConfig = { "Tension" : .85, "canvasSize" : 800, "dataFile" : "../data/projects.json", "linePadding" : 160, "textPadding" : 30, "arcPadding" : 5, "arcWidth" : 30 }
var pi = Math.PI;
var radius = chartConfig.canvasSize / 2,
splines = [];
var cluster = d3.layout.cluster() //Cluster is the diagram style, a node to link dendrogram dendrogram (tree diagram)
.size([360, radius - chartConfig.linePadding]); //MH - sets the size of the circle in relation to the size of the canvas
var bundle = d3.layout.bundle(); //Bundles the node link lines so that they spread at the end but keep close initially
var arcInner = radius - chartConfig.linePadding + chartConfig.arcPadding;
var arcOuter = arcInner + chartConfig.arcWidth;
var arc = d3.svg.arc().innerRadius(arcInner).outerRadius(arcOuter);
var line = d3.svg.line.radial()
.interpolate("bundle")
.tension(chartConfig.Tension) //How tightly to bundle the lines. No tension creates straight lines
.radius(function(d) { return d.y; })
.angle(function(d) { return d.x / 180 * Math.PI; });
var vis = d3.select("#chart").append("svg")
.attr("width", radius * 2)
.attr("height", radius * 2)
.attr("class","svg")
.append("g")
.attr("class","chart")
.attr("transform", "translate(" + radius + "," + radius + ")");
d3.json(chartConfig.dataFile, function(classes) {
var nodes = cluster.nodes(packages.root(classes)),
links = packages.imports(nodes),
splines = bundle(links);
var path = vis.selectAll ("path.link")
.data(links)
.enter().append("path")
.attr("class", function(d){ return "link source-" + d.source.key + " target-" + d.target.key; })
.attr("d", function(d,i){ return line(splines[i]); });
vis.selectAll("g.node")
.data(nodes.filter(function(n) { return !n.children; }))
.enter().append("g")
.attr("class", "node")
.attr("id",function(d){ return "node-" + d.key; })
.attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; })
.append("text")
.attr("dx", function(d) { return d.x < 180 ? chartConfig.textPadding : -chartConfig.textPadding; }) //dx Moves The text out away from the lines in a positive or negative direction, depending on which side of the axis it is on
.attr("dy", ".31em") //moves the text up or down radially around the circle
.attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
.attr("transform", function(d) { return d.x < 180 ? null : "rotate(180)"; })
.text(function(d) {
textString = d.key;
textString = textString.split('_').join(' '); //MH replace underscores with spaces
return textString;
})
.on("mouseover",textOver)
.on("mouseout",textOut);
});
/* ARCS ARE HARDCODED, SHOULD BE DYNAMIC */
var arcData = [
{aS: 0, aE: 45,rI:radius - chartConfig.linePadding + chartConfig.arcPadding,rO:radius - chartConfig.linePadding + chartConfig.textPadding-chartConfig.arcPadding}
];
var arcJobsData = d3.svg.arc().innerRadius(arcData[0].rI).outerRadius(arcData[0].rO).startAngle(degToRad(1)).endAngle(degToRad(15));
var g = d3.select(".chart").append("svg:g").attr("class","arcs");
var arcJobs = d3.select(".arcs").append("svg:path").attr("d",arcJobsData).attr("id","arcJobs").attr("class","arc");
g.append("svg:text").attr("x",3).attr("dy",15).append("svg:textPath").attr("xlink:href","#arcJobs").text("JOBS").attr("class","arcText"); //x shifts x pixels from the starting point of the arc. dy shifts the text y units from the top of the arc
...
function degToRad(degrees){
return degrees * (pi/180);
}
function updateNodes(name,value){
return function(d){
if (value) this.parentNode.appendChild(this);
vis.select("#node-"+d[name].key).classed(name,value);
}
}
Ответы
Ответ 1
Я видел вашу структуру данных json здесь: http://mikeheavers.com/transfers/projects/data/projects.json. Во-первых, чтобы сгруппировать данные и правильно добавить тег, лучше изменить свои данные следующим образом: https://raw.github.com/gist/4172625/4de3e6a68f9721d10e0068d33d1ebb9780db4ae2/flare-imports.json для создания иерархическая структура.
Затем мы можем использовать группы для рисования дуг.
Сначала мы создаем группы с помощью "selectAll" и фильтруем ваши узлы. Здесь вы можете добавить другие имена групп ваших данных:
var groupData = svg.selectAll("g.group")
.data(nodes.filter(function(d) {return (d.key=='Jobs' || d.key == 'Freelance' || d.key == 'Bayard') && d.children; }))
.enter().append("group")
.attr("class", "group");
Я просто проверил это в моем случае, поэтому вам лучше проверить результат фильтра и внести изменения в соответствии с вашим случаем (наша структура данных немного отличается).
Теперь мы получили список групп. Затем мы пройдем через детей каждой группы и выберем наименьший и самый большой х в качестве угла начала и конца. Мы можем создать такую функцию:
function findStartAngle(children) {
var min = children[0].x;
children.forEach(function(d){
if (d.x < min)
min = d.x;
});
return degToRad(min);
}
И аналогично функция findEndAngle, заменив min на max. Затем мы можем создать формат дуг:
var groupArc = d3.svg.arc()
.innerRadius(arcData[0].rI)
.outerRadius(arcData[0].rO)
.startAngle(function(d){return findStartAngle(d.children);})
.endAngle(function(d){return findEndAngle(d.children);});
Затем мы можем создавать дуги "динамическим" способом:
svg.selectAll("g.arc")
.data(groupData[0])
.enter().append("arc")
.attr("d", groupArc)
.attr("class", "arc")
.append("svg:text")
...;
В моем случае это groupData [0], возможно, вам стоит проверить его в своем случае.
Для добавления тегов в дуги вам просто нужно добавить d.key или d.name в соответствии с результатом вашего выбора.
![the arc example]()
Полный код доступен здесь: https://gist.github.com/4172625. Каждый раз, когда я получаю json из базы данных, поэтому, если нет динамического пути к родовым дугам, я буду мертв: P Надеюсь, это поможет вам!