A convenient way to view many barcharts organized by two variables of data.
End of rows and columns are the aggregate datasets of those rows/columns.
forked from BBischof's block: Matrix of Grouped Barcharts with Aggregate Scatterplots
xxxxxxxxxx
<meta charset="utf-8">
<style>
svg {
font: 10px sans-serif;
padding: 20px;
}
.axis,
.frame {
shape-rendering: crispEdges;
}
.axis line {
stroke: #ddd;
}
.axis path {
display: none;
}
.frame {
fill: none;
stroke: #aaa;
}
.d3-tip {
line-height: 1;
font-weight: bold;
padding: 8px;
background: rgba(0, 0, 0, 0.8);
color: #fff;
border-radius: 2px;
}
/* Creates a small triangle extender for the tooltip */
.d3-tip:after {
box-sizing: border-box;
display: inline;
font-size: 8px;
width: 100%;
line-height: 1;
color: rgba(0, 0, 0, 0.8);
content: "\25BC";
position: absolute;
text-align: center;
}
/* Style northward tooltips differently */
.d3-tip.n:after {
margin: -1px 0 0 0;
top: 100%;
left: 0;
}
circle {
fill-opacity: .7;
}
</style>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://labratrevenge.com/d3-tip/javascripts/d3.tip.v0.6.3.js"></script>
<script>
var width = 1200,
size = 320,
padding = 60;
var x = d3.scaleLinear()
.range([padding / 2, size - padding / 2]);
var y = d3.scaleLinear()
.range([size - padding / 2, padding / 2]);
var xAxis = d3.axisBottom(x)
.scale(x)
.ticks(5);
var yAxis = d3.axisLeft()
.scale(y)
.ticks(5);
var color = d3.scaleOrdinal(d3.schemeCategory20);
var pivotColumns = ["partSize", "httpThreads"];
d3.json("outputRedacted.log", function(error, jdata) {
if (error) return console.warn(error);
data = jdata;
dataMatrix = prepareJson(data);
traitMatrix = makeMatrix(data);
avgMatrix = makeAvgLists(data, dataMatrix);
n = data[pivotColumns[0]].length;
m = data[pivotColumns[1]].length;
var svg = d3.select("body").append("svg")
.attr("width", size * (n + 3.5) + padding + 200)
.attr("height", size * (m + 3.5) + padding + 200)
.append("g")
.attr("transform", "translate(" + padding + "," + padding / 2 + ")");
var cell = svg.selectAll(".cell")
.data(traitMatrix)
.enter().append("g")
.attr("class", "cell")
.attr("transform", function(d) { return "translate(" + d.i * size + "," + (d.j)* size + ")"; })
.each(myPlot);
var cell2 = svg.selectAll(".cell2")
.data(avgMatrix[1])
.enter().append("g")
.attr("class", "cell2")
.attr("transform", function(d, i) { return "translate(" + (n) * size + "," + (i) * size + ")"; })
.each(meanPlot);
var cell3 = svg.selectAll(".cell3")
.data(avgMatrix[0])
.enter().append("g")
.attr("class", "cell3")
.attr("transform", function(d, i) { return "translate(" + i * size + "," + (m) * size +")"; })
.each(meanPlot2);
function myPlot(p) {
width = size - padding;
height = size - padding;
var cell = d3.select(this);
var x0 = d3.scaleBand()
.range([0, width], .1);
var x1 = d3.scaleOrdinal();
var y = d3.scaleLinear()
.range([height, 0]);
var xAxis = d3.axisBottom()
.scale(x0);
var yAxis = d3.axisLeft()
.scale(y)
.tickFormat(d3.format(".2s"));
elementData = dataMatrix[p.x][p.y]["tests"];
tests = Object.keys(dataMatrix[p.x][p.y]["tests"]["DOWN"]);
downSpeeds = [];
upSpeeds = [];
groupedData = [];
for (i=0; i<tests.length; i++) {
tests[i] = "Test" + tests[i]
downSpeeds.push(parseFloat(elementData["DOWN"][i][" mbps"]));
upSpeeds.push(parseFloat(elementData["UP"][i][" mbps"]));
groupedData.push({"test" : tests[i], "trans": [{"name": "UP", "value": upSpeeds[i]}, {"name": "DOWN", "value": downSpeeds[i]}], "UP": upSpeeds[i], "DOWN": downSpeeds[i] });
};
var directions = ["UP", "DOWN"];
groupedData.forEach(function(d) {
d.direct = directions.map(function(name) { return {name: name, value: +d[name]}; }); //adds a function to data that allows you to request an array of the age ranges and their values for a given Interval
});
x0.domain(groupedData.map(function(d) { return d.test; }));
var x1 = d3.scaleBand().domain(directions).range([0,x0.bandwidth()]);
y.domain([globMin, globMax+10]);
cell.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
cell.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("y", 10)
.style("font-size","12px")
.attr("dy", ".71em")
.text("mbps: " + pivotColumns[0] + "= " + p.x + " vs. " + pivotColumns[1] + "= "+ p.y );
var Test = cell.selectAll(".Test")
.data(groupedData) //an array with each element a dict, keys given by Interval and ages
.enter().append("g")
.style("stroke", "black")
.attr("class", "g")
.attr("transform", function(d) { return "translate(" + x0(d.test) + ",0)"; });
Test.selectAll("rect")
.data(function(d) { return d.direct; })
.enter().append("rect")
.attr("width", x1.bandwidth())
.attr("x", function(d) { return x1(d.name); })
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); })
.style("fill", function(d) { return color(d.name); })
.on("mouseover", function(d) {
d3.select(this).style("fill-opacity", .3);
})
.on("mouseout", function(d) {
d3.select(this).style("fill-opacity", 1)
});
};
function meanPlot(p) {
width = size - padding;
height = size - padding;
var cell2 = d3.select(this);
elementData = p;
var x = d3.scaleLinear()
.range([0, width/n - padding]);
columnNames = Object.keys(dataMatrix).sort(function(a, b){return a-b});
var x = d3.scaleBand()
.range([0, width], 0);
x.domain(columnNames);
var y = d3.scaleLinear()
.range([height, 0]);
y.domain([globMin, globMax+10]);
var z = d3.schemeCategory10;
var xAxis = d3.axisBottom()
.scale(x)
.tickValues(x.domain())
.ticks(columnNames.length);
var yAxis = d3.axisLeft()
.scale(y)
var directions = ["up", "down"];
var series = directions.map(function(series, i){
return elementData["means"].map(function(d){
return {x: d["pivcol1"], y: d[directions[i]]};
});
});
cell2.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.attr("y", 0)
.attr("x", 9)
.attr("dy", ".35em")
.attr("transform", "rotate(90)")
.style("text-anchor", "start");
cell2.append("g")
.attr("class", "y axis")
.call(yAxis).append("text")
//.attr("transform", "rotate(-90)")
.attr("y", 10)
.style("font-size","12px")
.attr("dy", ".71em")
//.style("text-anchor", "end")
.html("Mean mbps vs. httpThreads");
binarySwitch = ["UP","DOWN"]
cell2.selectAll(".series")
.data(series)
.enter().append("g")
.attr("class", "series")
.style("fill", function(d, i) { return color(binarySwitch[i]); })
.selectAll(".point")
.data(function(d) { return d; })
.enter().append("circle")
.attr("class", "point")
.attr("r", 4.5)
.attr("cx", function(d) { return x(d.x) + x.bandwidth()/2; })
.attr("cy", function(d) { return y(d.y); });
// //.on('mouseover', tip.show);
};
function meanPlot2(p) {
width = size - padding;
height = size - padding;
var cell3 = d3.select(this);
columnNames = Object.keys(dataMatrix[Object.keys(dataMatrix)[1]]).sort(function(a, b){return a-b});
elementData = p;
var x = d3.scaleBand()
.range([0, width], 0);
x.domain(columnNames);
var y = d3.scaleLinear()
.range([height, 0]);
y.domain([globMin, globMax+10]);
var z = d3.schemeCategory10;
var xAxis = d3.axisBottom()
.scale(x)
.tickValues(x.domain())
.ticks(columnNames.length);
var yAxis = d3.axisLeft()
.scale(y)
var directions = ["up", "down"];
var series = directions.map(function(series, i){
return elementData["means"].map(function(d){
//console.log("point data", d, i);
return {x: d["pivcol2"], y: d[directions[i]]};
});
});
cell3.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.attr("y", 0)
.attr("x", 9)
.attr("dy", ".35em")
.attr("transform", "rotate(90)")
.style("text-anchor", "start");
cell3.append("g")
.attr("class", "y axis")
.call(yAxis).append("text")
//.attr("transform", "rotate(-90)")
.attr("y", 10)
.style("font-size","12px")
.attr("dy", ".71em")
//.style("text-anchor", "end")
.html("Mean mbps vs. partSize");// + "<br/>" + " partSize=" + elementData["pivcol2"]);
binarySwitch = ["UP","DOWN"]
cell3.selectAll(".series")
.data(series)
.enter().append("g")
.attr("class", "series")
.style("fill", function(d, i) { return color(binarySwitch[i]); })
.selectAll(".point")
.data(function(d) { return d; })
.enter().append("circle")
.attr("class", "point")
.attr("r", 4.5)
.attr("cx", function(d) { return x(d.x) + x.bandwidth()/2; })
.attr("cy", function(d) { return y(d.y); });
//.on('mouseover', tip.show);
};
});
function makeAvgLists(json, matrix) {
col1Avgs = [];
col2Avgs = [];
col1 = json[pivotColumns[0]];
col2 = json[pivotColumns[1]];
//console.log(col1,col2);
for (num1 in col1){
col1Avgs.push({"pivcol1": col1[num1], "means": []});
for (num2 in col2){
//console.log(matrix[col1[num1]][col2[num2]]["MeanUP"],matrix[col1[num1]][col2[num2]]["MeanDOWN"]);
col1Avgs[num1]["means"].push({"pivcol2": col2[num2], "up": matrix[col1[num1]][col2[num2]]["MeanUP"], "down": matrix[col1[num1]][col2[num2]]["MeanDOWN"]});
}
}
for (num2 in col2){
col2Avgs.push({"pivcol2": col2[num2], "means": []});
for (num1 in col1){
//console.log(matrix[col2[num2]][col1[num1]]["MeanUP"],matrix[col2[num2]][col1[num1]]["MeanDOWN"]);
col2Avgs[num2]["means"].push({"pivcol1": col1[num1], "up": matrix[col1[num1]][col2[num2]]["MeanUP"], "down": matrix[col1[num1]][col2[num2]]["MeanDOWN"]});
}
}
return [col1Avgs, col2Avgs];
}
function makeMatrix(json) {
//console.log(json[pivotColumns[0]]);
return cross(json[pivotColumns[0]].sort(function(a, b){return a-b}),json[pivotColumns[1]].sort(function(a, b){return a-b}));
}
function prepareJson(json) {
dataMatrix = {}
for (i=0; i<json[pivotColumns[0]].length; i++) {
dataMatrix[json[pivotColumns[0]][i]] = {}
for (j=0; j< json[pivotColumns[1]].length; j++) {
dataMatrix[json[pivotColumns[0]][i]][json[pivotColumns[1]][j]] = {}
}
}
for (i=0; i<json["data"].length; i++) {
dataMatrix[json["data"][i][pivotColumns[0]]][json["data"][i][pivotColumns[1]]] = json["data"][i];
}
mins = [];
maxes = [];
meansUP = [];
meansDOWN = [];
for (s in json["data"]){
tempMeansUP = [];
tempMeansDOWN = [];
for (k in json["data"][s]["tests"]["UP"]){
tempMeansUP.push(parseFloat(json["data"][s]["tests"]["UP"][k][" mbps"]));
tempMeansDOWN.push(parseFloat(json["data"][s]["tests"]["DOWN"][k][" mbps"]));
mins.push(d3.min([parseFloat(json["data"][s]["tests"]["UP"][k][" mbps"]),parseFloat(json["data"][s]["tests"]["DOWN"][k][" mbps"])]));
maxes.push(d3.max([parseFloat(json["data"][s]["tests"]["UP"][k][" mbps"]),parseFloat(json["data"][s]["tests"]["DOWN"][k][" mbps"])]));
}
dataMatrix[json["data"][s][pivotColumns[0]]][json["data"][s][pivotColumns[1]]]["MeanUP"] = d3.mean(tempMeansUP);
dataMatrix[json["data"][s][pivotColumns[0]]][json["data"][s][pivotColumns[1]]]["MeanDOWN"] = d3.mean(tempMeansDOWN);
}
globMin = d3.min(mins);
globMax = d3.max(maxes);
return dataMatrix;
};
function cross(a, b) { //this is cross product of two lists, outputs a list of dicts with keys i,j,x,y, where i is index in first list, j in second index, x is the value of a[i], y is the value of b[j], and the order of the output list is 00, 01, ..., 10, ...
var c = [], n = a.length, m = b.length, i, j;
for (i = -1; ++i < n;) for (j = -1; ++j < m;) c.push({x: a[i], i: i, y: b[j], j: j});
return c;
}
</script>
Modified http://d3js.org/d3.v4.min.js to a secure url
Modified http://labratrevenge.com/d3-tip/javascripts/d3.tip.v0.6.3.js to a secure url
https://d3js.org/d3.v4.min.js
https://labratrevenge.com/d3-tip/javascripts/d3.tip.v0.6.3.js