Skip to content

Instantly share code, notes, and snippets.

@jyohjyoh
Last active August 29, 2015 14:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jyohjyoh/380e9d1a17281446e715 to your computer and use it in GitHub Desktop.
Save jyohjyoh/380e9d1a17281446e715 to your computer and use it in GitHub Desktop.
80/20 Rule Visualized in Application Usage Pattern

This is a d3 visualization version of the data published in CHI 2013 Case Study by Ji-Young Oh and Ananth Uggirala.The chart demonstrates that there are top 20-30 commands that takes up 80% of usage in an application. The pattern is used to re-design the UI of the application. The data only represents the small part of the application (~1%), which is the sample of snapshot of four month period in year 2010.

The charts loads data stored in tsv (name, value, cumulative columns) and shows transition from the value (%usage) to the cumulative usage. To demonstrate the increment pattern, I customized tick marks by the values in the data. The y-axis is rendered twice, one for the long grid line for each value point, and then y-axis label for important threshold values (maximum and 80% point mark for cumulative usage). Overall, the code follows the typical pattern of creating a bar chart from D3.

For the pattern I followed, refer to: Bar Chart and Stacked Bar Chart by Mike Bostock.

We can make this file beautiful and searchable if this error is corrected: It looks like row 46 should actually have 12 columns, instead of 17. in line 45.
name value cumulative
Rotate View 11.34539917 11.34539917
Extrude 9.461131994 20.80653116
Sketch2D 7.968859395 28.77539056
Undo 6.863928452 35.63931901
New Sketch 5.462634066 41.10195308
Free Rotate View 5.077700977 46.17965405
Render Style 4.607373177 50.78702723
EditSketch 3.077877241 53.86490447
Delete 2.266791693 56.13169616
Visibility 2.264054683 58.39575085
FinishActive 2.2162 60.6120
Fillet 2.082262478 62.69422507
WorkPlane 2.079525468 64.77375054
Edit Extrude 1.818305232 66.59205577
Hole 1.715995798 68.30805157
Isometric View 1.568744659 69.87679623
ZoomAll 1.495283311 71.37207954
Measure Distance 1.323070641 72.69515018
iProperties 1.169086457 73.86423664
FaceProperties 1.133122146 74.99735879
OnClose 1.049314899 76.04667368
Revolve 0.987020551 77.03369424
PanView 0.956585 77.99027924
Redo 0.760998264 78.7512775
LookAt 0.727880443 79.47915794
Chamfer 0.720490516 80.19964846
EditHole 0.548277846 80.7479263
Local Update 0.496493616 81.24441992
Loft 0.455328986 81.69974891
Steering Wheel 0.446077892 82.1458268
Previous View 0.438961666 82.58478846
Dimension Edit 0.427192523 83.01198099
Shell 0.41816039 83.43014138
Parameters 0.379294848 83.80943623
Sweep 0.357836689 84.16727291
WorkAxis 0.350282542 84.51755546
MirrorFeature 0.338732359 84.85628782
WorkPlaneShowDimension 0.318861667 85.17514948
CircularPattern 0.317547902 85.49269738
Zoom 0.31431823 85.80701561
Thread 0.313551867 86.12056748
RectangularPattern 0.306654602 86.42722208
Repeat 0.29258637 86.71980845
EditFillet 0.284977483 87.00478594
ZoomWindow 0.27884658 87.28363252
<!DOCTYPE html>
<html>
<head>
<title>usage pattern visualization</title>
</head>
<body>
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.axis text {
font: 10px sans-serif;
fill: #333333;
}
.axis path {
display: none;
}
.y.axis line {
fill: none;
stroke: #eaeaea;
shape-rendering: crispEdges;
}
.x.axis path line{
display: none;
}
</style>
<svg class="chart"> </svg>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 20, right: 30, bottom: 200, left: 30 },
width = 1000 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], 0.1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.tickSize(-(width-margin.right*1.2))
.orient("left");
yAxis.tickFormat(function (d) { return ''; });
var chart = d3.select(".chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.tsv("data60.tsv", type, function(error, data) {
x.domain(data.map(function(d) { return d.name; }));
y.domain([0, 100]);
//create a stack for each point
data.forEach(function(d){
d.values = [{h0:0, y1:d.cumulative, h1:d.cumulative, color:"#fafafa"}, //initial bar height: 0 -> transitoin height to d.cumulative
{h0:d.value, y1:d.cumulative, h1:d.value, color:"#ff7f0e"}]; //initial bar height: d.value -> transition y offset by d.cumulative
});
yAxis.tickValues(data.map(function(d){return d.value;}));
var yLabelVar = [d3.max(function(d){return d.value;})];
var yAxisLabel = d3.svg.axis()
.scale(y)
.tickSize(-(width-margin.right*1.2))
.orient("left");
yAxisLabel.tickValues([0, d3.max(data.map(function(d){return d.value;}))]);
chart.append("g")
.attr("class", "x axis")
.attr("transform", "translate(" + -x.rangeBand()*.5+"," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dy", "0.5em")
.attr("transform", "rotate(-65)");
var gy = chart.append("g")
.attr("class", "y axis")
.attr("transform", "translate("+ x.rangeBand()*0.5 + ", 0)")
.call(yAxis);
var yText = gy.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -height/2)
.attr("y", -22)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Usage %");;
var gy2 = chart.append("g")
.attr("class", "y axis")
.attr("transform", "translate("+ x.rangeBand()*0.5 + ",0)")
.call(yAxisLabel);
var bars = chart.selectAll(".bar")
.data(data).enter()
.append("g")
.attr("transform", function(d){ return "translate("+ x(d.name) + ",0)";});
var rects = bars.selectAll("g")
.data(function(d){ return d.values;}).enter()
.append("rect")
.attr("y", function(d) { return y(d.h0); })
.attr("height", function(d) { return height - y(d.h0); })
.attr("width", x.rangeBand())
.attr("fill", function(d){ return d.color;} );
yAxis.tickValues(data.map(function(d){return d.cumulative;}));
var searchPoint = 80, min_diff= 100, min_val=0;
data.forEach(function(d){
var diff = Math.abs(d.cumulative-searchPoint);
if (diff<min_diff) {
min_diff = diff;
min_val = d.cumulative;
}
});
yAxisLabel.tickValues([0, min_val, d3.max(data.map(function(d){return d.cumulative;}))]);
rects.transition()
.delay(2500)
.duration(2000)
.attr("y", function(d){ return y(d.y1);})
.attr("height", function(d){ return height-y(d.h1);});
gy.transition()
.delay(3000)
.duration(1000)
.call(yAxis);
gy2.transition()
.delay(4500)
.duration(1000)
.call(yAxisLabel)
.selectAll("line")
.style("stroke", function(d){ return(d!=0)? "#aaaaaa":"#eaeaea"} );
yText.transition()
.delay(5000)
.duration(1000)
.text("Cumulative usage%");
});
function type(d) {
d.value = +d.value; // coerce to number
d.cumulative = +d.cumulative;
return d;
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment