如何将Observable径向图代码转换为纯JavaScript?

huangapple go评论78阅读模式
英文:

How to convert Observable radial chart code to plain Javascript?

问题

It seems you're trying to convert an Observable notebook to vanilla JavaScript using D3 for SVG elements. You encountered an error related to d.DATE.getUTCMonth. You should modify your code as follows:

Replace this line:

date: new Date(Date.UTC(2000, v[0].DATE.getUTCMonth(), v[0].DATE.getUTCDate())),

With this line:

date: new Date(Date.UTC(2000, v[0].DATE.getMonth(), v[0].DATE.getUTCDate())),

This change should fix the error, and your code should work as intended for creating the radial area chart using vanilla JS and D3 SVG elements.

英文:

I am trying to convert observable notebook (https://observablehq.com/@d3/radial-area-chart) to vanilla Javascript using d3 SVG elements. You can find the 'sfo-temperature.csv' by clicking on paper clip symbol on the link above.

I am still beginner in HTML, JS and D3. I am stuck in an error. I would appreciate any alternative solutions as well. Below, you can find my attempt.

Here is my index.HTML file:

<html>
  <head>
    <script src="https://d3js.org/d3.v6.min.js"></script>
    <script src="d3Chart.js"></script>
    <!-- <link rel="stylesheet" type="text/css" href="styles.css"> -->
  </head>
  <body>
    <div id="chart-container"></div>
    <div class="container"></div>
    <script>

            // Call the drawChart function with the container and data
      const container = d3.select("#chart-container");

      // Upload local CSV file
      d3.csv("sfo-temperature.csv").then(function(data) {
        drawChart('.container', data);
      });
      <!--drawChart('.container',data)-->
    </script>
  </body>
</html>

Here is my d3Chart.js file:

async function drawChart(container, data) {
    const rawdata = await d3.csv("sfo-temperature.csv");
    data = Array.from(d3.rollup(
        rawdata,
        v => ({
            date: new Date(Date.UTC(2000, v[0].DATE.getUTCMonth(), v[0].DATE.getUTCDate())),
            avg: d3.mean(v, d => d.TAVG || NaN),
            min: d3.mean(v, d => d.TMIN || NaN),
            max: d3.mean(v, d => d.TMAX || NaN),
            minmin: d3.min(v, d => d.TMIN || NaN),
            maxmax: d3.max(v, d => d.TMAX || NaN)
        }),
        d => `${d.DATE.getUTCMonth()}-${d.DATE.getUTCDate()}`
    ).values())
        .sort((a, b) => d3.ascending(a.date, b.date))

    const width = 954;
    const height = width;
    const margin = 10;
    const innerRadius = width / 5;
    const outerRadius = width / 2 - margin;

    const x = d3.scaleUtc()
        .domain([Date.UTC(2000, 0, 1), Date.UTC(2001, 0, 1) - 1])
        .range([0, 2 * Math.PI]);

    const y = d3.scaleLinear()
        .domain([d3.min(data, d => d.minmin), d3.max(data, d => d.maxmax)])
        .range([innerRadius, outerRadius]);

    const xAxis = g => g
        .attr("font-family", "sans-serif")
        .attr("font-size", 10)
        .call(g => g.selectAll("g")
            .data(x.ticks())
            .join("g")
            .each((d, i) => d.id = DOM.uid("month"))
            .call(g => g.append("path")
                .attr("stroke", "#000")
                .attr("stroke-opacity", 0.2)
                .attr("d", d => `
              M${d3.pointRadial(x(d), innerRadius)}
              L${d3.pointRadial(x(d), outerRadius)}
            `))
            .call(g => g.append("path")
                .attr("id", d => d.id.id)
                .datum(d => [d, d3.utcMonth.offset(d, 1)])
                .attr("fill", "none")
                .attr("d", ([a, b]) => `
              M${d3.pointRadial(x(a), innerRadius)}
              A${innerRadius},${innerRadius} 0,0,1 ${d3.pointRadial(x(b), innerRadius)}
            `))
            .call(g => g.append("text")
                .append("textPath")
                .attr("startOffset", 6)
                .attr("xlink:href", d => d.id.href)
                .text(d3.utcFormat("%B"))));

    const yAxis = g => g
        .attr("text-anchor", "middle")
        .attr("font-family", "sans-serif")
        .attr("font-size", 10)
        .call(g => g.selectAll("g")
            .data(y.ticks().reverse())
            .join("g")
            .attr("fill", "none")
            .call(g => g.append("circle")
                .attr("stroke", "#000")
                .attr("stroke-opacity", 0.2)
                .attr("r", y))
            .call(g => g.append("text")
                .attr("y", d => -y(d))
                .attr("dy", "0.35em")
                .attr("stroke", "#fff")
                .attr("stroke-width", 5)
                .text(y.tickFormat(5, "f")))
            .call(g => g.append("text")
                .attr("y", d => -y(d))
                .attr("dy", "0.35em")
                .text(y.tickFormat(5, "f"))));

    const line = d3.lineRadial()
        .angle(d => x(d.date))
        .radius(d => y(d.avg));

    const svg = d3.select(container)
        .append("svg")
        .attr("viewBox", [-width / 2, -height / 2, width, height])
        .attr("font-family", "sans-serif")
        .attr("font-size", 12)
        .attr("text-anchor", "middle");

    svg.append("g")
        .attr("fill", "none")
        .attr("stroke-opacity", 0.6)
        .selectAll("path")
        .data(data)
        .join("path")
        .style("mix-blend-mode", "multiply")
        .attr("stroke", "steelblue")
        .attr("d", d => line(d.values));

    svg.append("g")
        .call(xAxis);

    svg.append("g")
        .call(yAxis);

    svg.append("g")
        .selectAll("g")
        .data(data)
        .join("g")
        .attr("transform", d => `
          rotate(${((x(d.date) + x(d3.utcMonth.offset(d.date, 1))) / 2 * 180 / Math.PI - 90)})
          translate(${innerRadius},0)
        `)
        .append("line")
        .attr("x2", -5)
        .attr("stroke", "#000");

    svg.append("g")
        .selectAll("g")
        .data(data)
        .join("g")
        .attr("transform", d => `
          rotate(${((x(d.date) + x(d3.utcMonth.offset(d.date, 1))) / 2 * 180 / Math.PI - 90)})
          translate(${outerRadius},0)
        `)
        .append("line")
        .attr("x2", 5)
        .attr("stroke", "#000");
}

When I run my code, I encounter the following error: Uncaught (in promise) TypeError: d.DATE.getUTCMonth is not a function I tried replacing d.DATE.getUTCMonth with d.getUTCMonth, however, it still did not work. How should I modify my code so that I can create the radial area chart deined on obsrvable run using vanilla JS and d3 SVG elements?

答案1

得分: 0

Here's the translated content without the code parts:

"Instead of using asynchronous function, I changed it with d3.csv function to read the CSV file and define rollup on it.

Note that for xAxis variable I replaced d.id = DOM.uid("month") with d.id = month-${i}, since DOM.uid is not defined outside of the Observable environment. Also, I replaced xlink:href with href because the former is deprecated in SVG 2.0. Finally, I added a function call to d3.utcFormat("%B") to format the month names."

英文:

Instead of using asynchronous function, I changed it with d3.csv function to read the CSV file and define rollup on it.


    const width = 954
    const height = width
    const margin = 10
    const innerRadius = width / 5
    const outerRadius = width / 2 - margin

    // ++++++++++++++++++++++++++++++++++++++++ Step 1) Parsing the CSV file ++++++++++++++++++++++++++++
    d3.csv("sfo-temperature.csv", function(d) {
      return {
        // Parse the CSV data and return a JavaScript object
        // with the desired properties
        DATE: new Date(d.DATE),
        TAVG: +d.TAVG,
        TMAX: +d.TMAX,
        TMIN: +d.TMIN,
      };
    }).then(function(rawdata) {
      // ++++++++++++++++++++++++++++++++++++++++ Step 2) GROUPBY ++++++++++++++++++++++++++++
      const data = Array.from(d3.rollup(rawdata,
        v => ({
          date: new Date(Date.UTC(2000, v[0].DATE.getUTCMonth(), v[0].DATE.getUTCDate())),
          avg: d3.mean(v, d => d.TAVG || NaN),
          min: d3.mean(v, d => d.TMIN || NaN),
          max: d3.mean(v, d => d.TMAX || NaN),
          minmin: d3.min(v, d => d.TMIN || NaN),
          maxmax: d3.max(v, d => d.TMAX || NaN)
        }),
        d => `${d.DATE.getUTCMonth()}-${d.DATE.getUTCDate()}`
      ).values()).sort((a, b) => d3.ascending(a.date, b.date))

    const x = d3.scaleUtc()
    .domain([Date.UTC(2000, 0, 1), Date.UTC(2001, 0, 1) - 1])
    .range([0, 2 * Math.PI])

    const y = d3.scaleLinear()
    .domain([d3.min(data, d => d.minmin), d3.max(data, d => d.maxmax)])
    .range([innerRadius, outerRadius])

Note that for xAxis variable I replaced d.id = DOM.uid("month") with d.id = month-${i}, since DOM.uid is not defined outside of the Observable environment. Also, I replaced xlink:href with href because the former is deprecated in SVG 2.0. Finally, I added a function call to d3.utcFormat("%B") to format the month names.

const xAxis = (g) => g
  .attr("font-family", "sans-serif")
  .attr("font-size", 10)
  .call((g) => g.selectAll("g")
    .data(x.ticks())
    .join("g")
      .each((d, i) => d.id = `month-${i}`)
      .call((g) => g.append("path")
          .attr("stroke", "#000")
          .attr("stroke-opacity", 0.2)
          .attr("d", (d) => `
            M${d3.pointRadial(x(d), innerRadius)}
            L${d3.pointRadial(x(d), outerRadius)}
          `))
      .call((g) => g.append("path")
          .attr("id", (d) => d.id)
          .datum((d) => [d, d3.utcMonth.offset(d, 1)])
          .attr("fill", "none")
          .attr("d", ([a, b]) => `
            M${d3.pointRadial(x(a), innerRadius)}
            A${innerRadius},${innerRadius} 0,0,1 ${d3.pointRadial(x(b), innerRadius)}
          `))
      .call((g) => g.append("text")
        .append("textPath")
          .attr("startOffset", 6)
          .attr("href", (d) => `#${d.id}`)
          .text((d) => d3.utcFormat("%B")(d)))
  );



    const yAxis = g => g
    .attr("text-anchor", "middle")
    .attr("font-family", "sans-serif")
    .attr("font-size", 10)
    .call(g => g.selectAll("g")
      .data(y.ticks().reverse())
      .join("g")
        .attr("fill", "none")
        .call(g => g.append("circle")
            .attr("stroke", "#000")
            .attr("stroke-opacity", 0.2)
            .attr("r", y))
        .call(g => g.append("text")
            .attr("y", d => -y(d))
            .attr("dy", "0.35em")
            .attr("stroke", "#fff")
            .attr("stroke-width", 5)
            .text((x, i) => `${x.toFixed(0)}${i ? "" : "°F"}`)
          .clone(true)
            .attr("y", d => y(d))
          .selectAll(function() { return [this, this.previousSibling]; })
          .clone(true)
            .attr("fill", "currentColor")
            .attr("stroke", "none")))


    const line = d3.lineRadial()
    .curve(d3.curveLinearClosed)
    .angle(d => x(d.date))

    const area = d3.areaRadial()
    .curve(d3.curveLinearClosed)
    .angle(d => x(d.date))


  const svg = d3.create("svg")
      .attr("viewBox", [-width / 2, -height / 2, width, height])
      .attr("stroke-linejoin", "round")
      .attr("stroke-linecap", "round");

  svg.append("path")
      .attr("fill", "lightsteelblue")
      .attr("fill-opacity", 0.2)
      .attr("d", area
          .innerRadius(d => y(d.minmin))
          .outerRadius(d => y(d.maxmax))
        (data));

  svg.append("path")
      .attr("fill", "steelblue")
      .attr("fill-opacity", 0.2)
      .attr("d", area
          .innerRadius(d => y(d.min))
          .outerRadius(d => y(d.max))
        (data));

  svg.append("path")
      .attr("fill", "none")
      .attr("stroke", "steelblue")
      .attr("stroke-width", 1.5)
      .attr("d", line
          .radius(d => y(d.avg))
        (data));

  svg.append("g")
      .call(xAxis);

  svg.append("g")
      .call(yAxis);

const container = d3.select("body").append("div");
container.node().appendChild(svg.node());
      // console.log(xAxis())
    });

huangapple
  • 本文由 发表于 2023年4月17日 20:47:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/76035317.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定