Index: lams_central/web/includes/javascript/chart.js =================================================================== RCS file: /usr/local/cvsroot/lams_central/web/includes/javascript/chart.js,v diff -u -r1.1.2.4 -r1.1.2.5 --- lams_central/web/includes/javascript/chart.js 18 Nov 2016 17:11:12 -0000 1.1.2.4 +++ lams_central/web/includes/javascript/chart.js 3 Apr 2017 12:06:20 -0000 1.1.2.5 @@ -27,7 +27,7 @@ // build domain out of keys domainX = rawData.map(function(d) { return d.name }), // 10 color palette - scaleColor = d3.scale.category10() + scaleColor = d3.scaleOrdinal(d3.schemeCategory10) .domain(domainX), legend = null; @@ -86,32 +86,32 @@ .attr('transform', 'translate(' + CHART_MARGIN.left + ',-' + CHART_MARGIN.top + ')'), // map keys to bars - scaleX = d3.scale.ordinal() + scaleX = d3.scaleBand() .domain(domainX) - .rangeRoundBands([0, canvasWidth - CHART_MARGIN.left], .1), + .rangeRound([0, canvasWidth - CHART_MARGIN.left]) + .padding(0.1), + // map values to bar height - scaleY = d3.scale.linear() + scaleY = d3.scaleLinear() .domain([0, 1]) .range([height, CHART_MARGIN.top + 10]), // scale expects 0..1, we've got 0..100, this function converts it y = function(value) {return scaleY(value / 100)}, // declare Y axis - axisY = d3.svg.axis() - .scale(scaleY) - .orient("left") - .ticks(10, "%"); + axisY = d3.axisLeft(scaleY) + .ticks(10, "%"); - // draw Y axis - canvas.append("g") - .attr("class", "y axis") - .call(axisY); + // draw Y axis + canvas.append("g") + .attr("class", "y axis") + .call(axisY); // draw bars canvas.selectAll('.bar').data(rawData).enter() .append('rect') .attr('class', 'bar') .attr("x", function(d) {return scaleX(d.name)}) - .attr("width", scaleX.rangeBand()) + .attr("width", scaleX.bandwidth()) .attr("y", function(d) {return y(d.value)}) .attr("height", function(d){return height - y(d.value)}) .attr('fill', function(d) {return scaleColor(d.name)}) @@ -157,10 +157,10 @@ // set centre of the pie .attr("transform", "translate(" + radius + "," + radius + ")"), // pie chunk drawing function - arc = d3.svg.arc() + arc = d3.arc() .outerRadius(radius - 10) .innerRadius(0), - pie = d3.layout.pie() + pie = d3.pie() // for each data element extract the value to feed chart .value(function(d) { return d.value }); @@ -199,4 +199,271 @@ // move the legend to the right of the chart legend.attr('transform', 'translate(' + (radius * 2 + CHART_MARGIN.right) + ',' + (CHART_MARGIN.top + 20) + ')'); } -} \ No newline at end of file +} + +function drawHistogram(chartID, url, xAxisLabel, yAxisLabel){ + + // clear previous chart + var xaxisTicksHeight = 35, + xaxisHeight = 50, + yaxisLabelWidth = 20, + yaxisWidth = 50, + formatCount = d3.format(",.0f"), + chartDiv = $('#' + chartID).empty().show(), + svgWidth = chartDiv.width(), + svgHeightOffset = 10; + svgHeight = chartDiv.height() - svgHeightOffset, + margin = {top: 10, right: 40, bottom: 120, left: 0}, + margin2 = {top: 405, right: 40, bottom: xaxisHeight, left: 0}, + width = svgWidth - margin.left - margin.right, + height = svgHeight - margin.top - margin.bottom, + height2 = svgHeight - margin2.top - margin2.bottom; + + var svg = d3.select(chartDiv[0]) + .append("svg") + .attr('width', svgWidth) + .attr('height', svgHeight) + .append("g") + .attr("transform", "translate(0," + svgHeightOffset + ")"); + + var focus = svg.append("g") + .attr("class", "focus") + .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + + var context = svg.append("g") + .attr("class", "context") + .attr("transform", "translate(" + margin2.left + "," + margin2.top + ")"); + + var brush = d3.brushX() + .extent([[0, 0], [width, height2]]) + .on("brush end", _brushed); + + var zoom = d3.zoom() + .scaleExtent([1, Infinity]) + .translateExtent([[0, 0], [width, height]]) + .extent([[0, 0], [width, height]]) + .on("zoom", _zoomed); + + var x; + var x2; + var y = d3.scaleLinear() + .range([height, 0]); + + var y2 = d3.scaleLinear() + .range([height2, 0]); + + var xAxis; + var xAxis2; + var yAxis; + var contextbar; + var data; + var buckets; + + // get data with the given URL + d3.json(url, function(error, response){ + + if (error) { + // forward error to browser + throw error; + } + + if (!response || $.isEmptyObject(response)) { + // if there is no data to display + return; + } + + // clear previous chart + data = response.data; + var max = Number(d3.max(data))+1; + var min = Number(d3.min(data)); + if ( min < 0 ) + min = 0; + var buckets = (max-min) <= 11 ? (max-min) : 10; + + // x, y are top detailed graph, x2, y2 are the bottom overview graph. x, x2 declared already so that + // so that they can be accessed by function + x = d3.scaleLinear() + .domain([min, max]) + .rangeRound([0, width]); + + x2 = d3.scaleLinear() + .domain([min, max]) + .rangeRound([0, width]); + + xAxis = d3.axisBottom(x), + xAxis2 = d3.axisBottom(x2), + yAxis = d3.axisLeft(y); + + var histogram = d3.histogram() + .domain(x.domain()) + .thresholds(buckets); + + var bins = histogram(data); + + y.domain([0, d3.max(bins, function(d) { return d.length; })]); + y2.domain([0, d3.max(bins, function(d) { return d.length; })]); + + // top detailed histogram + focus.append("g") + .attr("class", "axis axis--x") + .attr("transform", "translate(" + yaxisWidth +"," + height + ")") + .call(xAxis); + + focus.append("g") + .attr("class", "axis axis--y") + .attr("transform", "translate(" + yaxisWidth + ",0)") + .call(yAxis); + + focus.append("text") + .attr("transform", "rotate(-90)") + .attr("y", 0) + .attr("x", 0 - (height / 2)) + .attr("dy", "1em") + .style("text-anchor", "middle") + .text(yAxisLabel); + + var focusbar = generateFocusBars(x, focus, bins); + + // bottom overall histogram + context.append("g") + .attr("class", "axis axis--x") + .attr("transform", "translate(" + yaxisWidth +"," + height2 + ")") + .call(xAxis); + + context.append("text") + .attr("x", (width - yaxisWidth) / 2 ) + .attr("y", height2 + xaxisTicksHeight) + .style("text-anchor", "middle") + .text(xAxisLabel); + + contextbar = context.selectAll(".bar") + .data(bins) + .enter().append("g") + .attr("class", "bar") + .attr("transform", function(d) { return "translate(" + (x2(d.x0) + yaxisWidth) + "," + y2(d.length) + ")"; }); + + contextbar.append("rect") + .attr("x", 1) + .attr("width", function(d) { return x2(d.x1) - x2(d.x0) - 1; }) + .attr("height", function(d) { return height2 - y2(d.length); }); + + contextbar.append("text") + .attr("dy", ".75em") + .attr("y", 6) + .attr("x", function(d) { return (x2(d.x1) - x2(d.x0)) / 2; }) + .attr("text-anchor", "middle") + .text(function(d) { return formatCount(d.length); }); + + context.append("g") + .attr("class", "brush") + .attr("transform", "translate("+yaxisWidth+",0)") + .call(brush) + .call(brush.move, x.range()); + + svg.append("rect") + .attr("class", "zoom") + .attr("width", width) + .attr("height", height) + .attr("transform", "translate(" + (margin.left + yaxisWidth) + "," + margin.top + ")") + .call(zoom); + + }); + + + function _brushed() { + if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom") return; // ignore brush-by-zoom + var s = d3.event.selection || x2.range(); + x.domain(s.map(x2.invert, x2)); + focus.select(".axis--x").call(xAxis); + svg.select(".zoom").call(zoom.transform, d3.zoomIdentity + .scale(width / (s[1] - s[0])) + .translate(-s[0], 0)); + } + + function _zoomed() { + if (d3.event.sourceEvent && d3.event.sourceEvent.type === "brush") return; // ignore zoom-by-brush + var t = d3.event.transform; + x.domain(t.rescaleX(x2).domain()); + var histogram = d3.histogram() + .domain(x.domain()) + .thresholds(10); + var bins = histogram(data); + y.domain([0, d3.max(bins, function(d) { return d.length; })]); + yAxis = d3.axisLeft(y); + var focusbar = generateFocusBars(x, focus, bins); + focus.select(".axis--x").call(xAxis); + focus.select(".axis--y").call(yAxis); + context.select(".brush").call(brush.move, x.range().map(t.invertX, t)); + } + + function generateFocusBars(x, focus, bins) { + focus.selectAll(".bar").remove(); + + var focusbar = focus.selectAll(".bar") + .data(bins) + .enter().append("g") + .attr("class", "bar") + .attr("transform", function(d) { return "translate(" + (x(d.x0) + yaxisWidth) + "," + y(d.length) + ")"; }); + + focusbar.append("rect") + .attr("x", 1) + .attr("width", function(d) { return x(d.x1) - x(d.x0) - 1; }) + .attr("height", function(d) { return height - y(d.length); }); + + focusbar.append("text") + .attr("dy", ".75em") + .attr("y", 6) + .attr("x", function(d) { return (x(d.x1) - x(d.x0)) / 2; }) + .attr("text-anchor", "middle") + .text(function(d) { return formatCount(d.length); }); + + return focusbar; + } +} + +function displayAsTable(tableID, url, columns, columnNames) { + + d3.json(url, function(error, response){ + if (error) { + // forward error to browser + throw error; + } + + if (!response || $.isEmptyObject(response)) { + // if there is no data to display + return; + } + + var tableDiv = $("#"+tableID); + if ( tableDiv.length > 0 ) { + var table = d3.select(tableDiv[0]) + .append('table') + .attr('class', 'table table-striped table-bordered table-condensed table-fluid'), + thead = table.append("thead"), + tbody = table.append("tbody"); + + thead.append("tr") + .selectAll("th") + .data(columnNames) + .enter() + .append("th") + .text(function(column) { return column; }); + + var rows = tbody.selectAll("tr") + .data(response.data) + .enter() + .append("tr"); + + var cells = rows.selectAll("td") + .data(function(row) { + return columns.map(function(column) { + return {column: column, value: row[column]}; + }); + }) + .enter() + .append("td") + .html(function(d) { return d.value; }); + } + }); +} + \ No newline at end of file