如何在D3中对g元素进行分组? [英] How to group g elements in D3?

查看:90
本文介绍了如何在D3中对g元素进行分组?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

例如,我有一个包含rect和text的g元素:

var cells = innercanvas
.selectAll(".newcell")
.data(treemap)
.enter()
.append("g")
.attr("class", "newcell");

 cells
    .append("rect")
    .attr("x", function (d) {
      return d.x;
    })
    .attr("y", function (d) {
      return d.y;
    })
    .attr("id", "rectangle")
    .attr("width", function (d) {
      return d.dx;
    })
    .attr("height", function (d) {
      return d.dy;
    })
    .style("fill", function (d) {
      return d.children ? cfg.color(d.name) : 'none';
    })
    .attr("stroke", "#000000")
    .attr('pointer-events', 'all')

cells
.append("text")
.attr("x", function (d) {
  return d.x + d.dx / 2;
})
.attr("y", function (d) {
  return d.y + d.dy / 2;
})
.attr('dy', '.95em')
.attr("text-anchor", "middle")
.text(function (d) { return d.children ? d.name : null })

我想添加一个父g元素,该元素根据其通用名称对cells进行分组.例如,

var parent = d3.selectAll("cells").attr("groupBy",function(d){ return d.children? d.name : null;})

这是为了让我可以将父名称显示为这些常见单元格的标题.

解决方案

节点不必进行物理分组即可将它们分组以应用标签或突出显示它们.目前,您使用的代码为每个父节点生成一个g元素,其中一个rect提供颜色填充,一个空白的text元素.然后,将子节点覆盖在此父节点上,并在其边缘上放置黑色的stroke并在其国家名称下放置一个text节点.

如果您希望能够突出显示具有相同父元素的所有元素并在该元素上放置标题,则可以采用以下两种方法:

  • 更改现有元素以显示标题

  • 创建一个尺寸与父节点相同的叠加层以显示标题

更改现有元素

为了能够做到这一点,我们首先需要能够识别父节点及其子节点.一种实现方法是根据节点在层次结构中的位置及其数据内容提供节点类.这是执行此操作的一种可能方法:

var cells = innercanvas
.selectAll(".newcell")
.data(treemap)
.enter()
.append("g")
.attr("class", function (d,i) {
  return 'newcell _' + i       // i provides a unique identifier for each node 
  + ' cell-level-' + d.depth   // cell-level-0 for root, cell-level-1, cell-level-2, etc 
  + ( d.name ? ' ' + safe_name(d.name) : '' ) // if d.name exists, use the 'safe' version
  + ( ! d.children
    ? ' leaf'                  // d has no children => it's a leaf node
    : (d.depth === 0
      ? ' root'                // d.depth = 0 => it's the root node
      : ' internal '));        // has children, depth > 0 => internal node
})

// strips non-alphanumeric characters out of `name` strings, replaces with _
function safe_name (txt) {
  return txt.replace(/\W/g, '_');
}

SVG g元素现在看起来像这样:

现在,我们可以通过使用

轻松访问任何国家/地区名称c.name的父节点

d3.select('.internal.' + safe_name(c.name))

或(例如)使用该国家/地区的叶子节点的text元素

d3.selectAll('.leaf.' + safe_name(c.name) + ' text')

然后我们可以使用CSS来显示或隐藏文本元素,rect笔画等;例如

 var bool = false;
var toggle = d3.select('#toggle')
  .on('click', toggleSvg);
  
function toggleSvg() {
    bool = !bool;
    d3.selectAll('.Cyprus')
      .classed('highlightAll', bool);
} 

 .newcell text {
  font-family: Arial, sans-serif;
  font-size: 10px;
}

.newcell.leaf rect {
  stroke: #000;
  stroke-width: 0.5px;
}

.oversize {
  display: none;
}

.internal text {
  opacity: 0
}

.internal.highlightAll text {
  opacity: 1
}

.highlightAll.leaf rect {
  opacity: 0.1
}

.highlightAll.leaf text {
  opacity: 0
} 

 <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<p><a id="toggle" href='#'>Toggle Cyprus highlighting</a></p>

<svg width="186" height="77">
      <g transform="translate(-30.1234, -258.33)">
        <g class="newcell _2331 cell-level-1 Cyprus internal ">
          <rect x="30.123480134121516" y="258.33086334171067" width="185.81893466750355" height="76.6094615363257" style="fill: rgb(100, 200, 75);"></rect>
          <title>Cyprus</title>
          <text x="123.0329474678733" y="296.63559410987354" dy=".35em" text-anchor="middle">Cyprus</text>
        </g>
        <g class="newcell _2332 cell-level-2 Cyprus leaf">
          <rect x="40.12348013412152" y="268.33086334171067" width="31.51365155392795" height="23.97574841366901" style="fill: none;"></rect>
          <title>Cyprus</title>
          <text x="55.88030591108549" y="280.3187375485452" dy=".35em" text-anchor="middle" class="oversize">Cyprus</text>
        </g>
        <g class="newcell _2333 cell-level-2 Cyprus leaf">
          <rect x="40.12348013412152" y="292.30661175537968" width="31.51365155392795" height="32.633713122656687" style="fill: none;"></rect>
          <title>Cyprus</title>
          <text x="55.88030591108549" y="308.62346831670802" dy=".35em" text-anchor="middle" class="oversize">Cyprus</text>
        </g>
        <g class="newcell _2334 cell-level-2 Cyprus leaf">
          <rect x="71.637131688049465" y="268.33086334171067" width="55.48181226963859" height="56.60946153632569" style="fill: none;"></rect>
          <title>Cyprus</title>
          <text x="99.37803782286876" y="296.63559410987354" dy=".35em" text-anchor="middle">Cyprus</text>
        </g>
        <g class="newcell _2335 cell-level-2 Cyprus leaf">
          <rect x="127.11894395768805" y="268.33086334171067" width="78.823470843937" height="56.60946153632569" style="fill: none;"></rect>
          <title>Cyprus</title>
          <text x="166.53067937965655" y="296.63559410987354" dy=".35em" text-anchor="middle">Cyprus</text>
        </g>
      </g>
    </svg> 

创建叠加层

这将在树图的顶部复制一个现有的父节点.

使用recttext节点作为子节点,将新的g元素添加到树图中:

var highlightG = canvas.append('g')
    .attr("transform", "translate(" + cfg.margin.left + "," + cfg.margin.top + ")")
    .append('g')
    .classed('highlighter', true)
    .attr('opacity',0);
highlightG.append('rect');
highlightG.append('text');

我们可以使用来自现有父节点的数据来设置rect元素的适当位置和大小以及text元素的内容.

var d = d3.select('.internal.Cyprus').datum();
highlightG.select('rect')
  .attr("x", d.x)
  .attr("y", d.y)
  .attr("width", d.dx)
  .attr("height", d.dy)

highlightG
  .select("text")
  .attr("x", d.x + d.dx / 2 )
  .attr("y", d.y + d.dy / 2 )
  .attr('dy', '.35em')
  .attr("text-anchor", "middle")
  .text(function () {
    return d.name;
  })

 d3.select('.internal.Cyprus').property('__data__', {
depth: 1,
dx: 185.81893466750355,
dy: 76.6094615363257,
name: "Cyprus",
value: 446770,
x: 30.123480134121516,
y: 258.33086334171067
});
var opacity = 1;
var highlightG = d3.select('svg')
  .append('g')
  .attr("transform", "translate(-30.1234, -258.33)")
  .append('g')
  .classed('highlighter', true)
  .attr('opacity', 0);
highlightG.append('rect');
highlightG.append('text');

d3.select('#toggle')
  .on('click', function() {
    var d = d3.select('.internal.Cyprus').datum();
    highlightG.attr('opacity', opacity);
    highlightG.select('rect')
      .attr("x", d.x)
      .attr("y", d.y)
      .attr("width", d.dx)
      .attr("height", d.dy)

    highlightG
      .select("text")
      .attr("x", d.x + d.dx / 2)
      .attr("y", d.y + d.dy / 2)
      .attr('dy', '.35em')
      .attr("text-anchor", "middle")
      .text(function() {
        return d.name;
      })
    opacity = opacity ? 0 : 1;
  }); 

 .newcell text {
  font-family: Arial, sans-serif;
  font-size: 10px;
}

.newcell.leaf rect {
  stroke: #000;
  stroke-width: 0.5px;
}

.oversize {
  display: none;
}

.internal text {
  opacity: 0
}

.highlighter rect {
  fill: #000;
  fill-opacity: 0.7;
  stroke: deepskyblue;
  stroke-width: 5px;
}
.highlighter text {
  fill: deepskyblue;
  opacity: 1
} 

 <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<p><a id="toggle" href='#'>Activate Cyprus overlay</a></p>

<svg width="186" height="77">
      <g transform="translate(-30.1234, -258.33)">
        <g class="newcell _2331 cell-level-1 Cyprus internal ">
          <rect x="30.123480134121516" y="258.33086334171067" width="185.81893466750355" height="76.6094615363257" style="fill: rgb(100, 200, 75);"></rect>
          <title>Cyprus</title>
          <text x="123.0329474678733" y="296.63559410987354" dy=".35em" text-anchor="middle">Cyprus</text>
        </g>
        <g class="newcell _2332 cell-level-2 Cyprus leaf">
          <rect x="40.12348013412152" y="268.33086334171067" width="31.51365155392795" height="23.97574841366901" style="fill: none;"></rect>
          <title>Cyprus</title>
          <text x="55.88030591108549" y="280.3187375485452" dy=".35em" text-anchor="middle" class="oversize">Cyprus</text>
        </g>
        <g class="newcell _2333 cell-level-2 Cyprus leaf">
          <rect x="40.12348013412152" y="292.30661175537968" width="31.51365155392795" height="32.633713122656687" style="fill: none;"></rect>
          <title>Cyprus</title>
          <text x="55.88030591108549" y="308.62346831670802" dy=".35em" text-anchor="middle" class="oversize">Cyprus</text>
        </g>
        <g class="newcell _2334 cell-level-2 Cyprus leaf">
          <rect x="71.637131688049465" y="268.33086334171067" width="55.48181226963859" height="56.60946153632569" style="fill: none;"></rect>
          <title>Cyprus</title>
          <text x="99.37803782286876" y="296.63559410987354" dy=".35em" text-anchor="middle">Cyprus</text>
        </g>
        <g class="newcell _2335 cell-level-2 Cyprus leaf">
          <rect x="127.11894395768805" y="268.33086334171067" width="78.823470843937" height="56.60946153632569" style="fill: none;"></rect>
          <title>Cyprus</title>
          <text x="166.53067937965655" y="296.63559410987354" dy=".35em" text-anchor="middle">Cyprus</text>
        </g>
      </g>
    </svg> 

我已经创建了.block ,其中包含这些选项的更全面版本. >

您应该能够找到可以用于您的目的的这些组合.

Say for example I have a g element that contains a rect and text :

var cells = innercanvas
.selectAll(".newcell")
.data(treemap)
.enter()
.append("g")
.attr("class", "newcell");

 cells
    .append("rect")
    .attr("x", function (d) {
      return d.x;
    })
    .attr("y", function (d) {
      return d.y;
    })
    .attr("id", "rectangle")
    .attr("width", function (d) {
      return d.dx;
    })
    .attr("height", function (d) {
      return d.dy;
    })
    .style("fill", function (d) {
      return d.children ? cfg.color(d.name) : 'none';
    })
    .attr("stroke", "#000000")
    .attr('pointer-events', 'all')

cells
.append("text")
.attr("x", function (d) {
  return d.x + d.dx / 2;
})
.attr("y", function (d) {
  return d.y + d.dy / 2;
})
.attr('dy', '.95em')
.attr("text-anchor", "middle")
.text(function (d) { return d.children ? d.name : null })

I want to add a parent g element that groups cells based on their common name. For example ,

var parent = d3.selectAll("cells").attr("groupBy",function(d){ return d.children? d.name : null;})

This is so that I can display the parent name as a header for these common cells.

解决方案

Nodes don't have to be grouped together physically to be able to group them to apply a label or highlight them. At present, the code you are using produces a g element for each parent node, with a rect that provides the colour fill and a blank text element. The child nodes are then overlaid on this parent node with a black stroke along the edges and a text node with the country name.

If you want to be able to highlight all the elements that have the same parent and put a header on the element, you can do it a couple of ways:

  • alter existing elements to show a header

  • create an overlay with the same dimensions as the parent node to display a header

Altering existing elements

To be able to do this, we first need to be able to identify parent nodes and their children. One way to do this is by giving nodes classes according to their position in the hierarchy and their data content. Here's one possible way to do this:

var cells = innercanvas
.selectAll(".newcell")
.data(treemap)
.enter()
.append("g")
.attr("class", function (d,i) {
  return 'newcell _' + i       // i provides a unique identifier for each node 
  + ' cell-level-' + d.depth   // cell-level-0 for root, cell-level-1, cell-level-2, etc 
  + ( d.name ? ' ' + safe_name(d.name) : '' ) // if d.name exists, use the 'safe' version
  + ( ! d.children
    ? ' leaf'                  // d has no children => it's a leaf node
    : (d.depth === 0
      ? ' root'                // d.depth = 0 => it's the root node
      : ' internal '));        // has children, depth > 0 => internal node
})

// strips non-alphanumeric characters out of `name` strings, replaces with _
function safe_name (txt) {
  return txt.replace(/\W/g, '_');
}

The SVG g elements now look like this:

Now we can easily access the parent node for any country name c.name by using

d3.select('.internal.' + safe_name(c.name))

or (e.g.) the text elements of the leaf nodes for the country using

d3.selectAll('.leaf.' + safe_name(c.name) + ' text')

We can then use CSS to show or hide text elements, rect strokes, etc.; e.g.

var bool = false;
var toggle = d3.select('#toggle')
  .on('click', toggleSvg);
  
function toggleSvg() {
    bool = !bool;
    d3.selectAll('.Cyprus')
      .classed('highlightAll', bool);
}

.newcell text {
  font-family: Arial, sans-serif;
  font-size: 10px;
}

.newcell.leaf rect {
  stroke: #000;
  stroke-width: 0.5px;
}

.oversize {
  display: none;
}

.internal text {
  opacity: 0
}

.internal.highlightAll text {
  opacity: 1
}

.highlightAll.leaf rect {
  opacity: 0.1
}

.highlightAll.leaf text {
  opacity: 0
}

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<p><a id="toggle" href='#'>Toggle Cyprus highlighting</a></p>

<svg width="186" height="77">
      <g transform="translate(-30.1234, -258.33)">
        <g class="newcell _2331 cell-level-1 Cyprus internal ">
          <rect x="30.123480134121516" y="258.33086334171067" width="185.81893466750355" height="76.6094615363257" style="fill: rgb(100, 200, 75);"></rect>
          <title>Cyprus</title>
          <text x="123.0329474678733" y="296.63559410987354" dy=".35em" text-anchor="middle">Cyprus</text>
        </g>
        <g class="newcell _2332 cell-level-2 Cyprus leaf">
          <rect x="40.12348013412152" y="268.33086334171067" width="31.51365155392795" height="23.97574841366901" style="fill: none;"></rect>
          <title>Cyprus</title>
          <text x="55.88030591108549" y="280.3187375485452" dy=".35em" text-anchor="middle" class="oversize">Cyprus</text>
        </g>
        <g class="newcell _2333 cell-level-2 Cyprus leaf">
          <rect x="40.12348013412152" y="292.30661175537968" width="31.51365155392795" height="32.633713122656687" style="fill: none;"></rect>
          <title>Cyprus</title>
          <text x="55.88030591108549" y="308.62346831670802" dy=".35em" text-anchor="middle" class="oversize">Cyprus</text>
        </g>
        <g class="newcell _2334 cell-level-2 Cyprus leaf">
          <rect x="71.637131688049465" y="268.33086334171067" width="55.48181226963859" height="56.60946153632569" style="fill: none;"></rect>
          <title>Cyprus</title>
          <text x="99.37803782286876" y="296.63559410987354" dy=".35em" text-anchor="middle">Cyprus</text>
        </g>
        <g class="newcell _2335 cell-level-2 Cyprus leaf">
          <rect x="127.11894395768805" y="268.33086334171067" width="78.823470843937" height="56.60946153632569" style="fill: none;"></rect>
          <title>Cyprus</title>
          <text x="166.53067937965655" y="296.63559410987354" dy=".35em" text-anchor="middle">Cyprus</text>
        </g>
      </g>
    </svg>

Creating an overlay

This will duplicate an existing parent node on top of the treemap.

Add a new g element to the treemap with rect and text nodes as children:

var highlightG = canvas.append('g')
    .attr("transform", "translate(" + cfg.margin.left + "," + cfg.margin.top + ")")
    .append('g')
    .classed('highlighter', true)
    .attr('opacity',0);
highlightG.append('rect');
highlightG.append('text');

We can use the data from an existing parent node to set the appropriate position and size for the rect element and the content for the text element.

var d = d3.select('.internal.Cyprus').datum();
highlightG.select('rect')
  .attr("x", d.x)
  .attr("y", d.y)
  .attr("width", d.dx)
  .attr("height", d.dy)

highlightG
  .select("text")
  .attr("x", d.x + d.dx / 2 )
  .attr("y", d.y + d.dy / 2 )
  .attr('dy', '.35em')
  .attr("text-anchor", "middle")
  .text(function () {
    return d.name;
  })

d3.select('.internal.Cyprus').property('__data__', {
depth: 1,
dx: 185.81893466750355,
dy: 76.6094615363257,
name: "Cyprus",
value: 446770,
x: 30.123480134121516,
y: 258.33086334171067
});
var opacity = 1;
var highlightG = d3.select('svg')
  .append('g')
  .attr("transform", "translate(-30.1234, -258.33)")
  .append('g')
  .classed('highlighter', true)
  .attr('opacity', 0);
highlightG.append('rect');
highlightG.append('text');

d3.select('#toggle')
  .on('click', function() {
    var d = d3.select('.internal.Cyprus').datum();
    highlightG.attr('opacity', opacity);
    highlightG.select('rect')
      .attr("x", d.x)
      .attr("y", d.y)
      .attr("width", d.dx)
      .attr("height", d.dy)

    highlightG
      .select("text")
      .attr("x", d.x + d.dx / 2)
      .attr("y", d.y + d.dy / 2)
      .attr('dy', '.35em')
      .attr("text-anchor", "middle")
      .text(function() {
        return d.name;
      })
    opacity = opacity ? 0 : 1;
  });

.newcell text {
  font-family: Arial, sans-serif;
  font-size: 10px;
}

.newcell.leaf rect {
  stroke: #000;
  stroke-width: 0.5px;
}

.oversize {
  display: none;
}

.internal text {
  opacity: 0
}

.highlighter rect {
  fill: #000;
  fill-opacity: 0.7;
  stroke: deepskyblue;
  stroke-width: 5px;
}
.highlighter text {
  fill: deepskyblue;
  opacity: 1
}

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<p><a id="toggle" href='#'>Activate Cyprus overlay</a></p>

<svg width="186" height="77">
      <g transform="translate(-30.1234, -258.33)">
        <g class="newcell _2331 cell-level-1 Cyprus internal ">
          <rect x="30.123480134121516" y="258.33086334171067" width="185.81893466750355" height="76.6094615363257" style="fill: rgb(100, 200, 75);"></rect>
          <title>Cyprus</title>
          <text x="123.0329474678733" y="296.63559410987354" dy=".35em" text-anchor="middle">Cyprus</text>
        </g>
        <g class="newcell _2332 cell-level-2 Cyprus leaf">
          <rect x="40.12348013412152" y="268.33086334171067" width="31.51365155392795" height="23.97574841366901" style="fill: none;"></rect>
          <title>Cyprus</title>
          <text x="55.88030591108549" y="280.3187375485452" dy=".35em" text-anchor="middle" class="oversize">Cyprus</text>
        </g>
        <g class="newcell _2333 cell-level-2 Cyprus leaf">
          <rect x="40.12348013412152" y="292.30661175537968" width="31.51365155392795" height="32.633713122656687" style="fill: none;"></rect>
          <title>Cyprus</title>
          <text x="55.88030591108549" y="308.62346831670802" dy=".35em" text-anchor="middle" class="oversize">Cyprus</text>
        </g>
        <g class="newcell _2334 cell-level-2 Cyprus leaf">
          <rect x="71.637131688049465" y="268.33086334171067" width="55.48181226963859" height="56.60946153632569" style="fill: none;"></rect>
          <title>Cyprus</title>
          <text x="99.37803782286876" y="296.63559410987354" dy=".35em" text-anchor="middle">Cyprus</text>
        </g>
        <g class="newcell _2335 cell-level-2 Cyprus leaf">
          <rect x="127.11894395768805" y="268.33086334171067" width="78.823470843937" height="56.60946153632569" style="fill: none;"></rect>
          <title>Cyprus</title>
          <text x="166.53067937965655" y="296.63559410987354" dy=".35em" text-anchor="middle">Cyprus</text>
        </g>
      </g>
    </svg>

I've created a .block with more comprehensive versions of these options.

You should be able to find some combination of these that you can use for your purposes.

这篇关于如何在D3中对g元素进行分组?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆