Public
Edited
Sep 29, 2023
Insert cell
Insert cell
### メモ

Enter, Update, Exit(の状態) 
* 明示的に書く
* joinを使う

```js
// Updateパターン
const textUpdate = svg.selectAll("text")
.data(randomLetters(), d => d) // 第2引数はデータポイントにidを付与する
.attr("fill", "black")
.attr("y", 0)
.call(update => update.transition(t)) // update
.attr("x", (d, i) => i * 16)

// Enterパターン
const textEnter = textUpdate.enter().append("text") // enter
.attr("fill", "green")
.attr("x", (d, i) => i * 16)
.attr("y", -30)
.text(d => d)
.call(enter => enter.transition(t))
.attr("y", 0)

// Eixtパターン
const textExit = textUpdate.exit() // exit
.attr("fill", "brown")
.call(exit => exit.transition(t)
.attr ("y", 30)
.remove()) // remove
```
---
joinを使うケース

```js
svg.selectAll("text")
.data(randomLetters(), d => d)
.join(
enter => enter.append("text")
.attr(...)
update => update.attr(...)
exit => exit.attr(...)
)
```

Insert cell
Insert cell
Insert cell
alphabet = [..."ABCDEFGHIJKLMNOPQRSTUVWXYZ"]
Insert cell
{
let data = d3
.shuffle(alphabet.slice())
.slice(Math.floor(Math.random() * 10) + 5)
.sort(d3.ascending);

const width = 500;
const height = 50;
const margin = { top: 10, bottom: 10, left: 50, right: 10 };

const svg = d3
.create("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);

const g = svg
.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);

const yScale = d3
.scalePoint()
.domain(["enter", "update", "exit"])
.range([0, height]);

const xScale = d3.scalePoint().domain(alphabet).range([10, width]);

g.append("g").call(d3.axisLeft(yScale));

// =============== //
while (true) {
yield svg.node();
// yield data;

// Selection
const textSelection = g.selectAll(".letter").data(data, (d) => d);

// Enter
textSelection
.enter()
.append("text")
.attr("class", "letter")
.attr("y", yScale("enter") + 5)
.attr("x", (d) => xScale(d))
.style("font", "14px sans-serif")
.style("fill", "green")
.text((d) => d);

// Update
textSelection
.transition()
.duration(1000)
.attr("y", yScale("update") + 5)
.style("fill", "#000");

// Exit
textSelection
.exit()
.transition()
.duration(1000)
.style("fill", "red")
.attr("y", yScale("exit") + 5)
.transition()
.duration(1000)
.style("opacity", 0)
.remove();

// データ
await Promises.delay(3000);
data = d3
.shuffle(alphabet.slice())
.slice(Math.floor(Math.random() * 10) + 5)
.sort(d3.ascending);
}
}
Insert cell
Insert cell
Insert cell
{
let data = d3
.shuffle(alphabet.slice())
.slice(Math.floor(Math.random() * 10) + 5)
.sort(d3.ascending);

const width = 500;
const height = 50;
const margin = { top: 10, bottom: 10, left: 50, right: 10 };

const svg = d3
.create("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);

const g = svg
.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);

const yScale = d3
.scalePoint()
.domain(["enter", "update", "exit"])
.range([0, height]);

const xScale = d3.scalePoint().domain(alphabet).range([10, width]);

g.append("g").call(d3.axisLeft(yScale));

while (true) {
yield svg.node();
// yield data

// selectAll()で選択 -> data()でデータをアタッチ -> join()で各状態のロジック
g.selectAll(".letter")
.data(data, (d) => d)
.join(
(enter) =>
enter
.append("text")
.attr("fill", "green")
.attr("class", "letter")
.attr("x", (d) => xScale(d))
.attr("y", yScale("enter") + 5)
.style("font", "14px sans-serif")
.text((d) => d),
(update) =>
update
.attr("fill", "black")
.call((update) =>
update.transition().attr("y", yScale("update") + 5) // オブジェクトの参照を操作する場合にcall()
),
(exit) =>
exit.attr("fill", "red").call((exit) =>
exit
.transition()
.attr("y", yScale("exit") + 5)
.transition()
.remove()
)
); // Anything we add after our join will apply to the enter + update elements that remain

// If we don't care about handling enter + update differently, and we just want to remove our exited elements, You can do it all this way.
// g.selectAll(".letter")
// .data(data, (d) => d)
// .join("text")
// .attr("class", "letter")
// .attr("y", yScale("enter") + 5)
// .attr("x", (d) => xScale(d))
// .style("font", "14px sans-serif")
// .text((d) => d);

await Promises.delay(3000);
data = d3
.shuffle(alphabet.slice())
.slice(Math.floor(Math.random() * 10) + 5)
.sort(d3.ascending);
}
}
Insert cell
Insert cell
Insert cell
Insert cell
{
const width = 500;
const height = 200;
const r = 25;

const svg = d3
.create("svg")
.attr("width", width)
.attr("height", height)
.style("overflow", "visible");

const x = d3
.scalePoint()
.domain([0, 1, 2, 3, 4])
.range([0, width])
.padding(0.5);

// ====== //
svg
.selectAll("circle")
.data(d3.range(5).map(Object))
.join("circle")
// circleの属性
.attr("cx", (_, i) => x(i))
.attr("cy", height / 2)
.attr("r", r)
// transitionの処理
.transition()
.duration(6000)
.delay((d) => d * 750)
.ease(d3.easeElastic)
.attr("cx", width - r);

return svg.node();
}
Insert cell
Insert cell
{
const width = 500;
const height = 200;
const r = 25;

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

const circle = svg
.append("circle")
.attr("r", r)
.attr("cx", r)
.attr("cy", height / 2)
// circleを右に動かすtransition
.transition()
.attr("cx", width - r)
.duration(3000)
// circleを左に動かすtransition
.transition()
.attr("cx", r)

return svg.node();
}
Insert cell
Insert cell
Insert cell
iris
Insert cell
d3.groups(iris, (d) => d.species)
Insert cell
{
const width = 600;
const height = 400;
const margin = { top: 20, bottom: 20, left: 50, right: 50 };

const div = htl.html`<div></div>`;

// カテゴリーごとの配列の配列を作成(上のセルを参照)
const categories = d3.groups(iris, (d) => d.species);

// ボタン
const buttons = d3
.select(div)
.selectAll("button")
.data(categories)
.join("button");

buttons.text((d) => d[0]);

const svg = d3
.select(div)
.append("div")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);

const g = svg
.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);

const x = d3
.scaleLinear()
.domain(d3.extent(iris, (d) => d["sepalLength"]))
.range([0, width]);

const y = d3
.scaleLinear()
.domain(d3.extent(iris, (d) => d["sepalWidth"]))
.range([height, 0]);

const color = d3
.scaleOrdinal()
.domain(categories.map((d) => d[0]))
.range(d3.schemeDark2);

g.append("g").call(d3.axisLeft(y));

g.append("g")
.attr("transform", `translate(0, ${height})`)
.call(d3.axisBottom(x));

// データ
const circles = g
.selectAll("circle")
.data(iris.filter((d) => d.species == "setosa"))
.join("circle")
.attr("r", 4)
.style("stroke", "#000")
.attr("cx", (d) => x(d.sepalLength))
.attr("cy", (d) => y(d.sepalWidth))
.style("fill", (d) => color(d.species));

// ボタンの操作
buttons.on("click", (event, d) => {
g.selectAll("circle")
.data(d[1], (d) => d) // d[1]はクリックした種類のデータ、
.join(
(enter) =>
enter
.append("circle")
.attr("r", 4)
.style("stroke", "#000")
.attr("cx", (d) => x(d.sepalLength))
.attr("cy", (d) => y(d.sepalWidth))
.style("fill", (d) => color(d.species)),
(update) =>
update
.attr("cx", (d) => x(d.sepalLength))
.attr("cy", (d) => y(d.sepalWidth))
.style("fill", (d) => color(d.species)),
(exit) =>
exit
.attr("fill", "red")
.call((exit) =>
exit.transition().duration(2000).attr("r", 0).remove()
)
);
});

return div;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
iris.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more