【发布时间】:2014-03-21 18:52:06
【问题描述】:
长期用户第一次在这里提问(我第一次无法使用我搜索的答案解决问题)。我用自己的数据在 JS D3 中重新创建了 uber 和弦图。我的实现可以在 here 找到,尽管该地址可能不会永远保持良好,所以这里是代码(请原谅一些错位):
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script type="text/javascript" src="d3/d3.js"></script>
<script type="text/javascript" src="d3/d3.layout.js"></script>
<link type="text/css" rel="stylesheet" href="style.css"/>
</head>
<body>
<div id="body">
<div id="footer">
Purdue OIR Testing - Migration
<div class="hint">mouseover groups to highlight</div>
</div>
</div>
<div id="tooltip"></div>
<script type="text/javascript">
//import the data and call the draw chords function
d3.text("migrationdata.csv", function(data) {
var matrix = d3.csv.parseRows(data).map(function(row) {
return row.map(function(value) {
return +value;
});
});
d3.text("headersColors.csv", function(headerdata) {
var headersColors = d3.csv.parseRows(headerdata);
var headers = headersColors[1];
var colors = headersColors[2];
drawChords(matrix, headers, colors);
});
});
//create the chord viz
function drawChords (matrix, headers, colors){
var w = 980,
h = 800,
r1 = h / 2,
r0 = r1 - 110,
fadeOutA = 0,
fadeInA = 0.8;
var fill = d3.scale.category20c();
var chord = d3.layout.chord()
.padding(.02)
.sortSubgroups(d3.descending)
.sortChords(d3.descending);
var arc = d3.svg.arc()
.innerRadius(r0)
.outerRadius(r0 + 20);
var svg = d3.select("body").append("svg:svg")
.attr("width", w)
.attr("height", h)
.append("svg:g")
.attr("id", "circle")
.attr("transform", "translate(" + w / 2 + "," + h / 2 + ")");
svg.append("circle")
.attr("r", r0 + 20);
//.attr("fill-opacity",0);
//assign the matrix
chord.matrix(matrix);
//create the groups
var g = svg.selectAll("g.group")
.data(chord.groups)
.enter().append("svg:g")
.attr("class", "group")
.on("mouseover", mouseover)
.on("mouseout", function (d) { d3.select("#tooltip").style("visibility", "hidden") });
g.append("svg:path")
.style("stroke", function(d) { return colors[d.index]; })
.style("fill", function(d) { return colors[d.index]; })
.attr("d", arc);
g.append("svg:text")
.each(function(d) { d.angle = (d.startAngle + d.endAngle) / 2; })
.attr("dy", ".35em")
.attr("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; })
.attr("transform", function(d) {
return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
+ "translate(" + (r0 + 26) + ")"
+ (d.angle > Math.PI ? "rotate(180)" : "");
})
.text(function(d) { return headers[d.index]; });
var chordPaths = svg.selectAll("path.chord")
.data(chord.chords)
.enter().append("svg:path")
.attr("class", "chord")
.style("stroke", function(d) { return d3.rgb(colors[d.source.index]).darker(); })
.style("fill", function(d) { return colors[d.source.index]; })
.attr("d", d3.svg.chord().radius(r0))
.on("mouseover", function (d) {
d3.select("#tooltip")
.style("visibility","visible")
.html(chordTip(d))
.style("left", (d3.event.pageX - 100) + "px")
.style("top", (d3.event.pageY - 100) + "px");
})
.on("mouseout", function (d) { d3.select("#tooltip").style("visibility", "hidden") });
function chordTip (d) {
var p = d3.format(".1%"), q = d3.format(",.2r")
return "Migration Info:<br/>"
+ headers[d.source.index] + " → " + headers[d.target.index]
+ ": " + Math.round(d.source.value) + "<br/>"
+ headers[d.target.index] + " → " + headers[d.source.index]
+ ": " + Math.round(d.target.value) + "<br/>";
}
function groupTip (d) {
return "College Info:<br/>"
+ headers[d.index] + " : " + Math.round(d.value) + "<br/>";
}
function mouseover(d, i) {
d3.select("#tooltip")
.style("visibility", "visible")
.html(groupTip(d))
.style("top", function () { return (d3.event.pageY - 80)+"px"})
.style("left", function () { return (d3.event.pageX - 130)+"px";})
chordPaths.classed("fade", function(p) {
return p.source.index != i
&& p.target.index != i;
});
}
}
// Returns an event handler for fading a given chord group.
function fade(opacity) {
return function(d, i) {
svg.selectAll("path.chord")
.filter(function(d) { return d.source.index != i && d.target.index != i; })
.transition()
.style("stroke-opacity", opacity)
.style("fill-opacity", opacity);
};
}
</script>
</body>
</html>
这很好用,而且功能齐全。它显示了从 2012 年秋季到 2013 年秋季大学之间的学生流动(我打算在屏幕上进行更好的解释)。我的下一步是使这个可钻。因此,例如,如果您单击一所大学,我想将该大学扩展到其部门,并显示各个部门之间的和弦以及“其他”,这将是所有其他大学的组合。此外,您将能够以相同的方式深入部门以获取主要级别的详细信息。我拥有执行此操作所需的所有原始数据,并且我相当有信心知道如何在单击组时使用它来动态创建新矩阵。
我的问题是,如果我每次都将当前的可视化项转储为具有新数据的新可视化项,那么执行我上面描述的所有操作都相当简单,但这将是一个艰难的过渡。我更喜欢像this 这样的动画。但是,该示例仅适用于您的矩阵在转换的每一端的大小相同的情况。由于我想深入研究这些数据,这对我来说并不总是正确的。我可以拥有比以前更少或更多的组/和弦。我的问题是,如果我创建一个新的和弦布局,其矩阵比当前屏幕上的矩阵更大/更小,我能以某种方式平滑地为新的/不需要的组/和弦设置动画吗?如果是这样,我将如何处理?是否有任何示例?
我对 D3 还很陌生,但我正在努力快速学习,因为这里对这些类型的可视化有大量需求。如果有关于这方面的教程,请随意简单地链接它,我已经断断续续地研究了几天,但没有找到任何令人满意的解释如何在 d3 布局中平滑添加/删除元素。
【问题讨论】:
-
只要你把所有矩阵和适当的标签数组组织起来,你应该能够找到一个平滑的过渡,即使是不同大小的矩阵。我不久前写了一个long discussion of chord diagrams and transitions,它应该可以帮助您入门,尽管该示例转换为不同的数字但相同的类别。你必须决定你想要过渡的样子。被点击的组会只是分成更小的部分,还是会扩大到整个圈子?
-
谢谢阿米莉亚。这是大量很棒的信息,我认为这可能就是我所需要的。我现在正在考虑该组将“分裂”并且所有这些新组都保留相同的颜色以表明它们属于同一学院/系。如果那太忙了,我会考虑到那里时如何处理。现在,看起来你对进入和退出的解释会很厚,但我会阅读,尝试一些事情,如果我有进一步的问题,请回来。谢谢!
-
好的!我在这方面取得了很大进展。到目前为止,这是我所拥有的:purdue.edu/oir/test/CODOchord 动画有点混乱,因为我并没有真正按照预期使用您的代码,但这是我以后可以改进的地方。关于我无法工作的事情的一个大问题。当我使用 uber 示例时,我会在鼠标悬停时立即弹出工具提示,而我无法获得淡出和弦的工具提示。在这个版本中,我可以获得淡出和弦的工具提示,我必须依赖悬停标题而不是工具提示 div。如何获取旧的工具提示?
-
就过渡而言,我认为您想要的是让您的进入/退出元素只是淡入到位而不是形状过渡。这实际上应该简化您的代码。对于您的工具提示,您可能希望将 mouseover 和 mouseout 事件处理程序分开为命名函数,以便您可以在更新时轻松添加或删除它们(通过将其替换为
null来删除事件处理程序,如.on("mouseover", null))。另一种选择是保留鼠标悬停功能,但在“淡出”元素上将pointer-events样式设置为none,这样它们就不会触发鼠标事件。 -
现在效果很好!在我继续清理页面之前,我需要与我的同事讨论一些功能/格式,但这完全符合我现在的想法。谢谢你的帮助!不过,不确定将什么标记为“答案”。
标签: javascript animation d3.js chord-diagram