如何将 Observable Notebook 示例翻译成 javascript?

How to translate an Observable Notebook example into javascript?

我正在尝试将 this observable notebook example 翻译或转换为纯 javascript 和 d3,以便我在网站中使用它。

我试着跟随 取得了类似的成就。 但它对我不起作用,我收到一条错误消息“Unexpected value NaN parsing cy attribute”。

这是我到目前为止所做的:

<!DOCTYPE HTML>
<meta charset="UTF-8">
<html>
<body>

<head>

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.6.2/d3.min.js"></script>
<script src="https://unpkg.com/simple-statistics@7.7.0/dist/simple-statistics.min.js"></script>

</head>

<svg width="300" height="300" />

<script>

const width = 600;
const height = 600;

const margin = ({top: 20, right: 40, bottom: 30, left: 40});

const data = [608.781, 569.67, 689.556, 747.541, 618.134, 612.182, 680.203, 607.766, 726.232, 605.38, 518.655, 589.226, 740.447, 588.375, 666.83, 531.384, 710.272, 633.417, 751.669, 619.06];

const erfinv = () => {
  const a = 8 * (Math.PI - 3) / (3 * Math.PI * (4 - Math.PI));
  return x => {
    const b = Math.log(1 - x * x);
    const c = b / 2 + (2 / (Math.PI * a));
    return Math.sign(x) * Math.sqrt(Math.sqrt((c * c) - b / a) - c);
  };
}    
    
const qnorm = p => Math.SQRT2 * erfinv(2 * p - 1);
const qy = Float64Array.from(data).sort(d3.ascending);
const n = qy.length;
const a = n <= 10 ? 5 / 8 : 0.5;
const z = i => qnorm((i + a) / (n + 1 - 2 * a));

const xAxis = g => g
    .attr("transform", `translate(0,${height - margin.bottom + 6})`)
    .call(d3.axisBottom(x.copy().interpolate(d3.interpolateRound)).ticks(null, "+f"))
    .call(g => g.select(".domain").remove())
    .call(g => g.selectAll(".tick line").clone()
        .attr("stroke-opacity", 0.1)
        .attr("y1", -height))
    .call(g => g.append("text")
        .attr("x", width - margin.right)
        .attr("y", -3)
        .attr("fill", "currentColor")
        .attr("font-weight", "bold")
        .text("z"));


const x = d3.scaleLinear()
    .domain([-3, 3])
    .range([margin.left, width - margin.right]);
    
const regression = x.domain().map(ss.linearRegressionLine(ss.linearRegression(Array.from(qy, (d, i) => ([z(i), d])))));
    
const y = d3.scaleLinear()
    .domain(regression).nice()
    .range([height - margin.bottom, margin.top]);
        
const yAxis = g => g
    .attr("transform", `translate(${margin.left - 6},0)`)
    .call(d3.axisLeft(y.copy().interpolate(d3.interpolateRound)))
    .call(g => g.select(".domain").remove())
    .call(g => g.selectAll(".tick line").clone()
        .attr("stroke-opacity", 0.1)
        .attr("x1", width))
    .call(g => g.select(".tick:last-of-type text").clone()
        .attr("x", 3)
        .attr("text-anchor", "start")
        .attr("font-weight", "bold")
        .text(data.title));

const chart = () => {
  const svg = d3.create("svg")
      .attr("viewBox", [0, 0, width, height])
      .style("max-width", `${width}px`);

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

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

  svg.append("line")
      .attr("stroke", "currentColor")
      .attr("stroke-opacity", 0.3)
      .attr("x1", x.range()[0])
      .attr("x2", x.range()[1])
      .attr("y1", y(regression[0]))
      .attr("y2", y(regression[1]));

  svg.append("g")
      .attr("fill", "none")
      .attr("stroke", "steelblue")
      .attr("stroke-width", 1.5)
    .selectAll("circle")
    .data(d3.range(n))
    .join("circle")
      .attr("cx", i => x(z(i)))
      .attr("cy", i => y(qy[i]))
      .attr("r", 3);

  return svg.node();
}
    
chart();
</script>

</body>
</html>

非常感谢任何帮助,谢谢。

这是一个工作示例:

<!DOCTYPE HTML>
<html>

<head>
  <meta charset="UTF-8">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.6.2/d3.min.js"></script>
  <script src="https://unpkg.com/simple-statistics@7.7.0/dist/simple-statistics.min.js"></script>
</head>

<body>
  <svg></svg>
  <script>
    const width = 600;
    const height = 600;

    const margin = ({ top: 20, right: 40, bottom: 30, left: 40 });

    const data = [
      608.781, 569.67, 689.556, 747.541,
      618.134, 612.182, 680.203, 607.766,
      726.232, 605.38, 518.655, 589.226,
      740.447, 588.375, 666.83, 531.384,
      710.272, 633.417, 751.669, 619.06
    ];

    const erfinv = x => {
      const a = 8 * (Math.PI - 3) / (3 * Math.PI * (4 - Math.PI));
      const b = Math.log(1 - x * x);
      const c = b / 2 + (2 / (Math.PI * a));
      return Math.sign(x) * Math.sqrt(Math.sqrt((c * c) - b / a) - c);
    };

    const qnorm = p => Math.SQRT2 * erfinv(2 * p - 1);
    const qy = Float64Array.from(data).sort(d3.ascending);
    const n = qy.length;
    const a = n <= 10 ? 5 / 8 : 0.5;
    const z = i => qnorm((i + a) / (n + 1 - 2 * a));

    const xAxis = g => g
        .attr("transform", `translate(0,${height - margin.bottom + 6})`)
        .call(d3.axisBottom(x.copy().interpolate(d3.interpolateRound)).ticks(null, "+f"))
        .call(g => g.select(".domain").remove())
        .call(g => g.selectAll(".tick line").clone()
          .attr("stroke-opacity", 0.1)
          .attr("y1", -height))
        .call(g => g.append("text")
          .attr("x", width - margin.right)
          .attr("y", -3)
          .attr("fill", "currentColor")
          .attr("font-weight", "bold")
          .text("z"));

    const x = d3.scaleLinear()
        .domain([-3, 3])
        .range([margin.left, width - margin.right]);

    const regression = x.domain()
        .map(ss.linearRegressionLine(ss.linearRegression(Array.from(qy, (d, i) => ([z(i), d])))));

    const y = d3.scaleLinear()
      .domain(regression).nice()
      .range([height - margin.bottom, margin.top]);

    const yAxis = g => g
        .attr("transform", `translate(${margin.left - 6},0)`)
        .call(d3.axisLeft(y.copy().interpolate(d3.interpolateRound)))
        .call(g => g.select(".domain").remove())
        .call(g => g.selectAll(".tick line").clone()
          .attr("stroke-opacity", 0.1)
          .attr("x1", width))
        .call(g => g.select(".tick:last-of-type text").clone()
          .attr("x", 3)
          .attr("text-anchor", "start")
          .attr("font-weight", "bold")
          .text("Stength"));

    const svg = d3.select("svg")
        .attr("width", width)
        .attr("height", height);

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

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

    svg.append("line")
        .attr("stroke", "currentColor")
        .attr("stroke-opacity", 0.3)
        .attr("x1", x.range()[0])
        .attr("x2", x.range()[1])
        .attr("y1", y(regression[0]))
        .attr("y2", y(regression[1]));

    svg.append("g")
        .attr("fill", "none")
        .attr("stroke", "steelblue")
        .attr("stroke-width", 1.5)
      .selectAll("circle")
      .data(d3.range(n))
      .join("circle")
        .attr("cx", i => x(z(i)))
        .attr("cy", i => y(qy[i]))
        .attr("r", 3);
  </script>
</body>

</html>

有几点需要注意。首先,erfinv 函数。你有:

const erfinv = () => {
  const a = 8 * (Math.PI - 3) / (3 * Math.PI * (4 - Math.PI));
  return x => {
    const b = Math.log(1 - x * x);
    const c = b / 2 + (2 / (Math.PI * a));
    return Math.sign(x) * Math.sqrt(Math.sqrt((c * c) - b / a) - c);
  };
}

这是一个正在返回另一个函数的函数。所以当你像 erfinv(2 * p - 1) 这样调用它时,它返回一个函数,而不是一个数字。你可以这样写:

const erfinv = x => {
  const a = 8 * (Math.PI - 3) / (3 * Math.PI * (4 - Math.PI));
  const b = Math.log(1 - x * x);
  const c = b / 2 + (2 / (Math.PI * a));
  return Math.sign(x) * Math.sqrt(Math.sqrt((c * c) - b / a) - c);
};

其次,您的 HTML 代码已经在 DOM 中包含了 <svg> 元素。因此,我们可以使用 d3.select('svg') select 而不是使用 d3.create('svg')。此外,return svg.node(); 仅在 Observable 上需要。