Public
Edited
Nov 20, 2023
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
rawdata
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
Insert cell
rawdata
X
hour
Y
day
Color
drink
Size
Facet X
Facet Y
person
Mark
Auto
Type Chart, then Shift-Enter. Ctrl-space for more options.

Insert cell
Insert cell
new Date(
Date.UTC(
rawdata.at(0).year,
rawdata.at(0).month - 1,
rawdata.at(0).day,
rawdata.at(0).hour,
rawdata.at(0).minute
)
)
Insert cell
Insert cell
demo_dataset = rawdata.map((d) => ({
date: new Date(Date.UTC(d.year, d.month - 1, d.day, d.hour, d.minute)),
...d
}))
Insert cell
Insert cell
import { dataset, people } from "ad2642b7e4be8015"
Insert cell
dataset
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
Insert cell
people
Insert cell
Insert cell
[...dataset].sort((a, b) => a.hour - b.hour)
Insert cell
Insert cell
dataset.filter((d) => d.person === "Tsuchiya")
Insert cell
Insert cell
dataset.find((d) => d.person === "Tsuchiya")
Insert cell
Insert cell
d3.group(dataset, (d) => d.person)
Insert cell
Insert cell
[...d3.group(dataset, (d) => d.drink).keys()]
Insert cell
Insert cell
Insert cell
people.get("Tsuchiya")
Insert cell
Insert cell
viewof dataOfPerson = Inputs.select(people, { label: "Person" })
Insert cell
dataOfPerson
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
dataOfPerson.at(0).date.getUTCDate()
Insert cell
dataOfDates = {
// データに dateOnly と msec を追加
// msecは、00:00からのミリ秒
const dataWithDayMsec = dataOfPerson.map((d) => ({
...d,
dateOnly: new Date(
Date.UTC(
d.date.getUTCFullYear(),
d.date.getUTCMonth(),
d.date.getUTCDate()
)
),
msec: (d.date.getUTCHours() * 60 + d.date.getUTCMinutes()) * 60 * 1000
}));

return d3.group(dataWithDayMsec, (d) => d.dateOnly);
}
Insert cell
Insert cell
{
const width = 800;
const height = 300;
const margin = { top: 30, right: 30, bottom: 30, left: 120 };

const xScale = d3
.scaleUtc()
.domain([0, 24 * 60 * 60 * 1000]) // 24 hours
.range([margin.left, width - margin.right]);

const yScale = d3
.scaleBand()
.domain([...dataOfDates.keys()])
.range([margin.top, height - margin.bottom]);

const dow = (d) => "日月火水木金土".at(new Date(d).getDay());

const xAxis = d3
.axisBottom()
.scale(xScale)
.ticks(5)
.tickFormat(d3.utcFormat("%H:%M")) // 目盛りのフォーマット(書式)を指定
.tickSize(-(height - margin.top - margin.bottom));
const yAxis = d3
.axisLeft()
.scale(yScale)
.ticks(7)
.tickFormat((d) => d3.utcFormat(`%m月%d日`)(d) + ` (${dow(d)})`) // 目盛りのフォーマット(書式)を指定
.tickSize(-(width - margin.left - margin.right));

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

svg
.append("rect")
.attr("width", width)
.attr("height", height)
.attr("opacity", 0.1);

svg
.append("g")
.attr("class", "xAxis")
.attr("transform", `translate(0, ${height - margin.bottom})`)
.call(xAxis);
svg
.append("g")
.attr("class", "yAxis")
.attr("transform", `translate(${margin.left}, 0)`)
.call(yAxis);

// 日付グループ
const dayG = svg
.append("g")
.selectAll("g")
.data(dataOfDates)
.join("g")
.attr("class", "day")
.attr(
"transform",
(d) => `translate(0, ${yScale(d[0]) + yScale.bandwidth() / 2})`
); // 垂直軸の原点を移動しておく

// ドット
dayG
.selectAll("circle")
.data((d) => d[1]) // データの値(配列)をバインドする
.join("circle")
.attr("cx", (d) => xScale(d.msec))
.attr("r", 10)
.attr("fill", "steelblue")
.attr("opacity", 0.25);

svg
.selectAll("g.xAxis, g.yAxis")
.selectAll("line, path")
.attr("stroke", "white")
.attr("opacity", 0.5);

svg.selectAll("g.xAxis, g.yAxis").selectAll("text").attr("font-size", 16);

return svg.node();
}
Insert cell
Insert cell
dataOfDatesTimes = {
// データに day と time を追加
// timeは6時間ごとの時間帯で0,1,2,3の値をとる
const dataWithDayTime = dataOfPerson.map((d) => ({
...d,
dateOnly: new Date(
Date.UTC(
d.date.getUTCFullYear(),
d.date.getUTCMonth(),
d.date.getUTCDate()
)
),
time: Math.floor(
((d.date.getUTCHours() * 60 + d.date.getUTCMinutes()) * 60 * 1000) /
(6 * 60 * 60 * 1000)
)
}));
return d3.group(
dataWithDayTime,
(d) => d.dateOnly,
(d) => d.time
);
}
Insert cell
{
const width = 800;
const height = 300;
const margin = { top: 30, right: 30, bottom: 30, left: 120 };

const xScale = d3
.scaleLinear()
.domain([0, 4])
.range([margin.left, width - margin.right]);

const yScale = d3
.scaleBand()
.domain([...dataOfDatesTimes.keys()])
.range([margin.top, height - margin.bottom]);

const dow = (d) => "日月火水木金土".at(new Date(d).getDay());

const xAxis = d3
.axisBottom()
.scale(xScale)
.ticks(4)
.tickFormat((d) => [0, 6, 12, 18, 24][d])
.tickSize(-(height - margin.top - margin.bottom));
const yAxis = d3
.axisLeft()
.scale(yScale)
.ticks(7)
.tickFormat((d) => d3.utcFormat(`%m月%d日`)(d) + ` (${dow(d)})`)
.tickSize(-(width - margin.left - margin.right));

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

svg
.append("rect")
.attr("width", width)
.attr("height", height)
.attr("opacity", 0.1);

svg
.append("g")
.attr("class", "xAxis")
.attr("transform", `translate(0, ${height - margin.bottom})`)
.call(xAxis);
svg
.append("g")
.attr("class", "yAxis")
.attr("transform", `translate(${margin.left}, 0)`)
.call(yAxis);

const dayG = svg
.selectAll("g.day")
.data(dataOfDatesTimes)
.join("g")
.attr("class", "day")
.attr("transform", (d) => `translate(0, ${yScale(d[0])})`);

const timeG = dayG
.selectAll("g.time")
.data((d) => d[1])
.join("g")
.attr("class", "time")
.attr("transform", (d) => `translate(${xScale(d[0])}, 0)`);

// circle の cx を求めるスケール関数
const circleX = d3
.scaleBand()
// .domain([]) // domainは後で決める
.range([10, xScale(1) - xScale(0) - 10]); // xScale(1) - xScale(0)は各時間帯の幅

timeG
.append("rect")
.attr("x", 5)
.attr("y", 5)
.attr("width", xScale(1) - xScale(0) - 10)
.attr("height", yScale.bandwidth() - 10)
.attr("rx", 10)
.attr("fill", "#fff")
.attr("fill-opacity", 0.5)
.attr("stroke", "steelblue")
.attr("stroke-width", 1);

timeG
.selectAll("circle")
.data((d) => d[1])
.join("circle")
.attr("cx", (d, i, nodes) => {
// circleノードの数(nodes.length)がわかるので、circleXのdomainをここで指定する
// d3.range(5) は、 [0, 1, 2, 3, 4] になる
circleX.domain(d3.range(nodes.length));
return circleX(i) + circleX.bandwidth() / 2;
})
.attr("cy", yScale.bandwidth() / 2)
.attr("r", 10)
.attr("fill", "steelblue")
.attr("opacity", 0.5);

svg
.selectAll("g.xAxis, g.yAxis")
.selectAll("line, path")
.attr("stroke", "white")
.attr("opacity", 0.5);
svg.selectAll("g.xAxis, g.yAxis").selectAll("text").attr("font-size", 16);

return svg.node();
}
Insert cell
Insert cell
{
const width = 800;
const height = 300;
const margin = { top: 30, right: 30, bottom: 30, left: 120 };

const xScale = d3
.scaleLinear()
.domain([0, 4])
.range([margin.left, width - margin.right]);

const yScale = d3
.scaleBand()
.domain([...dataOfDatesTimes.keys()])
.range([margin.top, height - margin.bottom]);

const drinks = d3.group(dataOfPerson, (d) => d.drink);
const color = d3
.scaleOrdinal()
.domain([...drinks.keys()])
.range(d3.schemeCategory10);

const dow = (d) => "日月火水木金土".at(new Date(d).getDay());

const xAxis = d3
.axisBottom()
.scale(xScale)
.ticks(4)
.tickFormat((d) => [0, 6, 12, 18, 24][d])
.tickSize(-(height - margin.top - margin.bottom));
const yAxis = d3
.axisLeft()
.scale(yScale)
.ticks(7)
.tickFormat((d) => d3.timeFormat(`%m月%d日`)(d) + ` (${dow(d)})`)
.tickSize(-(width - margin.left - margin.right));

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

svg
.append("rect")
.attr("width", width)
.attr("height", height)
.attr("opacity", 0.1);

svg
.append("g")
.attr("class", "xAxis")
.attr("transform", `translate(0, ${height - margin.bottom})`)
.call(xAxis);
svg
.append("g")
.attr("class", "yAxis")
.attr("transform", `translate(${margin.left}, 0)`)
.call(yAxis);

const dayG = svg
.selectAll("g.day")
.data(dataOfDatesTimes)
.join("g")
.attr("class", "day")
.attr("transform", (d) => `translate(0, ${yScale(d[0])})`);

const timeG = dayG
.selectAll("g.time")
.data((d) => d[1])
.join("g")
.attr("class", "time")
.attr("transform", (d) => `translate(${xScale(d[0])}, 0)`);

const circleX = d3.scaleBand().range([10, xScale(1) - xScale(0) - 10]);

timeG
.selectAll("circle")
.data((d) => d[1])
.join("circle")
.attr("cx", (d, i, nodes) => {
circleX.domain(d3.range(nodes.length));
return circleX(i) + circleX.bandwidth() / 2;
})
.attr("cy", yScale.bandwidth() / 2)
.attr("r", 10)
.attr("fill", (d) => color(d.drink))
.attr("opacity", 0.5);

svg
.selectAll("g.xAxis, g.yAxis")
.selectAll("line, path")
.attr("stroke", "white")
.attr("opacity", 0.5);
svg.selectAll("g.xAxis, g.yAxis").selectAll("text").attr("font-size", 16);

return svg.node();
}
Insert cell
drinks = d3.group(dataset, (d) => d.drink)
Insert cell
colorScale = d3
.scaleOrdinal()
.domain([...drinks.keys()])
.range([
"white",
"orange",
"silver",
"black",
"paleblue",
"gray",
"green",
"yellow",
"none",
"silver",
"red",
"blue",
"gold",
"green",
"gray",
"white",
"purple"
])
Insert cell
colorScale("水")
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
_heart = svg`<svg version="1.1" id="レイヤー_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
y="0px" viewBox="0 0 100 100" width="100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
<g>
<path d="M94.5,29.4c0,12.4-7.8,25-23.3,37.9c-10.8,8.9-24.2,17.2-40.1,24.8c0.6-3.1,0.9-6,0.9-8.8c0-7.2-2.3-13.7-6.8-19.6
c-4.3-4.7-8.5-9.4-12.8-14.1c-4.6-5.7-6.8-12-6.8-18.8c0-6.7,2.3-12.2,6.8-16.5s10.1-6.5,16.9-6.5c4.6,0,8.9,1.2,12.8,3.6
c4.3,2.6,7,6.1,8.2,10.3C54.8,12.5,61.8,7.9,71,7.9c6.2,0,11.6,2,16.2,6C92.1,18.1,94.5,23.3,94.5,29.4z"/>
</g>
</svg>`
Insert cell
Insert cell
heart = svg`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100"><path d="M94.5 29.4c0 12.4-7.8 25-23.3 37.9C60.4 76.2 47 84.5 31.1 92.1c.6-3.1.9-6 .9-8.8 0-7.2-2.3-13.7-6.8-19.6-4.3-4.7-8.5-9.4-12.8-14.1-4.6-5.7-6.8-12-6.8-18.8 0-6.7 2.3-12.2 6.8-16.5s10.1-6.5 16.9-6.5c4.6 0 8.9 1.2 12.8 3.6 4.3 2.6 7 6.1 8.2 10.3C54.8 12.5 61.8 7.9 71 7.9c6.2 0 11.6 2 16.2 6 4.9 4.2 7.3 9.4 7.3 15.5z"/></svg>`
Insert cell
Insert cell
heartPath = d3.select(heart).select("path")
Insert cell
heartD = heartPath.attr("d")
Insert cell
d3Heart = d3.select(heart)
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
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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