addPath()

in src/plugins/vis_type_vislib/public/vislib/visualizations/pie_chart.js [134:356]


  addPath(width, height, svg, slices) {
    const self = this;
    const marginFactor = 0.95;
    const isDonut = self._attr.isDonut;
    const radius = (Math.min(width, height) / 2) * marginFactor;
    const color = self.handler.data.getPieColorFunc();
    const tooltip = self.tooltip;
    const isTooltip = self._attr.addTooltip;

    const arcs = svg.append('g').attr('class', 'arcs');
    const labels = svg.append('g').attr('class', 'labels');

    const showLabels = self._attr.labels.show;
    const showValues = self._attr.labels.values;
    const truncateLabelLength = self._attr.labels.truncate;
    const showOnlyOnLastLevel = self._attr.labels.last_level;

    const partition = d3.layout
      .partition()
      .sort(null)
      .value(function (d) {
        return d.percentOfParent * 100;
      });

    const x = d3.scale.linear().range([0, 2 * Math.PI]);
    const y = d3.scale.sqrt().range([0, showLabels ? radius * 0.7 : radius]);

    const startAngle = function (d) {
      return Math.max(0, Math.min(2 * Math.PI, x(d.x)));
    };

    const endAngle = function (d) {
      if (d.dx < 1e-8) return x(d.x);
      return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));
    };

    const arc = d3.svg
      .arc()
      .startAngle(startAngle)
      .endAngle(endAngle)
      .innerRadius(function (d) {
        // option for a single layer, i.e pie chart
        if (d.depth === 1 && !isDonut) {
          // return no inner radius
          return 0;
        }

        return Math.max(0, y(d.y));
      })
      .outerRadius(function (d) {
        return Math.max(0, y(d.y + d.dy));
      });

    const outerArc = d3.svg
      .arc()
      .startAngle(startAngle)
      .endAngle(endAngle)
      .innerRadius(radius * 0.8)
      .outerRadius(radius * 0.8);

    let maxDepth = 0;
    const path = arcs
      .datum(slices)
      .selectAll('path')
      .data(partition.nodes)
      .enter()
      .append('path')
      .attr('d', arc)
      .attr('class', function (d) {
        if (d.depth === 0) {
          return;
        }
        if (d.depth > maxDepth) maxDepth = d.depth;
        return 'slice';
      })
      .attr('data-test-subj', function (d) {
        if (d.name) {
          return `pieSlice-${d.name.split(' ').join('-')}`;
        }
      })
      .call(self._addIdentifier, 'name')
      .style('fill', function (d) {
        if (d.depth === 0) {
          return 'none';
        }
        return color(d.name);
      });

    // add labels
    if (showLabels) {
      const labelGroups = labels.datum(slices).selectAll('.label').data(partition.nodes);

      // create an empty quadtree to hold label positions
      const svgParentNode = svg.node().parentNode.parentNode;
      const svgBBox = {
        width: svgParentNode.clientWidth,
        height: svgParentNode.clientHeight,
      };

      const labelLayout = d3.geom
        .quadtree()
        .extent([
          [-svgBBox.width, -svgBBox.height],
          [svgBBox.width, svgBBox.height],
        ])
        .x(function (d) {
          return d.position.x;
        })
        .y(function (d) {
          return d.position.y;
        })([]);

      labelGroups
        .enter()
        .append('g')
        .attr('class', 'label')
        .append('text')
        .text(function (d) {
          if (d.depth === 0) {
            d3.select(this.parentNode).remove();
            return;
          }
          if (showValues) {
            const value = numeral(d.value / 100).format('0.[00]%');
            return `${d.name} (${value})`;
          }
          return d.name;
        })
        .text(function () {
          return truncateLabel(this, truncateLabelLength);
        })
        .attr('text-anchor', function (d) {
          const midAngle = startAngle(d) + (endAngle(d) - startAngle(d)) / 2;
          return midAngle < Math.PI ? 'start' : 'end';
        })
        .attr('class', 'label-text')
        .each(function resolveConflicts(d) {
          if (d.depth === 0) return;

          const parentNode = this.parentNode;
          if (showOnlyOnLastLevel && maxDepth !== d.depth) {
            d3.select(parentNode).remove();
            return;
          }

          const bbox = this.getBBox();
          const pos = outerArc.centroid(d);
          const midAngle = startAngle(d) + (endAngle(d) - startAngle(d)) / 2;
          pos[1] += 4;
          pos[0] = (0.7 + d.depth / 10) * radius * (midAngle < Math.PI ? 1 : -1);
          d.position = {
            x: pos[0],
            y: pos[1],
            left: midAngle < Math.PI ? pos[0] : pos[0] - bbox.width,
            right: midAngle > Math.PI ? pos[0] + bbox.width : pos[0],
            bottom: pos[1] + 5,
            top: pos[1] - bbox.height - 5,
          };

          const conflicts = [];
          labelLayout.visit(function (node) {
            if (!node.point) return;
            if (conflicts.length) return true;

            const point = node.point.position;
            const current = d.position;
            if (point) {
              const horizontalConflict =
                (point.left < 0 && current.left < 0) || (point.left > 0 && current.left > 0);
              const verticalConflict =
                (point.top >= current.top && point.top <= current.bottom) ||
                (point.top <= current.top && point.bottom >= current.top);

              if (horizontalConflict && verticalConflict) {
                point.point = node.point;
                conflicts.push(point);
              }

              return true;
            }
          });

          if (conflicts.length) {
            d3.select(parentNode).remove();
            return;
          }

          labelLayout.add(d);
        })
        .attr('x', function (d) {
          if (d.depth === 0 || !d.position) {
            return;
          }
          return d.position.x;
        })
        .attr('y', function (d) {
          if (d.depth === 0 || !d.position) {
            return;
          }
          return d.position.y;
        });

      labelGroups
        .append('polyline')
        .attr('points', function (d) {
          if (d.depth === 0 || !d.position) {
            return;
          }
          const pos1 = outerArc.centroid(d);
          const x2 = d.position.x > 0 ? d.position.x - 10 : d.position.x + 10;
          const pos2 = [x2, d.position.y - 4];
          pos1[1] = pos2[1];
          return [arc.centroid(d), pos1, pos2];
        })
        .attr('class', 'label-line');
    }

    if (isTooltip) {
      path.call(tooltip.render());
    }

    return path;
  }