RadialDendro = function(data){
function autoBox() {
document.body.appendChild(this);
const {x, y, width, height} = this.getBBox();
document.body.removeChild(this);
return [x, y, width, height];
}
const width = 1080
const radius = width/2.5
const tree = d3.cluster().size([2*Math.PI, radius - 100])
const colorsPalette =["#338c83", "#ba3a0b", "#0e3354", "#725e7f", "#114b5f","#cc9ea6", "#75b9be", "#f0a202", "#eadb88", "#0b0632", "#768948", "#8eb7a3", "#cee1d1", "#af9543", "#b75f0e", "#af370b", "#842909", "#0b0664", "#338ce7", "#ba3a6f", "#0e33b8", "#725ee3", "#cc9eff", "#f0a266", "#7689ac", "#af95a7", "#b75f72", "#cee16d", "#af376f", "#84296d"]
const colorsToUse = colorsPalette.slice(0,data.children.length)
const color = d3.scaleOrdinal()
.domain(data.children.map(d => d.name))
.range(colorsToUse)
function setColor(d) {
var name = d.data.name;
d.color = color.domain().indexOf(name) >= 0 ? color(name) : d.parent ? d.parent.color : null;
if (d.children) d.children.forEach(setColor);
}
//get the min and max values of the topic word weightings. These values are obtained during the process of topic modeling. So, it is also possible to get the values from python and change the domain parts of CirSize and CircleAlpha manually.
function getMinMaxvalues() {
let mappedData = data.children.map(d => d.children)
var allthedata = []
for (let i = 0; i < mappedData.length; i++) {
let j = i * 2;
allthedata[j] = d3.min(mappedData[i].map(v => v.value));
allthedata[j+1] = d3.max(mappedData[i].map(v => v.value));
}
return allthedata;
}
//define circle sizes by using getMinMaxvalues(). It is also possible to use the values obtained from python. In this case, it is not needed to define getMinMaxvalues().
const CirSize = d3.scaleSqrt().domain([d3.min(getMinMaxvalues()),d3.max(getMinMaxvalues())]).range([1,8])
//define circle opacities by using getMinMaxvalues()
const CircleAlpha = d3.scaleLinear().domain([d3.min(getMinMaxvalues()),d3.max(getMinMaxvalues())]).range([0.3,1])
// the main radial dendogram component
const root = tree(d3.hierarchy(data)
//Sorting of words. Uncomment the adopted one.
//the following line is for sorting the topic words by alphabetical order.
//.sort((a, b) => a.data.name.toLowerCase().localeCompare (b.data.name.toLowerCase()))
//the following line is for sorting the topic words by their weightings.
.sort((a, b) => d3.descending(a.data.value, b.data.value))
);
setColor(root);
const svg = d3.create("svg");
svg.append("g")
.attr("fill", "none")
.attr("stroke", "grey")
.attr("stroke-opacity", 0.4)
.attr("stroke-width", 1.5)
.selectAll("path")
.data(root.links())
.join("path")
.attr("d", d3.linkRadial()
.angle(d => d.x)
.radius(d => d.y))
.each(function(d) { d.target.linkNode = this; })
.attr("stroke", d => d.target.color);
svg.append("g")
.selectAll("circle")
.data(root.descendants())
.join("circle")
// uncomment next 2 lines to create a transition on circles entering
// .transition()
// .duration(1500)
.attr("transform", d => `
rotate(${d.x * 180 / Math.PI - 90})
translate(${d.y},0)
`)
// .attr("fill", d => d.children ? color(d.data.name) : color(d.data.group))
.attr("fill", d => d.children ? color(d.data.name) : "#484e4e")
// //uncomment the following line to show circles with different opacities
// .attr("fill-opacity", d => d.children ? 1 : CircleAlpha(d.data.value))
.attr("r", d => d.children ? 4 : CirSize(d.data.value))
// .on("click", function(d) {
// d3.select(this)
// .each(function(node) {
// node.children.forEach(function(child) {
// d3.select(child.linkNode).style("visibility", function() {
// return d3.select(child.linkNode).style("visibility") === "hidden" ? "visible" : "hidden";
// });
// });
// });
// });
svg.append("g")
.attr("font-family", "Roboto, sans-serif")
.attr("font-size", 12)
.attr("font-weight", 400)
.attr("fill", "#484e4e")
.attr("stroke-linejoin", "round")
.attr("stroke-width", 3)
.selectAll("text")
.data(root.descendants())
.join("text")
.attr("transform", d => `
rotate(${d.x * 180 / Math.PI - 90})
translate(${d.y},0)
rotate(${d.x >= Math.PI ? 180 : 0})
`)
.attr("dy", "0.31em")
.attr("x", d => d.x < Math.PI === !d.children ? 6 : -6)
.attr("text-anchor", d => d.x < Math.PI === !d.children ? "start" : "end")
// exclude the dash if no name is defined - used only for the first title which we want to exclude
.text(d => d.data.name == "" ? "" : `${d.data.name} (${d.data.value})`)
.clone(true).lower()
.attr("stroke", "white");
return svg.attr("viewBox", autoBox).node();
}