Обновите многолинейный график с помощью D3 - PullRequest
2 голосов
/ 14 июля 2020

Я пытаюсь поиграть с D3 и сделать некоторую визуализацию с использованием данных COVID-19, но я не могу понять, как обновить многострочный график с помощью кнопки. Вот что у меня сейчас:

<!DOCTYPE html>
<meta charset="utf-8" />
<html lang="en">
<style>
  .line1 {
    fill: none;
    stroke: darkcyan;
    stroke-width: 2.5px;
  }
  
  .line2 {
    fill: none;
    stroke: red;
    stroke-width: 2.5px;
  }
  
  .line3 {
    fill: none;
    stroke: green;
    stroke-width: 2.5px;
  }
  
  .axis path,
  .axis line {
    fill: none;
    stroke: grey;
    stroke-width: 1;
    shape-rendering: crispEdges;
  }
  
  .grid line {
    stroke: lightgrey;
    stroke-opacity: 0.7;
    shape-rendering: crispEdges;
  }
  
  .grid path {
    stroke-width: 0;
  }
  
  .legend rect {
    fill: white;
    stroke: black;
    opacity: 0.8;
  }
</style>

<head>
  <script src="https://d3js.org/d3.v5.min.js"></script>
</head>

<body>
  <h1>Total Confirmed Coronavirus Cases in Germany</h1>

  <div id="option">
    <input name="updateButton" type="button" value="Linear" onclick="linear()" />
    <input name="updateButton" type="button" value="Logarithmic" onclick="logarithmic()" />
  </div>

  <script>
    // write your d3 code here..

    var margin = {
        top: 20,
        right: 20,
        bottom: 30,
        left: 50
      },
      width = 1000 - margin.left - margin.right,
      height = 600 - margin.top - margin.bottom;

    // parse the date / time
    var parseTime = d3.utcParse("%Y-%m-%dT%H:%M:%S%Z");
    var formatDate = d3.timeFormat("%m-%d");

    // set the ranges
    var x = d3.scaleUtc().range([0, width]);
    var y = d3.scaleLinear().range([height, 0]);
    var logy = d3.scaleLog().range([height, 0]);

    // Define the axes
    var xAxis = d3.axisBottom(x);
    var yAxis = d3.axisLeft(y);
    var logyAxis = d3.axisLeft(logy);

    // define the 1st line
    var valueline1 = d3
      .line()
      .x(function(d) {
        return x(d.date);
      })
      .y(function(d) {
        return y(d.confirmed);
      });

    // define the 2nd line
    var valueline2 = d3.line()
      .x(function(d) {
        return x(d.date);
      })
      .y(function(d) {
        return y(d.deaths);
      });

    // define the 3rd line
    var valueline3 = d3.line()
      .x(function(d) {
        return x(d.date);
      })
      .y(function(d) {
        return y(d.recovered);
      });


    // append the svg obgect to the body of the page
    // appends a 'group' element to 'svg'
    // moves the 'group' element to the top left margin
    var svg = d3
      .select("body")
      .append("svg")
      .attr("width", width + margin.left + margin.right)
      .attr("height", height + margin.top + margin.bottom)
      .append("g")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    // gridlines in x axis function
    function x_gridlines() {
      return d3.axisBottom(x)
    }

    // gridlines in y axis function
    function y_gridlines() {
      return d3.axisLeft(y)
    }

    // gridlines in y axis function
    function logy_gridlines() {
      return d3.axisLeft(logy)
    }

    d3.json(
      "https://api.covid19api.com/total/dayone/country/germany"
    ).then(function(data) {

      data.forEach(function(d) {
        d.country = d.Country
        d.date = parseTime(d.Date);
        d.confirmed = d.Confirmed;
        d.deaths = d.Deaths;
        d.recovered = d.Recovered;
      });

      // Scale the range of the data
      x.domain(d3.extent(data, function(d) {
        return d.date;
      }));
      y.domain([0, d3.max(data, function(d) {
        return Math.max(d.confirmed, d.deaths, d.recovered);
      })]);

      // Add the valueline path.
      svg.append("path")
        .data([data])
        .attr("class", "line1")
        .attr("d", valueline1);

      // Add the valueline2 path.
      svg.append("path")
        .data([data])
        .attr("class", "line2")
        .attr("d", valueline2);

      // Add the valueline3 path.
      svg.append("path")
        .data([data])
        .attr("class", "line3")
        .attr("d", valueline3);

      // Add the X Axis
      svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis);

      // Add the Y Axis
      svg.append("g")
        .attr("class", "y axis")
        .call(yAxis);

      // add the X gridlines
      svg.append("g")
        .attr("class", "x grid")
        .attr("transform", "translate(0," + height + ")")
        .call(x_gridlines()
          .tickSize(-height)
          .tickFormat("")
        )

      // add the Y gridlines
      svg.append("g")
        .attr("class", "y grid")
        .call(y_gridlines()
          .tickSize(-width)
          .tickFormat("")
        )

      legend = svg.append("g")
        .attr("class", "legend")
        .attr("transform", "translate(50,30)")
        .style("font-size", "12px")
        .call(d3.legend)
    });

    // ** Update data section (Called from the onclick)
    function logarithmic() {

      // Get the data again
      d3.json(
        "https://api.covid19api.com/total/dayone/country/germany"
      ).then(function(data) {

        data.forEach(function(d) {
          d.country = d.Country
          d.date = parseTime(d.Date);
          d.confirmedlog = Math.log(d.Confirmed);
          d.deathslog = Math.log(d.Deaths);
          d.recoveredlog = Math.log(d.Recovered);
        });

        // Scale the range of the data again
        x.domain(d3.extent(data, function(d) {
          return d.date;
        }));

        y.domain([0,
          d3.max(data, function(d) {
            return Math.max(d.confirmedlog, d.deathslog, d.recoveredlog);
          })
        ]);

        // Select the section we want to apply our changes to
        var svg = d3.select("body").data(data).transition();

        // Make the changes
        svg.select(".line1") // change the line
          .duration(750)
          .attr("d", function(d) {
            return valueline1(d.confirmedlog);
          })

        svg.select(".line2") // change the line
          .duration(750)
          .attr("d", function(d) {
            return valueline1(d.deathslog);
          });

        svg.select(".line3") // change the line
          .duration(750)
          .attr("d", function(d) {
            return valueline1(d.recoveredlog);
          });

        svg.select(".y.axis") // change the y axis
          .duration(750)
          .call(logyAxis);
        svg.select(".y.grid")
          .duration(750)
          .call(logy_gridlines()
            .tickSize(-width)
            .tickFormat(""))
      });
    }

    // ** Update data section (Called from the onclick)
    function linear() {

      // Get the data again
      d3.json(
        "https://api.covid19api.com/total/dayone/country/germany"
      ).then(function(data) {

        data.forEach(function(d) {
          d.country = d.Country
          d.date = parseTime(d.Date);
          d.confirmed = d.Confirmed;
          d.deaths = d.Deaths;
          d.recovered = d.Recovered;
        });

        // Scale the range of the data again 
        x.domain(d3.extent(data, function(d) {
          return d.date;
        }));
        y.domain([0, d3.max(data, function(d) {
          return d.confirmed, d.deaths, d.recovered;
        })]);

        // Select the section we want to apply our changes to
        var svg = d3.select("body").transition();

        // Make the changes
        svg.select(".line1") // change the line
          .duration(750)
          .attr("d", valueline1(data));

        svg.select(".line2") // change the line
          .duration(750)
          .attr("d", valueline2(data));

        svg.select(".line3") // change the line
          .duration(750)
          .attr("d", valueline3(data));

        svg.select(".x.axis") // change the x axis
          .duration(750)
          .call(xAxis);
        svg.select(".y.axis") // change the y axis
          .duration(750)
          .call(yAxis);
        svg.select(".y.grid") // change the y gridlines
          .duration(750)
          .call(y_gridlines()
            .tickSize(-width)
            .tickFormat("")
          );

      });
    }
  </script>
</body>

</html>

У меня проблемы с переключением со значения по умолчанию на представление Logarithmic. Всякий раз, когда я нажимаю кнопку для переключения, все линии исчезают. У меня есть функция под названием: logarithmic(), а код внутри функции следующий:

function logarithmic() {

            // Get the data again
            d3.json(
                "https://api.covid19api.com/total/dayone/country/germany"
            ).then(function (data) {

                data.forEach(function (d) {
                    d.country = d.Country
                    d.date = parseTime(d.Date);
                    d.confirmedlog = Math.log(d.Confirmed);
                    d.deathslog = Math.log(d.Deaths);
                    d.recoveredlog = Math.log(d.Recovered);
                });

                // Scale the range of the data again
                x.domain(d3.extent(data, function (d) { return d.date; }));

                y.domain([0,
                d3.max(data, function (d) {return Math.max(d.confirmedlog, d.deathslog, d.recoveredlog);
                })]);

                // Select the section we want to apply our changes to
                var svg = d3.select("body").data(data).transition();

                // Make the changes
                svg.select(".line1")   // change the line
                    .duration(750)
                    .attr("d", function(d) { return valueline1(d.confirmedlog); })

                svg.select(".line2")// change the line
                    .duration(750)
                    .attr("d", function(d) { return valueline1(d.deathslog); });

                svg.select(".line3")   // change the line
                    .duration(750)
                    .attr("d", function(d) { return valueline1(d.recoveredlog); });

                svg.select(".y.axis") // change the y axis
                    .duration(750)
                    .call(logyAxis);
                svg.select(".y.grid")
                    .duration(750)
                    .call(logy_gridlines()
                        .tickSize(-width)
                        .tickFormat(""))
            });
}

Любая помощь будет принята с благодарностью! Или, если есть какие-либо передовые методы обновления многолинейных графиков, мы также будем очень благодарны за любые советы. Большое спасибо

1 Ответ

1 голос
/ 14 июля 2020

Что ж, к сожалению, в вашем коде есть несколько проблем, и сейчас он далек от лучших практик D3 (или JavaScript).

Помимо привязки данных к телу и переназначения имен выбора, среди прочего, Самая первая проблема, которая привлекла мое внимание, заключалась в повторной загрузке (тех же) данных для обновлений. Это совершенно не нужно.

В этом очень быстром рефакторинге я уже просто вычисляю значения журнала. Обратите внимание на то, что логарифм нуля в JS равен минус бесконечности. Итак, просто проверьте это:

d.confirmedlog = d.Confirmed ? Math.log(d.Confirmed) : 0;
d.deathslog = d.Deaths ? Math.log(d.Deaths) : 0;
d.recoveredlog = d.Recovered ? Math.log(d.Recovered) : 0;

Затем для каждой строки измените метод y генератора строк. Например:

svg.select(".line1") // change the line
    .duration(750)
    .attr("d", function(d) {
        return valueline1.y(function(e) {
            return y(e.confirmedlog);
        })(d);
    })

Вот код:

<!DOCTYPE html>
<meta charset="utf-8" />
<html lang="en">
<style>
  .line1 {
    fill: none;
    stroke: darkcyan;
    stroke-width: 2.5px;
  }
  
  .line2 {
    fill: none;
    stroke: red;
    stroke-width: 2.5px;
  }
  
  .line3 {
    fill: none;
    stroke: green;
    stroke-width: 2.5px;
  }
  
  .axis path,
  .axis line {
    fill: none;
    stroke: grey;
    stroke-width: 1;
    shape-rendering: crispEdges;
  }
  
  .grid line {
    stroke: lightgrey;
    stroke-opacity: 0.7;
    shape-rendering: crispEdges;
  }
  
  .grid path {
    stroke-width: 0;
  }
  
  .legend rect {
    fill: white;
    stroke: black;
    opacity: 0.8;
  }
</style>

<head>
  <script src="https://d3js.org/d3.v5.min.js"></script>
</head>

<body>
  <h1>Total Confirmed Coronavirus Cases in Germany</h1>

  <div id="option">
    <input name="updateButton" type="button" id="option1" value="Linear" />
    <input name="updateButton" type="button" id="option2" value="Logarithmic" />
  </div>

  <script>
    // write your d3 code here..

    var margin = {
        top: 20,
        right: 20,
        bottom: 30,
        left: 50
      },
      width = 1000 - margin.left - margin.right,
      height = 600 - margin.top - margin.bottom;

    // parse the date / time
    var parseTime = d3.utcParse("%Y-%m-%dT%H:%M:%S%Z");
    var formatDate = d3.timeFormat("%m-%d");

    // set the ranges
    var x = d3.scaleUtc().range([0, width]);
    var y = d3.scaleLinear().range([height, 0]);
    var logy = d3.scaleLog().range([height, 0]);

    // Define the axes
    var xAxis = d3.axisBottom(x);
    var yAxis = d3.axisLeft(y);
    var logyAxis = d3.axisLeft(logy);

    // define the 1st line
    var valueline1 = d3
      .line()
      .x(function(d) {
        return x(d.date);
      })
      .y(function(d) {
        return y(d.confirmed);
      });

    // define the 2nd line
    var valueline2 = d3.line()
      .x(function(d) {
        return x(d.date);
      })
      .y(function(d) {
        return y(d.deaths);
      });

    // define the 3rd line
    var valueline3 = d3.line()
      .x(function(d) {
        return x(d.date);
      })
      .y(function(d) {
        return y(d.recovered);
      });


    // append the svg obgect to the body of the page
    // appends a 'group' element to 'svg'
    // moves the 'group' element to the top left margin
    var svg = d3
      .select("body")
      .append("svg")
      .attr("width", width + margin.left + margin.right)
      .attr("height", height + margin.top + margin.bottom)
      .append("g")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    // gridlines in x axis function
    function x_gridlines() {
      return d3.axisBottom(x)
    }

    // gridlines in y axis function
    function y_gridlines() {
      return d3.axisLeft(y)
    }

    // gridlines in y axis function
    function logy_gridlines() {
      return d3.axisLeft(logy)
    }

    d3.json(
      "https://api.covid19api.com/total/dayone/country/germany"
    ).then(function(data) {

      data.forEach(function(d) {
        d.country = d.Country
        d.date = parseTime(d.Date);
        d.confirmed = d.Confirmed;
        d.deaths = d.Deaths;
        d.recovered = d.Recovered;
        d.confirmedlog = d.Confirmed ? Math.log(d.Confirmed) : 0;
        d.deathslog = d.Deaths ? Math.log(d.Deaths) : 0;
        d.recoveredlog = d.Recovered ? Math.log(d.Recovered) : 0;
      });

      // Scale the range of the data
      x.domain(d3.extent(data, function(d) {
        return d.date;
      }));
      y.domain([0, d3.max(data, function(d) {
        return Math.max(d.confirmed, d.deaths, d.recovered);
      })]);

      // Add the valueline path.
      svg.append("path")
        .data([data])
        .attr("class", "line1")
        .attr("d", valueline1);

      // Add the valueline2 path.
      svg.append("path")
        .data([data])
        .attr("class", "line2")
        .attr("d", valueline2);

      // Add the valueline3 path.
      svg.append("path")
        .data([data])
        .attr("class", "line3")
        .attr("d", valueline3);

      // Add the X Axis
      svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis);

      // Add the Y Axis
      svg.append("g")
        .attr("class", "y axis")
        .call(yAxis);

      // add the X gridlines
      svg.append("g")
        .attr("class", "x grid")
        .attr("transform", "translate(0," + height + ")")
        .call(x_gridlines()
          .tickSize(-height)
          .tickFormat("")
        )

      // add the Y gridlines
      svg.append("g")
        .attr("class", "y grid")
        .call(y_gridlines()
          .tickSize(-width)
          .tickFormat("")
        )

      d3.select("#option1").on("click", linear);
      d3.select("#option2").on("click", logarithmic);

      // ** Update data section (Called from the onclick)
      function logarithmic() {
        y.domain([0,
          d3.max(data, function(d) {
            return Math.max(d.confirmedlog, d.deathslog, d.recoveredlog);
          })
        ]);

        // Select the section we want to apply our changes to
        var svg = d3.select("body").transition();

        // Make the changes
        svg.select(".line1") // change the line
          .duration(750)
          .attr("d", function(d) {
            return valueline1.y(function(e) {
              return y(e.confirmedlog);
            })(d);
          })

        svg.select(".line2") // change the line
          .duration(750)
          .attr("d", function(d) {
            return valueline1.y(function(e) {
              return y(e.deathslog);
            })(d);
          });

        svg.select(".line3") // change the line
          .duration(750)
          .attr("d", function(d) {
            return valueline1.y(function(e) {
              return y(e.recoveredlog);
            })(d);
          });

        svg.select(".y.axis") // change the y axis
          .duration(750)
          .call(logyAxis);
        svg.select(".y.grid")
          .duration(750)
          .call(logy_gridlines()
            .tickSize(-width)
            .tickFormat(""))
      }

      // ** Update data section (Called from the onclick)
      function linear() {

        y.domain([0, d3.max(data, function(d) {
          return d.confirmed, d.deaths, d.recovered;
        })]);

        // Select the section we want to apply our changes to
        var svg = d3.select("body").transition();

        // Make the changes
        svg.select(".line1") // change the line
          .duration(750)
          .attr("d", valueline1);

        svg.select(".line2") // change the line
          .duration(750)
          .attr("d", valueline2);

        svg.select(".line3") // change the line
          .duration(750)
          .attr("d", valueline3);

        svg.select(".x.axis") // change the x axis
          .duration(750)
          .call(xAxis);
        svg.select(".y.axis") // change the y axis
          .duration(750)
          .call(yAxis);
        svg.select(".y.grid") // change the y gridlines
          .duration(750)
          .call(y_gridlines()
            .tickSize(-width)
            .tickFormat("")
          );
      };
    });
  </script>
</body>

</html>

Однако я боюсь, что в вашем коде так много проблем, что его следует полностью отрефакторировать (пожалуйста, не принимайте это на свой счет). Теперь, когда это (своего рода) рабочий код, я думаю, вы можете обратиться за помощью по адресу Code Review . Но если вы это сделаете, перед публикацией убедитесь, что вы прочитали их раздел справки , так как Code Review сильно отличается от Stack Overflow.

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