Раскрывающийся pie chart d3.js

318
22 октября 2017, 23:50

Раскрывать элемент диаграммы по которому кликнули. На входе такой массив, где child - это это те элементы, которые надо раскрыть.

data = [{"label":"Расходы", "value":1000}, 
        {"label":"Доходы", "value":2000}, 
        {
            "label":"Другое", 
            "value":500, 
            "child":[
                {"label":"Другое 1", "value":400},
                {"label":"Другое 2", "value":70},
                {"label":"Другое 3", "value":30},
             ]
        }];

После клика должно быть что-то вроде этого:

var w = 300, 
h = 300, 
r = 100, 
color = d3.scale.category20b(); 
 
data = [{"label":"Расходы", "value":1000},  
        {"label":"Доходы", "value":2000},  
        { 
            "label":"Другое",  
            "value":500,  
            "child":[ 
                {"label":"Другое 1", "value":400}, 
                {"label":"Другое 2", "value":70}, 
                {"label":"Другое 3", "value":30}, 
            ] 
         }]; 
 
var vis = d3.select("body") 
    .append("svg:svg") 
    .data([data])  
    .attr("width", w) 
    .attr("height", h) 
    .append("svg:g") 
    .attr("transform", "translate(" + r + "," + r + ")"); 
 
var arc = d3.svg.arc() 
    .outerRadius(r); 
 
var pie = d3.layout.pie() 
    .value(function(d) { return d.value; }); 
 
var arcs = vis.selectAll("g.slice") 
    .data(pie) 
    .enter() 
    .append("svg:g") 
    .on("mouseover", function(d) { 
 
    }); 
 
    arcs.append("svg:path") 
            .attr("fill", function(d, i) { return color(i); } )  
            .attr("d", arc) 
            .attr("stroke-width", "3px") 
            .attr("stroke", "#fff"); 
 
    arcs.append("svg:text")                                      
            .attr("transform", function(d) {                    
            d.innerRadius = 0; 
            d.outerRadius = r; 
            return "translate(" + arc.centroid(d) + ")";        
        }) 
        .attr("text-anchor", "middle")   
        .attr("fill", "#fff")   
        .text(function(d, i) { return data[i].label; });
slice{ 
  color: #fff; 
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script> 
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Как это можно реализовать?

Answer 1

На самом деле идея проста. Нужно всего лишь определить arc, на который кликнули, и на его месте отрисовать дочерний pie chart.

  • https://bl.ocks.org/mbostock/3887235
  • http://bl.ocks.org/kiranml1/6872886

const w = 300 
const h = 300 
const r = 150 
const color = d3.scale.category20b(); 
const offset = 20 
 
const data = [{ 
    "label": "Расходы", 
    "value": 1000 
  }, 
  { 
    "label": "Доходы", 
    "value": 2000 
  }, 
  { 
    "label": "Другое", 
    "value": 500, 
    "child": [{ 
        "label": "Другое 1", 
        "value": 400 
      }, 
      { 
        "label": "Другое 2", 
        "value": 70 
      }, 
      { 
        "label": "Другое 3", 
        "value": 30 
      }, 
    ] 
  } 
]; 
 
const vis = d3.select("body") 
  .append("svg") 
  .data([data]) 
  .attr("width", w) 
  .attr("height", h) 
  .append("g") 
  .attr("transform", "translate(" + r + "," + r + ")"); 
 
const arc = d3.svg.arc() 
  .outerRadius(r - offset) 
  .innerRadius(0); 
 
const arcChild = d3.svg.arc() 
  .outerRadius(r) 
  .innerRadius(r / 2); 
 
const pie = d3.layout.pie() 
  .value(d => d.value); 
 
const pieChild = d3.layout.pie() 
  .value(d => d.value); 
 
const arcs = vis.selectAll("g.slice") 
  .data(pie) 
  .enter() 
  .append("g") 
  .attr('class', 'slice') 
  .on('click', function(d) { 
    // если нет дочерних полей, игнорим 
    if (!('child' in d.data)) { 
      return 
    } 
 
    // берём настройки текущего arc, 
    // чтоб рисовать не полный круг, 
    // а только нужную область 
    const { 
      startAngle, 
      endAngle 
    } = d; 
    const self = d3.select(this) 
 
    // настраиваем углы в дочернем pie chart 
    pieChild.startAngle(startAngle).endAngle(endAngle) 
 
    // удаляем целый arc с родительскими данными 
    self.selectAll('path').remove() 
    self.selectAll('text').remove() 
 
    // рисуем дочерние arc 
    const childs = self 
      .selectAll('g.child') 
      .data(pieChild(d.data.child)) 
      .enter() 
      .append('g') 
      .attr('class', 'child'); 
 
    childs.append("path") 
      .attr("fill", (d, i) => color(i + 10)) 
      .attr("d", arcChild) 
      .attr("stroke-width", "1px") 
      .attr("stroke", "#ccc"); 
  }); 
 
arcs.append("path") 
  .attr("fill", (d, i) => color(i)) 
  .attr("d", arc) 
  .attr("stroke-width", "3px") 
  .attr("stroke", "#fff"); 
 
arcs.append("text") 
  .attr("transform", d => "translate(" + arc.centroid(d) + ")") 
  .attr("text-anchor", "middle") 
  .attr("fill", "#fff") 
  .text((d, i) => data[i].label);
svg { 
  margin-top: 100px; 
  margin-left: 100px; 
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script> 
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

READ ALSO
Поддержка подсветки и синтаксиса JSX

Поддержка подсветки и синтаксиса JSX

Изучаю библиотеку React и использую Brackets

228
Проблема с heic форматом

Проблема с heic форматом

Проблема при конвертации heic в jpgИспользую официальную библиотеку на js

278
Как создать тип и использовать?

Как создать тип и использовать?

В официальном гайде angular2 для сущности Hero рекомендуется использовать пользовательский тип для данных:

363
JS/jQuery - Массив из Dom-элементов

JS/jQuery - Массив из Dom-элементов

Есть такой код в HTML:

364