Public
Edited
Sep 19, 2023
Insert cell
Insert cell
Insert cell
// color = d3.scaleSequential([-1, 1], d3.interpolateViridis);
Insert cell
// カスタムのカラーマップ関数を作成
customColorMap = (t) => {
// tを変換して、0.2から1.0の範囲で色を生成します
const transformedT = 0.3 + 0.8 * t; // tを変換
const color = d3.interpolateCool(transformedT); // 変換されたtを使用してカラーマップを生成
return color;
}
Insert cell
Insert cell
chart = {
// SVG要素を作成し、アスペクト比を維持して表示領域を設定します
const svg = d3

.create("svg")
.attr("preserveAspectRatio", "xMinYMin meet")
.attr("viewBox", "0 0 900 1030")
// .attr("viewBox", "-90 -250 900 1030")
// .style("background", mode === "dark" ? "#2e2e2e" : "#fff");
// .style("background-color", "rgb(250, 245, 240)"); // 背景色を設定します;
.style("background-color", "#FBFAF5"); // 背景色を設定します;

// グラデーションの開始色、中間色、終了色をカスタムカラーマップから取得
const gradientStartColor = customColorMap(0.0); // 開始色と終了色を逆に
const gradientMidColor = customColorMap(0.2);
const gradientEndColor = customColorMap(1.0); // 開始色と終了色を逆に

// 明るいグラデーションを定義します
const gradient_light = svg
.append("linearGradient")
.attr("id", "gradLight")
.attr("x1", "0%")
.attr("y1", "0%")
.attr("x2", "0%")
.attr("y2", "100%");

// グラデーションの開始色を追加します
gradient_light
.append("stop")
.attr("offset", "0%")
.attr("stop-color", gradientStartColor);

// グラデーションの中間色を追加します
gradient_light
.append("stop")
.attr("offset", "60%")
.attr("stop-color", gradientMidColor);

// グラデーションの終了色を追加します
gradient_light
.append("stop")
.attr("offset", "95%")
.attr("stop-color", gradientEndColor);

// 暗いグラデーションを定義します
const gradient_dark = svg
.append("linearGradient")
.attr("id", "gradDark")
.attr("x1", "0%")
.attr("y1", "0%")
.attr("x2", "0%")
.attr("y2", "100%");

// グラデーションの開始色を追加します
gradient_dark
.append("stop")
.attr("offset", "0%")
.attr("stop-color", gradientStartColor);

// グラデーションの中間色を追加します
gradient_dark
.append("stop")
.attr("offset", "60%")
.attr("stop-color", gradientMidColor);

// グラデーションの終了色を追加します
gradient_dark
.append("stop")
.attr("offset", "95%")
.attr("stop-color", gradientEndColor);

// x軸をSVGに追加します
svg.append("g").call(xAxis);

// 新しいY軸を右側に追加するための設定
const yAxisRight = d3
.axisRight(y)
.ticks(5) // 目盛りの数を設定
.tickSize(width); // 目盛り線の長さをグラフ全体の幅に設定

// Y軸を右側に追加
svg
.append("g")
.attr("class", "y-axis-right") // クラスを追加(任意)
.call(yAxisRight)
.attr("transform", `translate(${width},0)`); // 右側に配置

// データの各シリーズに対して要素を追加し、位置を調整します
const group = svg
.append("g")
.selectAll("g")
.data(data.series)
.join("g")
.attr("transform", (d) => `translate(0,${y(d.country) + 1})`);

// 領域を描画します
group
.append("path")
.attr("fill", mode === "dark" ? "url(#gradDark)" : "url(#gradLight)")
.attr("fill-opacity", 0.6)
.attr("d", (d) => area(d.viewss));

// 線を描画します
group
.append("path")
.attr("fill", "none")
// .attr("stroke", mode === "dark" ? "#f2e8d2" : "#544208")
.attr("stroke", "black")
.attr("stroke-width", 1.5) // 線の太さを設定します
.attr("stroke-opacity", 0.3) // 線の透明度を設定します
.attr("d", (d) => line(d.viewss));
// y軸をSVGに追加します;
svg
.append("g")
.call(yAxis)
.selectAll("text")
.attr("text-anchor", "end")
.attr("transform", "translate(85, -15)")
.style("font-size", "22px")
.style("font-weight", "bold")
// .style("stroke", "rgb(250, 245, 240)") // ストロークの色を指定します
.style("stroke", "white") // ストロークの色を指定します
.style("opacity", 0.9) // ストロークの透明度を指定します
.style("stroke-width", "0.3px"); // ストロークの太さを指定します

group
.append("image")
// .attr("x", 200) // 画像のX座標
// .attr("y", (d, i) => i * 1 - 15) // 画像のY座標(各国の間隔を調整するためにiを使用)
.attr("transform", "translate(200, -27)")
.attr("width", 25) // 画像の幅
.attr("height", 25) // 画像の高さ
.attr("xlink:href", (d) => d.Circle_Image_URL);

// SVG要素を返します
return svg.node();
}
Insert cell

// // handleMouseOver関数: マウスオーバー時にハイライトする処理
// function handleMouseOver(event, d) {
// // 選択されたシリーズのパスをハイライト
// d3.select(this)
// .select("path")
// .attr("fill-opacity", 1.0) // ハイライト時の不透明度
// .attr("stroke-opacity", 1.0); // ハイライト時の不透明度
// }

Insert cell
// // handleMouseOut関数: マウスアウト時にハイライトを解除する処理
// function handleMouseOut(event, d) {
// // 選択されたシリーズのパスのハイライトを解除
// d3.select(this)
// .select("path")
// .attr("fill-opacity", 0.7) // 元の不透明度に戻す
// .attr("stroke-opacity", 0.5); // 元の不透明度に戻す
// }
Insert cell
230920_YouTubeMC_YOASOBI_sort.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
mode = Generators.observe(notify => {
const query = matchMedia("(prefers-color-scheme: dark)");
const changed = () => notify(query.matches ? "dark" : "light");
changed();
query.addListener(changed);
return () => query.removeListener(changed);
})
Insert cell
adj = 50
Insert cell
overlap = 10
Insert cell
width = 900
Insert cell
height = 1030
Insert cell
margin = ({ top: 315, right: 20, bottom: 30, left: 110 })
Insert cell
x = d3.scaleTime()
.domain(d3.extent(data.dates))
.range([margin.left, width - margin.right])
Insert cell
y = d3.scalePoint()
.domain(data.series.map(d => d.country))
.range([margin.top, height - margin.bottom])
Insert cell
z = d3
.scaleLinear()
.domain([0, d3.max(data.series, (d) => d3.max(d.viewss))])
.nice()
.range([0, -overlap * y.step()])
Insert cell
xAxis = g => g
.attr("transform", `translate(0,${height - margin.bottom})`)
.style("color", mode==="dark"?"#f2e8d2":"#544208")
.call(d3.axisBottom(x)
.ticks(width / 80)
.tickSizeOuter(0))
Insert cell
yAxis = g => g
.attr("transform", `translate(${margin.left},0)`)
.style("color", mode==="dark"?"#f2e8d2":"#544208")
.call(d3.axisLeft(y).tickSize(0).tickPadding(4))
.call(g => g.select(".domain").remove())
Insert cell
area = d3
.area()
.curve(d3.curveCatmullRom.alpha(0.5))
.defined((d) => !isNaN(d))
.x((d, i) => x(data.dates[i]))
.y0(0)
.y1((d) => z(d))
Insert cell
line = area.lineY1()
Insert cell
// data = {
// const data = d3.csvParse(
// await FileAttachment("230920_YouTubeMC_YOASOBI@1.csv").text(),
// (d) => {
// // 必要なデータを読み込む
// return {
// date: new Date(d.date),
// country: d.country,
// views: +d.views,
// Circle_Image_URL: d.Circle_Image_URL // Circle_Image_URL列を読み込む
// };
// }
// );
// const dates = Array.from(d3.group(data, (d) => +d.date).keys()).sort(
// d3.ascending
// );

// // 最初の10ヶ国を抽出します
// const top10Countries = Array.from(
// d3.groups(data, (d) => d.country),
// ([country]) => country
// ).slice(0, 20);

// return {
// dates: dates.map((d) => new Date(d)),
// series: d3
// .groups(data, (d) => d.country)
// .filter(([country]) => top10Countries.includes(country)) // 最初の10ヶ国だけをフィルタリング
// .map(([country, viewss]) => {
// const views = new Map(viewss.map((d) => [+d.date, d.views]));
// const Circle_Image_URL = viewss[0].Circle_Image_URL; // 同じ国の全ての行が同じCircle_Image_URLを持つと仮定しています
// return {
// country,
// viewss: dates.map((d) => views.get(d)),
// Circle_Image_URL
// };
// })
// };
// }

data = {
const data = d3.csvParse(
await FileAttachment("230920_YouTubeMC_YOASOBI_sort.csv").text(),
(d) => {
// 必要なデータを読み込む
return {
date: new Date(d.date),
country: d.country,
views: +d.views,
Circle_Image_URL: d.Circle_Image_URL // Circle_Image_URL列を読み込む
};
}
);
const dates = Array.from(d3.group(data, (d) => +d.date).keys()).sort(
d3.ascending
);

// 各国のviewsの合計を計算
const countryViewsSum = Array.from(
d3.rollup(
data,
(v) => d3.sum(v, (d) => d.views),
(d) => d.country
),
([country, viewsSum]) => ({ country, viewsSum })
);

// viewsの合計が多い順にソート
countryViewsSum.sort((a, b) => d3.descending(a.viewsSum, b.viewsSum));

// 上位20カ国をフィルタリング
const top20Countries = countryViewsSum.slice(0, 20).map((d) => d.country);

return {
dates: dates.map((d) => new Date(d)),
series: d3
.groups(data, (d) => d.country)
.filter(([country]) => top20Countries.includes(country)) // 上位20カ国だけをフィルタリング
.map(([country, viewss]) => {
const views = new Map(viewss.map((d) => [+d.date, d.views]));
const Circle_Image_URL = viewss[0].Circle_Image_URL; // 同じ国の全ての行が同じCircle_Image_URLを持つと仮定しています
return {
country,
viewss: dates.map((d) => views.get(d)),
Circle_Image_URL
};
})
};
}
Insert cell
d3 = require("d3@5", "d3-array@2")
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