我试图创建与D3气泡图。 一切工作完全一样的例子,但后来我发现数据被错误地呈现。
所以我跑一个实验:我已经把4“基团”具有不同的儿童的组合以创建一组的合计值100
: 1 x 100
, 2 x 50
, 3 x 33.33
和4 x 25
。 例如,我有这样的数据:
[{
title: "X",
children: [
{
title: "100",
weight: 100
},
]
},
{
title: "X",
children: [
{
title: "50",
weight: 50
},
{
title: "50",
weight: 50
},
]
},
{
title: "X",
children: [
{
title: "33",
weight: 33.33
},
{
title: "33",
weight: 33.33
},
{
title: "33",
weight: 33.33
},
]
},
{
title: "X",
children: [
{
title: "25",
weight: 25
},
{
title: "25",
weight: 25
},
{
title: "25",
weight: 25
},
{
title: "25",
weight: 25
},
]
}]
然后,我使这样的图表:
const rootNode = d3.hierarchy(data);
rootNode.sum(d => d.weight || 0);
const bubbleLayout = d3.pack()
.size([chartHeight, chartHeight])
.radius(d => d.data.weight); // toggling this line on and off makes no difference
let nodes = null;
try {
nodes = bubbleLayout(rootNode).descendants();
} catch (e) {
console.error(e);
throw e;
}
但由此产生的气泡,甚至没有在任何方式:
要定义此渲染的不正确,考虑在截图中间的泡沫:蓝某没有子女具有半径100
和它的实际尺寸为180 px
。 两个气泡的它既右侧具有半径50
,因此它们应该是180 px
宽(沿着相同的轴线放置时)。 但是,什么情况是他们的总直径为256 px
,这让我觉得这是不正确呈现:
这些问题是 :为什么出现这种情况,如何正确地做这个图上看,以便与圆r = 100
具有相同的大小两个圆与r = 50
两者兼而有之?
基于这个问题,我不一定最终目标明确的,但我可以去通过各种可能性的完整性。
我想你想隔代界具有相同面积的比例因子或直径比例因子(面积或直径成正比,跨代的每个节点的一些特定的值)。
或者,你可能只是想有正比于一代每个节点的一些特定值的区域或直径,但我觉得这是不太可能。
除了这些组织的策略,我们可以有正比于叶节点的一些价值的领域或直径。
鉴于该意见的讨论和最近的另一项问题,关于这个主题,我要抓住这个机会去在每个上述组织战略。 理想的情况是一个涵盖这个问题,并链接一个。
下面是基于上述的六大战略:
地区相称
- 包圈,使叶子(无子女的儿童)界有正比区
- 包圆,这样一代圈有正比区
- 包圈,让所有或者多代具有在区域比例圈。
直径/半径的比例
- 包圈,使叶子(无子女的儿童)界有直径成正比
- 包圆,这样一代圈有直径成正比
- 包圈,让所有或者多代有比例有直径的圆。
成果
主要有:一,二,四,五可以实现d3.pack()
三是不可能的。 六是不是一个圆包。
1.比例领域桑叶
这是预期的行为d3.pack()
它不需要太多的讨论。 只有树叶将有比例的地区,任何父母将包括中最小封闭循环圈。 他们的半径是由需要附上孩子,而不是由任何考虑为孩子的大小值是什么决定的。
2.比例领域对于一个世代
这也是可能的d3.pack()
开箱即用-但与一捻。 d3.pack()
将给予叶节点正比于一些施胶值的区域。 这离不开基本上重新编写模块(这已经是最少的友好所有D3模块的篡改)被改变。
该算法无法一些任意代给比例的地区,所以除非我们使用多个圆圈包,我们不能做到这一点的策略。
例
如果我们想扩展最高水平的父母(根的第一代后代,父母叫了本节的剩余部分),那么我们可以创建一个父圆包。 父圈包才会被送入包含根和家长的层次结构。 由于所有的父母都是叶子在这个截层次,他们都将在比例基于一些分配的值区域进行缩放。 然后,我们得出使用这个圈子包g
每个节点。
我们做的圆包酿出自己的圈子包为自己的后代每一父节点之后(这也有一截层次,降低原来的根,而不是根将为每个圆圈包父)。 每一个孩子圈组内的叶节点的面积将是大小成比例的一些分配的值。 叶节点的比例将每个孩子圆包之间的不同,因为这些现在分开包装层次的性质和结构将决定叶子缩放。
这种方法要求我们跟踪的父节点的半径来设置儿童圆包的大小,并在子包(我在下面的代码片段使用局部变量后者)正确定位圈。 这是作为实现获取有关困难,代码主要是因为这将是,如果你是在同一个页面上附加两圈包相同。
这里有一个粗演示:
var svg = d3.select("svg"), diameter = +svg.attr("width"), g = svg.append("g").attr("transform", "translate(2,2)"), colors = ["#ffffcc","#a1dab4","#41b6c4","#225ea8"]; var pack = d3.pack().size([diameter - 4, diameter - 4]); var local = d3.local(); var root = {"name": "root","children": [{"name": "Node A","size": 100},{"name": "Node B","size": 100},{"name": "Node C","size": 100}]} var children = [{"name":"NodeA","children":[{"name":"Node1","size":34},{"name":"Node2","size":33},{"name":"Node3","size":33}]},{"name":"NodeB","children":[{"name":"Node1","size":50},{"name":"Node2","size":50}]},{"name":"NodeC","children":[{"name":"Node1","children":[{"name":"Nodea","size":15},{"name":"Nodeb","size":12},{"name":"Nodec","size":10}]},{"name":"Node2","size":10},{"name":"Node3","size":13},{"name":"Node4","size":9},{"name":"Node5","size":6},{"name":"Node6","size":10},{"name":"Node7","size":15}]}] // parent pack: root = d3.hierarchy(root) .sum(function(d) { return d.size; }) .sort(function(a, b) { return b.value - a.value; }); // Parent Circle Pack var node = g.selectAll(null) .data(pack(root).descendants()) .enter().append("g") .attr("transform", function(d) { return "translate(" + dx + "," + dy + ")"; }) .attr("fill", function(d) { return colors[d.depth]; }); // Parent circle: node.append("circle") .attr("r", function(d) { return dr; }); // get radii var radii = pack(root).descendants().filter(function(d) { return d.depth == 1; }).map(function(d) { return dr; }); // Create child pack data: var childRoots = children.map(function(child,i) { var childPack = d3.pack().size([radii[i]*2 - 2, radii[i]*2 - 2]); var childRoot = d3.hierarchy(child) .sum(function(d) { return d.size; }) .sort(function(a,b) { return b.value - a.value; }); return childPack(childRoot).descendants(); }) // Swap node data for child node data, but keep the original data handy. var childNodes = node.each(function(d,i) { local.set(this, d); // but store the data in the local variable. }) .filter(function(d,i) { return i > 0; }) .data(childRoots) .selectAll("g") .data(function(d) { return d; }) .enter() .append("g") .attr("transform", function(d) { var offset = local.get(this).r; return "translate(" + (dx-offset) + "," + (dy-offset) + ")"; }) .attr("fill", function(d) { return colors[d.depth+1]; }); // Append child elements to each node: childNodes.filter(function(d) { return d.depth > 0 }) // skip parent - it's already drawn. .append("circle") .attr("r", function(d) { return dr; }); childNodes.filter(function(d) { return !d.children }) .append("text") .text(function(d) { return d.data.name; }) .attr("fill","black") .style("text-anchor","middle") .attr("dy", 5);
<svg width="600" height="600"></svg> <script src="https://d3js.org/d3.v4.min.js"></script>
每个父具有100的尺寸值,这是巧合(确定,不是巧合,我有意这样做是)累积总所有最深子(叶)的节点的尺寸为每个父的。 每个父节点也是同样的尺寸:
当然,我们可以养活父圈包根的孙子,如果我们想要比例缩放那一代。
3.跨代地区相称
让我们用一个简单的二代圈包:有一些孩子家长。
如果父母有相同的面比例因子作为其子那么孩子的总面积将等于其母公司的区域。
如果我们要收拾这些孩子到他们的父母,我们必须在不产生空隙的方式做到这一点。 用圆圈时,这是不可能的。
空隙也就是为什么这是不可能的 - 不止一个孩子的父母总是比其子的面积之和更大的面积。
如果代间比例是关键然后树形图可以实现这一点,因为D3文档中描述的代价是:
虽然圈包装不作为有效地利用空间,树形图中,“浪费”空间更突出显示的层次结构。 ( 文档 )
例外
如果父母有大小值是比他们的孩子则取决于值的累积大小值大,圆形包装是可能的。 为了证明这种有限性,考虑两个同样大小的孩子的父母。 在最有效的圆包与这两个孩子都需要父母是孩子(注:合并区域面积的两倍,如果大于2倍大,然后我们是不是圆的包装为我们要么不使用最小封闭环或孩子不接触)。
同样有可能(取决于值和层次),以具有跨越世代相同的面缩放因子是否存在的代与代之间或在父母足够叶节点,使得两个世代的累计大小值(并且因此区域)节点不相等。
如果我们使用上述第二种方法(单代比例缩放)和所选择的生成仅具有独生子女,那么孩子将被按比例缩放,以包在另一圆圈没有空隙的唯一方法是包装在一个单一的圆家长。
前两个子弹就可能需要手动修改/验证值仍然是圆形包装,如果他们从圈误入包装(最小外接圆作为父母 - 没有填充或利润),然后d3.pack()不再是正确的工具。
我添加这些例外的完整性,我认为他们是非常不可能的,除了从单一的儿童所产生的异常(但如果比例相同,他们的父母,完全覆盖父母反正)。
4,比例为直径叶片
如果d3.pack()
假定大小值应该是成正比的叶圆的面积,那么我们就可以用面积和直径之间的关系得到一个大小值,将创建比例领域直径:
size = Math.pow(size/2,2);
我们正在处理的初始大小值作为直径,找出与直径圆的面积(按比例,所以我们并不需要π,因为我们将通过π乘以每个结果)。 这里有一个快速演示:
var svg = d3.select("svg"), diameter = +svg.attr("width"), g = svg.append("g").attr("transform", "translate(2,2)"), colors = ["#ffffcc","#a1dab4","#41b6c4","#225ea8"]; var pack = d3.pack().size([diameter - 4, diameter - 4]); var root = {"name": "root","children": [{"name": "Node A","size": 100},{"name": "Node B",children:[{"name": "Node 1", "size":50},{"name": "Node 2", "size":50}]}]} root = d3.hierarchy(root) .sum(function(d) { return Math.pow(d.size/2,2); }) .sort(function(a, b) { return b.value - a.value; }); var node = g.selectAll(null) .data(pack(root).descendants()) .enter().append("g") .attr("transform", function(d) { return "translate(" + dx + "," + dy + ")"; }) .attr("fill", function(d) { return colors[d.depth]; }); node.append("circle") .attr("r", function(d) { return dr; });
<svg width="600" height="600"></svg> <script src="https://d3js.org/d3.v4.min.js"></script>
和视觉的片断:
左边的(叶)圆圈的大小为100,右边的(父)圈有两个孩子(叶),每个大小为50(累计100)。 它可以通过缩放这样,我们同时缩放树叶和父母一样出现像。 这仅仅是用两个相同大小的孩子界打交道时,会出现一个令人高兴的巧合。
在一个包中的孩子必须接触。 如果孩子是相同的尺寸和感人的,最小的封闭圈将永远有一个直径等于孩子的总和。 与同样大小的两个孩子打交道时,这种模式是唯一的真实。
我们可以看到,有五个孩子家长不会出现相同的尺寸,有两个孩子,即使孩子们圆的直径总和是每个父母同父:
这里,叶节点的直径(或半径)方面的所有比例。 例如,左侧的大的第一代叶具有100的尺寸值 - 它是横跨298个像素(1:2.98),两个叶片在大右手圆具有的每个50的尺寸值和跨是149个像素(1:2.98)。 的五个叶在下部圆具有20中的每个的大小值,并且可以在(1:2.98)59.6像素。
在底部的五个孩子圆和两个孩子圈在右边具有相同的累积径(和相同的:尽管叶节点直径(或半径)的比例,作为一个向上移动的层次,这是因为一旦丢失在数据累积大小值),但父母有明显的不同尺寸。
5.比例直径对于一代
使用直径和面积,我们可以创建缩放值之间的关系,以传递给d3.pack(),其代表对于给定的直径的区域(与在上文#4)。
一旦我们得到的面积值,该过程是相同的缩放区域为单个代(同上#2)。 而已。
6.相称直径的各代
不同的地区,这是可能的。 但不能与d3.pack()
我们在这里不是包装界 - 我们正在收拾直径和直径线。 我们包装的一条维线(其发生在用圆来表示)。
让我们假设一个简单的父母多生几个孩子的例子。 父母的直径必须等于孩子的直径总和,如果缩放倍率是跨代保持一致。 只有一个办法用最少的封闭圈来实现这一点:
如果你是这适用于所有的世代,然后各界将上线锚定 - 因为我们实际上线的包装。
d3.pack
因为它包在二维空间的2D圈,在这里我们只需要包上的一维管道1d线实现这一战略将不会在这里工作。
这种策略或许可以用一些比较简单的数学实现。
例外
但也有例外在某些情况下,如在战略#3检查。
有一个其他异常:其中每个节点有两个孩子与同等大小的层次结构。 我不知道,D3可能只是它绘制一条线,但它会与工作d3.pack
。 但是,目前尚不清楚为什么某种树的布局不会是这里优越。
摘要
肥皂盒
圈包装是层次传达定量数据较差的方法。 它是用于输送层次,与迈克的报价上面提到的更好。 我想进一步创业,通过人分布区界的判断较差。 我也建议,在相同的比例因子大小叶节点很可能不是直观的读者,如果叶子散落在不同的世代。 如果需要的基本价值的快速和直观的定量理解,圆形包装是不理想的解决方案。 这就是说,
结论
圈包装不并不能代表一个一致的面积比例因子的所有领域:圆包装是指空隙,空隙意味着家长圈会比他们的孩子圆面积的总和更大的区域。 如果你需要的所有代具有恒定的面积规模,一个树形图可能是你所需要的。 是的,也有一些例外的#3所指出的,但这些都是很少实际使用中基本上是理论性 。
圈填料只能代表具有恒定面积的比例因子或全部叶节点的任一个代 - 不能同时使用。 两种方法都可以实现d3.pack
圈包装可以按比例代表直径为无论是叶或一代。 再次两种方法可以实现d3.pack
。
跨越部分或全部代直径相称圈包装是不可能的。 布局可以做 - 但它不是圆形包装。 我们可以收紧上面的图片中的三个子圈的安排,但我们没有一个最小的封闭圈(因此我们没有圈包装)。 在一行离开他们也不是圆包装。 对于这个d3.pack()
是没有用的-因为我们没有包装界了。
有可能是不使用最小外接圆或使用不同世代不同大小的尺度(这将可能 (实际上,不是理论)总是需要开沟最小外接圆为好)其他布局选项。 这使我们以及外圆包装,我不知道是什么,有可以提供帮助。