UNPKG

doevisualization

Version:

Data Visualization Library based on RequireJS and D3.js (v4+)

688 lines (583 loc) 29.3 kB
define(['jquery', 'underscore', 'jqueryuiwidget', 'handlebars', 'd3' ], function($, _, ui, Handlebars, d3) { $.widget("doe.doestackedbar", { // Options to be used as defaults options: {}, _globalData: {}, _template: function() { var arrHTML = []; arrHTML.push('<div class="doestackedbar">'); 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.element.addClass('doestackedbarcontainer'); this._bindEvents(); }, _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() { var minWidth = 700, minHeight = 620, maxStackedBarSize = 80, widthSVG = this.element.width() < minWidth ? minWidth : this.element.width(), heightSVG = this.element.height() < minHeight ? minHeight : this.element.height(), width = widthSVG * 0.8, height = heightSVG * 0.7 - 40; this._globalData.heightSVG = heightSVG; this._globalData.widthSVG = widthSVG; this._globalData.width = width; this._globalData.height = height; var color = d3.scaleOrdinal() .range(this.options.ColorPalette); // .range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]); var formatValue = d3.format(".2s"); var xField = this.options["XFields"]; var yField = this.options["YFields"]; var xTitle = this.options["XTitle"]; var yTitle = this.options["YTitle"]; var title = this.options["ChartTitle"] var subTitle = this.options["ChartSubTitle"]; 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 tooltipTemplate = this.options["TooltipTemplate"]; 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 showDataLabel = this.options["ShowDataLabel"]; var showLegend = this.options["Legend"]; var SuppressionFlag = false; var that = this; var maxValue = d3.max(this.options.Data, function(d) { return parseFloat(d[yField[0]]); }) var groupeddata = _.groupBy(this.options.Data, $.proxy(function(item) { return item[this.options["Groupings"][0]]; }, this)); // console.log("grouped data", groupeddata); var keys = []; for (var key in groupeddata) { keys.push(key); } keys.reverse(); // var format = d3.format(",d"); var finalobject = []; for (var i = 0; i < keys.length; i++) { var k = keys[i] var uniquescope = _.uniq(_.map(groupeddata[k], $.proxy(function(d) { return d[this.options["XFields"][0]]; }, this))); var uniquelabel = _.uniq(_.map(groupeddata[k], $.proxy(function(d) { return d[this.options["YFields"][1]]; }, this))); // console.log("X Field:", uniquescope); // console.log("Y labels:", uniquelabel); _.each(uniquescope, $.proxy(function(item) { var index = uniquelabel.length; var filtereddata = _.filter(groupeddata[k], $.proxy(function(sitem) { return sitem[this.options["XFields"][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) { var defaultvalue = maxValue > 0 ? maxValue : 100; if (tem[this.options["YFields"][0]] == "*") { tem[this.options["YFields"][0]] = -0.15 * defaultvalue; SuppressionFlag = true; } obj[tem[this.options["YFields"][1]]] = tem[this.options["YFields"][0]]; // if (tem[this.options["YFields"][0]] !== 0) { // tem[this.options["YFields"][0]] = format(tem[this.options["YFields"][0]]); // } obj["origdata"].push(tem); }, this)); finalobject.push(obj); }, this)); // console.log("final obj:", finalobject); } var data = finalobject; 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); 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] : 0); }); var y0 = 0; row.boxes = rateNames.map(function(name) { return { name: name, y0: y0, y1: y0 += row['relative' + name], total: row.total, absolute: row[name], origdata: row.origdata }; }); }); var top = -heightSVG * 0.05 + 10; var svg = d3.select(this.element.find('.doestackedbar').get(0)) .append("svg") .attr("width", widthSVG) .attr("height", heightSVG) .append("g") .attr("transform", "translate(" + widthSVG * 0.2 + "," + (heightSVG * 0.15 + top) + ")"); this._globalData.svg = svg; var tooltip = d3.select(this.element.find('.doestackedbar').get(0)) .append('div') .attr("class", "tooltip"); if (data.length < 4) { var x = d3.scaleBand() .rangeRound([0, Math.min(maxStackedBarSize * data.length * 2, width)]) .padding(0.5); var left = (width - widthSVG * 0.15 - Math.min(maxStackedBarSize * data.length * 2, width)) / 2; } else { var x = d3.scaleBand() // .rangeRound([0, width]) .rangeRound([0, Math.min(maxStackedBarSize * data.length * 1.5, width)]) .padding(0.3); var left = (width - widthSVG * 0.15 - Math.min(maxStackedBarSize * data.length * 1.5, width)) / 2; } var y = d3.scaleLinear() .rangeRound([height, 0]); // var keys = this.options["Groupings"]; // console.log("#stack#", keys); // data.forEach(function(d) { // d.total = 0; // keys.forEach(function(k) { // d.total += d[k]; // }) // }); // data.sort(function(a, b) { return b.total - a.total; }); // x.domain(data.map(function(d) { // return d.key; // d.SchoolYear // })); x.domain(uniquescope); if (maxValue > 0) { y.domain([0, d3.max(data, function(d) { return d.total; })]).nice(); } else { y.domain([0, 100]).nice(); } // var stack = d3.stack() // .keys(rateNames) // .order(d3.stackOrderNone) // .offset(d3.stackOffsetNone); //var tempX = left + x(d.rows); var cat = svg.selectAll("g") .data(data) .enter().append("g") .attr("transform", function(d) { return "translate(" + x(d.rows) + ",0)"; }); var rect = cat.selectAll("rect") .data(function(d) { return d.boxes; }) .enter() 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]]); } } }); } rect.append("rect") .attr("class", "bar") // .attr("x", function(d) { // return x(d.data.rows); //d.data.SchoolYear // }) .attr("transform", function(d) { return "translate(" + left + ",0)"; }) .attr("y", function(d) { return y(Math.abs(d.y1)); }) .attr("height", function(d) { return y(Math.abs(d.y0)) - y(Math.abs(d.y1)); }) .attr("width", x.bandwidth()) .style("fill", function(d) { return color(d.name); }) .on('mouseover', function(d, i) { var compileTmpl = tooltipCompiled(d.origdata[i]); if (d.absolute < 0) { tooltip .style("left", (d3.event.layerX + 10) + "px") .style("top", (d3.event.layerY + 10) + "px") .style("display", "block") .html(tooltipSuppressedCompiled); // .html("This data has been suppressed for student privacy."); } else { tooltip .style("left", (d3.event.layerX + 10) + "px") .style("top", (d3.event.layerY + 10) + "px") .style("display", "block") .html(compileTmpl); // .html(d3.format(".3s")(d[1] - d[0]) + '%'); } that._trigger("tooltiprefreshed", null, {}); svg.selectAll(".bar") .transition() .attr('opacity', 0.5); d3.select(this) .transition() // .duration(300) .attr('opacity', 1) .style("stroke", function(d) { return color(d.name); }); var highlight = d.origdata[0][xField[0]]; svg.selectAll(".axis text") .filter(function(d) { return d == highlight }) .classed("highlight", true); // svg.selectAll(".bartext") // .transition() // .attr('opacity', 0); }) .on('mouseout', function(d) { tooltip.style("display", "none"); svg.selectAll(".bar") .transition() // .duration(300) .attr('opacity', 1) .style("stroke", "none"); var highlight = d.origdata[0][xField[0]]; svg.selectAll(".axis text") .filter(function(d) { return d == highlight }) .classed("highlight", false); // svg.selectAll(".bartext") // .transition() // .attr('opacity', 1); }); if (SuppressionFlag) { rect.append("text") .style('font-family', 'FontAwesome') .style('fill', "black") .style('font-size', function(d) { var barSize = 0.9 * Math.min(x.bandwidth(), y(Math.abs(d.y0)) - y(Math.abs(d.y1))); var maxSize = 25; if (barSize > maxSize) { return maxSize; } else { return barSize; } }) .text(function(d) { if (d.absolute < 0) { return '\uf023'; // \uf023 } else { if (showDataLabel == true) { if (d.absolute != 0) { return d.absolute; } else { return ""; } } else { return ""; } } }) .attr("class", "bartext notranslate") .attr("transform", function(d) { return "translate(" + left + ",0)"; }) // .attr("y", function(d) { return y(Math.abs(d.y1)); }) // .attr("x", function(d) { // return x(d.data.rows); // }) //y(Math.abs(d.y0)) - y(Math.abs(d.y1)) .attr("text-anchor", "middle") .attr("dx", x.bandwidth() / 2) .attr("dy", ".35em") .attr("y", function(d) { return y(Math.abs(d.y1)) + (y(Math.abs(d.y0)) - y(Math.abs(d.y1))) / 2; }); // .attr("dy", function(d) { // return (y(Math.abs(d.y0)) - y(Math.abs(d.y1))) / 2; // }); } else { rect.append("text") // .text(function(d) { return d.absolute; }) .text(function(d) { if (showDataLabel == true) { if (d.absolute != 0) { return d.absolute; } else { return ""; } } else { return ""; } }) .attr("class", "bartext notranslate") .attr("transform", function(d) { return "translate(" + left + ",0)"; }) .attr("y", function(d) { return y(d.y1); }) // .attr("x", function(d) { // return x(d.data.rows); // }) .attr("dx", x.bandwidth() / 4) .attr("dy", function(d) { return (y(d.y0) - y(d.y1)) / 2; }); } svg.append("g") .attr("class", "x axis") .attr("transform", "translate(" + left + "," + height + ")") .call(d3.axisBottom(x)) .selectAll("text") .attr("transform", "rotate(-25)") .style("text-anchor", "end"); //var yAxis = d3.axisLeft(y).ticks(null, "s"); var yAxis = d3.axisLeft(y).tickFormat(function(d) { return formatValue(d) }); svg.append("g") .attr("class", "y axis") .attr("transform", "translate(" + left + ", 0)") .call(yAxis) .append("text") .attr("x", 2) .attr("y", y(y.ticks().pop()) + 0.5) .attr("dy", "0.32em") .attr("fill", "#000") .attr("font-weight", "bold") .attr("text-anchor", "start"); // .text("Population"); svg.append("text") .attr("class", "xLabel") .attr("y", height * 1.1 + 10) .attr("x", (width - widthSVG * 0.15) / 2) // .attr("dy", "1em") .style("text-anchor", "middle") .text(xTitle); svg.append("text") .attr("class", "yLabel") .attr("transform", "rotate(-90)") // .attr("y", 0 - 40) // .attr("x", 0 - (height / 2)) .attr("y", -45 + left) .attr("x", -height / 2) // .attr("dy", "1em") .style("text-anchor", "middle") .text(yTitle); svg.append("text") .attr('class', 'title') .attr("x", (width - widthSVG * 0.15) / 2) .attr("y", -height * 0.1) .style("text-anchor", "middle") .text(title); svg.append("text") .attr('class', 'subTitle') .attr("x", (width - widthSVG * 0.15) / 2) .attr("y", -height * 0.03) .style("text-anchor", "middle") .text(subTitle); if (SuppressionFlag) { svg.append("text") .attr('class', 'note') .attr("x", 0) .attr("y", heightSVG * 0.8 + 45) .style('font-family', 'FontAwesome') .style("text-anchor", "start") .text("(\uf023) Some information may be protected for student privacy."); } var legendArr = rateNames.slice(); this._globalData.legendArr = legendArr; if (showLegend == true) { this._drawLegend(legendArr); } // this._trigger("complete", null, {}); this._trigger("complete", null, { "legendArr": legendArr }); // var test = ["150000000000", "160000000000", "170000000000"] // this.setLegend(test); this._generateRawTable(); }, _drawLegend: function(data) { svg = this._globalData.svg; var color = d3.scaleOrdinal() .range(this.options.ColorPalette); heightSVG = this._globalData.heightSVG; widthSVG = this._globalData.widthSVG; width = this._globalData.width; height = this._globalData.height; var dataOrigin = this._globalData.legendArr; d3.select('.stackLegend').remove(); var stackLegend = svg.append('g') .attr('class', 'stackLegend'); var legend = svg.select('.stackLegend') .selectAll("g") // .data(rateNames.slice().reverse()) .data(data) .enter().append("g"); // .attr("transform", function(d, i) { // return "translate(" + width / rateNames.slice().reverse().length * i + "," + heightSVG * 0.8 + ")"; // }); legend.append("rect") .attr("x", 0) .attr("width", 20) .attr("height", 20) .attr("fill", color) .on('mouseover', function(d, i) { d3.select(this) .style("stroke", function(d) { return color(d); }); var tem = i; svg.selectAll(".bar") .filter(function(d, i) { return dataOrigin.indexOf(d.name) !== tem; }) .attr('opacity', 0); svg.selectAll(".bartext") .filter(function(d) { return dataOrigin.indexOf(d.name) !== tem; }) .attr('opacity', 0); }) .on('mouseout', function(d, i) { d3.select(this) .style("stroke", "none"); svg.selectAll(".bar") .attr('opacity', 1); svg.selectAll(".bartext") .attr('opacity', 1); }); legend.append("text") .attr("x", 22) .attr("y", 11) .attr("dy", "0.32em") .attr("class", "legend") .style("text-anchor", "begin") .text(function(d) { return d; }) .on('mouseover', function(d, i) { d3.select(this) .classed("highlight", true); var tem = i; svg.selectAll(".bar") .filter(function(d) { return dataOrigin.indexOf(d.name) !== tem; }) .attr('opacity', 0); svg.selectAll(".bartext") .filter(function(d) { return dataOrigin.indexOf(d.name) !== tem; }) .attr('opacity', 0); }) .on('mouseout', function(d, i) { d3.select(this) .classed("highlight", false); svg.selectAll(".bar") .attr('opacity', 1); svg.selectAll(".bartext") .attr('opacity', 1); }); var sum = 0; var xPos = 0; var yPos = heightSVG * 0.8; legend .attr( "transform", function(d, i) { var ret = "translate(" + (xPos) + "," + yPos + ")"; xPos += this.getBBox().width + 10; sum += this.getBBox().width + 10; // xPos += d.length; if (xPos > width - widthSVG * 0.15) { xPos = 0; yPos += 30; } return ret; } ); //this.element.find('.stackLegend').get(0) var legendWidth = d3.select(this.element.find('.stackLegend').get(0)).node().getBBox().width; stackLegend .attr("transform", function(d, i) { return "translate(" + (width - widthSVG * 0.2 - legendWidth) / 2 + "," + 0 + ")"; }); }, destroy: function() { }, setLegend: function(data) { this._drawLegend(data); }, getLegend: function() { }, _setOption: function(key, value) { this._super(key, value); }, _setOptions: function(options) { this._super(options); } }); });