Here is an attempt to implement this visualization using Asymptote. The algorithm is pretty simple:
For every data point its label is constructed, preferably with equal width and length (to fit inside the
bubbles more often)
All data points are processed in order, and the current stem height is chosen as the minimum for which there's no overlap of the bubble and the label with previously processed bubbles and labels
Here is the code:
import graph;
Label FitLabel(string text, real width0, real width1, real height0) {
if (width1 - width0 < 1pt)
return Label(minipage(text, width1), align=E, filltype=UnFill(0.5pt));
real width2 = (width0 + width1) / 2;
frame f;
label(f, minipage(text, width2));
real height = max(f).y - min(f).y;
if (height <= height0)
return Label(minipage(text, width1), align=E, filltype=UnFill(0.5pt));
if (width2 <= height)
return FitLabel(text, width2, width1, height0);
else
return FitLabel(text, width0, width2, height);
}
Label FitLabel(string text) {
frame f;
label(f, text);
real width = max(f).x - min(f).x;
real height = max(f).y - min(f).y;
if (width <= height)
return Label(text, align=E, filltype=UnFill(0.5pt));
return FitLabel(text, 0, width, height);
}
struct Lollipop {
int time; // time in seconds since epoch
real height; // handle height
real radius; // bubble radius
Label label; // bubble label
real width; // label width
pen color;
path bubble;
pair min;
pair max;
bool inside; // is the label inside
static Lollipop Lollipop(string date, real minstem, real area, string label, pen color) {
Lollipop l = new Lollipop;
l.time = seconds(date, "%Y-%m-%d");
l.height = minstem;
l.radius = sqrt(area/pi);
l.label = FitLabel(label);
l.color = color;
l.bubble = scale(l.radius)*shift(0,1)*unitcircle;
l.min = min(l.bubble);
l.max = max(l.bubble);
frame f;
label(f, l.label);
pair fmin = min(f);
pair fmax = max(f);
real dist = sqrt((fmax.x-fmin.x)^2 + (fmax.y-fmin.y)^2);
if (dist >= 2*l.radius - 1pt) {
l.inside = false;
l.max += (fmax.x, 0);
if (fmax.y-fmin.y > l.max.y-l.min.y) {
l.min = (l.min.x, (l.min.y+l.max.y+fmin.y-fmax.y)/2);
l.max = (l.max.x, (l.min.y+l.max.y-fmin.y+fmax.y)/2);
l.height = -l.min.y+minstem;
}
} else {
l.label = Label(l.label, align=Center);
l.inside = true;
}
return l;
}
void DrawStem(real dx) {
draw(shift(this.time*dx,0)*((0,0)--(0,this.height)), this.color+linewidth(1));
}
void DrawBubble(real dx) {
path p = shift(this.time*dx,this.height)*this.bubble;
fill(p, this.color);
if (this.inside)
label(this.label, (this.time*dx,this.height+this.radius));
else
label(this.label, max(p) - (0, (max(p).y-min(p).y)/2));
}
}
from Lollipop unravel Lollipop;
Lollipop[] FromCSV(string filename, real scale=1) {
int nfields = 6;
Lollipop[] res;
file fd = input(filename);
string[] data = fd.csv();
int i = 0;
for(int row = 0; row < data.length/nfields; ++row) {
real Area = (real) data[i+1];
real Red = (real) data[i+2];
real Blue = (real) data[i+3];
real Green = (real) data[i+4];
Lollipop l = Lollipop(data[i], max(scale,10), Area*scale^2, data[i+5], rgb(Red, Blue, Green));
res.push(l);
i = i + nfields;
}
return res;
}
bool less(Lollipop a, Lollipop b) {
return a.height+a.min.y < b.height+b.min.y;
}
bool overlap(Lollipop a, Lollipop b, real dx, real delta) {
if (a.time*dx+a.min.x > b.time*dx+b.max.x + 2*delta || 2*delta + a.time*dx+a.max.x < b.time*dx+b.min.x) {
return false;
}
if (a.height+a.min.y > b.height+b.max.y + 2*delta || 2*delta + a.height+a.max.y < b.height+b.min.y) {
return false;
}
return true;
}
real[] CreateTicks(int mintime, int maxtime, real dx) {
real[] Ticks;
int minyear = (int) time(mintime, "%Y");
for(int year = minyear; true; ++year) {
for(int month = 1; month <= 12; ++month) {
int secs = seconds(format("%d-",year)+format("%d-01", month), "%Y-%m-%d");
if(secs > maxtime+5*31*24*60*60) {
return Ticks;
}
if(secs >= mintime) {
Ticks.push(secs*dx);
}
}
}
return Ticks;
}
void DrawLollipopDiagram(string filename, real scale, real width, real delta=3pt) {
Lollipop[] data = FromCSV(filename, scale);
int mintime = data[0].time;
int maxtime = mintime;
for(Lollipop l : data) {
if (mintime > l.time) {
mintime = l.time;
}
if (maxtime < l.time) {
maxtime = l.time;
}
}
real dx = width / (maxtime - mintime);
Lollipop[] processed;
for(Lollipop l : data) {
for(Lollipop m : processed) {
if (overlap(l, m, dx, delta)) {
l.height = m.height + m.max.y - l.min.y + 2*delta;
}
}
processed.push(l);
processed = sort(processed, less);
}
for(Lollipop l : data[reverse(data.length)]) {
l.DrawStem(dx);
}
for(Lollipop l : data) {
l.DrawBubble(dx);
}
real[] Ticks = CreateTicks(mintime, maxtime, dx);
xaxis(ticks=RightTicks(format=Label(align=NE),
ticklabel=new string(real x) {return time((int)(x/dx)," %b");},
Ticks=Ticks));
}
DrawLollipopDiagram("convoutages.csv", 0.3, 1800);
The convoutages.csv is a converted Outages.csv. Here are the first few lines:
2017-01-03,300,0.14914345375687976,0.6540272918781392,0.23669459588671782,System~1
2017-01-04,900,0.12607306806653415,0.9100549942394974,0.2942881832338349,System~2
2017-01-04,900,0.10149561106296984,0.8367351353339549,0.0074195577797571,System~3
2017-01-04,900,0.7005076043775806,0.43130677399752043,0.9729505763263211,System~4
2017-01-04,1560,0.3803363164795266,0.31247107140369296,0.7012970818678369,System~1
2017-01-05,5160,0.7000549527351069,0.8235906189417422,0.08753255386256266,System~2
2017-01-05,5160,0.15963276809064333,0.9479332994427221,0.914963733830938,System~3
The fields: date, outage duration, red, green, blue (at the moment I've just generate the color components randomly), label (the usual LaTeX conventions apply, e.g. ~ is a non-breaking space).
To get the result you should run
asy -f pdf lollipop.asy
The result:

\foreachinTikZ. The y-position and placement of the labels is more difficult, because at every step the position depends on where the previous steps placed the objects. I would suggest manually add y-position and label placement (for example,\node[anchor=west] at (bubble.east)) as new entries in the.csvfile, adjusting as appropriate. – Jānis Lazovskis Dec 22 '17 at 08:16.csvfile and, if possible, an example of what you've tried would encourage people to try stuff out. – cfr Dec 22 '17 at 16:47.csv, but it won't be automatic on the TeX side. If you want automation there, that's your main desideratum and that's where you need to start. The only thing I know which seems likely is TikZ graph-drawing algorithms. But I've already suggested looking at that. – cfr Dec 24 '17 at 03:09