可缩放旭日形图 (D3.js) 中损坏的图例和工具提示
Posted
技术标签:
【中文标题】可缩放旭日形图 (D3.js) 中损坏的图例和工具提示【英文标题】:broken legend and tooltip in zoomable sunburst chart (D3.js) 【发布时间】:2018-07-31 13:23:42 【问题描述】:我有一个可缩放的旭日形图,但存在以下问题:
图例是垂直显示而不是水平显示。我认为类图例上的 float:left 可以解决问题,但标签会显示在新行上。
允许用户禁用图例中的类别以重新计算旭日形图。
工具提示未显示。我到底错过了什么?
我想在甜甜圈的中间附加总计,它会随着缩放转换而动态变化。怎么做呢?
对于混乱的代码,我深表歉意。我大约 2 周前开始学习 D3,下面的代码混合了一堆不同的教程和堆栈溢出论坛。
提前谢谢你!
// define json object
var root =
"name": "TOTAL",
"children": [
"name": "UNASSIGNED",
"children": [
"name": "high", "size": 170,
"name": "med", "size": 701,
"name": "low", "size": 410
]
,
"name": "CLOSED",
"children": [
"name": "high", "size": 1701,
"name": "med", "size": 584,
"name": "low", "size": 606
]
,
"name": "ATTACHED",
"children": [
"name": "high", "size": 220,
"name": "med", "size": 179,
"name": "low", "size": 322
]
,
"name": "NOTIFIED",
"children": [
"name": "high", "size": 883,
"name": "med", "size": 132,
"name": "low", "size": 1066
]
,
"name": "INTEGRATED",
"children": [
"name": "high", "size": 883,
"name": "med", "size": 132,
"name": "low", "size": 416
]
,
"name": "DELIVERED",
"children": [
"name": "high", "size": 170,
"name": "med", "size": 701,
"name": "low", "size": 410
]
,
"name": "ESCALATED",
"children": [
"name": "high", "size": 170,
"name": "med", "size": 701,
"name": "low", "size": 410
]
,
"name": "COMMITTED",
"children": [
"name": "high", "size": 170,
"name": "med", "size": 701,
"name": "low", "size": 410
]
,
"name": "VERIFIED",
"children": [
"name": "high", "size": 170,
"name": "med", "size": 701,
"name": "low", "size": 410
]
,
"name": "SUBMITTED",
"children": [
"name": "high", "size": 170,
"name": "med", "size": 701,
"name": "low", "size": 410
]
]
// set width, height, and radius
var width = 650,
height = 475,
radius = (Math.min(width, height) / 2) - 10; // lowest number divided by 2. Then subtract 10
// legend dimensions
var legendRectSize = 15; // defines the size of the colored squares in legend
var legendSpacing = 6; // defines spacing between squares
var formatNumber = d3.format(",d"); // formats floats
var x = d3.scaleLinear() // continuous scale. preserves proportional differences
.range([0, 2 * Math.PI]); // setting range from 0 to 2 * circumference of a circle
var y = d3.scaleSqrt() // continuous power scale
.range([0, radius]); // setting range from 0 to radius
// setting color scheme
var color =
'TOTAL': '#FFF',
'UNASSIGNED': '#DADFE1',
'ASSIGNED_TO_EDITOR': '#5BCAFF',
'ATTACHED': '#87D37C',
'ASSIGNED_TO_MENTOR': '#F64747',
'ASSIGNED_TO_REVIEWER': '#7BDDDD',
'ASSIGNED_TO_APPROVER': '#1e90ff',
'INTEGRATION_FAILED': '#F1A9A0',
'DELIVERED': '#4183D7',
'INTEGRATED': '#90C695',
'PUBLISHED': '#E4F1FE',
'COMMIT_FAILED': '#F62459',
'NOTIFIED': '#4ECDC4',
'BLOCKED': '#D24D57',
'ESCALATED': '#DB0A5B',
'SUBMITTED': '#86a531',
'REVIEWED': '#bfba00',
'APPROVED': '#C86DEF',
'ASSIGNED_TO_VERIFIER': '#D2527F',
'COMMITTED': '#5AD427',
'VERIFIED': '#81CFE0',
'CLOSED': '#CF000F'
;
var partition = d3.partition(); // subdivides layers
// define arcs
var arc = d3.arc()
.startAngle(function(d) return Math.max(0, Math.min(2 * Math.PI, x(d.x0))); )
.endAngle(function(d) return Math.max(0, Math.min(2 * Math.PI, x(d.x1))); )
.innerRadius(function(d) return Math.max(0, y(d.y0)); )
.outerRadius(function(d) return Math.max(0, y(d.y1)); );
// define tooltip
var tooltip = d3.select('body') // select element in the DOM with id 'chart'
.append('div') // append a div element to the element we've selected
.style("opacity","0")
.style("position","absolute");
tooltip.append('div') // add divs to the tooltip defined above
.attr('class', 'label'); // add class 'label' on the selection
tooltip.append('div') // add divs to the tooltip defined above
.attr('class', 'count'); // add class 'count' on the selection
tooltip.append('div') // add divs to the tooltip defined above
.attr('class', 'percent'); // add class 'percent' on the selection
// define SVG element
var svg = d3.select("#chart").append("svg")
.attr("width", width) // set width
.attr("height", height) // set height
.append("g") // append g element
.attr("transform", "translate(" + width / 2 + "," + (height / 2) + ")");
root = d3.hierarchy(root);
root.sum(function(d) return d.size; );// must call sum on the hierarchy first
var path = svg.selectAll("path")
.data(partition(root).descendants()) // path for each descendant
.enter().append("path")
.attr("d", arc) // draw arcs
.style("fill", function (d) return color[(d.children ? d : d.parent).data.name]; )
.on("click", click)
.append("title")
.text(function(d) return d.data.name + "\n" + formatNumber(d.value);
);
// mouse event handlers are attached to path so they need to come after its definition
path.on('mouseover', function(d) // when mouse enters div
var total = d.data.size
var percent = Math.round(1000 * d.value / total) / 10; // calculate percent
tooltip.select('.label').html(d.data.name); // set current label
tooltip.select('.count').html(total); // set current count
tooltip.select('.percent').html(percent + '%'); // set percent calculated above
tooltip.style('display', 'block'); // set display
);
path.on('mouseout', function() // when mouse leaves div
tooltip.style('display', 'none'); // hide tooltip for that element
);
path.on('mousemove', function(d) // when mouse moves
tooltip.style('top', (d3.event.layerY + 10) + 'px') // always 10px below the cursor
.style('left', (d3.event.layerX + 10) + 'px'); // always 10px to the right of the mouse
);
function click(d)
svg.transition()
.duration(750)
.tween("scale", function()
var xd = d3.interpolate(x.domain(), [d.x0, d.x1]),
yd = d3.interpolate(y.domain(), [d.y0, 1]),
yr = d3.interpolate(y.range(), [d.y0 ? 20 : 0, radius]);
return function(t) x.domain(xd(t)); y.domain(yd(t)).range(yr(t)); ;
)
.selectAll("path")
.attrTween("d", function(d) return function() return arc(d); ; );
d3.select(self.frameElement).style("height", height + "px");
// define legend element
var legendWidth = legendRectSize + legendSpacing; // height of element is the height of the colored square plus the spacing
var width= 500;
var height = 75; // height of element is the height of the colored square plus the spacing
var offset = 80; // vertical offset of the entire legend = height of a single element &
var svgw = 20;
var svgh = 20;
var legendContainer = d3.select("#legend").append("svg")
.attr("width", width) // set width
.attr("height", height) // set height
.append("g") // append g element
.attr("transform", function(d, i)
return "translate(" + i * 20 + ",0)";
);
var legend = legendContainer.selectAll('.legend') // selecting elements with class 'legend'
.data(d3.entries(color)) // refers to an array of labels from our dataset
.enter() // creates placeholder
.append('g') // replace placeholders with g elements
.attr('class', 'legend') // each g is given a legend class
.style('background-color', 'orange')
.attr('transform', function(d, i)
return "translate(0," + i * 20 + ")" //return translation
);
// adding colored squares to legend
legend.append('rect') // append rectangle squares to legend
.attr('x', 0)
.attr('y', 0)
.attr('width', 10) // width of rect size is defined above
.attr('height', 10) // height of rect size is defined above
.style('fill', function (d) return color[d.key]; ) // each fill is passed a color
// adding text to legend
legend.append('text')
.attr('x', 20)
.attr('y', 10)
.attr("text-anchor", "start")
.text(function(d) return d.key; ); // return label
function getRootmostAncestorByWhileLoop(node)
while (node.depth > 1) node = node.parent;
return node;
html, body
height: 100%;
path
stroke: #fff;
/* legend */
#legend
background-color:yellow;
.legend
font-size: 14px;
float: left;
margin-right:1em;
rect
stroke-width: 2;
/* #tooltip
position: absolute;
width: 200px;
height: auto;
padding: 10px;
background-color: white;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
-webkit-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
-moz-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
pointer-events: none;
#tooltip.hidden
display: none;
#tooltip p
margin: 0;
font-family: sans-serif;
font-size: 16px;
line-height: 20px;
*/
.tooltip
opactiy: 0;
positon: absolute;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>D3.js Donut Chart</title>
<link href="https://fonts.googleapis.com/css?family=Open+Sans+Condensed:300|Pacifico" rel="stylesheet">
<link href="styles.css" rel="stylesheet">
</head>
<body>
<div id="chart"></div>
<div id="tooltip" class="hidden">
<p><span id="category"><strong>Important Label Heading</strong></span></p>
<p><span id="value">100</span></p>
</div>
<div id="legend"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.11.0/d3.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.11.0/d3.min.js"></script>
<script src="script.js"></script> <!-- remove if no javascript -->
</body>
</html>
【问题讨论】:
2.工具提示对我来说显示得很好。至于 1. 您正在使用转换来显示页面上的每个矩形/文本比之前的低 -return "translate(0," + i * 20 + ")" //return translation
【参考方案1】:
这是一个工作演示:
SUNBURST CHART WITH HTML LEGENDS AND TOOLTIP
float
、background-color
等 CSS 属性不适用于 SVG 元素。要使用 SVG 实现这一点,您必须使用 'transform('+(i*100)+',0)'
,它将从左侧转换。看看docs for the transform attribute.
但是会有两个主要问题:
a) 值 100
不会根据各自的宽度正确对齐文本(即它们会重叠,看起来很糟糕)。
解决方案 是根据每个键的宽度计算来定位图例。
b) 您还必须根据页面宽度计算位置,即 legend wrapping 必须手动完成。 SVG 元素不介意离开页面:P
为了克服上述问题,在这里转换为 HTML 是有意义的。现在都是<div>
s 和<span>
s。
相应地,CSS 将位于以下行(查看代码了解详细信息):
div#legend .rect
width: 10px;
height: 10px;
margin-right: 4px;
display: inline-block;
对于基于图例点击的数据过滤,这将是一些要编写的代码。我建议您先尝试一下,如果您遇到任何问题,请返回。很抱歉,不建议为一个问题编写整个代码。但我已经给了你它的起点:
legend.on('click', function(d)
if(d3.select(this).classed('clicked'))
d3.select(this).classed('clicked', false)
.style('background-color', function(d) return color[d.key]; );
// filter data and rerender
else
d3.select(this).classed('clicked', true)
.style('background-color', 'transparent');
// filter data and rerender
单击图例,您会注意到这是如何工作的。查看它并尝试根据此key
过滤数据并重新渲染图表。这也应该相当简单。如果没有,请返回一个新问题。
您的页面上有多个工具提示。我修复了这个错误并添加了一些类和 CSS。
var tooltip = d3.select('body') // select element in the DOM with id 'chart'
.append('div').classed('tooltip', true);
我从 HTML 代码中删除了 <div class="tooltip"></div>
。
您能否详细说明这一点?我不确定这里的具体要求是什么。也许您可以尝试使用我创建的小提琴,如果您无法在中心附加该标签,请告诉我。
希望这会有所帮助(是的,请修复小错误,如果有的话)。 :)
【讨论】:
非常感谢您花时间向我解释错误!我已经到了可以判断启用了哪些类别的地步。但是,当我禁用时,整个图表就会消失。我很接近吧? LOL codepen.io/lisaofalltrades/pen/rJYNRv?editors=0011 至于#4,让我尝试一下,然后回复你。我只想在旭日形中间显示图表的总和。 是的,当然!一定要回到我身边。就#3 而言,图表并未根据过滤后的数据重绘(使用启用标志)。为此,sum
计算和绑定到路径的数据必须更改,即partition(filteredRoot)
(观察那里的过滤根)。今天下班后我来帮你。
好的,我试了一下。我能够将文本附加到甜甜圈的中间并更好地重新对齐图例。当我禁用图例中的类别并重新计算总和时,我在重绘图表时仍然遇到一些麻烦。如果你有时间,你能看看我的代码吗?先感谢您。你真棒! codepen.io/lisaofalltrades/pen/rJYNRv
现在这么近了!剩下的唯一一件事是当我禁用图例中的类别时,图表不会重绘。我使用 .tween 还是 .attrTween。或两者? codepen.io/lisaofalltrades/pen/rJYNRv?editors=0011
嘿,对不起,我放了一个月的长假,所以无法回复(我今天回来了)。现在看起来很棒。我会看一下在 legend-toggle 上重绘图表的情况,并通过 EOD 提供修复。以上是关于可缩放旭日形图 (D3.js) 中损坏的图例和工具提示的主要内容,如果未能解决你的问题,请参考以下文章