在可缩放的旭日形图(D3.js)中破碎的传说和工具提示

时间:2018-02-21 03:45:21

标签: d3.js tooltip legend sunburst-diagram

我有一个可缩放的旭日形图表,其中包含以下问题:

  1. 图例垂直显示而不是水平显示。我认为浮动:留在类传奇上就可以了,但标签显示在新行上。

  2. 允许用户在图例中禁用类别以重新计算旭日形图。

  3. 工具提示未显示。我错过了什么?

  4. 我想在圆环中间追加总计,在变焦过渡时动态变化。怎么去那个?

  5. 我为凌乱的代码道歉。我在两周前开始学习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>

1 个答案:

答案 0 :(得分:0)

这是一个有效的演示:

SUNBURST CHART WITH HTML LEGENDS AND TOOLTIP

  1. floatbackground-color等CSS属性不适用于SVG元素。要使用SVG实现这一点,您必须使用将从左侧转换的'transform('+(i*100)+',0)'。看看docs for the transform attribute.

    但是会有两个主要问题:

    a)值100,它不会根据各自的宽度正确对齐文本(即它们会重叠,看起来很糟糕)。 一个解决方案就是根据每个键的宽度计算来定位图例。

    b)您必须根据页面宽度计算位置,即图例包装必须手动完成。 SVG 元素不要介意离开页面:P

    要克服上述问题,转换为 HTML 在这里是有道理的。它现在只有<div><span>

    因此,CSS将在下面的行中(检查代码的详细信息):

    div#legend .rect {
      width: 10px;
      height: 10px;
      margin-right: 4px;
      display: inline-block;
    }
    
  2. 对于基于图例的数据过滤点击,这就是要编写的一些代码。我建议你先试试,如果你遇到任何问题,请回来。对不起,写一个问题的整个代码是不可取的。但是我已经给你了起点:

    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重新呈现图表来过滤数据。这也应该相当简单。如果没有,请回复一个新问题。

  3. 您的网页上有多个工具提示。我修复了这个bug并添加了一些类和CSS。

    var tooltip = d3.select('body') // select element in the DOM with id 'chart'
       .append('div').classed('tooltip', true);
    

    我从HTML代码中删除了<div class="tooltip"></div>

  4. 你能详细说明这一点吗?我不确定这里的要求究竟是什么。也许你可以尝试我创造的小提琴,让我知道如果你在中心贴上那个标签。

  5. 希望这有帮助(是的,请修复小错误,如果有的话)。 :)