{
const svg = makeSvg()
const basePane = svg.append('g')
const densityPane = svg.append('g')
const paperPane = svg.append('g')
const projection = initializeProjection(data, width, height)
basePane.selectAll('ellipse.cluster')
.data(data)
.join('ellipse')
.attr('class', 'cluster')
.attr('cx', d => projection.projectX(d.x))
.attr('cy', d => projection.projectY(d.y))
.attr('rx', d => projection.scaleX(d.r))
.attr('ry', d => projection.scaleY(d.r))
data.forEach(cluster => {
const clusterId = `cluster-${cluster.topicId}`
const clusterProjection = projection.translate(cluster.x, cluster.y)
basePane.selectAll(`ellipse.subcluster.${clusterId}`)
.data(cluster.subclusters)
.join('ellipse')
.attr('class', `cluster subcluster ${clusterId}`)
.attr('cx', d => clusterProjection.projectX(d.x))
.attr('cy', d => clusterProjection.projectY(d.y))
.attr('rx', d => clusterProjection.scaleX(d.r))
.attr('ry', d => clusterProjection.scaleY(d.r))
cluster.subclusters.forEach(subcluster => {
const subclusterId = `subcluster-${cluster.topicId}-${subcluster.topicId}`
const subclusterProjection = clusterProjection.translate(subcluster.x, subcluster.y)
const papers = subcluster.papers
paperPane.selectAll(`ellipse.paper.${subclusterId}`)
.data(papers)
.join('ellipse')
.attr('class', `paper ${subclusterId}`)
.attr('cx', d => subclusterProjection.projectX(d.x))
.attr('cy', d => subclusterProjection.projectY(d.y))
.attr('rx', 0.5)
.attr('ry', 0.5)
// renders density contours
const {
domain,
estimatorSize,
contours
} = subcluster.densityContours
const minX = subclusterProjection.projectX(domain[0])
const maxX = subclusterProjection.projectX(domain[1])
const minY = subclusterProjection.projectY(domain[0])
const maxY = subclusterProjection.projectY(domain[1])
const densityProjectX = d3.scaleLinear()
.domain([0, estimatorSize])
.range([minX, maxX])
const densityProjectY = d3.scaleLinear()
.domain([0, estimatorSize])
.range([minY, maxY]) // minY and maxY are flipped in the screen coordinates
const geoPath = d3.geoPath()
.projection(d3.geoTransform({
point (x, y) {
this.stream.point(densityProjectX(x), densityProjectY(y))
}
}))
densityPane.selectAll(`path.density-contour.${subclusterId}`)
.data(contours)
.join('path')
.attr('class', `density-contour ${subclusterId}`)
.attr('d', geoPath)
.attr('fill', d => d3.interpolateGreys(d.meanProb))
})
// renders island contours
{
const {
domain,
estimatorSize,
contours
} = cluster.islandContours
const minX = clusterProjection.projectX(domain[0])
const maxX = clusterProjection.projectX(domain[1])
const minY = clusterProjection.projectY(domain[0])
const maxY = clusterProjection.projectY(domain[1])
const contourProjectX = d3.scaleLinear()
.domain([0, estimatorSize])
.range([minX, maxX])
const contourProjectY = d3.scaleLinear()
.domain([0, estimatorSize])
.range([minY, maxY]) // minX and maxY are flipped in the screen coordinates
const geoPath = d3.geoPath()
.projection(d3.geoTransform({
point (x, y) {
this.stream.point(contourProjectX(x), contourProjectY(y))
}
}))
basePane.selectAll(`path.island-contour.${clusterId}`)
.data(contours.slice(0, 1)) // only outmost contour
.join('path')
.attr('class', `island-contour ${clusterId}`)
.attr('d', geoPath)
}
})
return svg.node()
}