doevisualization
Version:
Data Visualization Library based on RequireJS and D3.js (v4+)
728 lines (619 loc) • 31.9 kB
JavaScript
define(['jquery',
'underscore',
'jqueryuiwidget',
'handlebars',
'd3'
],
function($, _,ui, Handlebars, d3) {
$.widget("doe.doedivergedbar", {
// Options to be used as defaults
options: {},
_privateData: {},
_template: function() {
var arrHTML = [];
arrHTML.push('<div class="doedivergedbar">');
arrHTML.push('<div class="titleV">');
arrHTML.push('</div>');
arrHTML.push('<div class="subTitleV">');
arrHTML.push('</div>');
arrHTML.push('<div class="legendcontainer">');
arrHTML.push('</div>');
arrHTML.push('<div class="divergedbarcontainer">');
arrHTML.push('</div>');
arrHTML.push('<div class="note">');
arrHTML.push('</div>');
arrHTML.push('<div class="clearfix">');
arrHTML.push('</div>');
arrHTML.push('</div>');
arrHTML.push('<div class="clearfix">');
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.element.addClass('doedivergedbarcontainer');
this._bindEvents();
//this._generateRawTable();
},
_compileTooltip: function() {
this._privateData.tooltipCompiled = Handlebars.compile(this.options.TooltipTemplate);
},
_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(''));
},
_bindEvents: function() {
this.element.find('div.titleV').html(this.options.ChartTitle);
this.element.find('div.subTitleV').html(this.options.ChartSubTitle);
// this.options.Data = _.map(this.options.Data, $.proxy(function(value, key) {
// value[this.options["Groupings"][0]] = value[this.options["Groupings"][0]];
// return value;
// }, this));
var groupeddata = _.groupBy(this.options.Data, $.proxy(function(item) {
return item[this.options["Groupings"][0]];
}, this));
// console.log("grouped data", groupeddata);
var keys = [];
var isNumber = false;
for (var key in groupeddata) {
if (isNaN(key)) {
isNumber = false;
} else {
isNumber = true;
}
keys.push(key);
}
if (isNumber) {
keys.reverse();
}
// var modObject = {};
// for (var i in keys) {
// modObject[keys[i]].toString() = groupeddata[keys[i]];
// }
// var modObject = groupeddata;
// console.log("modified data", modObject);
var groupField = this.options["Groupings"];
var tooltipData = _.chain(this.options.Data)
.groupBy(function(d) { return d[groupField] })
.pairs()
.map(function(currentItem) {
return _.object(_.zip(["groupItem", "values"], currentItem));
})
.value();
temp = _.sortBy(tooltipData, "groupItem").reverse();
//console.log("tooltip data", temp);
// var format = d3.format(".1f"); // round numbers to 1 decimal
var format = d3.format(",d");
var defaultValue = 10.07788414;
var finalheight = 0;
var suppressedFlag = false;
for (var i = 0; i < keys.length; i++) {
//var suppressedFlag = false;
var k = keys[i]
var uniquescope = _.uniq(groupeddata[k], $.proxy(function(x) {
return x[this.options["YFields"][0]];
}, this));
uniquescope = uniquescope.map($.proxy(function(item, index) {
return item[this.options["YFields"][0]];
}, this));
var uniquelabel = _.uniq(groupeddata[k], $.proxy(function(x) {
return x[this.options["XFields"][1]];
}, this));
uniquelabel = uniquelabel.map($.proxy(function(item, index) {
return item[this.options["XFields"][1]];
}, this));
// console.log("Y Field:", uniquescope);
var finalobject = [];
var scopeMeetsReqs = [];
_.each(uniquescope, $.proxy(function(item) {
var index = uniquelabel.length;
var filtereddata = _.filter(groupeddata[k], $.proxy(function(sitem) {
return sitem[this.options["YFields"][0]] === item;
}, this));
if (filtereddata.length > uniquelabel.length) {
filtereddata = filtereddata.slice(index); // just in case we have redundant data
}
var obj = {};
obj["rows"] = item;
obj["origdata"] = [];
_.each(filtereddata, $.proxy(function(tem) {
if (tem[this.options["XFields"][0]] == "*") {
tem[this.options["XFields"][0]] = defaultValue;
suppressedFlag = true;
}
// tem[this.options["XFields"][0]] = parseFloat((tem[this.options["XFields"][0]]));
obj[tem[this.options["XFields"][1]]] = parseFloat(tem[this.options["XFields"][0]]);
if (tem[this.options["XFields"][0]] !== 0) {
tem[this.options["XFields"][0]] = format(tem[this.options["XFields"][0]]);
}
obj["origdata"].push(tem);
}, this));
finalobject.push(obj);
}, this));
// console.log(finalobject);
// console.log(uniquelabel);
this._privateData.finalobject = finalobject;
this._privateData.uniquelabel = uniquelabel;
this._privateData.uniquescope = uniquescope;
this._privateData.defaultValue = defaultValue;
// this._privateData.flag = suppressedFlag;
// this._privateData.legendFlag = legendFlag;
this._setupD3Element(k);
//finalheight = finalheight + this._setupD3Element(k).height;
}
if (this.options["Legend"] == true) {
this._drawLegend();
}
if (suppressedFlag) { // 
this.element.find('div.note').html("\uf023  <em>Some information may be protected for student privacy.</em>");
}
finalheight = this.element.find('.divergedbarcontainer').outerHeight() + this.element.find('.legendcontainer').outerHeight() + this.element.find('.note').outerHeight() +
this.element.find('.subTitleV').outerHeight() + this.element.find('.titleV').outerHeight();
// console.log('Final Height is ' + finalheight);
this._trigger("complete", null, { height: finalheight });
// console.log(this.element.find('div.doedivergedbar').height());
// if (this.element.find('div.doedivergedbar').height() > this.element.height()) {
// this.element.height(this.element.find('div.doedivergedbar').height());
// }
},
_setupD3Element: function(year) {
var margin = {
top: 20,
right: 20,
bottom: 20,
left: 150
},
// 590 * 230
width = 700 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
var maxBarSize = 70;
var y = d3.scaleBand()
//.rangeRound([0, height])
.range([0, Math.min(maxBarSize * this._privateData.finalobject.length, height)]) //set the max bar size
.padding(0.3);
var x = d3.scaleLinear()
.rangeRound([0, width]);
// var xAxis = d3.svg.axis()
// .scale(x)
// .tickFormat(d3.format(",%"))
// .orient("top");
// var xAxis = d3.axisBottom(x).tickFormat(function(d) {
// if (d === 0) return ''; // No label for '0'
// else if (d < 0) d = -d; // No nagative labels
// return d * 100 + '%';
// });
var xAxis = d3.axisBottom(x)
// .ticks(0)
.tickSize(0)
.tickFormat(function(d) {
return "";
});
// var xAxis = d3.axisBottom(x).tickSize(0);
// var yAxis = d3.svg.axis()
// .scale(y)
// .tickSize(0)
// .orient("left");
var yAxis = d3.axisLeft(y).tickSize(0);
var color = d3.scaleOrdinal()
.range(this.options.ColorPalette);
var svg = d3.select(this.element.find('div.divergedbarcontainer').get(0)).append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", Math.min(maxBarSize * this._privateData.finalobject.length, height) + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// var tooltipcontainer = d3.select(this.element.find('div.doedivergedbar').get(0)).append("div")
// .attr("class", "doedivergedbartooltip")
// .style("opacity", 0);
this._drawDivergedChart(this._privateData.finalobject, this._privateData.uniquelabel, this._privateData.uniquescope, year, svg, x, y, color, xAxis, yAxis,
height, width, this._privateData.defaultValue, maxBarSize);
return { height: (Math.min(maxBarSize * this._privateData.finalobject.length, height) + margin.top + margin.bottom), width: width + margin.left + margin.right };
},
_drawDivergedChart: function(data, uniquelabel, uniquescope, year, svg, x, y, color, xAxis, yAxis, height,
width, defaultValue, maxBarSize) {
svg.append("text")
.attr("x", -40)
.attr("y", 0)
.text(function() {
if (year == "undefined") {
return "";
} else {
return year;
}
})
.attr("class", "instancetitle")
var rateNames = d3.keys(data[0]).filter(function(key) {
return (key !== "rows" && key !== "origdata");
});
var rowsNames = data.map(function(d) {
return d.rows;
});
var neutralIndex = Math.floor(rateNames.length / 2);
color.domain(rateNames);
this._privateData.rateNames = rateNames;
this._privateData.color = color;
data.forEach(function(row) {
row.total = d3.sum(rateNames.map(function(name) {
return +row[name];
}));
rateNames.forEach(function(name) {
row['relative' + name] = (row.total !== 0 ? +row[name] / row.total : 0);
});
var x0 = -1 * d3.sum(rateNames.map(function(name, i) {
return i < neutralIndex ? +row['relative' + name] : 0;
}));
if (rateNames.length & 1) x0 += -1 * row['relative' + rateNames[neutralIndex]] / 2;
var idx = 0;
row.boxes = rateNames.map(function(name) {
return {
name: name,
x0: x0,
x1: x0 += row['relative' + name],
total: row.total,
absolute: row[name],
origdata: row.origdata
};
});
});
var min = d3.min(data, function(d) {
return d.boxes["0"].x0;
});
var max = d3.max(data, function(d) {
return d.boxes[d.boxes.length - 1].x1;
});
x.domain([-1, 1]).nice();
y.domain(rowsNames);
svg.append("g")
.attr("class", "xaxis")
.attr("transform", "translate(0," + Math.min(maxBarSize * this._privateData.finalobject.length, height) + ")")
.call(customXAxis);
//customize X axis
function customXAxis(g) {
g.call(xAxis);
g.select(".domain").remove();
}
// move row
var move = 60;
svg.append("g")
.attr("class", "yaxis")
.attr("transform", "translate(" + move + ",0)")
.call(customYAxis);
//customize Y axis, remove axis while keeping domain names
function customYAxis(g) {
g.call(yAxis);
g.select(".domain").remove();
}
// if (flag) {
// this._privateData.tooltipCompiled = Handlebars.compile('The percentage of ');
// } else {
// //this._privateData.tooltipCompiled = Handlebars.compile(this.options.TooltipTemplate);
// this._privateData.tooltipCompiled = Handlebars.compile('Not Suppressed');
// }
var that = this;
var toolTipMasterTemplate = [];
var toolTipSuppressedTemplate = [];
toolTipMasterTemplate.push('<div class="tooltipprogress">');
toolTipMasterTemplate.push('<i class="fa fa-spinner fa-spin fa-3x fa-fw"></i>');
toolTipMasterTemplate.push('</div>');
toolTipMasterTemplate.push('<div class="tooltipcontent doehide">');
toolTipMasterTemplate.push(this.options["TooltipTemplate"]);
toolTipMasterTemplate.push('</div>');
toolTipSuppressedTemplate.push('<div class="tooltipprogress">');
toolTipSuppressedTemplate.push('<i class="fa fa-spinner fa-spin fa-3x fa-fw"></i>');
toolTipSuppressedTemplate.push('</div>');
toolTipSuppressedTemplate.push('<div class="tooltipcontent doehide">');
toolTipSuppressedTemplate.push("This data has been suppressed for student privacy.");
toolTipSuppressedTemplate.push('</div>');
var tooltipCompiled = Handlebars.compile(toolTipMasterTemplate.join(''));
var tooltipSuppressedCompiled = Handlebars.compile(toolTipSuppressedTemplate.join(''));
var formatComma = d3.format(","); // format large number with commas
var tooltipFields = this.options["TooltipFields"];
// var tooltipTemplate = this.options["TooltipTemplate"];
// var tooltipCompiled = Handlebars.compile(tooltipTemplate);
var tooltipcontainer = d3.select(this.element.find('div.doedivergedbar').get(0))
.append("div")
.attr("class", "tooltip ");
//.style("opacity", 0);
var rows = svg.selectAll(".row")
.data(data)
.enter().append("g")
.attr("class", "bar")
.attr("transform", function(d) {
return "translate(" + move + "," + y(d.rows) + ")";
});
// .on("mouseover", function(d) {
// svg.selectAll('.y').selectAll('text').filter(function(text) {
// return text === d.rows;
// })
// .transition().duration(5).style('font', '9px');
// })
// .on("mouseout", function(d) {
// svg.selectAll('.y').selectAll('text').filter(function(text) {
// return text === d.rows;
// })
// .transition().duration(5).style('font', '9px');
// });
var bars = rows.selectAll("rect")
.data(function(d) {
return d.boxes;
})
.enter().append("g");
for (var j = 0; j < data.length; j++) {
var dataset = data[j].origdata;
dataset.forEach(function(d) { //format data for tooltip
for (var i = 0; i < tooltipFields.length; i++) {
if (isNaN(parseFloat(d[tooltipFields[i]]))) {
d[tooltipFields[i]] = d[tooltipFields[i]];
} else {
d[tooltipFields[i]] = parseFloat(d[tooltipFields[i]]);
}
if (typeof(d[tooltipFields[i]]) === "number") {
d[tooltipFields[i]] = formatComma(d[tooltipFields[i]]);
}
}
});
}
bars.append("rect")
.attr("class", "rectBar")
.attr("height", y.bandwidth())
.attr("x", function(d) {
return x(d.x0);
})
.attr("width", function(d) {
return x(d.x1) - x(d.x0);
})
.style("fill", function(d) {
return color(d.name);
})
.on('mouseover', function(d, i) {
if (d.origdata[i]["StudentCount"] === "*") {
tooltipcontainer
.style("left", (d3.event.layerX + 10) + "px")
.style("top", (d3.event.layerY + 10) + "px")
.style("display", "block")
.html(tooltipSuppressedCompiled);
} else {
var compileTmpl = tooltipCompiled(d.origdata[i]);
tooltipcontainer
.style("left", (d3.event.layerX + 10) + "px")
.style("top", (d3.event.layerY + 10) + "px")
.style("display", "block")
.html(compileTmpl);
}
that._trigger("tooltiprefreshed", null, {});
// svg.selectAll(".rectBar")
// .transition()
// .attr('opacity', 0.5);
d3.select(this)
.transition()
// .duration(300)
.attr('opacity', 0.6);
// .style("stroke", function(d) {
// return color(d.name);
// });
})
.on('mouseout', function(d) {
tooltipcontainer.style("display", "none");
svg.selectAll(".rectBar")
.transition()
// .duration(300)
.attr('opacity', 1)
.style("stroke", "none");
});
var tags = rows.selectAll(".bar")
.data(function(d) {
return d.boxes;
})
.enter().append("g");
// var format = d3.format(".1f"); // round numbers to 1 decimal
var format = d3.format(",d");
var tagWidth = 32,
speicalTag = 20,
oneDigitTag = 20,
threshhold = 5;
//if (flag) {
tags.append("rect")
.attr("height", y.bandwidth() / 2)
// .attr("height", 15)
.attr("x", function(d) {
if (d.absolute === defaultValue) {
return x(d.x0) + (x(d.x1) - x(d.x0)) / 2 - speicalTag / 2;;
} else {
return x(d.x0) + (x(d.x1) - x(d.x0)) / 2 - tagWidth / 2;
}
// return x(d.x0) + 0.5;
// return x(d.x0) + (x(d.x1) - x(d.x0)) / 2 - tagWidth / 2;
})
.attr("width", function(d) {
if (d.absolute === defaultValue) {
return speicalTag;
} else {
return tagWidth;
}
})
.attr("y", y.bandwidth() / 4)
.attr("rx", 5)
.attr("ry", 5)
// .attr("dx", function(d) {
// return (x(d.x1) - x(d.x0)) / 2;
// })
// .attr("width", function(d) {
// return (x(d.x1) - x(d.x0)) / 2;
// })
.style("fill", function(d) { //&& d.absolute !== defaultValue
if (d.absolute !== 0 && format(d.absolute) > threshhold) {
return 'white';
} else {
return "transparent";
}
})
.style("stroke", function(d) {
if (d.absolute !== 0 && format(d.absolute) > threshhold) {
return color(d.name);
}
});
// .on("mouseover", $.proxy(function(d, i) {
// d3.select(this.element.find(".doedivergedbartooltip").get(0)).transition()
// // .duration(200)
// .style("opacity", .9);
// if (d.origdata[i]["StudentCount"] === "*") {
// d3.select(this.element.find(".doedivergedbartooltip").get(0)).html("This data has been suppressed for student privacy.")
// .style("left", (d3.event.pageX) + "px")
// .style("top", (d3.event.pageY - 28) + "px");;
// } else {
// d3.select(this.element.find(".doedivergedbartooltip").get(0)).html(this._privateData.tooltipCompiled(d.origdata[i]))
// .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(".doedivergedbartooltip").get(0)).transition()
// .duration(200)
// .style("opacity", 0);
// }, this));
//}
tags.append("text")
.attr("x", function(d) {
//return x(d.x0);
return x(d.x0) + (x(d.x1) - x(d.x0)) / 2;
})
.attr("y", y.bandwidth() / 2 + y.bandwidth() / 8)
// .attr("dy", "0.4em")
// .attr("dx", "0.4em")
.style("text-anchor", "middle")
.style('font-family', 'FontAwesome')
// .style('font-size', function(d) {
// return Math.min(y.bandwidth() / 4, x(d.x0));
// })
.attr("class", "bartext notranslate")
// .attr("width", "12px")
// .attr("height", "12px")
.text(function(d) {
// return format(d.absolute !== 0 && (d.x1 - d.x0) > 0 ? d.absolute : "");
if (d.absolute !== 0 && (d.x1 - d.x0) > 0) {
//if (false) {
if (d.absolute === defaultValue) {
return "\uf023"; // "Protected" defined by Kristi Lloyd
} else {
if (format(d.absolute) > threshhold) {
return format(d.absolute) + "%";
} else {
return "";
}
}
} else {
return "";
}
});
svg.append("g")
.attr("class", "y axis")
.append("line")
.attr("transform", "translate(" + move + ",0)")
.attr("x1", x(0) + 1)
.attr("x2", x(0) + 1)
.attr("y2", Math.min(maxBarSize * this._privateData.finalobject.length, height));
},
_drawLegend: function() {
var data = this._privateData.rateNames;
var color = this._privateData.color;
var width = 700;
var height = 60;
var svg = d3.select(this.element.find('div.legendcontainer').get(0))
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + 60 + "," + 80 + ")");
var gap = width / (data.length + 1);
var legend = svg.selectAll(".legend")
.data(data)
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) {
return "translate(" + gap * i + ",-75)";
});
legend.append("rect")
.attr("x", 0)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", 22)
.attr("y", 12)
.attr("dy", ".12em")
.attr("class", "legend")
.style("text-anchor", "begin")
.text(function(d) {
return d;
});
// svg.append("text")
// .attr("class", "legend label")
// .attr("x", 340)
// .attr("y", -30)
// .text("% Ready for Next Level")
var start = gap * (data.length / 2) - 60,
end = gap * (data.length) - 60 - gap + 60;
//end = (data.length - 1) * width / (data.length) + 10;
var xLegend = d3.scaleLinear()
.rangeRound([start, end]);
svg.append("g")
.attr("class", "legend label")
.append("text")
.attr("x", start + 60 + (end - start) / 2)
.attr("y", -30)
.style("text-anchor", "middle")
.text("% Ready for Next Level")
var xAxis = d3.axisTop(xLegend)
.ticks(0);
// .tickSize(0)
// .tickFormat(function(d) {
// return "";
// });
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(60" + ",-45)")
.call(xAxis);
},
destroy: function() {
},
_setOption: function(key, value) {
this._super(key, value);
},
_setOptions: function(options) {
this._super(options);
}
});
});