UNPKG

doevisualization

Version:

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

728 lines (619 loc) 31.9 kB
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) { //&nbsp this.element.find('div.note').html("\uf023 &nbsp<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); } }); });