chart3 = {
const svg = d3
.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("width", width)
.attr("height", height)
.attr(
"style",
"max-width: 100%; height: 675; font: 10px 'Noto Sans JP', sans-serif; font-weight: bold;"
);
const color = d3.scaleOrdinal(d3.schemeTableau10);
const shadow = DOM.uid("shadow");
svg
.append("filter")
.attr("id", shadow.id)
.append("feDropShadow")
.attr("flood-opacity", 0.3)
.attr("dx", 0)
.attr("stdDeviation", 3);
const g = svg.append("g").attr("class", "treemap-container");
// .attr("fill", "gray") // フォントの色を灰色に設定
// .style("font-size", (d) => {
// const cellWidth = d.x1 - d.x0; // 子ノードの横幅
// const textLength = d.data.name.length; // テキストの長さ
// const isAlphabetOnly = /^[A-Za-z\s]+$/.test(d.data.name); // アルファベットのみかどうかをチェック
// const isJapanese = /[\u3040-\u30FF\u3400-\u4DBF\u4E00-\u9FFF]+/.test(
// d.data.name
// ); // 日本語が含まれているかどうかをチェック
// let fontSizeMultiplier = 1; // フォントサイズの倍率の初期値
// if (isAlphabetOnly) {
// fontSizeMultiplier = 1.2; // アルファベットのみの場合は倍率を上げる
// } else if (isJapanese) {
// fontSizeMultiplier = 0.8; // 日本語が含まれている場合は倍率を下げる
// }
// const fontSize = (cellWidth / textLength) * fontSizeMultiplier; // 倍率を適用したフォントサイズを計算
// return `${fontSize}px`; // フォントサイズを設定
// })
// .text((d) => d.data.name);
// 葉ノードの処理
const leaf = g
.selectAll("g.leaf") // クラスが "leaf" のグループ要素を選択
.data(root.leaves()) // ルートの葉ノードをデータとしてバインド
.join("g") // グループ要素を追加または既存の要素と結合
// .attr("filter", shadow)
.attr("class", "leaf") // グループ要素にクラス属性 "leaf" を設定
.attr("transform", (d) => `translate(${d.x0},${d.y0})`); // グループ要素の位置を設定
// .style("font-size", 30); // フォントサイズを設定
// ツールチップのタイトルを追加
leaf
.append("title")
.text(
(d) =>
`${d.parent.data.name}-${d.data.name}\n${
d.value.toLocaleString() + " Views"
}`
);
// 葉ノードの矩形を描画
leaf
.append("rect")
// .attr("fill", (d) => colorScale(d.parent.data.name))
.attr("fill", (d) => {
if (d.depth === 1) {
// ルートの子供(国)に対して異なる色を使用
return color(d.data.name);
} else {
// アーティストのセルは親の国の色を引き継ぐ
while (d.depth > 2) d = d.parent; // 親を国の階層までたどる
return color(d.data.name); // 親の国の色を設定
}
})
.attr("opacity", 0.7)
.attr("width", (d) => d.x1 - d.x0)
.attr("height", (d) => d.y1 - d.y0)
.attr("rx", 3)
.attr("ry", 3);
// 複数行のテキストを追加します。最後の行は値を表示し、特定の書式を持っています。
leaf
.append("text") // text要素を追加
.attr("fill", "#FFFFFF") // フォントの色を白に設定
.attr("opacity", 0.9)
.attr("clip-path", (d) => d.clipUid) // clip-pathを設定
.selectAll("tspan") // すべてのtspan要素を選択
.data(
(d) => d.data.name.split(/(?=[A-Z][a-z])|\s+/g) //.concat(format(d.value / 100)) // テキストデータを処理
)
.join("tspan") // tspan要素を追加
.attr("x", 3) // x位置を設定 フォルト3
.attr("y", (d, i) => (i === 0 ? "1em" : `${1 + i * 0.9}em`)) // y位置を動的に設定。1行目は1em、その後はそれに続けて配置
.attr("font-size", (d, i, nodes) => {
const parent = nodes[i].parentNode.__data__;
const cellWidth = parent.x1 - parent.x0;
const cellHeight = parent.y1 - parent.y0;
const textLength = d.length; //ラベルのテキストの文字数を加味
const fontSize = Math.min(
cellWidth / textLength,
cellHeight / (nodes.length + 1)
);
return fontSize * 1.3 + "px";
})
.text((d) => d); // テキストを設定
// タイトルを追加
const title = svg
.append("text") // text要素をsvgに追加
.attr("class", "title") // クラス名を設定
.attr("x", width / 2) // 幅の中央に配置
.attr("y", 19) // 上から20pxの位置に配置
.attr("text-anchor", "middle") // 中央揃えに設定
.style("font-size", "39px") // フォントサイズを設定 39
.text("J-POPアーティストの「日本以外での聴かれ方」を可視化してみる") // タイトルのテキストを設定
.attr("fill", "#535353");
// キャプションテキストを追加
svg
.append("text")
.attr("x", 10) // 左から10pxの位置
.attr("y", height + 13) // 下から10pxの位置(キャプションのベースライン)
.text(
"YouTubeデータより徒然研究室(仮称)作成。データ取得日は2023年10月28日。対象期間は23年9月28日から10月25日。セルの面積は各国の視聴回数に対応する。 Billboard JAPAN Global Japan Songs excl. Japan 上位10組のうち現役アーティストを分析対象とし、YouTubeデータより日本以外の99カ国を採録。"
) // キャプションのテキスト
.attr("fill", "#535353")
.attr(
"style",
"max-width: 100%; height: auto; font: 10px 'Noto Sans JP', sans-serif; font-weight: normal;"
)
.style("font-size", "8.3px"); // フォントサイズを12pxに設定
// 既存のグループ要素と葉ノードをタイトルの下に移動
g.attr("transform", `translate(0, ${5})`); // y方向に5px下に移動
// 選択された国に基づいてハイライトを更新する関数
function updateHighlight(selectedCountries) {
svg.selectAll("g.leaf rect").attr("opacity", (d) => {
// 選択なし(配列が空)の場合はすべての透明度をデフォルトに戻す
if (selectedCountries.length === 0) return 0.7;
let node = d;
while (node.depth > 2) node = node.parent; // depthが2より大きい間、親ノードを辿る
// 選択された国に含まれていれば透明度を高くし、そうでなければ低くする
return selectedCountries.includes(node.data.name) ? 0.95 : 0.2;
});
}
// `selectedCountry`の選択肢に基づいてツリーマップを更新
// `selectedCountry`が配列であるため、その値をそのまま渡す
updateHighlight(selectedCountry);
// アーティスト名のテキストを描画
g.selectAll("text.artist")
.data(root.children)
.join("text")
.attr("class", "artist")
.attr("x", (d) => (d.x0 + d.x1) / 2) // 子ノードの中央X座標を計算
.attr("y", (d) => (d.y0 + d.y1) / 2) // 子ノードの中央Y座標を計算
.attr("dy", "0.35em") // テキストを縦の中心に配置するために調整
.style("text-anchor", "middle") // テキストを中央揃えに
.attr("fill", "black") // フォントの色を灰色に設定
.attr("fill-opacity", "0.5") // テキストを半透明に設定
.style("stroke", "white") // テキストのストロークを白色に設定
.style("stroke-width", "1.5px") // ストロークの太さを設定
.style("paint-order", "stroke fill") // this CSS attribute is the key (along with setting both stroke and fill)
.style("font-size", (d) => {
const cellWidth = d.x1 - d.x0; // 子ノードの横幅
const textLength = d.data.name.length; // テキストの長さ
const isAlphabetOnly = /^[A-Za-z\s]+$/.test(d.data.name); // アルファベットのみかどうかをチェック
const isJapanese = /[\u3040-\u30FF\u3400-\u4DBF\u4E00-\u9FFF]+/.test(
d.data.name
); // 日本語が含まれているかどうかをチェック
let fontSizeMultiplier = 1; // フォントサイズの倍率の初期値
if (isAlphabetOnly) {
fontSizeMultiplier = 1.2; // アルファベットのみの場合は倍率を上げる
} else if (isJapanese) {
fontSizeMultiplier = 0.8; // 日本語が含まれている場合は倍率を下げる
}
const fontSize = (cellWidth / textLength) * fontSizeMultiplier; // 倍率を適用したフォントサイズを計算
return `${fontSize}px`; // フォントサイズを設定
})
.text((d) => d.data.name);
// // アーティスト名のテキストを描画
// g.selectAll("text.artist")
// .data(root.children)
// .join("text")
// .attr("class", "artist")
// .attr("x", (d) => (d.x0 + d.x1) / 2) // 子ノードの中央X座標を計算
// .attr("y", (d) => (d.y0 + d.y1) / 2) // 子ノードの中央Y座標を計算
// .attr("dy", "0.35em") // テキストを縦の中心に配置するために調整
// .style("text-anchor", "middle") // テキストを中央揃えに
// .attr("fill", "gray") // フォントの色を灰色に設定
// .attr("fill-opacity", "0.5") // テキストを半透明に設定
// .style("stroke", "white") // テキストのストロークを白色に設定
// // .style("text-shadow", "1px 1px 2px rgba(255, 255, 255, 0.5)") // アウトラインを半透明の白で描画
// .style("stroke-width", "1px") // ストロークの太さを設定
// // .style("paint-order", "nomal") // this CSS attribute is the key (along with setting both stroke and fill)
// .style("font-size", (d) => {
// const cellWidth = d.x1 - d.x0; // 子ノードの横幅
// const textLength = d.data.name.length; // テキストの長さ
// const isAlphabetOnly = /^[A-Za-z\s]+$/.test(d.data.name); // アルファベットのみかどうかをチェック
// const isJapanese = /[\u3040-\u30FF\u3400-\u4DBF\u4E00-\u9FFF]+/.test(
// d.data.name
// ); // 日本語が含まれているかどうかをチェック
// let fontSizeMultiplier = 1; // フォントサイズの倍率の初期値
// if (isAlphabetOnly) {
// fontSizeMultiplier = 1.2; // アルファベットのみの場合は倍率を上げる
// } else if (isJapanese) {
// fontSizeMultiplier = 0.8; // 日本語が含まれている場合は倍率を下げる
// }
// const fontSize = (cellWidth / textLength) * fontSizeMultiplier; // 倍率を適用したフォントサイズを計算
// return `${fontSize}px`; // フォントサイズを設定
// })
// .text((d) => d.data.name);
return svg.node();
}