doevisualizations
Version:
Data Visualization Library based on RequireJS and D3.js (v4+)
372 lines (321 loc) • 15.1 kB
JavaScript
define(['jquery',
'underscore',
'jqueryuiwidget',
'handlebars',
'd3'
],
function($, _, ui, Handlebars, d3) {
$.widget("doe.doeboxplot", {
// Options to be used as defaults
options: {},
_privateData: {},
_template: function() {
var arrHTML = [];
arrHTML.push('<div class="doeboxplot">');
arrHTML.push('<div class="title">');
arrHTML.push('</div>');
arrHTML.push('<div class="subTitle">');
arrHTML.push('</div>');
arrHTML.push('</div>');
return arrHTML.join('');
},
_create: function() {
this._fetchAndRender();
},
_fetchAndRender: function() {
this._compileTemplate();
},
_compileTemplate: function() {
var compiled = Handlebars.compile(this._template());
this.element.html(compiled({}));
this._compileTooltip();
this._buildVisualization();
this._bindEvents();
this._generateRawTable();
},
_generateRawTable: function() {
var arrHTML = [];
var data = this.options["Data"];
if (data.length > 0) {
arrHTML.push('<div class ="doehide">');
arrHTML.push('<table>');
arrHTML.push('<thead>');
arrHTML.push('<tr>');
for (p in data[1]) {
arrHTML.push('<th>');
arrHTML.push(p);
arrHTML.push('</th>');
}
arrHTML.push('</tr>');
for (var i = 0; i < data.length; i++) {
arrHTML.push('<tr>');
for (prop in data[i]) {
arrHTML.push('<td>');
arrHTML.push(data[i][prop]);
arrHTML.push('</td>');
}
arrHTML.push('</tr>');
}
arrHTML.push('</thead>');
arrHTML.push('</table>');
arrHTML.push('</div>');
}
this.element.append(arrHTML.join(''));
},
_compileTooltip: function() {
this._privateData.tooltipCompiled = Handlebars.compile(this.options.TooltipTemplate);
},
_bindEvents: function() {
},
_buildVisualization: function() {
var groupeddata = _.groupBy(this.options.Data, $.proxy(function(item) {
return item[this.options["Groupings"][0]];
}, this));
var globalCounts = [];
for (var k in groupeddata) {
for (var i = 0; i < k.length; i++) {
globalCounts.push(groupeddata[k][i][this.options["YFields"][0]]);
}
}
console.log(groupeddata);
console.log(globalCounts);
this._setupD3Element(groupeddata, globalCounts);
},
_setupD3Element: function(groupCounts, globalCounts) {
this.element.find('div.title').html(this.options.ChartTitle);
this.element.find('div.subTitle').html(this.options.ChartSubTitle);
var width = 590;
var height = 300;
var barWidth = 30;
var margin = {
top: 20,
right: 10,
bottom: 20,
left: 10
};
var width = width - margin.left - margin.right,
height = height - margin.top - margin.bottom;
var totalWidth = width + margin.left + margin.right;
var totalheight = height + margin.top + margin.bottom;
//var groupCounts = {};
//var globalCounts = [];
// var meanGenerator = d3.randomUniform(10);
// for (i = 0; i < 7; i++) {
// var randomMean = meanGenerator();
// var generator = d3.randomNormal(randomMean);
// var key = i.toString();
// groupCounts[key] = [];
// for (j = 0; j < 100; j++) {
// var entry = generator();
// groupCounts[key].push(entry);
// globalCounts.push(entry);
// }
// }
//console.log(groupCounts);
//console.log(globalCounts);
// Sort group counts so quantile methods work
for (var key in groupCounts) {
var groupCount = groupCounts[key];
groupCounts[key] = groupCount.sort($.proxy(this._sortNumber, this));
}
// Setup a color scale for filling each box
// var colorScale = d3.scaleOrdinal(d3.schemeCategory20)
// .domain(Object.keys(groupCounts));
var colorScale = d3.scaleOrdinal(this.options.ColorPalette)
.domain(Object.keys(groupCounts));
// Prepare the data for the box plots
var boxPlotData = [];
for (var [key, groupCount] of Object.entries(groupCounts)) {
var record = {};
var localMin = d3.min(groupCount, $.proxy(function(d) {
return d[this.options.YFields[0]];
}, this));
var localMax = d3.max(groupCount, $.proxy(function(d) {
return d[this.options.YFields[0]];
}, this));
var mappeddata = _.map(groupCount, $.proxy(function(item) {
return item[this.options.YFields[0]];
}, this));
record["key"] = key;
record[this.options.XFields[0]] = key;
record["counts"] = groupCount;
record["quartile"] = this._boxQuartiles(mappeddata);
record["whiskers"] = [localMin, localMax];
record["color"] = colorScale(key);
boxPlotData.push(record);
}
console.log(boxPlotData);
var tooltipcontainer = d3.select(this.element.find('div.doeboxplot').get(0)).append("div")
.attr("class", "doeboxplottooltip")
.style("opacity", 0);
// Compute an ordinal xScale for the keys in boxPlotData
var xScale = d3.scalePoint()
.domain(Object.keys(groupCounts))
.rangeRound([0, width])
.padding([0.5]);
// Compute a global y scale based on the global counts
var min = d3.min(globalCounts);
var max = d3.max(globalCounts);
var yScale = d3.scaleLinear()
.domain([min, max])
.range([0, height]);
// Setup the svg and group we will draw the box plot in
var svg = d3.select(this.element.find('div.doeboxplot').get(0)).append("svg")
.attr("width", totalWidth)
.attr("height", totalheight)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Move the left axis over 25 pixels, and the top axis over 35 pixels
var axisG = svg.append("g").attr("transform", "translate(25,0)");
var axisTopG = svg.append("g").attr("transform", "translate(35,0)");
// Setup the group the box plot elements will render in
var g = svg.append("g")
.attr("transform", "translate(20,5)");
// Draw the box plot vertical lines
var verticalLines = g.selectAll(".verticalLines")
.data(boxPlotData)
.enter()
.append("line")
.attr("x1", function(datum) {
return xScale(datum.key) + barWidth / 2;
})
.attr("y1", function(datum) {
var whisker = datum.whiskers[0];
return yScale(whisker);
})
.attr("x2", function(datum) {
return xScale(datum.key) + barWidth / 2;
})
.attr("y2", function(datum) {
var whisker = datum.whiskers[1];
return yScale(whisker);
})
.attr("stroke", "#000")
.attr("stroke-width", 1)
.attr("fill", "none");
// Draw the boxes of the box plot, filled in white and on top of vertical lines
var rects = g.selectAll("rect")
.data(boxPlotData)
.enter()
.append("rect")
.attr("width", barWidth)
.attr("height", function(datum) {
var quartiles = datum.quartile;
var height = yScale(quartiles[2]) - yScale(quartiles[0]);
return height;
})
.attr("x", function(datum) {
return xScale(datum.key);
})
.attr("y", function(datum) {
return yScale(datum.quartile[0]);
}).on("mouseover", $.proxy(function(d, i) {
d3.select(this.element.find(".doeboxplottooltip").get(0)).transition()
.duration(200)
.style("opacity", .9);
d3.select(this.element.find(".doeboxplottooltip").get(0)).html(this._privateData.tooltipCompiled(d))
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
}, this))
.on("mouseout", $.proxy(function(d, i) {
d3.select(this.element.find(".doeboxplottooltip").get(0)).transition()
.duration(500)
.style("opacity", 0);
}, this))
.attr("fill", function(datum) {
return datum.color;
})
.attr("stroke", "#000")
.attr("stroke-width", 1);
// Now render all the horizontal lines at once - the whiskers and the median
var horizontalLineConfigs = [
// Top whisker
{
x1: function(datum) {
return xScale(datum.key)
},
y1: function(datum) {
return yScale(datum.whiskers[0])
},
x2: function(datum) {
return xScale(datum.key) + barWidth
},
y2: function(datum) {
return yScale(datum.whiskers[0])
}
},
// Median line
{
x1: function(datum) {
return xScale(datum.key)
},
y1: function(datum) {
return yScale(datum.quartile[1])
},
x2: function(datum) {
return xScale(datum.key) + barWidth
},
y2: function(datum) {
return yScale(datum.quartile[1])
}
},
// Bottom whisker
{
x1: function(datum) {
return xScale(datum.key)
},
y1: function(datum) {
return yScale(datum.whiskers[1])
},
x2: function(datum) {
return xScale(datum.key) + barWidth
},
y2: function(datum) {
return yScale(datum.whiskers[1])
}
}
];
for (var i = 0; i < horizontalLineConfigs.length; i++) {
var lineConfig = horizontalLineConfigs[i];
// Draw the whiskers at the min for this series
var horizontalLine = g.selectAll(".whiskers")
.data(boxPlotData)
.enter()
.append("line")
.attr("x1", lineConfig.x1)
.attr("y1", lineConfig.y1)
.attr("x2", lineConfig.x2)
.attr("y2", lineConfig.y2)
.attr("stroke", "#000")
.attr("stroke-width", 1)
.attr("fill", "none");
}
// Setup a scale on the left
var axisLeft = d3.axisLeft(yScale);
axisG.append("g")
.call(axisLeft);
// Setup a series axis on the top
var axisTop = d3.axisTop(xScale);
axisTopG.append("g")
.call(axisTop);
},
_sortNumber: function(a, b) {
return a[this.options.YFields[0]] - b[this.options.YFields[0]];
},
_boxQuartiles: function(d) {
return [
d3.quantile(d, .25),
d3.quantile(d, .5),
d3.quantile(d, .75)
];
},
destroy: function() {
},
_setOption: function(key, value) {
this._super(key, value);
},
_setOptions: function(options) {
this._super(options);
}
});
});