D3. js: xScale возвращает мне undefined - PullRequest
2 голосов
/ 08 мая 2020

Я совершенно новичок ie в D3, и у меня действительно большая проблема. Я сделал разработку, чтобы показать многополосную диаграмму c, которая отлично работает, и вы можете проверить здесь:

Edit MultipleBarChart

Я скопировал эту разработку на свой приложение, и у меня есть ошибка, и я не знаю, почему это произошло.

Это код моего приложения:

import React, { Component } from "react";
import * as d3 from "d3";
import "../../../css/content.css";

const HEIGHT = 360;
let WIDTH = 0;

const OFFSET_TOP = 30;
const OFFSET_BOTTOM = 30;
const OFFSET_LEFT = 80;
const OFFSET_RIGHT = 50;

/**
 * To build this graphic we need to pass some values in these props:
 *      - idContainer: id of the div container. This must be unique
 *      - data: data to build the graphic. This is an array of json objects where each json object has the next structure:
 *              {
 *                  shoot: String with the name of shoot, 
 *                  abrev_home_team: % of shoot for that team, 
 *                  abrev_away_team: % of shoot for that team
 *              }
 *      - groupKey: Name of the key which are goingto use to group the bars
 *      - keys: Array with the strings of all the keys which we are going to group around the groupKey
 *      - y_label: String with the name of Y Axis
 *      - lang: language to use to show the float numbers
 */

let data = [
  {
    name: "SPAR CITYLIFT GIRONA",
    ortg: 95.17,
    drtg: 81.96
  },
  {
    name: "PERFUMERIAS AVENIDA",
    ortg: 109.08,
    drtg: 79.26
  },
  {
    name: "IDK EUSKOTREN",
    ortg: 89.50,
    drtg: 95.13
  },
  {
    name: "QUESOS EL PASTOR",
    ortg: 87.30,
    drtg: 99.58
  },
  {
    name: "VALENCIA B.C.",
    ortg: 93.40,
    drtg: 86.81
  },
  {
    name: "DURÁN MAQUINARIA ENSINO",
    ortg: 93.12,
    drtg: 95.69
  },
  {
    name: "CADI LA SEU",
    ortg: 95.37,
    drtg: 94.97
  },
  {
    name: "RPK ARASKI",
    ortg: 89.50,
    drtg: 88.85
  },
  {
    name: "EMBUTIDOS PAJARIEL BEMBIBRE PD",
    ortg: 81.85,
    drtg: 95.47
  },
  {
    name: "CAMPUS PROMETE",
    ortg: 86.38,
    drtg: 92.29
  },
  {
    name: "CIUDAD DE LA LAGUNA TENERIFE",
    ortg: 88.69,
    drtg: 96.81
  },
  {
    name: "MANN-FILTER CASABLANCA",
    ortg: 85.44,
    drtg: 93.78
  },
  {
    name: "LOINTEK GERNIKA BIZKAIA",
    ortg: 100.12,
    drtg: 85.28
  },
  {
    name: "NISSAN  AL-QÁZERES EXTREMADURA",
    ortg: 81.39,
    drtg: 99.10
  }
];

class MultipleBarChart extends Component {
  constructor(props) {
    super();
    this.props = props;
    this.state = {
      loaded: false
    };
  }

  componentDidMount() {
     //Don't do anything!!!
  }

  componentWillReceiveProps(nextProps){
    this.props = nextProps;

    console.log("OFFSET_TOP: " + OFFSET_TOP);
    console.log("FFSET_BOTTOM: " + OFFSET_BOTTOM);
    console.log("OFFSET_LEFT: " + OFFSET_LEFT);
    console.log("OFFSET_RIGHT): " + OFFSET_RIGHT);



    let data2 = this.props.data.map((item, index) =>{
      let item2 = {};
      item2.name = item.name;
      item2.ortg = parseFloat(item.ortg).toFixed(2);
      item2.drtg = parseFloat(item.drtg).toFixed(2);
      console.log("DATA name: " + typeof data[index].name + " PROPS name: " + typeof item2.name);
      console.log("DATA ortg: " + typeof data[index].ortg.toFixed(2) + " PROPS ortg: " + typeof item2.ortg);     
      console.log("DATA drtg: " + typeof data[index].drtg.toFixed(2) + " PROPS drtg: " + typeof item2.drtg); 
      return item2;
    });

    let canvas = this.setCanvas();
    let colors = this.setColors();
    let scales = this.setScales(data2, this.props.keys);
    //let scales = this.setScales(data, this.props.keys, this.props.groupKey);
    this.setAxis(canvas, scales);
    this.setLegend(canvas, colors, this.props.keys);
    this.setBars(canvas, data2, scales, this.props.keys, colors, this.props.lang, this.props.groupKey);
    //this.setBars(canvas, data, scales, this.props.keys, colors, this.props.lang, this.props.groupKey);
    this.setState({
      loaded: (typeof data2 !== "undefined" && typeof this.props.keys !== "undefined") ? true : false
    });

}

  setAxis(canvas, scales) {
    //x axis
    canvas
      .append("g")
      .attr("class", "axis")
      .attr(
        "transform",
        "translate(" + 0 + ", " + (HEIGHT - OFFSET_TOP - OFFSET_BOTTOM) + ")"
      )
      .call(d3.axisBottom(scales.x0Scale))
      .selectAll("text")
      .style("text-anchor", "middle")
      .style("font-weight", "bold")
      .attr("font-size", "10pt")
      .attr("dx", "-.1em");

    //y axis
    console.log("OFFSET_LEFT: " + OFFSET_LEFT);
    canvas
      .append("g")
      .attr("class", "axis")
      //.call(d3.axisLeft(scales.yScale).ticks(null, ".00%"))
      .call(d3.axisLeft(scales.yScale).tickFormat(d3.format(".2f")))
      //.selectAll("text")
      .style("font-size", "8pt")
      .style("font-weight", "bold")
      .attr("transform", "translate(" + OFFSET_LEFT + ", 0)")
      .append("text")
      .attr("x", OFFSET_LEFT / 2 + 12)
      .attr("y", OFFSET_TOP)
      .attr("dx", "-3em")
      .attr("dy", "-1em")
      .attr("fill", "#FFFFFF")
      .style("font-weight", "bold")
      .style("font-size", "8pt")
      .attr("text-anchor", "end")
      .text(this.props.y_label);

    //return { xAxis: xAxis, yAxis: yAxis };
  }

  setBars(canvas, data, scales, keys, colors, lang, groupKey) {
    let height = HEIGHT - OFFSET_TOP - OFFSET_BOTTOM;
    //var formatPercent = d3.format(".0%");

    let bar = canvas
      .append("g")
      .selectAll("g")
      .data(data)
      .enter()
      .append("g")
      .attr("transform", function(d) {
        //return "translate(" + scales.x0Scale(d.shoot) + ",0)";
        console.log("d[groupKey]: " + d[groupKey]);
        console.log("scales1: " + scales.x0Scale(d[groupKey]));
        return "translate(" + scales.x0Scale(d[groupKey]) + ",0)";
      });

    //Here, you append rects to the groups:
    bar
      .selectAll("rect")
      .data(function(d) {
        return keys.map(function(key) {
          return {
            key: key,
            value: d[key]
          };
        });
      })
      .enter()
      .append("rect")
      .attr("class", "rect")
      .attr("x", function(d) {
        return scales.x1Scale(d.key);
      })
      .attr("y", function(d) {
        return scales.yScale(d.value);
      })
      .attr("width", scales.x1Scale.bandwidth())
      .attr("height", function(d) {
        return height - scales.yScale(d.value);
      })
      .attr("fill", function(d) {
        return colors(d.key);
      });

    //Finally, here, you append texts to the groups:
    bar
      .selectAll("text")
      .data(function(d) {
        return keys.map(function(key) {
          return {
            key: key,
            value: d[key]
          };
        });
      })
      .enter()
      .append("text")
      .attr("text-anchor", "middle")
      .attr("class", "label-multiple-bar")
      .attr("x", function(d, i) {
        return scales.x1Scale.bandwidth() * (i + 0.5);
      })
      .attr("y", function(d, i) {
        return scales.yScale(d.value) - 5;
      })
      .text(function(d) {
        return lang === "es"
          ? String(d.value).replace(".", ",") + "%"
          : d.value + "%";
      });
  }

  setCanvas() {
    console.log("WIDTH: " + d3.select("#" + this.props.idContainer).style("width"));
    WIDTH = parseFloat(
      d3.select("#" + this.props.idContainer).style("width")
    ).toFixed(2);
    let svg = d3
      .select("#" + this.props.idContainer)
      .append("svg")
      .style("background-color", "#354560")
      .style("color", "#FFFFFF") //With this we've got the color of the axis too
      .attr("height", HEIGHT)
      .attr("width", WIDTH);

    return svg;
  }

  setColors() {
    return d3
      .scaleOrdinal()
      .range(["#DC3545", "#FFC107", "#007BFF", "#28A745", "#17A2B8", "#cf0eed"]);
  }

  /**
   * Set a legend to the graphic at the top right of it
   *
   * @param {*} canvas
   * @param {*} colors
   * @param {*} keys
   */
  setLegend(canvas, colors, keys) {
    let legend = canvas
      .append("g")
      .selectAll("g")
      .data(keys.slice())
      .enter()
      .append("g")
      .attr("transform", function(d, i) {
        return "translate(0," + i * 20 + ")";
      });

    legend
      .append("rect")
      .attr("x", WIDTH - OFFSET_RIGHT - OFFSET_LEFT)
      .attr("y", 12)
      .attr("width", 25)
      .attr("height", 10)
      .attr("fill", colors);

    legend
      .append("text")
      .attr("x", WIDTH - OFFSET_RIGHT - OFFSET_LEFT + 60)
      .attr("y", 18)
      .attr("dy", "0.32em")
      .text(function(d) {
        return d;
      })
      .attr("fill", "#FFFFFF")
      .attr("font-family", "Roboto")
      .style("font-size", "10pt")
      .style("font-weight", "bold")
      .attr("text-anchor", "end");
  }

  setScales(data, keys, groupKey) {
    let xRange = [OFFSET_LEFT + 0.05, WIDTH - OFFSET_RIGHT];
    let yRange = [HEIGHT - OFFSET_TOP - OFFSET_BOTTOM, OFFSET_TOP];

    let x0 = d3
      .scaleBand()
      .domain(
        data.map(function(d) {
          return d[groupKey];
        })
      )
      .rangeRound(xRange)
      .paddingInner(0.2);

    let x1 = d3
      .scaleBand()
      .domain(keys)
      .rangeRound([0, x0.bandwidth()])
      .padding(0);

    var y = d3
      .scaleLinear()
      .domain([
        0,
        d3.max(data, function(d) {
          return d3.max(keys, function(key) {
            return parseFloat(d[key]);
          });
        })
      ])
      .nice()
      .rangeRound(yRange);

    return {
      x0Scale: x0,
      x1Scale: x1,
      yScale: y,
      xRange: xRange,
      yRange: yRange
    };
  }

  render() {
    return (
      <div>
        <div id={this.props.idContainer} style = {{width: 1155 + "px"}}></div>
      </div>
    );
  }
}

module.exports.MultipleBarChart = MultipleBarChart;

Этот компонент может работать как с получением данных, так и с данными из переменной "data" в начале кода. Если я запускаю свой код, беря данные из переменной «data», приложения работают нормально. enter image description here

Вы можете увидеть все столбчатые диаграммы, сгруппированные по два. Пожалуйста, не заботьтесь о наложении сейчас:)

В созданном вами журнале вы можете проверить, правильно ли создается xScale, потому что он возвращает правильное значение. Вы можете проверить это на этой картинке:

enter image description here

Хорошо, все правильно. Но, если я прокомментирую эти строки, которые вы можете найти в componentWillReceiveProps

let scales = this.setScales(data, this.props.keys, this.props.groupKey);
this.setBars(canvas, data, scales, this.props.keys, colors, this.props.lang, this.props.groupKey);

, и раскомментируйте другие тезисы, которые также находятся в componentWillReceiveProps:

//this.setBars(canvas, data2, scales, this.props.keys, colors, this.props.lang, this.props.groupKey);
//let scales = this.setScales(data2, this.props.keys);

Чтобы взять данные из реквизита (получаются из другого компонента) мы получили такой результат:

enter image description here

Это связано с ошибкой с xScale, в этом журнале вы можете увидеть, как это возвращает undefined:

enter image description here

Эта ошибка возвращается с помощью этого кода:

let bar = canvas
  .append("g")
  .selectAll("g")
  .data(data)
  .enter()
  .append("g")
  .attr("transform", function(d) {
    //return "translate(" + scales.x0Scale(d.shoot) + ",0)";
    console.log("d[groupKey]: " + d[groupKey]);
    console.log("scales1: " + scales.x0Scale(d[groupKey]));
    return "translate(" + scales.x0Scale(d[groupKey]) + ",0)";
  });

Этот фрагмент кода вы можете найти по адресу начало setBars. Итак, ошибка в return "translate( + scales.X0Scale(d[groupKey] ", 0)" `

Но я не знаю почему !!! Я схожу с ума !!! : S

Я искал информацию и обнаружил, что при создании шкал вам нужно назначить массив для функций диапазона и домена из Scale, но я добавил функцию диапазона, дающую массив и удаляющую rangeBound из мой код, но он по-прежнему не работает.

Почему работает нормально, когда я беру данные из переменной данных, а неправильно работает с данными из другого компонента? Ничего не понимаю ...

1 Ответ

1 голос
/ 10 мая 2020

Есть пара проблем с вашим кодом

Первая и самая важная

Тот факт, что вы видите агрегированную панель с данными2, объясняется тем, что вы не передаете groupKeys

Когда вы используете

this.setBars(canvas, data2, scales, this.props.keys, colors, this.props.lang, this.props.groupKey);
let scales = this.setScales(data2, this.props.keys); // you forgot groupKeys here

Правильный способ будет

this.setBars(canvas, data2, scales, this.props.keys, colors, this.props.lang, this.props.groupKey);
let scales = this.setScales(data2, this.props.keys, this.props.groupKeys);

Теперь другие важные вещи, на которые следует обратить внимание:

  • componentWillReceiveProps устарело в последней версии response, и поэтому вы должны указать componentDidUpdate, если вам нужно.
  • Вы никогда не должны переопределять такие реквизиты, как this.props = nextProps;, как в componentWillReceiveProps
  • Вы не должны напрямую реализуйте журнал обновлений c в componentDidUpdate, как вы это делаете сейчас в componentWillReceiveProps. Вместо этого вы должны сравнить предыдущие значения с текущими и продолжить обновление только в том случае, если оно изменилось.
  • Теперь я вижу, что вы не получаете данные с удаленного ресурса. Если это правда, вы должны не только использовать componentDidUpdate, но и комбинацию componentDidMount и componentDidUpdate, а также выделить весь лог c в служебной функции

Ваш код должен выглядеть что-то вроде

class MultipleBarChart extends Component {
  constructor(props) {
    super();
    this.props = props;
    this.state = {
      loaded: false
    };
  }

  componentDidMount() {
     if(this.props.data) {
       this.updateChart();
     }
  }

  updateChart = () => {

    console.log("OFFSET_TOP: " + OFFSET_TOP);
    console.log("FFSET_BOTTOM: " + OFFSET_BOTTOM);
    console.log("OFFSET_LEFT: " + OFFSET_LEFT);
    console.log("OFFSET_RIGHT): " + OFFSET_RIGHT);



    let data2 = this.props.data.map((item, index) =>{
      let item2 = {};
      item2.name = item.name;
      item2.ortg = parseFloat(item.ortg).toFixed(2);
      item2.drtg = parseFloat(item.drtg).toFixed(2);
      // console.log("DATA name: " + typeof data[index].name + " PROPS name: " + typeof item2.name);
      // console.log("DATA ortg: " + typeof data[index].ortg.toFixed(2) + " PROPS ortg: " + typeof item2.ortg);     
      // console.log("DATA drtg: " + typeof data[index].drtg.toFixed(2) + " PROPS drtg: " + typeof item2.drtg); 
      return item2;
    });

    let canvas = this.setCanvas();
    let colors = this.setColors();
    let scales = this.setScales(data2, this.props.keys, this.props.groupKey);
    //let scales = this.setScales(data, this.props.keys, this.props.groupKey);
    this.setAxis(canvas, scales);
    this.setLegend(canvas, colors, this.props.keys);
    this.setBars(canvas, data2, scales, this.props.keys, colors, this.props.lang, this.props.groupKey);
    //this.setBars(canvas, data, scales, this.props.keys, colors, this.props.lang, this.props.groupKey);
    this.setState({
      loaded: (typeof data2 !== "undefined" && typeof this.props.keys !== "undefined") ? true : false
    });

  }

  componentDidUpdate(prev){
    const { data, keys, lang, groupKey} = this.props
    if(data !== prev.data || keys !== prev.keys || lang !== prev.lang || groupKey !== prev.groupKey) {
        this.updateChart();
     }
  }

  setAxis(canvas, scales) {
    //x axis
    canvas
      .append("g")
      .attr("class", "axis")
      .attr(
        "transform",
        "translate(" + 0 + ", " + (HEIGHT - OFFSET_TOP - OFFSET_BOTTOM) + ")"
      )
      .call(d3.axisBottom(scales.x0Scale))
      .selectAll("text")
      .style("text-anchor", "middle")
      .style("font-weight", "bold")
      .attr("font-size", "10pt")
      .attr("dx", "-.1em");

    //y axis
    console.log("OFFSET_LEFT: " + OFFSET_LEFT);
    canvas
      .append("g")
      .attr("class", "axis")
      //.call(d3.axisLeft(scales.yScale).ticks(null, ".00%"))
      .call(d3.axisLeft(scales.yScale).tickFormat(d3.format(".2f")))
      //.selectAll("text")
      .style("font-size", "8pt")
      .style("font-weight", "bold")
      .attr("transform", "translate(" + OFFSET_LEFT + ", 0)")
      .append("text")
      .attr("x", OFFSET_LEFT / 2 + 12)
      .attr("y", OFFSET_TOP)
      .attr("dx", "-3em")
      .attr("dy", "-1em")
      .attr("fill", "#FFFFFF")
      .style("font-weight", "bold")
      .style("font-size", "8pt")
      .attr("text-anchor", "end")
      .text(this.props.y_label);

    //return { xAxis: xAxis, yAxis: yAxis };
  }

  setBars(canvas, data, scales, keys, colors, lang, groupKey) {
    let height = HEIGHT - OFFSET_TOP - OFFSET_BOTTOM;
    //var formatPercent = d3.format(".0%");

    let bar = canvas
      .append("g")
      .selectAll("g")
      .data(data)
      .enter()
      .append("g")
      .attr("transform", function(d) {
        //return "translate(" + scales.x0Scale(d.shoot) + ",0)";
        console.log("d[groupKey]: " + d[groupKey]);
        console.log("scales1: " + scales.x0Scale(d[groupKey]));
        return "translate(" + scales.x0Scale(d[groupKey]) + ",0)";
      });

    //Here, you append rects to the groups:
    bar
      .selectAll("rect")
      .data(function(d) {
        return keys.map(function(key) {
          return {
            key: key,
            value: d[key]
          };
        });
      })
      .enter()
      .append("rect")
      .attr("class", "rect")
      .attr("x", function(d) {
        return scales.x1Scale(d.key);
      })
      .attr("y", function(d) {
        return scales.yScale(d.value);
      })
      .attr("width", scales.x1Scale.bandwidth())
      .attr("height", function(d) {
        return height - scales.yScale(d.value);
      })
      .attr("fill", function(d) {
        return colors(d.key);
      });

    //Finally, here, you append texts to the groups:
    bar
      .selectAll("text")
      .data(function(d) {
        return keys.map(function(key) {
          return {
            key: key,
            value: d[key]
          };
        });
      })
      .enter()
      .append("text")
      .attr("text-anchor", "middle")
      .attr("class", "label-multiple-bar")
      .attr("x", function(d, i) {
        return scales.x1Scale.bandwidth() * (i + 0.5);
      })
      .attr("y", function(d, i) {
        return scales.yScale(d.value) - 5;
      })
      .text(function(d) {
        return lang === "es"
          ? String(d.value).replace(".", ",") + "%"
          : d.value + "%";
      });
  }

  setCanvas() {
    console.log("WIDTH: " + d3.select("#" + this.props.idContainer).style("width"));
    WIDTH = parseFloat(
      d3.select("#" + this.props.idContainer).style("width")
    ).toFixed(2);
    let svg = d3
      .select("#" + this.props.idContainer)
      .append("svg")
      .style("background-color", "#354560")
      .style("color", "#FFFFFF") //With this we've got the color of the axis too
      .attr("height", HEIGHT)
      .attr("width", WIDTH);

    return svg;
  }

  setColors() {
    return d3
      .scaleOrdinal()
      .range(["#DC3545", "#FFC107", "#007BFF", "#28A745", "#17A2B8", "#cf0eed"]);
  }

  /**
   * Set a legend to the graphic at the top right of it
   *
   * @param {*} canvas
   * @param {*} colors
   * @param {*} keys
   */
  setLegend(canvas, colors, keys) {
    let legend = canvas
      .append("g")
      .selectAll("g")
      .data(keys.slice())
      .enter()
      .append("g")
      .attr("transform", function(d, i) {
        return "translate(0," + i * 20 + ")";
      });

    legend
      .append("rect")
      .attr("x", WIDTH - OFFSET_RIGHT - OFFSET_LEFT)
      .attr("y", 12)
      .attr("width", 25)
      .attr("height", 10)
      .attr("fill", colors);

    legend
      .append("text")
      .attr("x", WIDTH - OFFSET_RIGHT - OFFSET_LEFT + 60)
      .attr("y", 18)
      .attr("dy", "0.32em")
      .text(function(d) {
        return d;
      })
      .attr("fill", "#FFFFFF")
      .attr("font-family", "Roboto")
      .style("font-size", "10pt")
      .style("font-weight", "bold")
      .attr("text-anchor", "end");
  }

  setScales(data, keys, groupKey) {
    let xRange = [OFFSET_LEFT + 0.05, WIDTH - OFFSET_RIGHT];
    let yRange = [HEIGHT - OFFSET_TOP - OFFSET_BOTTOM, OFFSET_TOP];

    let x0 = d3
      .scaleBand()
      .domain(
        data.map(function(d) {
          return d[groupKey];
        })
      )
      .rangeRound(xRange)
      .paddingInner(0.2);

    let x1 = d3
      .scaleBand()
      .domain(keys)
      .rangeRound([0, x0.bandwidth()])
      .padding(0);

    var y = d3
      .scaleLinear()
      .domain([
        0,
        d3.max(data, function(d) {
          return d3.max(keys, function(key) {
            return parseFloat(d[key]);
          });
        })
      ])
      .nice()
      .rangeRound(yRange);

    return {
      x0Scale: x0,
      x1Scale: x1,
      yScale: y,
      xRange: xRange,
      yRange: yRange
    };
  }

  render() {
    return (
      <div>
        <div id={this.props.idContainer} style = {{width: 1155 + "px"}}></div>
      </div>
    );
  }
}

Рабочая демонстрация

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