Best way to fit variably-sized text into a semi-circle in d3.js?

147 Views Asked by At

I am currently working on a visualization in d3.js with the goal to visualize data similarity. I wish to compare my data within circles by creating two semi-circles for each node and putting the comparison data within these semicircles. My data consists of strings (each semicircle receives a single sentence).

My current approach is as follows:
First, I create my necessary node data using the pack-layout.

var bubble = d3.pack().size([SVG_WIDTH,SVG_HEIGHT]).padding(CIRCLE_PADDING),
    root = d3.hierarchy({children: COMPARISON_DATA}).sum(function(d){  return d.children ? 0 : d[2]});

var nodeData = bubble(root).children;

d[2] is the maximum string length of the two sentences that are being put into the semicircles and thus decides the radius of the circles.

Next, I iterate over each node and create the corresponding semicircles. I have removed all the code-parts which are irrelevant to my question.

 nodeData.forEach(function (data, index) {
    //upperCircleGroup simply adds a small y-translate, so that the semicircles have a margin 
    var gUpper = upperCircleGroup.append("g");
    var gLower = lowerCircleGroup.append("g");


    var lowerCircle = gLower.append('path')
        .attr('d', d3.arc()({
            innerRadius: 0,
            outerRadius: data.r,
            startAngle: Math.PI / 2,
            endAngle: 3 / 2 * Math.PI
        }))
        .attr('transform', `translate(${data.x},${data.y})`)


      var upperCircle = gUpper.append('path')
        .attr('d', d3.arc()({
            innerRadius: 0,
            outerRadius: data.r,
            startAngle: 1 / 2 * Math.PI,
            endAngle: - 1 / 2 * Math.PI
        }))
        .attr('transform', `translate(${data.x},${data.y})`)

    var upperText = gUpper
        .append("foreignObject")
        .attr("width", () => {return data.r*Math.sqrt(2)})
        .attr("height", () => {return data.r*(Math.sqrt(2)/2)})
        .attr('transform', `translate(${data.x - (data.r / Math.sqrt(2))},${data.y - (data.r/Math.sqrt(2)) })`)
        .text(() => {return data.data[0]})

    var lowerText = gLower
        .append("foreignObject")
        .attr("width", () => {return data.r*Math.sqrt(2)})
        .attr("height", () => {return data.r*(Math.sqrt(2)/2)})
        .attr('transform', `translate(${data.x - (data.r / Math.sqrt(2))},${data.y  })`)
        .text(() => {return data.data[1]})
});

As you can see, I draw my semicircles using d3's arc. Now this is where my question arises. I've had trouble putting my textual content inside the arc, so after searching for a while I chose this solution to put a div inside my semicircles which then receives the text. The sqrt(2) operations are used to fit the square into the semicircle.

My problem with this solution is, that at times, the sentence simply won't fit into the div and some content is lost. Is there a way to calculate the font-size of a string necessary, so that it fits the div of a given size? If this were possible, I could simply calculate the appropriate font-size and add a zoom option to the visualization. Also, if there are better ways to achieve what I am trying to do I would also be happy to get some feedback from you guys as I am a complete beginner when it comes to using d3.

1

There are 1 best solutions below

0
On

Making text responsive to an element is difficult but CSS-Tricks have made a great article about different ways to approach it...

https://css-tricks.com/fitting-text-to-a-container/