[Tex/LaTex] Bubble smartdiagram with weighted bubbles / bubble sizes

smartdiagramtikz-pgf

I really like the bubble smartdiagram:

http://texample.net/tikz/examples/smart-bubbles/

But I would like to assign custom node sizes.

More specifically I would like to recreate this graphic:

bubble chart

Can this be done? Is the smartdiagram the wrong tool for this? Is there a similar tool or what should I use?


Image source: http://reset.me/study/study-medical-error-is-the-third-leading-cause-of-death-in-the-united-states/

Best Answer

Just for fun, I wrote some asymptote code to mostly recreate your example. The diagram is laid out automatically. The only inputs are the labels and numbers.

Note that the spacing between circles is the same regardless of circle size. Also, the program logic decides if a label will fit in its circle and adjusts the label position and color accordingly.

The areas of all the circles are proportionally correct based on the numbers. Note however that the numbers don't add up to 2597, so the sum of the areas of the blue and red circles doesn't equal the area of the gray circle.

enter image description here

\documentclass{standalone}
\usepackage{asypictureB}
\begin{document}
\begin{asypicture}{name=smartDiagram}

unitsize(1inch); // Because I'm from USA

int allNumber = 2597;
real allRadius = 1.25; // Changing this size will effect the relative font size 
real allCircumference = 2.0 * pi * allRadius;

real labelSpacing = 0.18;

real sumRadii = 0.0;

struct Cause // This is a structure representing a cause
{
    string[] name;
    int number;
    real radius;
    real angularPosition;
    pen color;

    void operator init(string name, int number, pen color = blue)
    {
        this.name = split(name, " ");
        this.number = number;
        this.color = color;
        this.radius = allRadius * (number / allNumber)^0.5;
        sumRadii += this.radius;
    }
}

Cause[] causes;
causes.push(Cause("Heart disease", 611));
causes.push(Cause("Cancer",        585));
causes.push(Cause("Medical error", 251, red));
causes.push(Cause("COPD",          149));
causes.push(Cause("Suicide",        41));
causes.push(Cause("Firearms",       34));
causes.push(Cause("Motor vehicles", 34));

// set angular positions to achieve equal spacing
real spaceBetween = (allCircumference - 2*sumRadii) / causes.length;
causes[0].angularPosition = 180.0;
for (int i = 1; i < causes.length; ++i)
{
    causes[i].angularPosition = causes[i-1].angularPosition -
        360 * (causes[i-1].radius + causes[i].radius + spaceBetween) /
        allCircumference;
}

// Function to draw a label on a picture
void drawLabel(picture p, Cause c, pair loc = (0,0), pen color = black)
{
    real yOffset = labelSpacing * c.name.length / 2.0;
    for (int i = 0; i < c.name.length; ++i)
    {
        label(p, baseline(c.name[i]), loc+(0,yOffset), color);
        yOffset -= labelSpacing;
    }
    label(p, baseline(string(c.number)), loc+(0,yOffset), color);
}

// draw gray circle and label
fill(scale(allRadius)*unitcircle, mediumgray);
label(baseline("All causes"), (0,labelSpacing/2));
label(baseline(string(allNumber)), (0,-labelSpacing/2));

// draw other circles and labels
for (int i = 0; i < causes.length; ++i)
{
    pair center = rotate(causes[i].angularPosition,(0,0))*(allRadius,0);
    fill(shift(center)*scale(causes[i].radius)*unitcircle, causes[i].color);

    // determine size of a label to see if it will fit in circle
    picture p;
    unitsize(p, 1inch);
    drawLabel(p, causes[i]);
    pair labelSize = size(p, true);

    if (labelSize.x > 2*causes[i].radius ||
        labelSize.y > 2*causes[i].radius)
    {
        // draw label outside of circle if it won't fit
        add(shift(1.4*center)*p);
    }
    else
    {
        // otherwise draw label inside of circle
        drawLabel(currentpicture, causes[i], center, white);
    }
}

\end{asypicture} 
\end{document}

A different plot can be created by making changes to only the lines shown below.

int allNumber = 2597;
real allRadius = 1.25; // Changing this size will effect the relative font size 

causes.push(Cause("Heart disease", 611));
causes.push(Cause("Cancer",        585));
causes.push(Cause("Medical error", 251, red));
causes.push(Cause("COPD",          149));
causes.push(Cause("Suicide",        41));
causes.push(Cause("Firearms",       34));
causes.push(Cause("Motor vehicles", 34));
Related Question