我使用D3.js树 for each node 呈现一个量规.只有根 node 呈现仪表指针(路径对象).我研究了几个来源,试图了解这个问题,但我无法确定原因.

在本例中,我的目标是显示指向所有 node 的指针,使用Json"pointerValue"属性定位指针.感谢大家的关注.

这是我的代码

<html>
<style>

    g.arc {
      fill: steelblue;
    }

    g.pointer {
      fill: #e85116;
      stroke: #b64011;
    }

    g.label text {
      text-anchor: middle;
      font-size: 10px;
      font-weight: bold;
      fill: #666;
    }

    .node {
        cursor: pointer;
    }

    .node circle {
        fill: #fff;
        stroke: steelblue;
        stroke-width: 3px;
    }

    .node text {
        font: 14px sans-serif;
    }

    .node text:hover {
      font-weight: bold;
    }

    .link {
        fill: none;
        stroke: #ccc;
        stroke-width: 2px;
    }
</style>

<body>

    <!-- load the d3.js library -->
    <script src="https://d3js.org/d3.v5.min.js"></script>
    <script>

    function deg2rad(deg) {
        return deg * Math.PI / 180;
    }

    function newAngle(d) {
        var ratio = scale(d);
        var newAngle = config.minAngle + (ratio * range);
        return newAngle;
    }

    function centerTranslation(r) {
        return 'translate('+ r +','+ r +')';
    }

    var config = {
        size                        : 100,
        clipWidth                   : 100,
        clipHeight                  : 60,
        ringInset                   : 20,
        ringWidth                   : 20,

        pointerWidth                : 10,
        pointerTailLength           : 5,
        pointerHeadLengthPercent    : 0.8,

        minValue                    : 0,
        maxValue                    : 100,

        minAngle                    : -90,
        maxAngle                    : 90,

        transitionMs                : 3000,

        majorTicks                  : 5,
        labelFormat                 : d3.format('d'),
        labelInset                  : 10,

        arcColorFn                  : d3.interpolateHsl(d3.rgb('#e8e2ca'), d3.rgb('#3e6c0a'))
    };

    range = config.maxAngle - config.minAngle;
    r = config.size / 2;
    pointerHeadLength = Math.round(r * config.pointerHeadLengthPercent);


    scale = d3.scaleLinear()
        .range([0,1])
        .domain([config.minValue, config.maxValue]);

    ticks = scale.ticks(config.majorTicks);
    tickData = d3.range(config.majorTicks).map(function() {return 1/config.majorTicks;});

    var arc = d3.arc()
    .innerRadius(r - config.ringWidth - config.ringInset)
    .outerRadius(r - config.ringInset)
    .startAngle(function(d, i) {
        var ratio = d * i;
        return deg2rad(config.minAngle + (ratio * range));
    })
    .endAngle(function(d, i) {
        var ratio = d * (i+1);
        return deg2rad(config.minAngle + (ratio * range));
    });

    var lineData = [ [config.pointerWidth / 2, 0],
                     [0, -pointerHeadLength],
                     [-(config.pointerWidth / 2), 0],
                     [0, config.pointerTailLength],
                     [config.pointerWidth / 2, 0] ];

    let treeData =
      {
        "id": "0",
        "name": "Root",
        "pointerValue": "10",
        "children": [
          {
            "id": "1",
            "name": "Node x",
            "pointerValue": "55",
            "children": [
              {
                "id": "2",
                "name": "Node y",
                "pointerValue": "75"
              },
              {
                "id": "3",
                "name": "Node z",
                "pointerValue": "95"
              }
            ]
          },
          {
            "id": "4",
            "name": "Node w",
            "pointerValue": "35"
          }
        ]
      };


// Set the dimensions and margins of the diagram
var margin = {top: 20, right: 90, bottom: 30, left: 90},
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

// append the svg object to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3.select("body").append("svg")
    .attr("width", width + margin.right + margin.left)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform", "translate("
          + margin.left + "," + margin.top + ")");

var i = 0,
    duration = 750,
    root;

// declares a tree layout and assigns the size
var treemap = d3.tree().size([height, width]);

// Assigns parent, children, height, depth
root = d3.hierarchy(treeData, function(d) { return d.children; });
root.x0 = height / 2;
root.y0 = 0;

// Collapse after the second level
root.children.forEach(collapse);

update(root);

// Collapse the node and all it's children
function collapse(d) {
  if(d.children) {
    d._children = d.children
    d._children.forEach(collapse)
    d.children = null
  }
}

function update(source) {

    // Assigns the x and y position for the nodes
    var treeData = treemap(root);

    // Compute the new tree layout.
    var nodes = treeData.descendants(),
    links = treeData.descendants().slice(1);

    // Normalize for fixed-depth.
    nodes.forEach(function(d){ d.y = d.depth * 400});

    // ****************** Nodes section ***************************

    // Update the nodes...
    var node = svg.selectAll('g.node')
        .data(nodes, function(d) {
            //alert(d.data.name);
            return d.id || (d.id = ++i);
        });

    // Enter any new nodes at the parent's previous position.
    var nodeEnter = node.enter().append('g')
        .attr('class', 'node')
        .attr("transform", function(d) {
            return "translate(" + source.y0 + "," + source.x0 + ")";
        }).on('click', click);

    nodeEnter.append('text')
          .attr("dy", ".35em")
          .attr("x", d => d.children || d._children ? 50 : -55)
          .attr("y", -5)
          //.attr("id", function(d) {
                //alert(d.data.id);
          //    return "node-" + d.data.id;
          //})
          .attr("text-anchor", d => d.children || d._children ? "end" : "start")
          .text(d => d.data.name);

    //#########################################################################

    var vis = nodeEnter.append('svg')
            //.attr("id", function(d) {
            //    return "gauge-" + d.data.id;
            //})
            .attr('class', 'gauge')
            .attr('width', config.clipWidth)
            .attr('height', config.clipHeight)
            .attr("y", -70)
            .attr("x", -50);

    var arcs = vis.append('g')
            //.attr("id", function(d) {
            //    return "arc-" + d.data.id;
            //})
            .attr('class', 'arc')
            .attr('transform', centerTranslation(r));
    arcs.selectAll('path')
            .data(tickData)
        .enter().append('path')
            .attr('fill', function(d, i) {
                return config.arcColorFn(d * i);
            })
            .attr('d', arc);

    var lg = vis.append('g')
            //.attr("id", function(d) {
            //    return "label-" + d.data.id;
            //})
            .attr('class', 'label')
            .attr('transform', centerTranslation(r));
        lg.selectAll('text')
            .data(ticks)
        .enter().append('text')
            .attr('transform', function(d) {
                var ratio = scale(d);
                var newAngle = config.minAngle + (ratio * range);
                return 'rotate(' + newAngle +') translate(0,' +(config.labelInset - r) +')';
            })
            .text(config.labelFormat);

    var pg = vis.append('g')
            .data([lineData])
            .attr('class', 'pointer')
            .attr('transform', centerTranslation(r));

    //########################################
    //THIS PART JUST WORKS FOR ROOT NODE
    //########################################

    var ratio = scale(87);
    var newAngle = config.minAngle + (ratio * range);

    pg.append('path')
        //d attribute that defines its shape and position.
        .attr("d", d3.line().curve(d3.curveLinear) )
        .attr("stroke", "black")
        .attr('transform', 'rotate(' +config.minAngle +')')
            .transition()
            .duration(config.transitionMs)
            .ease(d3.easeElastic)
        .attr('transform', 'rotate(' + newAngle +')');

//#########################################################################

  // UPDATE
  var nodeUpdate = nodeEnter.merge(node);

  // Transition to the proper position for the node
  nodeUpdate.transition()
    .duration(duration)
    .attr("transform", function(d) {
        return "translate(" + d.y + "," + d.x + ")";
     });

  // Update the node attributes and style
  nodeUpdate.select('circle.node')
    .attr('r', 10)
    .style("fill", function(d) {
        return d._children ? "lightsteelblue" : "#fff";
    })
    .attr('cursor', 'pointer');

  // Remove any exiting nodes
  var nodeExit = node.exit().transition()
      .duration(duration)
      .attr("transform", function(d) {
          return "translate(" + source.y + "," + source.x + ")";
      })
      .remove();

  // On exit reduce the node circles size to 0
  nodeExit.select('circle')
    .attr('r', 1e-6);

  // On exit reduce the opacity of text labels
  nodeExit.select('text')
    .style('fill-opacity', 1e-6);

  // ****************** links section ***************************

  // Update the links...
  var link = svg.selectAll('path.link')
      .data(links, function(d) { return d.id; });

  // Enter any new links at the parent's previous position.
  var linkEnter = link.enter().insert('path', "g")
      .attr("class", "link")
      .attr('d', function(d){
        var o = {x: source.x0, y: source.y0}
        return diagonal(o, o)
      });

  // UPDATE
  var linkUpdate = linkEnter.merge(link);

  // Transition back to the parent element position
  linkUpdate.transition()
      .duration(duration)
      .attr('d', function(d){ return diagonal(d, d.parent) });

  // Remove any exiting links
  var linkExit = link.exit().transition()
      .duration(duration)
      .attr('d', function(d) {
        var o = {x: source.x, y: source.y}
        return diagonal(o, o)
      })
      .remove();

  // Store the old positions for transition.
  nodes.forEach(function(d){
    d.x0 = d.x;
    d.y0 = d.y;
  });

  // Creates a curved (diagonal) path from parent to the child nodes
  function diagonal(s, d) {

    path = `M ${s.y} ${s.x}
            C ${(s.y + d.y) / 2} ${s.x},
              ${(s.y + d.y) / 2} ${d.x},
              ${d.y} ${d.x}`

    return path
  }

  // Toggle children on click.
  function click(d) {
    if (d.children) {
        d._children = d.children;
        d.children = null;
      } else {
        d.children = d._children;
        d._children = null;
      }
    update(d);
  }
}

</script>
</body>
</html>

推荐答案

您可以使用datum将每个数据数组附加到<path>,或者,如果您想要使用data,则创建一个适当的Enter Select :

var pg = vis.selectAll(null)
    .data([lineData])
    .enter()
    .append("g")
    .attr('class', 'pointer')

以下是更改后的代码:

<html>
<style>
  g.arc {
    fill: steelblue;
  }
  
  g.pointer {
    fill: #e85116;
    stroke: #b64011;
  }
  
  g.label text {
    text-anchor: middle;
    font-size: 10px;
    font-weight: bold;
    fill: #666;
  }
  
  .node {
    cursor: pointer;
  }
  
  .node circle {
    fill: #fff;
    stroke: steelblue;
    stroke-width: 3px;
  }
  
  .node text {
    font: 14px sans-serif;
  }
  
  .node text:hover {
    font-weight: bold;
  }
  
  .link {
    fill: none;
    stroke: #ccc;
    stroke-width: 2px;
  }
</style>

<body>

  <!-- load the d3.js library -->
  <script src="https://d3js.org/d3.v5.min.js"></script>
  <script>
    function deg2rad(deg) {
      return deg * Math.PI / 180;
    }

    function newAngle(d) {
      var ratio = scale(d);
      var newAngle = config.minAngle + (ratio * range);
      return newAngle;
    }

    function centerTranslation(r) {
      return 'translate(' + r + ',' + r + ')';
    }

    var config = {
      size: 100,
      clipWidth: 100,
      clipHeight: 60,
      ringInset: 20,
      ringWidth: 20,

      pointerWidth: 10,
      pointerTailLength: 5,
      pointerHeadLengthPercent: 0.8,

      minValue: 0,
      maxValue: 100,

      minAngle: -90,
      maxAngle: 90,

      transitionMs: 3000,

      majorTicks: 5,
      labelFormat: d3.format('d'),
      labelInset: 10,

      arcColorFn: d3.interpolateHsl(d3.rgb('#e8e2ca'), d3.rgb('#3e6c0a'))
    };

    range = config.maxAngle - config.minAngle;
    r = config.size / 2;
    pointerHeadLength = Math.round(r * config.pointerHeadLengthPercent);


    scale = d3.scaleLinear()
      .range([0, 1])
      .domain([config.minValue, config.maxValue]);

    ticks = scale.ticks(config.majorTicks);
    tickData = d3.range(config.majorTicks).map(function() {
      return 1 / config.majorTicks;
    });

    var arc = d3.arc()
      .innerRadius(r - config.ringWidth - config.ringInset)
      .outerRadius(r - config.ringInset)
      .startAngle(function(d, i) {
        var ratio = d * i;
        return deg2rad(config.minAngle + (ratio * range));
      })
      .endAngle(function(d, i) {
        var ratio = d * (i + 1);
        return deg2rad(config.minAngle + (ratio * range));
      });

    var lineData = [
      [config.pointerWidth / 2, 0],
      [0, -pointerHeadLength],
      [-(config.pointerWidth / 2), 0],
      [0, config.pointerTailLength],
      [config.pointerWidth / 2, 0]
    ];

    let treeData = {
      "id": "0",
      "name": "Root",
      "pointerValue": "10",
      "children": [{
          "id": "1",
          "name": "Node x",
          "pointerValue": "55",
          "children": [{
              "id": "2",
              "name": "Node y",
              "pointerValue": "75"
            },
            {
              "id": "3",
              "name": "Node z",
              "pointerValue": "95"
            }
          ]
        },
        {
          "id": "4",
          "name": "Node w",
          "pointerValue": "35"
        }
      ]
    };


    // Set the dimensions and margins of the diagram
    var margin = {
        top: 20,
        right: 90,
        bottom: 30,
        left: 90
      },
      width = 960 - margin.left - margin.right,
      height = 500 - margin.top - margin.bottom;

    // append the svg object to the body of the page
    // appends a 'group' element to 'svg'
    // moves the 'group' element to the top left margin
    var svg = d3.select("body").append("svg")
      .attr("width", width + margin.right + margin.left)
      .attr("height", height + margin.top + margin.bottom)
      .append("g")
      .attr("transform", "translate(" +
        margin.left + "," + margin.top + ")");

    var i = 0,
      duration = 750,
      root;

    // declares a tree layout and assigns the size
    var treemap = d3.tree().size([height, width]);

    // Assigns parent, children, height, depth
    root = d3.hierarchy(treeData, function(d) {
      return d.children;
    });
    root.x0 = height / 2;
    root.y0 = 0;

    // Collapse after the second level
    root.children.forEach(collapse);

    update(root);

    // Collapse the node and all it's children
    function collapse(d) {
      if (d.children) {
        d._children = d.children
        d._children.forEach(collapse)
        d.children = null
      }
    }

    function update(source) {

      // Assigns the x and y position for the nodes
      var treeData = treemap(root);

      // Compute the new tree layout.
      var nodes = treeData.descendants(),
        links = treeData.descendants().slice(1);

      // Normalize for fixed-depth.
      nodes.forEach(function(d) {
        d.y = d.depth * 400
      });

      // ****************** Nodes section ***************************

      // Update the nodes...
      var node = svg.selectAll('g.node')
        .data(nodes, function(d) {
          //alert(d.data.name);
          return d.id || (d.id = ++i);
        });

      // Enter any new nodes at the parent's previous position.
      var nodeEnter = node.enter().append('g')
        .attr('class', 'node')
        .attr("transform", function(d) {
          return "translate(" + source.y0 + "," + source.x0 + ")";
        }).on('click', click);

      nodeEnter.append('text')
        .attr("dy", ".35em")
        .attr("x", d => d.children || d._children ? 50 : -55)
        .attr("y", -5)
        //.attr("id", function(d) {
        //alert(d.data.id);
        //    return "node-" + d.data.id;
        //})
        .attr("text-anchor", d => d.children || d._children ? "end" : "start")
        .text(d => d.data.name);

      //#########################################################################

      var vis = nodeEnter.append('svg')
        //.attr("id", function(d) {
        //    return "gauge-" + d.data.id;
        //})
        .attr('class', 'gauge')
        .attr('width', config.clipWidth)
        .attr('height', config.clipHeight)
        .attr("y", -70)
        .attr("x", -50);

      var arcs = vis.append('g')
        //.attr("id", function(d) {
        //    return "arc-" + d.data.id;
        //})
        .attr('class', 'arc')
        .attr('transform', centerTranslation(r));
      arcs.selectAll('path')
        .data(tickData)
        .enter().append('path')
        .attr('fill', function(d, i) {
          return config.arcColorFn(d * i);
        })
        .attr('d', arc);

      var lg = vis.append('g')
        //.attr("id", function(d) {
        //    return "label-" + d.data.id;
        //})
        .attr('class', 'label')
        .attr('transform', centerTranslation(r));
      lg.selectAll('text')
        .data(ticks)
        .enter().append('text')
        .attr('transform', function(d) {
          var ratio = scale(d);
          var newAngle = config.minAngle + (ratio * range);
          return 'rotate(' + newAngle + ') translate(0,' + (config.labelInset - r) + ')';
        })
        .text(config.labelFormat);

      var pg = vis.selectAll(null)
        .data([lineData])
        .enter()
        .append("g")
        .attr('class', 'pointer')
        .attr('transform', centerTranslation(r));

      //########################################
      //THIS PART JUST WORKS FOR ROOT NODE
      //########################################

      var ratio = scale(87);
      var newAngle = config.minAngle + (ratio * range);

      pg.append('path')
        //d attribute that defines its shape and position.
        .attr("d", d3.line().curve(d3.curveLinear))
        .attr("stroke", "black")
        .attr('transform', 'rotate(' + config.minAngle + ')')
        .transition()
        .duration(config.transitionMs)
        .ease(d3.easeElastic)
        .attr('transform', 'rotate(' + newAngle + ')');

      //#########################################################################

      // UPDATE
      var nodeUpdate = nodeEnter.merge(node);

      // Transition to the proper position for the node
      nodeUpdate.transition()
        .duration(duration)
        .attr("transform", function(d) {
          return "translate(" + d.y + "," + d.x + ")";
        });

      // Update the node attributes and style
      nodeUpdate.select('circle.node')
        .attr('r', 10)
        .style("fill", function(d) {
          return d._children ? "lightsteelblue" : "#fff";
        })
        .attr('cursor', 'pointer');

      // Remove any exiting nodes
      var nodeExit = node.exit().transition()
        .duration(duration)
        .attr("transform", function(d) {
          return "translate(" + source.y + "," + source.x + ")";
        })
        .remove();

      // On exit reduce the node circles size to 0
      nodeExit.select('circle')
        .attr('r', 1e-6);

      // On exit reduce the opacity of text labels
      nodeExit.select('text')
        .style('fill-opacity', 1e-6);

      // ****************** links section ***************************

      // Update the links...
      var link = svg.selectAll('path.link')
        .data(links, function(d) {
          return d.id;
        });

      // Enter any new links at the parent's previous position.
      var linkEnter = link.enter().insert('path', "g")
        .attr("class", "link")
        .attr('d', function(d) {
          var o = {
            x: source.x0,
            y: source.y0
          }
          return diagonal(o, o)
        });

      // UPDATE
      var linkUpdate = linkEnter.merge(link);

      // Transition back to the parent element position
      linkUpdate.transition()
        .duration(duration)
        .attr('d', function(d) {
          return diagonal(d, d.parent)
        });

      // Remove any exiting links
      var linkExit = link.exit().transition()
        .duration(duration)
        .attr('d', function(d) {
          var o = {
            x: source.x,
            y: source.y
          }
          return diagonal(o, o)
        })
        .remove();

      // Store the old positions for transition.
      nodes.forEach(function(d) {
        d.x0 = d.x;
        d.y0 = d.y;
      });

      // Creates a curved (diagonal) path from parent to the child nodes
      function diagonal(s, d) {

        path = `M ${s.y} ${s.x}
            C ${(s.y + d.y) / 2} ${s.x},
              ${(s.y + d.y) / 2} ${d.x},
              ${d.y} ${d.x}`

        return path
      }

      // Toggle children on click.
      function click(d) {
        if (d.children) {
          d._children = d.children;
          d.children = null;
        } else {
          d.children = d._children;
          d._children = null;
        }
        update(d);
      }
    }
  </script>
</body>

</html>

Javascript相关问答推荐

在Chart.js 4.4.2中移动到不同大小的容器时,图表不会调整大小

橡皮擦完全让画布变成白色

我无法在NightWatch.js测试中获取完整的Chrome浏览器控制台日志(log)

深嵌套的ng-container元素仍然可以在Angular 布局组件中正确渲染内容吗?

将自定义排序应用于角形数字数组

点击按钮一次有文本出现和褪色,而不是点击两次?(html,CSS,JavaScript)

WebRTC关闭navigator. getUserMedia正确

如何从Intl.DateTimeFormat中仅获取时区名称?

使用JQuery单击元素从新弹出窗口获取值

虚拟滚动实现使向下滚动可滚动到末尾

闭包是将值复制到内存的另一个位置吗?

在css中放置所需 colored颜色 以填充图像的透明区域

以编程方式聚焦的链接将被聚焦,但样式不适用

如何使本地html页面在重新加载时保持当前可隐藏部分的打开状态?

Django导入问题,无法导入我的应用程序,但我已在设置中安装了它

如何在AG-Grid文本字段中创建占位符

在SuperBase JS客户端中寻址JSON数据

TypeORM QueryBuilder限制联接到一条记录

在Java脚本中构建接口的对象

无法使用Redux异步函数读取未定义的useEffect钩子的属性';map';