chart = {
const svg = d3
.create("svg")
.attr("viewBox", [0, 0, 1200, 675])
.attr(
"style",
"max-width: 100%; height: 675; font: 10px 'Noto Sans JP', sans-serif; font-weight: bold;"
);
const width = 1200;
const height = 675;
const margin = { top: 10, right: 10, bottom: 60, left: 100 };
const latestDate = day2;
const dateFormat = d3.timeFormat("%Y-%m-%d");
svg
.append("text")
.attr("text-anchor", "start")
.attr("x", margin.left + 10)
.attr("y", margin.top + 60)
.text(`${dateFormat(latestDate)}`)
.attr("font-size", "80px")
.attr("opacity", 0.2)
.attr("fill", "black");
svg
.append("text")
.attr("text-anchor", "start")
.attr("x", margin.left + 10)
.attr("y", margin.top + 100)
.text(`NHK紅白歌合戦 on 公式YouTUbe`)
.attr("font-size", "28px")
.attr("opacity", 0.2)
.attr("fill", "black");
const xScale = d3
.scaleLinear()
.domain([0, 12000000])
.range([margin.left, width - margin.right]);
const yScale = d3
.scaleLinear()
.domain([0, 200000])
.range([height - margin.bottom, margin.top]);
const lineGenerator = d3
.line()
.x((d) => xScale(d.平均ViewCount))
.y((d) => yScale(d.平均LikeCount));
const groupedData = d3.group(
data2.filter((d) => d.日付 <= day2),
(d) => d.TitleShort
);
const latestDataPoints = Array.from(groupedData, ([key, values]) => {
return values.sort((a, b) => new Date(b.日付) - new Date(a.日付))[0];
});
groupedData.forEach((values, key) => {
values.forEach((d, i) => {
if (i < values.length - 1) {
const nextD = values[i + 1];
svg
.append("path")
.attr(
"d",
`M ${xScale(d.平均ViewCount)} ${yScale(d.平均LikeCount)} L ${xScale(
nextD.平均ViewCount
)} ${yScale(nextD.平均LikeCount)}`
)
.attr("stroke", color(key))
.attr("stroke-width", Math.sqrt(d.平均ViewCount) * 0.05)
.attr("stroke-opacity", 0.05)
.attr("fill", "none");
}
});
});
data2
.filter((d) => d.日付 <= day2)
.forEach((d) => {
const isLatestDate = latestDataPoints.some(
(latestD) => latestD.日付.getTime() === d.日付.getTime()
);
svg
.append("circle")
.attr("cx", xScale(d.平均ViewCount))
.attr("cy", yScale(d.平均LikeCount))
.attr("r", Math.sqrt(d.平均ViewCount) * 0.02)
.attr("fill", color(d.TitleShort))
.attr("fill-opacity", isLatestDate ? 0.6 : 0.2);
});
latestDataPoints.forEach((d) => {
svg
.append("text")
.attr("text-anchor", "middle")
.attr("x", xScale(d.平均ViewCount))
.attr("y", yScale(d.平均LikeCount))
.text(d.TitleShort)
.attr("font-size", `${Math.max(0.000004 * d.平均ViewCount, 10)}px`)
.attr("fill", "black")
.attr("dy", "0.35em")
.attr("stroke", "white")
.attr("stroke-width", `${Math.max(0.000005 * d.平均ViewCount, 10) * 0.1}`)
.attr("style", "font-weight: bold; fill-opacity: 1.0;")
.style("paint-order", "stroke fill");
});
const yAxis = svg
.append("g")
.attr("class", "y-axis")
.attr("transform", `translate(${margin.left}, 0)`)
.call(d3.axisLeft(yScale).ticks(5))
.selectAll("text")
.style("font-size", "16px")
.attr("fill", "#666666");
yAxis.filter((d) => d === 0).remove();
const xAxis = svg
.append("g")
.attr("class", "x-axis")
.attr("transform", `translate(0, ${height - margin.bottom})`)
.call(d3.axisBottom(xScale).ticks(5))
.selectAll("text")
.style("font-size", "16px")
.attr("fill", "#666666");
xAxis.filter((d) => d === 0).remove();
svg
.append("text")
.attr("text-anchor", "middle")
.attr("x", width / 2)
.attr("y", height - margin.bottom + 50)
.text("視聴回数")
.attr("font-size", "20px")
.attr("fill", "#666666");
svg.select(".x-axis").selectAll("path").remove();
svg.select(".y-axis").selectAll("path").remove();
svg
.append("text")
.attr("text-anchor", "middle")
.attr("x", -height / 2)
.attr("y", margin.left - 80)
.attr("transform", "rotate(-90)")
.text("いいね数")
.attr("font-size", "20px")
.attr("fill", "#666666");
svg
.append("text")
.attr("x", 10)
.attr("y", height + 13)
.text("YouTubeデータより徒然研究室(仮称)作成。")
.attr("fill", "#535353")
.attr(
"style",
"max-width: 100%; height: auto; font: 10px 'Noto Sans JP', sans-serif; font-weight: normal;"
)
.style("font-size", "12px");
return svg.node();
}