如何使图例文本响应D3图表

时间:2019-01-22 21:01:54

标签: d3.js

我正在解决一个D3甜甜圈图,其图例位于右侧的问题。图例的文字不断被截断。它在容器外部可见,或者不在容器外部显示。无论哪种方式,它都不能容纳在容器中,即使我可以看到图例和甜甜圈图都是同一SVG的一部分。您可以在这张图片中看到我的意思:

https://imgur.com/a/J3KrTA6

我对使用D3非常陌生,但是我在这个问题上已经停留了一段时间。这不是我要修复的代码,但是在这里传递了生成SVG的选项:

  const donutOptions: DonutChartOptions = {
    showPercentageInDonut: false,
    width: 500,
    height: 260,
    title: {text: '', textStyle: {fontSize: '14px'}},
    margin: {top: 120, right: 10, bottom: 65, left: 100},
    colors: [Theme.Emerald.hex, Theme.Lime.hex, Theme.Teal.hex, Theme.SkyBlue.hex,
      Theme.Marigold.hex, Theme.Azure.hex, Theme.Red.hex, Theme.Orange.hex]
  };

  const legendTextWrapWidthEdge = 1440;
  const donutEthnicityOptions: DonutChartOptionsExtended = {
    showPercentageInDonut: false,
    width: 470,
    height: 260,
    title: {text: '', textStyle: {fontSize: '14px'}},
    margin: {top: 120, right: 10, bottom: 65, left: 85},
    colors: [Theme.Emerald.hex, Theme.Lime.hex, Theme.Teal.hex, Theme.SkyBlue.hex,
      Theme.Marigold.hex, Theme.Azure.hex, Theme.Red.hex, Theme.Orange.hex],
    legend: {textStyle: {fontSize: '14px'}},
    legendOptions: {
      legendRectVSpace: 10,
      legendPositionX: 110,
      legendPositionY: 85,
      legendPercentagePositionX: 46,
      legendPercentagePositionY: 15,
      legendTextPositionX: 20,
      legendTextWidth: (!!this.browserScreenWidth && this.browserScreenWidth < legendTextWrapWidthEdge) ? 100 : 200

    }
  };

我尝试过尝试使用viewBox和preserveAspectRatio属性,但是显然我做的不正确。

这是使用上述选项实际创建图表的代码。如果可以避免的话,使用此代码是最后的选择。我将其视为仅用于上下文的黑匣子:

import { DataSet } from '../data-set';
import { DonutChartOptionsExtended, ILegendOptions } from '../interfaces/DonutChartOptionsExtended';
import { XYChartSettings } from '../interfaces/XYChartSettings';
import { DEFAULTS } from '../interfaces/DEFAULTS';
import { TextStyle } from '../interfaces/TextStyle';

import Defaults from 'lodash-es/defaults';

import { getColorScale, initializeSvg, wrapText } from '../d3-fns';

export class DonutChart {
  options: any;
  dataset: any;

  draw(dataSet?: DataSet, options?: DonutChartOptionsExtended) {
    Promise.all([
      import(/* webpackChunkName: "d3" */ 'd3-shape'),
      import(/* webpackChunkName: "d3" */ 'd3-interpolate'),
      import(/* webpackChunkName: "d3" */ 'd3-selection'),
      import(/* webpackChunkName: "d3" */ 'd3-scale'),
      import(/* webpackChunkName: "d3" */ 'd3-transition')
    ]).then(([d3Shape, d3Interpolate, d3Select, d3Scale, trans]) => {
      if (dataSet) {
        this.dataset = dataSet;
      }
      if (options) {
        this.options = options;
      }

      const pie = d3Shape.pie()
        .value((d: any) => d.value)
        .sort(null)
        .padAngle(.03);

      const width = this.options.width;

      const outerRadius = (width - 300) / 2;
      const innerRadius = outerRadius / 2;

      const arc = d3Shape.arc()
        .outerRadius(outerRadius)
        .innerRadius(innerRadius);

      const settings: any = new XYChartSettings(this.options);
      const svg = initializeSvg(d3Select, settings);
      const color = getColorScale(d3Scale, settings.colors);
      const dataRows = this.dataset.dataRows;

      const path = svg.selectAll('path')
        .data(pie(dataRows as any))
        .enter()
        .append('path')
        .attr('d', (d: any, i: number, groups: any[]) => arc(d))
        .attr('fill', (d: any, i: number, groups: any[]) => String(color(String(d.data.label))));

      if (options && options.showPieAnimation) {
        path.transition()
          .duration(1000)
          .attrTween('d', function (d: any) {
            const interpolate = d3Interpolate.interpolate({startAngle: 0, endAngle: 0}, d);
            return function (t: any) {
              return arc(interpolate(t));
            };
          });
      }

      const restOfTheData = (mydata: any) => {
        try {
          const legendOptions: ILegendOptions = this.options.legendOptions;
          const legendRectSize = !!legendOptions && legendOptions.legendRectHeight ? legendOptions.legendRectHeight : 20;
          const legendSpacing = !!legendOptions && legendOptions.legendRectVSpace ? legendOptions.legendRectVSpace : 7;
          const legendHeight = legendRectSize + legendSpacing;
          const positionx = !!legendOptions && legendOptions.legendPositionX ? legendOptions.legendPositionX : 115;
          const positiony = !!legendOptions && legendOptions.legendPositionY ? legendOptions.legendPositionY : 65;

          if (options && options.showPercentageInDonut) {
            this.displayPercentageOnThePie(mydata, svg, pie, arc);
          }
          const defaultColor = getColorScale(d3Scale, settings.colors);
          if (this.options.colors) {
            this.displayPercentageNextToLegend(
              mydata, svg, defaultColor, positionx,
              positiony, legendHeight,
              settings.legend.textStyle.fontSize || '14px'
            );

            this.displayLengend(
              d3Select, mydata, svg, defaultColor, legendHeight,
              positionx, positiony, legendRectSize,
              settings.legend.textStyle.fontSize || '14px'
            );
          } else {
            this.displayPercentageNextToLegendDefault(
              mydata, svg, positionx, positiony, legendHeight,
              settings.legend.textStyle.fontSize || '14px'
            );

            this.displayLengendDefault(
              svg, defaultColor, legendHeight,
              positionx, positiony, legendRectSize,
              settings.legend.textStyle.fontSize || '14px'
            );
          }
          this.displayTitle(svg, settings);
        } catch (ex) {
          console.log(ex);
        }
      };
      setTimeout(restOfTheData(dataRows), 1000);
    })
  }

  private displayPercentageOnThePie(mydata: any, svg: any, pie: any, arc: any) {
    svg.selectAll('text')
      .data(pie(mydata))
      .enter()
      .append('text')
      .transition()
      .duration(200)
      .attr('transform', (d: any, i: number, groups: any[]) => 'translate(' + arc.centroid(d) + ')')
      .attr('dy', '.4em')
      .attr('text-anchor', 'middle')
      .text((d: any) => d.data.value + '%')
      .style('fill', '#fff')
      .style('font-size', '10px');
  }

  private displayPercentageNextToLegend(
    mydata: any, svg: any, defaultColor: any, positionX: any,
    positionY: any, legendHeight: any, fontSize: any) {
    svg.selectAll('.percentage')
      .data(mydata)
      .enter().append('g')
      .attr('class', 'percentage')
      .attr('transform', (d: any, i: number, groups: any[]) => 'translate(' + (positionX + 40) +
                                                                ',' + ((i * legendHeight) - positionY) + ')')
      .append('text')
      .style('fill', (d: any, i: number, groups: any[]) => defaultColor(i))
      .style('text-anchor', 'end')
      .style('font-size', fontSize)
      .text((d: any) => d.value + '%');
  }

  private displayPercentageNextToLegendDefault(mydata: any, svg: any, positionX: any, positionY: any, legendHeight: any, fontSize: any) {
    svg.selectAll('.percentage')
      .data(mydata)
      .enter().append('g')
      .attr('class', 'percentage')
      .attr('transform', (d: any, i: number, groups: any[]) => 'translate(' + (positionX + 40) +
                                                                ',' + ((i * legendHeight) - positionY) + ')')
      .append('text')
      .style('fill', '#000')
      .style('text-anchor', 'end')
      .style('font-size', fontSize)
      .text((d: any)  => d.value + '%');
  }

  private displayLengend(d3Select: any, mydata: any, svg: any, defaultColor: any, legendHeight: any,
  positionX: any, positionY: any, legendRectSize: any, fontSize: any) {
    const legendOptions: ILegendOptions = this.options.legendOptions;
    const legendRectWidth = !!legendOptions && legendOptions.legendRectWidth ? legendOptions.legendRectWidth : 10;
    const percentageOffsetX = !!legendOptions && legendOptions.legendPercentagePositionX ? legendOptions.legendPercentagePositionX : 56;
    const percentageOffsetY = !!legendOptions && legendOptions.legendPercentagePositionY ? legendOptions.legendPercentagePositionY : 15;
    const textOffsetX = !!legendOptions && legendOptions.legendTextPositionX ? legendOptions.legendTextPositionX : 30;
    const textOffsetY = !!legendOptions && legendOptions.legendTextPositionY ? legendOptions.legendTextPositionY : 15;
    const textWidth = !!legendOptions && legendOptions.legendTextWidth ? legendOptions.legendTextWidth : 200;

    const legend = svg.selectAll('.legend')
      .data(mydata)
      .enter()
      .append('g')
      .attr('class', 'legend')
      // Just a calculation for x & y position
      .attr('transform',
        (d: any, i: number, groups: any[]) => `translate(${positionX
        + percentageOffsetX},${(i * legendHeight) - (positionY + percentageOffsetY)})`);

    legend.append('rect')
      .attr('width', legendRectWidth)
      .attr('height', legendRectSize)
      .attr('rx', 1)
      .attr('ry', 1)
      .style('fill', (d: any, i: number, groups: any[]) => defaultColor(i))
      .style('stroke', (d: any, i: number, groups: any[]) => defaultColor(i));

    legend.append('text')
      .attr('x', textOffsetX)
      .attr('y', textOffsetY)
      .text((d: any) => d.label)
      .style('fill', '#000')
      .style('font-size', fontSize)
      .call(wrapText, d3Select, textWidth);
  }

  private displayLengendDefault(svg: any, defaultColor: any, legendHeight: any,
  positionX: any, positionY: any, legendRectSize: any, fontSize: any) {
    const legendRectWidth = 10;

    const legend = svg.selectAll('.legend')
      .data(defaultColor.domain())
      .enter()
      .append('g')
      .attr('class', 'legend')
      // Just a calculation for x & y position
      .attr('transform', (d: any, i: number, groups: any[]) => 'translate(' + (positionX + 50) +
                                                                ',' + ((i * legendHeight) - (positionY + 15)) + ')');

    legend.append('rect')
      .attr('width', legendRectWidth)
      .attr('height', legendRectSize)
      .attr('rx', 1)
      .attr('ry', 1)
      .style('fill', defaultColor)
      .style('stroke', defaultColor);

    legend.append('text')
      .attr('x', 30)
      .attr('y', 15)
      .text((d: any) => d)
      .style('fill', '#929DAF')
      .style('font-size', fontSize);
  }

  private displayTitle(svg: any, settings: any) {
    const textStyle = <TextStyle>Defaults(settings.title.textStyle || {}, DEFAULTS.textStyleTitle);
    svg.append('text')
      .attr('x', settings.widthInner / 2)
      .attr('y', 0 - (settings.margin.top / 1.15 ))
      .attr('text-anchor', 'middle')
      .style('font-size', textStyle.fontSize)
      .style('text-decoration', textStyle.textDecoration)
      .text(settings.title.text);
  }
}

0 个答案:

没有答案