scripts: treemap[d3].py: Show redundant datasets as redundant tiles
A painful lesson learned from plot[mpl].py: we should never implicitly
sum results in a late-stage rendering script. It just makes it way to
easy to accidentally render incorrect/misleading data, while being
difficult to notice.
We should always render redundant results as redundant results.
If the redundant results are an error, this hopefully makes the problem
more obvious to the user. And if the user really does want summed
results, they can always use csv.py as an intermediate step:
$ ./scripts/treemap.py \
<(./scripts/csv.py lfs.code.csv -bfile -fsize -q -o-)
-fsize
This commit is contained in:
+41
-35
@@ -111,6 +111,8 @@ def fold(results, by=None, fields=None, labels=None, defines=[]):
|
|||||||
for key in (keys if by else [()]):
|
for key in (keys if by else [()]):
|
||||||
for field in fields:
|
for field in fields:
|
||||||
# organize by 'by' and field
|
# organize by 'by' and field
|
||||||
|
dataset = []
|
||||||
|
label = None
|
||||||
for r in results:
|
for r in results:
|
||||||
# filter by 'by'
|
# filter by 'by'
|
||||||
if by and not all(
|
if by and not all(
|
||||||
@@ -129,20 +131,23 @@ def fold(results, by=None, fields=None, labels=None, defines=[]):
|
|||||||
else:
|
else:
|
||||||
v = None
|
v = None
|
||||||
|
|
||||||
# hide 'field' if there is only one field
|
|
||||||
key_ = key
|
|
||||||
if len(fields or []) > 1 or not key_:
|
|
||||||
key_ += (field,)
|
|
||||||
# do _not_ sum v here, it's tempting but risks
|
# do _not_ sum v here, it's tempting but risks
|
||||||
# incorrect and misleading results
|
# incorrect and misleading results
|
||||||
datasets[key_] = v
|
dataset.append(v)
|
||||||
|
|
||||||
# also find label?
|
# also find label?
|
||||||
if labels is not None:
|
if labels is not None:
|
||||||
for label_ in labels:
|
for label_ in labels:
|
||||||
if label_ not in r:
|
if label_ in r:
|
||||||
continue
|
label = r[label_]
|
||||||
labels_[key_] = r[label_]
|
|
||||||
|
# hide 'field' if there is only one field
|
||||||
|
key_ = key
|
||||||
|
if len(fields or []) > 1 or not key_:
|
||||||
|
key_ += (field,)
|
||||||
|
datasets[key_] = dataset
|
||||||
|
if label is not None:
|
||||||
|
labels_[key_] = label
|
||||||
|
|
||||||
return datasets, labels_
|
return datasets, labels_
|
||||||
|
|
||||||
@@ -422,7 +427,12 @@ class Tile:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# our parititioning schemes
|
# bounded division, limits result to dividend, useful for avoiding
|
||||||
|
# divide-by-zero issues
|
||||||
|
def bdiv(a, b):
|
||||||
|
return a / max(b, 1)
|
||||||
|
|
||||||
|
# our partitioning schemes
|
||||||
|
|
||||||
def partition_binary(children, total, x, y, width, height):
|
def partition_binary(children, total, x, y, width, height):
|
||||||
sums = [0]
|
sums = [0]
|
||||||
@@ -455,13 +465,13 @@ def partition_binary(children, total, x, y, width, height):
|
|||||||
|
|
||||||
# split horizontally?
|
# split horizontally?
|
||||||
if width > height:
|
if width > height:
|
||||||
dx = ((sums[k] - sums[i]) / value) * width
|
dx = bdiv(sums[k] - sums[i], value) * width
|
||||||
partition_(i, k, l, x, y, dx, height)
|
partition_(i, k, l, x, y, dx, height)
|
||||||
partition_(k, j, r, x+dx, y, width-dx, height)
|
partition_(k, j, r, x+dx, y, width-dx, height)
|
||||||
|
|
||||||
# split vertically?
|
# split vertically?
|
||||||
else:
|
else:
|
||||||
dy = ((sums[k] - sums[i]) / value) * height
|
dy = bdiv(sums[k] - sums[i], value) * height
|
||||||
partition_(i, k, l, x, y, width, dy)
|
partition_(i, k, l, x, y, width, dy)
|
||||||
partition_(k, j, r, x, y+dy, width, height-dy)
|
partition_(k, j, r, x, y+dy, width, height-dy)
|
||||||
|
|
||||||
@@ -473,7 +483,7 @@ def partition_slice(children, total, x, y, width, height):
|
|||||||
for t in children:
|
for t in children:
|
||||||
t.x = x_
|
t.x = x_
|
||||||
t.y = y
|
t.y = y
|
||||||
t.width = (t.value / total) * width
|
t.width = bdiv(t.value, total) * width
|
||||||
t.height = height
|
t.height = height
|
||||||
|
|
||||||
x_ += t.width
|
x_ += t.width
|
||||||
@@ -485,20 +495,12 @@ def partition_dice(children, total, x, y, width, height):
|
|||||||
t.x = x
|
t.x = x
|
||||||
t.y = y_
|
t.y = y_
|
||||||
t.width = width
|
t.width = width
|
||||||
t.height = (t.value / total) * height
|
t.height = bdiv(t.value, total) * height
|
||||||
|
|
||||||
y_ += t.height
|
y_ += t.height
|
||||||
|
|
||||||
def partition_squarify(children, total, x, y, width, height, *,
|
def partition_squarify(children, total, x, y, width, height, *,
|
||||||
aspect_ratio=(1,1)):
|
aspect_ratio=(1,1)):
|
||||||
if width == 0 or height == 0:
|
|
||||||
for t in children:
|
|
||||||
t.x = x
|
|
||||||
t.y = y
|
|
||||||
t.width = width
|
|
||||||
t.height = height
|
|
||||||
return
|
|
||||||
|
|
||||||
# this algorithm is described here:
|
# this algorithm is described here:
|
||||||
# https://www.win.tue.nl/~vanwijk/stm.pdf
|
# https://www.win.tue.nl/~vanwijk/stm.pdf
|
||||||
i = 0
|
i = 0
|
||||||
@@ -509,16 +511,17 @@ def partition_squarify(children, total, x, y, width, height, *,
|
|||||||
height_ = height
|
height_ = height
|
||||||
# note we don't really care about width vs height until
|
# note we don't really care about width vs height until
|
||||||
# actually slicing
|
# actually slicing
|
||||||
ratio = max(aspect_ratio[0]/aspect_ratio[1],
|
ratio = max(bdiv(aspect_ratio[0], aspect_ratio[1]),
|
||||||
aspect_ratio[1]/aspect_ratio[0])
|
bdiv(aspect_ratio[1], aspect_ratio[0]))
|
||||||
|
|
||||||
while i < len(children):
|
while i < len(children):
|
||||||
# calculate initial aspect ratio
|
# calculate initial aspect ratio
|
||||||
sum_ = children[i].value
|
sum_ = children[i].value
|
||||||
min_ = children[i].value
|
min_ = children[i].value
|
||||||
max_ = children[i].value
|
max_ = children[i].value
|
||||||
w = total_ * (ratio / max(width_/height_, height_/width_))
|
w = total_ * bdiv(ratio,
|
||||||
ratio_ = max((max_*w)/(sum_**2), (sum_**2)/(min_*w))
|
max(bdiv(width_, height_), bdiv(height_, width_)))
|
||||||
|
ratio_ = max(bdiv(max_*w, sum_**2), bdiv(sum_**2, min_*w))
|
||||||
|
|
||||||
# keep adding children to this row/col until it starts to hurt
|
# keep adding children to this row/col until it starts to hurt
|
||||||
# our aspect ratio
|
# our aspect ratio
|
||||||
@@ -527,7 +530,7 @@ def partition_squarify(children, total, x, y, width, height, *,
|
|||||||
sum__ = sum_ + children[j].value
|
sum__ = sum_ + children[j].value
|
||||||
min__ = min(min_, children[j].value)
|
min__ = min(min_, children[j].value)
|
||||||
max__ = max(max_, children[j].value)
|
max__ = max(max_, children[j].value)
|
||||||
ratio__ = max((max__*w)/(sum__**2), (sum__**2)/(min__*w))
|
ratio__ = max(bdiv(max__*w, sum__**2), bdiv(sum__**2, min__*w))
|
||||||
if ratio__ > ratio_:
|
if ratio__ > ratio_:
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -539,14 +542,14 @@ def partition_squarify(children, total, x, y, width, height, *,
|
|||||||
|
|
||||||
# vertical col? dice horizontally?
|
# vertical col? dice horizontally?
|
||||||
if width_ > height_:
|
if width_ > height_:
|
||||||
dx = (sum_ / total_) * width_
|
dx = bdiv(sum_, total_) * width_
|
||||||
partition_dice(children[i:j], sum_, x_, y_, dx, height_)
|
partition_dice(children[i:j], sum_, x_, y_, dx, height_)
|
||||||
x_ += dx
|
x_ += dx
|
||||||
width_ -= dx
|
width_ -= dx
|
||||||
|
|
||||||
# horizontal row? slice vertically?
|
# horizontal row? slice vertically?
|
||||||
else:
|
else:
|
||||||
dy = (sum_ / total_) * height_
|
dy = bdiv(sum_, total_) * height_
|
||||||
partition_slice(children[i:j], sum_, x_, y_, width_, dy)
|
partition_slice(children[i:j], sum_, x_, y_, width_, dy)
|
||||||
y_ += dy
|
y_ += dy
|
||||||
height_ -= dy
|
height_ -= dy
|
||||||
@@ -634,12 +637,15 @@ def main(csv_paths, *,
|
|||||||
datasets, labels_ = fold(results, by, fields, labels, defines)
|
datasets, labels_ = fold(results, by, fields, labels, defines)
|
||||||
|
|
||||||
# build tile heirarchy
|
# build tile heirarchy
|
||||||
tile = Tile.merge([
|
children = []
|
||||||
Tile(k, v, label=labels_.get(k))
|
for key, dataset in datasets.items():
|
||||||
for k, v in datasets.items()
|
for i, v in enumerate(dataset):
|
||||||
# discard anything with the value 0 early, otherwise these
|
children.append(Tile(
|
||||||
# cause a lot of problems
|
key + ((str(i),) if len(dataset) > 1 else ()),
|
||||||
if v != 0])
|
v,
|
||||||
|
label=labels_.get(key)))
|
||||||
|
|
||||||
|
tile = Tile.merge(children)
|
||||||
|
|
||||||
# sort
|
# sort
|
||||||
tile.sort()
|
tile.sort()
|
||||||
@@ -657,7 +663,7 @@ def main(csv_paths, *,
|
|||||||
t.char = chars_[i % len(chars_)]
|
t.char = chars_[i % len(chars_)]
|
||||||
|
|
||||||
# scale width/height if requested now that we have our data
|
# scale width/height if requested now that we have our data
|
||||||
if to_scale and (width is None or height is None) and tile.value:
|
if to_scale and (width is None or height is None) and tile.value != 0:
|
||||||
# scale if needed
|
# scale if needed
|
||||||
if braille:
|
if braille:
|
||||||
xscale, yscale = 2, 4
|
xscale, yscale = 2, 4
|
||||||
|
|||||||
+40
-35
@@ -127,6 +127,8 @@ def fold(results, by=None, fields=None, labels=None, defines=[]):
|
|||||||
for key in (keys if by else [()]):
|
for key in (keys if by else [()]):
|
||||||
for field in fields:
|
for field in fields:
|
||||||
# organize by 'by' and field
|
# organize by 'by' and field
|
||||||
|
dataset = []
|
||||||
|
label = None
|
||||||
for r in results:
|
for r in results:
|
||||||
# filter by 'by'
|
# filter by 'by'
|
||||||
if by and not all(
|
if by and not all(
|
||||||
@@ -145,20 +147,23 @@ def fold(results, by=None, fields=None, labels=None, defines=[]):
|
|||||||
else:
|
else:
|
||||||
v = None
|
v = None
|
||||||
|
|
||||||
# hide 'field' if there is only one field
|
|
||||||
key_ = key
|
|
||||||
if len(fields or []) > 1 or not key_:
|
|
||||||
key_ += (field,)
|
|
||||||
# do _not_ sum v here, it's tempting but risks
|
# do _not_ sum v here, it's tempting but risks
|
||||||
# incorrect and misleading results
|
# incorrect and misleading results
|
||||||
datasets[key_] = v
|
dataset.append(v)
|
||||||
|
|
||||||
# also find label?
|
# also find label?
|
||||||
if labels is not None:
|
if labels is not None:
|
||||||
for label_ in labels:
|
for label_ in labels:
|
||||||
if label_ not in r:
|
if label_ in r:
|
||||||
continue
|
label = r[label_]
|
||||||
labels_[key_] = r[label_]
|
|
||||||
|
# hide 'field' if there is only one field
|
||||||
|
key_ = key
|
||||||
|
if len(fields or []) > 1 or not key_:
|
||||||
|
key_ += (field,)
|
||||||
|
datasets[key_] = dataset
|
||||||
|
if label is not None:
|
||||||
|
labels_[key_] = label
|
||||||
|
|
||||||
return datasets, labels_
|
return datasets, labels_
|
||||||
|
|
||||||
@@ -269,8 +274,12 @@ class Tile:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# bounded division, limits result to dividend, useful for avoiding
|
||||||
|
# divide-by-zero issues
|
||||||
|
def bdiv(a, b):
|
||||||
|
return a / max(b, 1)
|
||||||
|
|
||||||
# our parititioning schemes
|
# our partitioning schemes
|
||||||
|
|
||||||
def partition_binary(children, total, x, y, width, height):
|
def partition_binary(children, total, x, y, width, height):
|
||||||
sums = [0]
|
sums = [0]
|
||||||
@@ -303,13 +312,13 @@ def partition_binary(children, total, x, y, width, height):
|
|||||||
|
|
||||||
# split horizontally?
|
# split horizontally?
|
||||||
if width > height:
|
if width > height:
|
||||||
dx = ((sums[k] - sums[i]) / value) * width
|
dx = bdiv(sums[k] - sums[i], value) * width
|
||||||
partition_(i, k, l, x, y, dx, height)
|
partition_(i, k, l, x, y, dx, height)
|
||||||
partition_(k, j, r, x+dx, y, width-dx, height)
|
partition_(k, j, r, x+dx, y, width-dx, height)
|
||||||
|
|
||||||
# split vertically?
|
# split vertically?
|
||||||
else:
|
else:
|
||||||
dy = ((sums[k] - sums[i]) / value) * height
|
dy = bdiv(sums[k] - sums[i], value) * height
|
||||||
partition_(i, k, l, x, y, width, dy)
|
partition_(i, k, l, x, y, width, dy)
|
||||||
partition_(k, j, r, x, y+dy, width, height-dy)
|
partition_(k, j, r, x, y+dy, width, height-dy)
|
||||||
|
|
||||||
@@ -321,7 +330,7 @@ def partition_slice(children, total, x, y, width, height):
|
|||||||
for t in children:
|
for t in children:
|
||||||
t.x = x_
|
t.x = x_
|
||||||
t.y = y
|
t.y = y
|
||||||
t.width = (t.value / total) * width
|
t.width = bdiv(t.value, total) * width
|
||||||
t.height = height
|
t.height = height
|
||||||
|
|
||||||
x_ += t.width
|
x_ += t.width
|
||||||
@@ -333,20 +342,12 @@ def partition_dice(children, total, x, y, width, height):
|
|||||||
t.x = x
|
t.x = x
|
||||||
t.y = y_
|
t.y = y_
|
||||||
t.width = width
|
t.width = width
|
||||||
t.height = (t.value / total) * height
|
t.height = bdiv(t.value, total) * height
|
||||||
|
|
||||||
y_ += t.height
|
y_ += t.height
|
||||||
|
|
||||||
def partition_squarify(children, total, x, y, width, height, *,
|
def partition_squarify(children, total, x, y, width, height, *,
|
||||||
aspect_ratio=(1,1)):
|
aspect_ratio=(1,1)):
|
||||||
if width == 0 or height == 0:
|
|
||||||
for t in children:
|
|
||||||
t.x = x
|
|
||||||
t.y = y
|
|
||||||
t.width = width
|
|
||||||
t.height = height
|
|
||||||
return
|
|
||||||
|
|
||||||
# this algorithm is described here:
|
# this algorithm is described here:
|
||||||
# https://www.win.tue.nl/~vanwijk/stm.pdf
|
# https://www.win.tue.nl/~vanwijk/stm.pdf
|
||||||
i = 0
|
i = 0
|
||||||
@@ -357,16 +358,17 @@ def partition_squarify(children, total, x, y, width, height, *,
|
|||||||
height_ = height
|
height_ = height
|
||||||
# note we don't really care about width vs height until
|
# note we don't really care about width vs height until
|
||||||
# actually slicing
|
# actually slicing
|
||||||
ratio = max(aspect_ratio[0]/aspect_ratio[1],
|
ratio = max(bdiv(aspect_ratio[0], aspect_ratio[1]),
|
||||||
aspect_ratio[1]/aspect_ratio[0])
|
bdiv(aspect_ratio[1], aspect_ratio[0]))
|
||||||
|
|
||||||
while i < len(children):
|
while i < len(children):
|
||||||
# calculate initial aspect ratio
|
# calculate initial aspect ratio
|
||||||
sum_ = children[i].value
|
sum_ = children[i].value
|
||||||
min_ = children[i].value
|
min_ = children[i].value
|
||||||
max_ = children[i].value
|
max_ = children[i].value
|
||||||
w = total_ * (ratio / max(width_/height_, height_/width_))
|
w = total_ * bdiv(ratio,
|
||||||
ratio_ = max((max_*w)/(sum_**2), (sum_**2)/(min_*w))
|
max(bdiv(width_, height_), bdiv(height_, width_)))
|
||||||
|
ratio_ = max(bdiv(max_*w, sum_**2), bdiv(sum_**2, min_*w))
|
||||||
|
|
||||||
# keep adding children to this row/col until it starts to hurt
|
# keep adding children to this row/col until it starts to hurt
|
||||||
# our aspect ratio
|
# our aspect ratio
|
||||||
@@ -375,7 +377,7 @@ def partition_squarify(children, total, x, y, width, height, *,
|
|||||||
sum__ = sum_ + children[j].value
|
sum__ = sum_ + children[j].value
|
||||||
min__ = min(min_, children[j].value)
|
min__ = min(min_, children[j].value)
|
||||||
max__ = max(max_, children[j].value)
|
max__ = max(max_, children[j].value)
|
||||||
ratio__ = max((max__*w)/(sum__**2), (sum__**2)/(min__*w))
|
ratio__ = max(bdiv(max__*w, sum__**2), bdiv(sum__**2, min__*w))
|
||||||
if ratio__ > ratio_:
|
if ratio__ > ratio_:
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -387,14 +389,14 @@ def partition_squarify(children, total, x, y, width, height, *,
|
|||||||
|
|
||||||
# vertical col? dice horizontally?
|
# vertical col? dice horizontally?
|
||||||
if width_ > height_:
|
if width_ > height_:
|
||||||
dx = (sum_ / total_) * width_
|
dx = bdiv(sum_, total_) * width_
|
||||||
partition_dice(children[i:j], sum_, x_, y_, dx, height_)
|
partition_dice(children[i:j], sum_, x_, y_, dx, height_)
|
||||||
x_ += dx
|
x_ += dx
|
||||||
width_ -= dx
|
width_ -= dx
|
||||||
|
|
||||||
# horizontal row? slice vertically?
|
# horizontal row? slice vertically?
|
||||||
else:
|
else:
|
||||||
dy = (sum_ / total_) * height_
|
dy = bdiv(sum_, total_) * height_
|
||||||
partition_slice(children[i:j], sum_, x_, y_, width_, dy)
|
partition_slice(children[i:j], sum_, x_, y_, width_, dy)
|
||||||
y_ += dy
|
y_ += dy
|
||||||
height_ -= dy
|
height_ -= dy
|
||||||
@@ -484,12 +486,15 @@ def main(csv_paths, output, *,
|
|||||||
datasets, labels_ = fold(results, by, fields, labels, defines)
|
datasets, labels_ = fold(results, by, fields, labels, defines)
|
||||||
|
|
||||||
# build tile heirarchy
|
# build tile heirarchy
|
||||||
tile = Tile.merge([
|
children = []
|
||||||
Tile(k, v, label=labels_.get(k))
|
for key, dataset in datasets.items():
|
||||||
for k, v in datasets.items()
|
for i, v in enumerate(dataset):
|
||||||
# discard anything with the value 0 early, otherwise these
|
children.append(Tile(
|
||||||
# cause a lot of problems
|
key + ((str(i),) if len(dataset) > 1 else ()),
|
||||||
if v != 0])
|
v,
|
||||||
|
label=labels_.get(key)))
|
||||||
|
|
||||||
|
tile = Tile.merge(children)
|
||||||
|
|
||||||
# sort
|
# sort
|
||||||
tile.sort()
|
tile.sort()
|
||||||
@@ -503,7 +508,7 @@ def main(csv_paths, output, *,
|
|||||||
t_.color = colors_[i % len(colors_)]
|
t_.color = colors_[i % len(colors_)]
|
||||||
|
|
||||||
# scale width/height if requested now that we have our data
|
# scale width/height if requested now that we have our data
|
||||||
if to_scale and (width is None or height is None) and tile.value:
|
if to_scale and (width is None or height is None) and tile.value != 0:
|
||||||
# scale width only
|
# scale width only
|
||||||
if height is not None:
|
if height is not None:
|
||||||
width_ = mt.ceil((tile.value * to_scale) / height_)
|
width_ = mt.ceil((tile.value * to_scale) / height_)
|
||||||
|
|||||||
Reference in New Issue
Block a user