Как добавить цвета к диаграмме солнечных лучей D3 js из JSON -файла? - PullRequest
1 голос
/ 25 мая 2020

Я создал диаграмму солнечных лучей на основе этого JSON файла. (рассматриваются существующие очереди)

Первые несколько строк выглядят так:

{
   "name": "A", "children": [
      {
         "name": "B",
         "color": "#A9DFBF",
         "children": [
            {
               "name": "C",
               "color": "#F9E79F",
               "children": [
       ....
       ....
       ....

Я пытаюсь использовать цвета из файла JSON в солнечных лучах: в этом примере все имеет быть зеленым помимо одного желтого ar c. Вместо этого мой код (см. Ниже) дает мне следующие визуализации солнечных лучей с двумя вариантами, которые я пробовал:

Альтернатива 1 относящейся части дает: enter image description here Альтернатива 2 соответствующей части (поиск «альтернативы») дает: enter image description here

Приветствую любую помощь по этому поводу.

Соответствующая часть кода:

    newSlice.append('path')
        .attr('class', 'main-arc')
        //Alternative 1
        //.style('fill', d => color((d.children ? d : d.parent).data.name))
        //Alternative 2
        .style('fill', function (d) { return color(d.color);})
        .attr('d', arc);

Полный код:

<head>
    <style>
        body {
            font-family: Sans-serif;
            font-size: 11px;
        }

        .slice {
            cursor: pointer;
        }

        .slice .main-arc {
            stroke: #fff;
            stroke-width: 1px;
        }

        .slice .hidden-arc {
            fill: none;
        }

        .slice text {
            pointer-events: none;
            dominant-baseline: middle;
            text-anchor: middle;
        }
    </style>

    <script src='https://d3js.org/d3.v4.min.js'></script>
</head>
<body>
    <script>
        const width = window.innerWidth,
            height = window.innerHeight,
            maxRadius = (Math.min(width, height) / 2) - 5;

        const formatNumber = d3.format(',d');

        const x = d3.scaleLinear()
            .range([0, 2 * Math.PI])
            .clamp(true);

        const y = d3.scaleSqrt()
            .range([maxRadius*.1, maxRadius]);

        const color = d3.scaleOrdinal(d3.schemeCategory20);

        const partition = d3.partition();

        const arc = d3.arc()
            .startAngle(d => x(d.x0))
            .endAngle(d => x(d.x1))
            .innerRadius(d => Math.max(0, y(d.y0)))
            .outerRadius(d => Math.max(0, y(d.y1)));

        const middleArcLine = d => {
            const halfPi = Math.PI/2;
            const angles = [x(d.x0) - halfPi, x(d.x1) - halfPi];
            const r = Math.max(0, (y(d.y0) + y(d.y1)) / 2);

            const middleAngle = (angles[1] + angles[0]) / 2;
            const invertDirection = middleAngle > 0 && middleAngle < Math.PI; // On lower quadrants write text ccw
            if (invertDirection) { angles.reverse(); }

            const path = d3.path();
            path.arc(0, 0, r, angles[0], angles[1], invertDirection);
            return path.toString();
        };

        const textFits = d => {
            const CHAR_SPACE = 6;

            const deltaAngle = x(d.x1) - x(d.x0);
            const r = Math.max(0, (y(d.y0) + y(d.y1)) / 2);
            const perimeter = r * deltaAngle;

            return d.data.name.length * CHAR_SPACE < perimeter;
        };

        const svg = d3.select('body').append('svg')
            .style('width', '100vw')
            .style('height', '100vh')
            .attr('viewBox', `${-width / 2} ${-height / 2} ${width} ${height}`)
            .on('click', () => focusOn()); // Reset zoom on canvas click

        d3.json('https://raw.githubusercontent.com/graphineer/viz/master/flare-3.json', (error, root) => {
            if (error) throw error;

            root = d3.hierarchy(root);
            root.sum(d => d.size);

            const slice = svg.selectAll('g.slice')
                .data(partition(root).descendants());

            slice.exit().remove();

            const newSlice = slice.enter()
                .append('g').attr('class', 'slice')
                .on('click', d => {
                    d3.event.stopPropagation();
                    focusOn(d);
                });

            newSlice.append('title')
                .text(d => d.data.name + '\n' + formatNumber(d.value));

            newSlice.append('path')
                .attr('class', 'main-arc')
                //Alternative 1
                //.style('fill', d => color((d.children ? d : d.parent).data.name))
                //Alternative 2
                .style('fill', function (d) { return color(d.color);})
                .attr('d', arc);


            newSlice.append('path')
                .attr('class', 'hidden-arc')
                .attr('id', (_, i) => `hiddenArc${i}`)
                .attr('d', middleArcLine);

            const text = newSlice.append('text')
                .attr('display', d => textFits(d) ? null : 'none');

            // Add white contour
            text.append('textPath')
                .attr('startOffset','50%')
                .attr('xlink:href', (_, i) => `#hiddenArc${i}` )
                .text(d => d.data.name)
                .style('fill', 'none')
                .style('stroke', '#fff')
                .style('stroke-width', 5)
                .style('stroke-linejoin', 'round');

            text.append('textPath')
                .attr('startOffset','50%')
                .attr('xlink:href', (_, i) => `#hiddenArc${i}` )
                .text(d => d.data.name);
        });

        function focusOn(d = { x0: 0, x1: 1, y0: 0, y1: 1 }) {
            // Reset to top-level if no data point specified

            const transition = svg.transition()
                .duration(750)
                .tween('scale', () => {
                    const xd = d3.interpolate(x.domain(), [d.x0, d.x1]),
                        yd = d3.interpolate(y.domain(), [d.y0, 1]);
                    return t => { x.domain(xd(t)); y.domain(yd(t)); };
                });

            transition.selectAll('path.main-arc')
                .attrTween('d', d => () => arc(d));

            transition.selectAll('path.hidden-arc')
                .attrTween('d', d => () => middleArcLine(d));

            transition.selectAll('text')
                .attrTween('display', d => () => textFits(d) ? null : 'none');

            moveStackToFront(d);

            //

            function moveStackToFront(elD) {
                svg.selectAll('.slice').filter(d => d === elD)
                    .each(function(d) {
                        this.parentNode.appendChild(this);
                        if (d.parent) { moveStackToFront(d.parent); }
                    })
            }
        }
    </script>
</body>

1 Ответ

2 голосов
/ 26 мая 2020

Три наблюдения:

  1. Требуемое свойство находится внутри свойства data, созданного генератором иерархии;
  2. Вам не нужна порядковая шкала color , поскольку у вас уже есть шестнадцатеричное значение цвета в виде строки;
  3. Ваш root узел не имеет такого свойства color, поэтому назначьте ему определенный c цвет (в примере «красный» ниже, используя логическое ИЛИ).

Итак, это:

    .style('fill', function(d) {
      return d.data.color || "red"
    })

Вот ваш код только с этим изменением:

<head>
  <style>
    body {
      font-family: Sans-serif;
      font-size: 11px;
    }

    .slice {
      cursor: pointer;
    }

    .slice .main-arc {
      stroke: #fff;
      stroke-width: 1px;
    }

    .slice .hidden-arc {
      fill: none;
    }

    .slice text {
      pointer-events: none;
      dominant-baseline: middle;
      text-anchor: middle;
    }

  </style>

  <script src='https://d3js.org/d3.v4.min.js'></script>
</head>

<body>
  <script>
    const width = window.innerWidth,
      height = window.innerHeight,
      maxRadius = (Math.min(width, height) / 2) - 5;

    const formatNumber = d3.format(',d');

    const x = d3.scaleLinear()
      .range([0, 2 * Math.PI])
      .clamp(true);

    const y = d3.scaleSqrt()
      .range([maxRadius * .1, maxRadius]);

    const color = d3.scaleOrdinal(d3.schemeCategory20);

    const partition = d3.partition();

    const arc = d3.arc()
      .startAngle(d => x(d.x0))
      .endAngle(d => x(d.x1))
      .innerRadius(d => Math.max(0, y(d.y0)))
      .outerRadius(d => Math.max(0, y(d.y1)));

    const middleArcLine = d => {
      const halfPi = Math.PI / 2;
      const angles = [x(d.x0) - halfPi, x(d.x1) - halfPi];
      const r = Math.max(0, (y(d.y0) + y(d.y1)) / 2);

      const middleAngle = (angles[1] + angles[0]) / 2;
      const invertDirection = middleAngle > 0 && middleAngle < Math.PI; // On lower quadrants write text ccw
      if (invertDirection) {
        angles.reverse();
      }

      const path = d3.path();
      path.arc(0, 0, r, angles[0], angles[1], invertDirection);
      return path.toString();
    };

    const textFits = d => {
      const CHAR_SPACE = 6;

      const deltaAngle = x(d.x1) - x(d.x0);
      const r = Math.max(0, (y(d.y0) + y(d.y1)) / 2);
      const perimeter = r * deltaAngle;

      return d.data.name.length * CHAR_SPACE < perimeter;
    };

    const svg = d3.select('body').append('svg')
      .style('width', '100vw')
      .style('height', '100vh')
      .attr('viewBox', `${-width / 2} ${-height / 2} ${width} ${height}`)
      .on('click', () => focusOn()); // Reset zoom on canvas click

    d3.json('https://raw.githubusercontent.com/graphineer/viz/master/flare-3.json', (error, root) => {
      if (error) throw error;

      root = d3.hierarchy(root);
      root.sum(d => d.size);

      const slice = svg.selectAll('g.slice')
        .data(partition(root).descendants());

      slice.exit().remove();

      const newSlice = slice.enter()
        .append('g').attr('class', 'slice')
        .on('click', d => {
          d3.event.stopPropagation();
          focusOn(d);
        });

      newSlice.append('title')
        .text(d => d.data.name + '\n' + formatNumber(d.value));

      newSlice.append('path')
        .attr('class', 'main-arc')
        //Alternative 1
        //.style('fill', d => color((d.children ? d : d.parent).data.name))
        //Alternative 2
        .style('fill', function(d) {
          return d.data.color || "red"
        })
        .attr('d', arc);


      newSlice.append('path')
        .attr('class', 'hidden-arc')
        .attr('id', (_, i) => `hiddenArc${i}`)
        .attr('d', middleArcLine);

      const text = newSlice.append('text')
        .attr('display', d => textFits(d) ? null : 'none');

      // Add white contour
      text.append('textPath')
        .attr('startOffset', '50%')
        .attr('xlink:href', (_, i) => `#hiddenArc${i}`)
        .text(d => d.data.name)
        .style('fill', 'none')
        .style('stroke', '#fff')
        .style('stroke-width', 5)
        .style('stroke-linejoin', 'round');

      text.append('textPath')
        .attr('startOffset', '50%')
        .attr('xlink:href', (_, i) => `#hiddenArc${i}`)
        .text(d => d.data.name);
    });

    function focusOn(d = {
      x0: 0,
      x1: 1,
      y0: 0,
      y1: 1
    }) {
      // Reset to top-level if no data point specified

      const transition = svg.transition()
        .duration(750)
        .tween('scale', () => {
          const xd = d3.interpolate(x.domain(), [d.x0, d.x1]),
            yd = d3.interpolate(y.domain(), [d.y0, 1]);
          return t => {
            x.domain(xd(t));
            y.domain(yd(t));
          };
        });

      transition.selectAll('path.main-arc')
        .attrTween('d', d => () => arc(d));

      transition.selectAll('path.hidden-arc')
        .attrTween('d', d => () => middleArcLine(d));

      transition.selectAll('text')
        .attrTween('display', d => () => textFits(d) ? null : 'none');

      moveStackToFront(d);

      //

      function moveStackToFront(elD) {
        svg.selectAll('.slice').filter(d => d === elD)
          .each(function(d) {
            this.parentNode.appendChild(this);
            if (d.parent) {
              moveStackToFront(d.parent);
            }
          })
      }
    }

  </script>
</body>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...