chart2 = {
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: 60, bottom: 60, left: 100 };
const rSize = R_range;
const fSize = font_range;
const sWidth = width_range;
const latestDate = day2;
const dateFormat = d3.timeFormat("%Y-%m-%d %H:%M");
svg
.append("text")
.attr("text-anchor", "start")
.attr("x", margin.left + 10)
.attr("y", margin.top + 40)
.text(`${dateFormat(latestDate)}`) // get_timeをフォーマットしてテキストに設定
.attr("font-size", "70px") // フォントサイズを設定
.attr("opacity", 0.2) // フォントサイズを設定
.attr("fill", "black"); // フォントの色を設定
svg
.append("text")
.attr("text-anchor", "start")
.attr("x", margin.left + 10) // x座標を左マージンから10px右に設定
.attr("y", margin.top + 80) // y座標を上マージンから20px下に設定
.text(`NHK紅白歌合戦2023 on 公式YouTube`) // get_timeをフォーマットしてテキストに設定
.attr("font-size", "30px") // フォントサイズを設定
.attr("opacity", 0.2) // フォントサイズを設定
.attr("fill", "black"); // フォントの色を設定
// 軸のスケールを設定
const xScale = d3
.scaleLinear()
// .domain([0, 2900000])
.domain([0, x_range])
.range([margin.left, width - margin.right]);
const yScale = d3
.scaleLinear()
// .domain([0, 50000])
.domain([0, y_range])
.range([height - margin.bottom, margin.top]);
// 折れ線グラフ用のラインジェネレータを作成
const lineGenerator = d3
.line()
.x((d) => xScale(d.views))
.y((d) => yScale(d.likes));
// データをtitleごとにグループ化
const groupedData = d3.group(
data2.filter((d) => d.get_time <= day2),
(d) => d.link
);
// 各グループの最新のデータポイントを取得
const latestDataPoints = Array.from(groupedData, ([key, values]) => {
return values.sort(
(a, b) => new Date(b.get_time) - new Date(a.get_time)
)[0];
});
// ユニークなtitleの数を計算
const uniqueTitleCount = new Set(latestDataPoints.map((d) => d.title)).size;
// 最新日付の全動画の合計視聴回数を計算
const totalViews = latestDataPoints.reduce((sum, d) => sum + d.views, 0);
// 合計視聴回数を表示するテキストを追加
svg
.append("text")
.attr("text-anchor", "start")
.attr("x", margin.left + 10) // x座標を左マージンから10px右に設定
.attr("y", margin.top + 120) // y座標を上マージンから120px下に設定
.text(
`全歌唱動画${uniqueTitleCount}本の合計視聴回数: ${d3.format(",")(
totalViews
)} 回`
) // フォーマットした視聴回数を表示
.attr("font-size", "20px") // フォントサイズを設定
.attr("opacity", 0.2)
.attr("fill", "black"); // フォントの色を設定
// 各グループごとに線セグメントを描画
groupedData.forEach((values, key) => {
const isSelected = checkboxes.includes(values[0].title); // グループのタイトルで選択状態を判定
values.forEach((d, i) => {
if (i < values.length - 1) {
const nextD = values[i + 1];
svg
.append("path")
.attr(
"d",
`M ${xScale(d.views)} ${yScale(d.likes)} L ${xScale(
nextD.views
)} ${yScale(nextD.likes)}`
)
.attr("stroke", color(key))
.attr("stroke-width", Math.sqrt(d.views) * sWidth) // 【線の太さを動的に設定】
.attr("stroke-opacity", isSelected ? 0.4 : 0.05) // グループの選択状態に基づいて透明度を設定
.attr("fill", "none");
}
});
});
// 既存のcircle要素の代わりにクリップパスと画像を追加する
data2
.filter((d) => d.get_time <= day2)
.forEach((d) => {
const isLatestDate = latestDataPoints.some(
(latestD) => latestD.get_time.getTime() === d.get_time.getTime()
);
// 最新の日付のデータポイントにだけクリップパスと画像を適用
if (isLatestDate) {
const radius = Math.sqrt(d.views * d.likes) * rSize; // 【半径を調整】
const newRadius = radius * 1.8; // 新しい半径を1.7倍に設定
const uniqueId = `clip-${d.link}`; // クリップパスのID
// クリップパスの定義
svg
.append("clipPath")
.attr("id", uniqueId)
.append("circle")
.attr("cx", xScale(d.views))
.attr("cy", yScale(d.likes))
.attr("r", radius); // 元の半径を使用
// 画像の追加
svg
.append("image")
.attr("xlink:href", d.thumbnail)
.attr("width", newRadius * 2) // 新しい半径の2倍の幅
.attr("height", newRadius * 2) // 新しい半径の2倍の高さ
.attr("x", xScale(d.views) - newRadius) // 新しい半径を使って中心を合わせる
.attr("y", yScale(d.likes) - newRadius) // 新しい半径を使って中心を合わせる
.attr("clip-path", `url(#${uniqueId})`)
.attr("opacity", checkboxes.includes(d.title) ? 1 : 0.15); // 透明度を設定 // ★opacityで設定
}
const isSelected = checkboxes.includes(d.title);
// 通常の円を描画
svg
.append("circle")
.attr("cx", xScale(d.views))
.attr("cy", yScale(d.likes))
.attr("r", Math.sqrt(d.views * d.likes) * rSize) // 【半径を調整】
.attr("fill", color(d.link)) // titleに基づいて色を設定
.attr("fill-opacity", isSelected ? 0.4 : 0.05); // 透明度を設定
// 白色の円を重ねて描画(透明度を調整)
// svg
// .append("circle")
// .attr("cx", xScale(d.views))
// .attr("cy", yScale(d.likes))
// .attr("r", Math.sqrt(d.views) * rSize) // 通常の円と同じ半径を使用
// .attr("fill", "white")
// .attr("fill-opacity", isSelected ? 0.0 : 0.7); // チェックボックスの状態によって透明度を変更
});
// 最新のデータポイントにラベルテキストを描画
latestDataPoints.forEach((d) => {
svg
.append("text")
.attr("text-anchor", "middle")
.attr("x", xScale(d.views))
.attr("y", yScale(d.likes))
.text(d.title_artist)
// .attr("font-size", `${Math.max(0.00001 * d.views, 10)}px`)
.attr("font-size", `${Math.max(fSize * d.views, 10)}px`) // 【フォントサイズを調整】
.attr("fill", "black")
.attr("dy", "0.35em") // Y軸方向の調整を追加
.attr("stroke", "white")
.attr("stroke-width", `${Math.max(0.000005 * d.views, 10) * 0.2}`)
.attr("style", "font-weight: bold; fill-opacity: 1.0;")
.style("paint-order", "stroke fill")
.attr("opacity", checkboxes.includes(d.title) ? 1 : 0.2);
});
// Y軸の目盛りラベルを設定
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"); // フォントの色を設定;;
// 0を非表示にするため、0の要素を選択して非表示に設定
yAxis.filter((d) => d === 0).remove();
// X軸の目盛りラベルを設定
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"); // フォントの色を設定;
// 0を非表示にするため、0の要素を選択して非表示に設定
xAxis.filter((d) => d === 0).remove();
// x軸のラベルを追加
svg
.append("text")
.attr("text-anchor", "middle")
.attr("x", width / 2) // x座標をSVGの中央に設定
.attr("y", height - margin.bottom + 50) // y座標をx軸の下に設定
.text("視聴回数") // テキストを設定
.attr("font-size", "20px") // フォントサイズを設定
.attr("fill", "#666666"); // フォントの色を設定
// X軸の線を非表示にする
svg.select(".x-axis").selectAll("path").remove();
// X軸の線を非表示にする
svg.select(".y-axis").selectAll("path").remove();
// y軸のラベルを追加
svg
.append("text")
.attr("text-anchor", "middle")
.attr("x", -height / 2) // x座標をy軸の左に設定
.attr("y", margin.left - 80) // y座標をSVGの上に設定
.attr("transform", "rotate(-90)") // テキストを90度回転
.text("いいね数") // テキストを設定
.attr("font-size", "20px") // フォントサイズを設定
.attr("fill", "#666666"); // フォントの色を設定;
svg
.append("text")
.attr("x", 10) // 左から10pxの位置
.attr("y", height + 13) // 下から10pxの位置(キャプションのベースライン)
.text("YouTubeチャンネル「NHK MUSIC」データより徒然研究室(仮称)作成。") // キャプションのテキスト
.attr("fill", "#535353")
.attr(
"style",
"max-width: 100%; height: auto; font: 10px 'Noto Sans JP', sans-serif; font-weight: normal;"
)
.style("font-size", "12px"); // フォントサイズを12pxに設定
// SVG要素を返す
return svg.node();
}