drawRadar = function(data, params) {
const { height, width, margin, axisTick, vennRatio } = params;
const axisName = ["Aroma", "Flavor", "Aftertaste", "Acidity", "Body", "Balance"];
const venn = ["#EFC3A4", "#F8CD29", "#F2AB39", "#92617E", "#2F1812", "#D66C44"];
const maxValue = 10;
const angleSlice = Math.PI * 2 / axisName.length;
const lineStroke = "#201E1F";
const photoImgWidth = 58;
const shapeColor = "#2F1812";
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height + (2 * margin)])
.style("background-color", "#E5D67B")
.style("border-radius", `${width / 4}px`)
let placement = {
radius: (height - 2 * margin) / 2,
xOffset: width / 2,
yOffset: margin + (height / 2)
};
let rScale = d3.scaleLinear()
.domain([0, maxValue])
.range([0, placement.radius])
let radarLine = d3.lineRadial()
.curve(d3.curveLinearClosed)
.radius(d => rScale(d))
.angle((d, i) => i * angleSlice)
const axisGrid = svg.append("g")
.attr("class", "axisWrapper")
.attr('transform', `translate(${placement.xOffset}, ${placement.yOffset})`);
// venn
const vennRadius = (placement.radius / (axisTick * 2)) * axisTick * vennRatio;
const transR = (placement.radius - vennRadius * 2) / 2;
axisGrid.append("g")
.attr("class", "venn")
.selectAll("circle")
.data(venn)
.enter()
.append("circle")
.attr("cx", (d, i) => rScale(maxValue / 2) * Math.cos(angleSlice * i - Math.PI / 2))
.attr("cy", (d, i) => rScale(maxValue / 2) * Math.sin(angleSlice * i - Math.PI / 2))
.attr("r", vennRadius)
.attr("fill", d => d)
.attr("fill-opacity", 0.4)
.attr("stroke", d => d)
.attr("transform", (d, i) =>
`translate(
${transR * Math.cos(angleSlice * i - Math.PI / 2)},
${transR * Math.sin(angleSlice * i - Math.PI / 2)})
`)
const tickData = new Array(axisTick)
.fill([])
.map((item, index) => {
let r = (maxValue / axisTick) * (index + 1);
return [r, r, r, r, r, r];
});
// grid line
axisGrid.append("g")
.selectAll("g")
.data(tickData)
.join("g")
.attr("fill", "transparent")
.attr("opacity", 0.3)
.attr("stroke", lineStroke)
.attr("stroke-dasharray", "5 5")
.append("path")
.attr("d", d => radarLine(d))
// axis line
let axisLine = axisGrid.selectAll(".axis-line")
.data(axisName)
.enter()
.append("g")
.attr("class", "axis-line");
axisLine.append("line")
.attr("x1", (d, i) => rScale(0) * Math.cos(angleSlice * i - Math.PI / 2))
.attr("y1", (d, i) => rScale(0) * Math.sin(angleSlice * i - Math.PI / 2))
.attr("x2", (d, i) => rScale(maxValue) * Math.cos(angleSlice * i - Math.PI / 2))
.attr("y2", (d, i) => rScale(maxValue) * Math.sin(angleSlice * i - Math.PI / 2))
.attr("stroke", lineStroke)
.attr("opacity", 0.3);
axisLine.append("circle")
.attr("cx", (d, i) => rScale(maxValue) * Math.cos(angleSlice * i - Math.PI / 2))
.attr("cy", (d, i) => rScale(maxValue) * Math.sin(angleSlice * i - Math.PI / 2))
.attr("r", 1)
.attr("stroke", "#201E1F");
axisLine.append("text")
.attr("x", (d, i) => rScale(maxValue) * Math.cos(angleSlice * i - Math.PI / 2))
.attr("y", (d, i) => rScale(maxValue) * Math.sin(angleSlice * i - Math.PI / 2))
.attr("dy", (d, i) => (i > 1 && i!=5)? 14: -10)
.attr("dx", (d, i) => i > 3? -65: 5)
.attr("text-anchor", "start")
.attr("fill", "rgba(32,30,31,1)")
.style("font-size", "1.2em")
.text(d => d);
data().then(data => {
// shape
let shape = axisGrid.append("g")
.attr("class", "color-shape")
.append("path")
.attr("d", d => "M0,0L0,0L0,0L0,0L0,0Z")
.attr("stroke", shapeColor)
.attr("stroke-width", 2)
.attr("fill", shapeColor)
.attr("fill-opacity", 0.25);
// photos
const photoGroup = axisGrid.append("g")
.attr("class", "photo")
.selectAll("g")
.data(data.map(d => ({ ...d, center: polygonCentroid(radarLine(d.score)) })))
.enter().append("g");
const clips = photoGroup.append("clipPath")
.attr("id", (d) => `round-clip${d.center[0]}${d.center[1]}`)
.append("circle")
.attr("cx", (d) => d.center[0])
.attr("cy", (d) => d.center[1])
.attr("r", photoImgWidth / 2)
.attr("transform", `translate(${photoImgWidth / 2}, ${photoImgWidth / 2})`);
const photos = photoGroup.append("image")
.attr("x", (d) => d.center[0])
.attr("y", (d) => d.center[1])
.attr("href", (d) => d.photo)
.attr("width", photoImgWidth)
.attr("clip-path", (d) => `url(#round-clip${d.center[0]}${d.center[1]})`)
.attr("transform", `translate(${-photoImgWidth / 2}, ${-photoImgWidth / 2})`);
photos.append("title")
.text(d => {
let strTitle = d.country + " - ";
for( let i=0;i<d.score.length;i++)
strTitle = strTitle.concat(axisName[i], ": ", d.score[i], " ");
return strTitle;
});
const borders = photoGroup.append("circle")
.attr("cx", (d) => d.center[0])
.attr("cy", (d) => d.center[1])
.attr("r", photoImgWidth / 2 + 2)
.attr("fill", "none")
.attr("stroke-width", 2)
.attr("stroke", `${shapeColor}`);
photos.on("mouseenter", (curItem) => {
borders
.transition()
.duration(1500)
.attr("stroke", (d) => curItem.photo === d.photo? "#fff": `${shapeColor}`);
shape.datum(curItem)
.transition()
.duration(1500)
.attr("d", d => radarLine(d.score))
});
});
return svg.node();
}