https://www.gravatar.com/avatar/ecd1e184bdf4016f24c818bde9e65b3d?s=240&d=mp
记录点滴、留住时光

js备忘1

JavaScript Array some,map 方法

some() 方法用于检测数组元素中是否有元素符合指定条件。 map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。

1
2
3
4
5
6
7
8
9
color = {
  const scale = d3.scaleOrdinal(d3.schemeTableau10);
  if (data.some(d => d.category !== undefined)) { // 是否有某个元素定义了category
    const categoryByName = new Map(data.map(d => [d.name, d.category])) // map遍历构造健值对,用new Map构建映射表
    scale.domain(Array.from(categoryByName.values())); //values取Map对象的value迭代器,再用Array.from还原数组
    return d => scale(categoryByName.get(d.name)); // 通过d.name取颜色序号
  }
  return d => scale(d.name);
}

d3,Observable备忘3

动画

与在纸上绘制的图形不同,计算机图形不必是静态的。就像科学怪人的怪物一样,它们可以通过动画来栩栩如生! ✨

生成一个控制按钮

1
viewof replay = html`<button>Replay`

https://note.youdao.com/yws/public/resource/40e7acccfd342428f39d3dc7cca9ce31/xmlnote/WEBRESOURCEa86462d7035f4634acd56be0413fa192/134

逐步画完的拆线图

1
2
3
4
5
replay, html`<svg viewBox="0 0 ${width} ${height}">
  ${d3.select(svg`<path d="${line(data)}" fill="none" stroke="steelblue" stroke-width="1.5" stroke-miterlimit="1" stroke-dasharray="0,1"></path>`).call(reveal).node()}  // reveal函数控制动画过程
  ${d3.select(svg`<g>`).call(xAxis).node()}
  ${d3.select(svg`<g>`).call(yAxis).node()}
</svg>`
1
2
3
4
5
6
7
reveal = path => path.transition()
    .duration(5000)
    .ease(d3.easeLinear)
    .attrTween("stroke-dasharray", function() {
      const length = this.getTotalLength();
      return d3.interpolate(`0,${length}`, `${length},${length}`); // 代表隐藏部分的起止
    })

手动控制逐步画完的拆线图

  • 可手动调整的范围输入框
1
2
3
4
5
6
viewof t = Scrubber(d3.ticks(0, 1, 100), { // 0->1,分成100份
  autoplay: false,
  loop: false,
  initial: 50, // 初始值为50
  format: x => `t = ${x.toFixed(3)}` // 标签格式化为3位小数
})
  • 配合的画图代码
1
2
3
4
5
html`<svg viewBox="0 0 ${width} ${height}">
  <path d="${line(data)}" fill="none" stroke="steelblue" stroke-width="1.5" stroke-miterlimit="1" stroke-dasharray="${lineLength * t},${lineLength}"></path>
  ${d3.select(svg`<g>`).call(xAxis).node()}
  ${d3.select(svg`<g>`).call(yAxis).node()}
</svg>`
* 估计stroke-dasharray代表为曲线忽略的部分

三种制作动画的方法

  1. 使用D3’s transitions
  2. 使用时间t,当t改变时整个图重绘,这种方法效率低,但更易于缩写
  3. 使用循环yield,每秒生成60次
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
{
  replay2;

  const path = svg`<path d="${line(data)}" fill="none" stroke="steelblue" stroke-width="1.5" stroke-miterlimit="1">`;

  const chart = html`<svg viewBox="0 0 ${width} ${height}">
    ${path}
    ${d3.select(svg`<g>`).call(xAxis).node()}
    ${d3.select(svg`<g>`).call(yAxis).node()}
  </svg>`;

  for (let i = 0, n = 300; i < n; ++i) {
    const t = (i + 1) / n; // 01t值,动画持续时间=300/60,即每秒60
    path.setAttribute("stroke-dasharray", `${t * lineLength},${lineLength}`);
    yield chart;
  }
}

综合示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
chart = {
  const svg = d3.create("svg")
      .attr("viewBox", [0, 0, width, height]);

  const zx = x.copy(); // x, but with a new domain.

  const line = d3.line()
      .x(d => zx(d.date))
      .y(d => y(d.close));

  const path = svg.append("path")
      .attr("fill", "none")
      .attr("stroke", "steelblue")
      .attr("stroke-width", 1.5)
      .attr("stroke-miterlimit", 1)
      .attr("d", line(data));

  const gx = svg.append("g")
      .call(xAxis, zx);

  const gy = svg.append("g")
      .call(yAxis, y);

  return Object.assign(svg.node(), { // update置入到svg.node()中,以便可以用chart.update来调用
    update(domain) {
      const t = svg.transition().duration(750);
      zx.domain(domain);
      gx.transition(t).call(xAxis, zx);
      path.transition(t).attr("d", line(data));
    }
  });
}

Joins

用svg展示一组字符

  1. 使用代码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
chart1 = {
  const svg = d3.create("svg")
      .attr("viewBox", [0, 0, width, 33])
      .attr("font-family", "sans-serif")
      .attr("font-size", 10)
      .style("display", "block");

  svg.selectAll("text")
    .data(alphabet)
    .join("text")
      .attr("x", (d, i) => i * 17)
      .attr("y", 17)
      .attr("dy", "0.35em")
      .text(d => d);

  return svg.node();
}
  1. 使用html标签
1
2
3
html`<svg viewBox="0 0 ${width} 33" font-family="sans-serif" font-size="10" style="display: block;">
  ${alphabet.map((d, i) => svg`<text x="${i * 17}" y="17" dy="0.35em">${d}</text>`)}
</svg>`

以上两种方法结果相同

D3, Observable备忘2

html标签嵌入svg

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
html`<svg viewBox="0 0 ${width} ${height}" style="max-width: ${width}px; font: 10px sans-serif;">
  <g fill="steelblue">
    ${fruits.map(d => svg`<rect y="${y(d.name)}" x="${x(0)}" width="${x(d.count) - x(0)}" height="${y.bandwidth()}"></rect>`)}
  </g>
  <g fill="white" text-anchor="end" transform="translate(-6,${y.bandwidth() / 2})">
    ${fruits.map(d => svg`<text y="${y(d.name)}" x="${x(d.count)}" dy="0.35em">${d.count}</text>`)}
  </g>
  ${d3.select(svg`<g transform="translate(0,${margin.top})">`)
    .call(d3.axisTop(x))
    .call(g => g.select(".domain").remove())
    .node()}
  ${d3.select(svg`<g transform="translate(${margin.left},0)">`)
    .call(d3.axisLeft(y))
    .call(g => g.select(".domain").remove())
    .node()}
</svg>`
  • 引入外面变量${width}
  • 遍历嵌入svg标签
1
2
3
<g fill="steelblue">
    ${fruits.map(d => svg`<rect y="${y(d.name)}" x="${x(0)}" width="${x(d.count) - x(0)}" height="${y.bandwidth()}"></rect>`)}
</g>

对应

d3,Observable备忘1

在Observable备忘引入附件图片并说明

1
2
3
4
md`<figure>
  <img src="${await FileAttachment("changing-stuff@1.jpg").url()}" style="width: 274px; height: 360px;">
  <figcaption>A legitimate learning strategy. Image: [DEV Community](https://twitter.com/thepracticaldev)</figcaption>
</figure>`

最最大值,中间值,最小值

1
2
d3 = require("d3@6")
[d3.min(values), d3.median(values), d3.max(values)]

直接生成直方图

1
import {chart as chart1} with {values as data} from "@d3/histogram"

随便一个范围内的值

2000个mu值到上下2区间的值

1
values3 = Float64Array.from({length: 2000}, d3.randomNormal(mu, 2))

定义一个横坐标

-10, 10范围,定义起止坐标