chart = {
const width = 928;
const height = width;
const radius = width / 6;
const hatch_size = 2 ;
const color = d3.scaleOrdinal(d3.quantize(d3.interpolateRainbow, data.children.length + 1));
const hierarchy = d3.hierarchy(data)
.sum(d => d.value)
.sort((a, b) => b.value - a.value);
const root = d3.partition()
.size([2 * Math.PI, hierarchy.height + 1])
(hierarchy);
root.each(d => d.current = d);
const arc = d3.arc()
.startAngle(d => d.x0)
.endAngle(d => d.x1)
.padAngle(d => Math.min((d.x1 - d.x0) / 2, 0.005))
.padRadius(radius * 1.5)
.innerRadius(d => d.y0 * radius)
.outerRadius(d => Math.max(d.y0 * radius, d.y1 * radius - 1))
const svg = d3.create("svg")
.attr("viewBox", [-width / 2, -height / 2, width, width])
.style("font-size", "12px")
.attr("font-family", "Gluten");
////////////////////////////////////////////////////////////
////////////////// Create hatched patterns /////////////////
////////////////////////////////////////////////////////////
const defs = svg.append("defs")
//Create hatched pattern for RED pattern
const hatch_pattern_1 = defs
.append("pattern")
.attr("id", "hatch-red")
.attr("width", hatch_size)
.attr("height", hatch_size)
.attr("patternTransform", `rotate(45)`)
.attr("patternUnits","userSpaceOnUse") //used to make the pattern independent of location of circle
//Add the actual pattern shape
hatch_pattern_1.append("rect")
.attr("width", hatch_size/4)
.attr("height", hatch_size)
.style("fill", "#FF0000")
//Create hatched pattern for VIOLET pattern
const hatch_pattern_2 = defs
.append("pattern")
.attr("id", "hatch-violet")
.attr("width", hatch_size)
.attr("height", hatch_size)
.attr("patternUnits","userSpaceOnUse") //used to make the pattern independent of location of circle
.attr("patternTransform", `rotate(145)`)
//Add the actual pattern shape
hatch_pattern_2.append("rect")
.attr("width", hatch_size/4)
.attr("height", hatch_size)
.style("fill", "#9933ff")
//Create hatched pattern for toki pattern
const hatch_pattern_3 = defs
.append("pattern")
.attr("id", "hatch-pink")
.attr("width", hatch_size)
.attr("height", hatch_size)
//.attr("patternTransform", `rotate(${rotation})`)
.attr("patternUnits","userSpaceOnUse") //used to make the pattern independent of location of circle
//Add the actual pattern shape
hatch_pattern_3.append("rect")
.attr("x", hatch_size/2) //notice this difference, which moves the blue pattern from the pink
.attr("width", hatch_size/4)
.attr("height", hatch_size)
.style("fill", "#EEA9A9")
////////////////////////////////////////////////////////////
////////////////// FIN Create hatched patterns /////////////
////////////////////////////////////////////////////////////
// FONTS
// IMAGES
var myimage = svg.append('image')
.attr('xlink:href', 'https://minisatsuki.com/wp-content/uploads/2025/01/Minisatsuki-2024-New-1.png')
.attr('width', 200)
.attr('height', 200)
myimage.attr('x', -100)
myimage.attr('y', -100)
// GRADIENTS
var lg = defs.append("linearGradient")
.attr("id", "jiaibeni")//id of the gradient
.attr("x1", "0%")
.attr("x2", "20%")
.attr("y1", "0%")
.attr("y2", "100%")//since its a vertical linear gradient
.attr("gradientTransform", `rotate("180")`)
;
lg.append("stop")
.attr("offset", "0%")
.style("stop-color", "#ff0066")//end in red
.style("stop-opacity", 1)
lg.append("stop")
.attr("offset", "40%")
.style("stop-color", "#ff99ff")//end in red
.style("stop-opacity", 1)
lg.append("stop")
.attr("offset", "100%")
.style("stop-color", "#ff0000")//start in blue
.style("stop-opacity", 0.7)
var lg = defs.append("linearGradient")
.attr("id", "shiboviolet")//id of the gradient
.attr("x1", "0%")
.attr("x2", "0%")
.attr("y1", "0%")
.attr("y2", "0")//since its a vertical linear gradient
.attr("gradientTransform", `rotate("180")`)
;
lg.append("stop")
.attr("offset", "0%")
.style("stop-color", "#FFFFFF")//end in red
.style("stop-opacity", 1)
lg.append("stop")
.attr("offset", "40%")
.style("stop-color", "#ff99ff")//end in red
.style("stop-opacity", 1)
lg.append("stop")
.attr("offset", "100%")
.style("stop-color", "000000")//start in blue
.style("stop-opacity", 0.7)
var lg = defs.append("linearGradient")
.attr("id", "jiaiviolet")//id of the gradient
.attr("x1", "0%")
.attr("x2", "0%")
.attr("y1", "0%")
.attr("y2", "100%")//since its a vertical linear gradient
.attr("gradientTransform", `rotate("180")`)
;
lg.append("stop")
.attr("offset", "0%")
.style("stop-color", "#EEA9A9")//end in red
.style("stop-opacity", 1)
lg.append("stop")
.attr("offset", "40%")
.style("stop-color", "#ff99ff")//end in red
.style("stop-opacity", 1)
lg.append("stop")
.attr("offset", "100%")
.style("stop-color", "#9933ff")//start in blue
.style("stop-opacity", 0.7)
var lg = defs.append("radialGradient")
.attr("id", "mujisoko")//id of the gradient
.attr("cx", "50%")
.attr("cy", "50%")
.attr("r", "40%")
//.attr("fx", "0.4")
//.attr("fy", "0.4")//since its a vertical linear gradient
//.attr("spreadMethod", "repeat")//since its a vertical linear gradient
;
lg.append("stop")
.attr("offset", "0%")
.style("stop-color", "white")//end in red
.style("stop-opacity", 1)
lg.append("stop")
.attr("offset", "100%")
.style("stop-color", "#9933ff")//start in blue
.style("stop-opacity", 1)
//<radialGradient id="bullseye"cx="50%" cy="50%" fx=".2" fy=".2" r="10%" spreadMethod="repeat">
// <stop stop-color="tomato" offset="50%"/>
// <stop stop-color="#222" offset="50%"/>
var sg = defs.append("radialGradient")
.attr("id", "benisoko")//id of the gradient
.attr("cx", "50%")
.attr("cy", "50%")
.attr("r", "40%")
//.attr("fx", "0.4")
//.attr("fy", "0.4")//since its a vertical linear gradient
//.attr("spreadMethod", "repeat")//since its a vertical linear gradient
;
sg.append("stop")
.attr("offset", "0%")
.style("stop-color", "white")//end in red
.style("stop-opacity", 1)
sg.append("stop")
.attr("offset", "100%")
.style("stop-color", "#ff0000")//start in blue
.style("stop-opacity", 1)
//<radialGradient id="bullseye"cx="50%" cy="50%" fx=".2" fy=".2" r="10%" spreadMethod="repeat">
// <stop stop-color="tomato" offset="50%"/>
// <stop stop-color="#222" offset="50%"/>
var fg = defs.append("radialGradient")
.attr("id", "test")//id of the gradient
.attr("cx", "50%")
.attr("cy", "50%")
.attr("r", "10%")
.attr("fx", "100")
.attr("fy", "100")//since its a vertical linear gradient
.attr("spreadMethod", "repeat")//since its a vertical linear gradient
;
fg.append("stop")
.attr("offset", "50%")
.style("stop-color", "white")//end in red
.style("stop-opacity", 1)
fg.append("stop")
.attr("offset", "50%")
.style("stop-color", "#9933ff")//start in blue
.style("stop-opacity", 1)
var fg2 = defs.append("radialGradient")
.attr("id", "fukurin")//id of the gradient
.attr("cx", "80%")
.attr("cy", "80%")
.attr("r", "80%")
//.attr("fx", "0.4")
//.attr("fy", "0.4")//since its a vertical linear gradient
//.attr("spreadMethod", "repeat")//since its a vertical linear gradient
;
fg2.append("stop")
.attr("offset", "0%")
.style("stop-color", "white")//end in red
.style("stop-opacity", 1)
fg2.append("stop")
.attr("offset", "100%")
.style("stop-color", "#EEA9A9")//start in blue
.style("stop-opacity", 1)
var lg = defs.append("linearGradient")
.attr("id", "2jiro")//id of the gradient
.attr("x1", "0%")
.attr("x2", "20%")
.attr("y1", "0%")
.attr("y2", "100%")//since its a vertical linear gradient
.attr("gradientTransform", `rotate("180")`)
;
lg.append("stop")
.attr("offset", "0%")
.style("stop-color", "#7400ff")//end in red
.style("stop-opacity", 1)
lg.append("stop")
.attr("offset", "40%")
.style("stop-color", "#f014d9")//end in red
.style("stop-opacity", 1)
lg.append("stop")
.attr("offset", "100%")
.style("stop-color", "#f0141e")//start in blue
.style("stop-opacity", 1)
// GRADIENTS
// Append the arcs.
const path = svg.append("g")
.selectAll("path")
.data(root.descendants().slice(1))
.join("path")
.attr("fill", d => d.data.color)
.attr("fill-opacity", d => arcVisible(d.current) ? (d.children ? 0.6 : 0.4) : 0)
.attr("pointer-events", d => arcVisible(d.current) ? "auto" : "none")
.attr("d", d => arc(d.current));
// Make them clickable if they have children.
path.filter(d => d.children)
.style("cursor", "pointer")
.on("click", clicked);
path.filter(d => !d.children).style("cursor", "pointer").on("click", launched);
const format = d3.format(",d");
path.append("title")
.text(d => `${d.ancestors().map(d => d.data.name).reverse().join("/")}\n${format(d.value)}`)
.on("click", function(d) {
window.open("www.minisatsuki.com", "_blank");});
const label = svg.append("g")
.attr("pointer-events", "none")
.attr("text-anchor", "middle")
.style("user-select", "none")
.selectAll("text")
.data(root.descendants().slice(1))
.join("text")
.attr("dy", "0.55em")
.attr("fill-opacity", d => +labelVisible(d.current))
.attr("transform", d => labelTransform(d.current))
.text(d => d.data.name)
//.attr("font-weight","900");
svg
.append("g")
.selectAll("path")
.enter()
.append("circle")
.attr("cx", function(d) { return 5 } )
.attr("cy", function(d) { return 5 } )
.attr("r", 5)
.attr("fill", "#69b3a2")
// TEST
const imageCenter = svg.append("g")
.selectAll("text")
.data(root.descendants().slice(1))
d3.selectAll("path")
.attr("class", "g11")
.style("color", "red")
.append('image')
.attr('xlink:href', "https://minisatsuki.com/wp-content/uploads/2025/01/Minisatsuki-2024-New-1.png")
//.attr('clip-path', d => `url(#${d.group}-clip)`)
//.attr('clip-path', d => `url(#${d.group}-clip)`)
.attr('width', '50')
.attr('height', '50')
.attr('x', '250')
.attr('y', '250') ;
//TEST
const parent = svg.append("circle")
.datum(root)
.attr("r", radius)
.attr("fill", "none")
.attr("pointer-events", "all")
.on("click", clicked);
//d3.selectAll("circle").style("fill", "url(#hatch-1)") ; //pink;
//path.filter(d => d.children).style("fill", "url(#hatch-1)") ;
path.filter(d => d.data.group == "3").style("fill", "url(#benisoko)") ;
path.filter(d => d.data.group == "4").style("fill", "url(#hatch-red)") ;
path.filter(d => d.data.group == "5").style("fill", "url(#jiaibeni)");
path.filter(d => d.data.group == "7").style("fill", "url(#mujisoko)");
path.filter(d => d.data.group == "8").style("fill", "url(#hatch-violet)");
//path.filter(d => d.data.group == "8").style("fill", "url(#shiboviolet)");
path.filter(d => d.data.group == "9").style("fill", "url(#jiaiviolet)");
path.filter(d => d.data.group == "11").style("fill", "url(#hatch-pink)");
path.filter(d => d.data.group == "12").style("fill", "url(#fukurin)").attr('id', 'g11');
path.filter(d => d.data.group == "13").style("fill", "url(#2jiro)");
//path.filter(d => d.data.group == "8").style("fill", "url(#myFgrad)");
path.filter(d => d.data.group == "3").append("g")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
.append("svg:image")
//.attr("xlink:href",function(d) {return d.data.image;});
.attr("xlink:href","https://www.minisatsuki.com/wp-content/uploads/2025/01/Minisatsuki-2024-New-1.png");
// .attr("width",image_width)
// .attr("height",image_heigth)
// .attr("x",-1*image_width/2)
// .attr("y",-1*image_heigth/2);
const image = svg.append("g")
.selectAll("image")
.data(root.descendants().slice(0))
.join("image")
//.attr("xlink:href", "https://www.minisatsuki.com/wp-content/uploads/2024/02/Plain-TOKI-New.png")
.attr("xlink:href", d => d.data.lien)
//.attr("x", d => Math.cos(((d.x0 + d.x1) / 2 )* 180 / Math.PI) * radius * d.depth)
//.attr("y", d => Math.cos(((d.x0 + d.x1) / 2 * 180 / Math.PI) * (radius * (d.depth )) + (d.y1 - d.y0) / 2 * radius)
//.attr("x", d => Math.cos((d.x0/2 * 180 / Math.PI) + ((d.x0 + d.x1) / 2 )* 180 / Math.PI) * ((radius ) * (d.depth + 0.5)))
//.attr("y", d => Math.sin((d.x0/2 * 180 / Math.PI) + ((d.x0 + d.x1) / 2 )* 180 / Math.PI) * ((radius ) * (d.depth + 0.5)))
//.attr("visibility", d => +imageVisible(d.current))
.attr("visibility", d => imageVisible(d.current))
.attr("transform", d => imageTransform(d.current))
.attr("width", 32)
.attr("height", 32)
.style("stroke", "black")
.attr("dy", "0.55em")
.style("stroke-width", "1px");
// Handle zoom on click.
function launched(event, p) {
parent.datum(p.parent || root);
var url = "";
switch (p.data.group) {
case "1" :
//url = "https://www.minisatsuki.com/les-varietes-blances/?idb=" + p.data.id ;
url = "../les-varietes-blances/?idb=" + p.data.id ;
break ;
case "2":
case "3":
case "4":
case "5":
//url = "https://www.minisatsuki.com/les-varietes-rouges/?idb=" + p.data.id ;
url = "../les-varietes-rouges/?idb=" + p.data.id ;
break ;
case "6":
case "7":
case "8":
case "9":
//url = "https://www.minisatsuki.com/les-varietes-mauves/?idb=" + p.data.id ;
url = "../les-varietes-mauves/?idb=" + p.data.id ;
break ;
case "10":
case "11":
case "12":
//url = "https://www.minisatsuki.com/les-varietes-roses/?idb=" + p.data.id ;
url = "../les-varietes-roses/?idb=" + p.data.id ;
break ;
case "13" :
//url = "https://www.minisatsuki.com/les-varietes-gold/?idb=" + p.data.id ;
url = "../les-varietes-gold/?idb=" + p.data.id ;
break ;
}
window.open(url);
};
// Handle zoom on click.
function clicked(event, p) {
parent.datum(p.parent || root);
root.each(d => d.target = {
x0: Math.max(0, Math.min(1, (d.x0 - p.x0) / (p.x1 - p.x0))) * 2 * Math.PI,
x1: Math.max(0, Math.min(1, (d.x1 - p.x0) / (p.x1 - p.x0))) * 2 * Math.PI,
y0: Math.max(0, d.y0 - p.depth),
y1: Math.max(0, d.y1 - p.depth)
});
const t = svg.transition().duration(event.altKey ? 7500 : 750);
// Transition the data on all arcs, even the ones that aren’t visible,
// so that if this transition is interrupted, entering arcs will start
// the next transition from the desired position.
path.transition(t)
.tween("data", d => {
const i = d3.interpolate(d.current, d.target);
return t => d.current = i(t);
})
.filter(function(d) {
return +this.getAttribute("fill-opacity") || arcVisible(d.target);
})
.attr("fill-opacity", d => arcVisible(d.target) ? (d.children ? 0.6 : 0.4) : 0)
.attr("pointer-events", d => arcVisible(d.target) ? "auto" : "none")
.attrTween("d", d => () => arc(d.current));
label.filter(function(d) {
return +this.getAttribute("fill-opacity") || labelVisible(d.target);
}).transition(t)
.attr("fill-opacity", d => +labelVisible(d.target))
.attrTween("transform", d => () => labelTransform(d.current));
image.filter(function(d) {
return +this.getAttribute("fill-opacity") || imageVisible(d.target);
}).transition(t)
.attr("visibility", d => imageVisible(d.target))
.attrTween("transform", d => () => imageTransform(d.current));
}
function arcVisible(d) {
return d.y1 <= 3 && d.y0 >= 1 && d.x1 > d.x0;
}
function labelVisible(d) {
return d.y1 <= 3 && d.y0 >= 1 && (d.y1 - d.y0) * (d.x1 - d.x0) > 0.03;
}
function imageVisible(d) {
if (d.y1 <= 3 && d.y0 >= 1 && (d.y1 - d.y0) * (d.x1 - d.x0) > 0.03) {
return "display" ;
} else
return "hidden" ;
}
function labelTransform(d) {
const x = (d.x0 + d.x1) / 2 * 180 / Math.PI;
const y = (d.y0 + d.y1) / 2 * radius;
return `rotate(${x - 90}) translate(${y},0) rotate(${x < 180 ? 0 : 180})`;
}
function imageTransform(d) {
const x = ((d.x0 + d.x1 + Math.min((d.x1 + d.x0) / 2, 0.07)) / 2 * 180 / Math.PI) ;
const y = ((d.y0 + d.y1) / 2 * radius) + (radius - radius * 1.25) ;
if (x < 180 ) {
const x = ((d.x0 + d.x1 - Math.min((d.x1 + d.x0) / 2, 0.07)) / 2 * 180 / Math.PI) ;
const y = ((d.y0 + d.y1) / 2 * radius) + (radius - radius * 1.45) ;
return `rotate(${x - 90}) translate(${y},0) rotate(${x < 180 ? 0 : 180})`;
} else {
return `rotate(${x - 90}) translate(${y},0) rotate(${x < 180 ? 0 : 180})`;
}
}
return svg.node();
}