Last active
December 17, 2015 15:29
-
-
Save And-How/5631985 to your computer and use it in GitHub Desktop.
Working on Update/Refresh
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<head> | |
<title>Fresh</title> | |
<style> | |
body { | |
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; | |
margin: auto; | |
width: 960px; | |
} | |
h2 { | |
text-align: right; | |
margin: 0 30px 0 0; | |
} | |
text { | |
font: 20px sans-serif; | |
} | |
.axis path, .axis line { | |
fill: none; | |
stroke: black; | |
shape-rendering: crispEdges; | |
} | |
form { | |
right: 10px; | |
top: 10px; | |
} | |
</style> | |
</head> | |
<body> | |
<h1>Bar Chart</h1> | |
<div> | |
<h2>Legend</h2> <br> | |
</div> | |
<form> | |
<!--form for grouped/stacked buttons--> | |
</form> | |
<input name="updateButton" | |
type="button" | |
value="Update" | |
onclick="updateData()"/> | |
<script src="http://d3js.org/d3.v3.js"></script> | |
<script> | |
d3.select(self.frameElement).style("height", "1000px"); | |
d3.select(self.frameElement).style("width", "1000px"); | |
/*** | |
* A 2 Dimensional Bar Chart | |
* The 'samples' of the chart are Scenarios | |
* The 'series' of the chart are an accumulated result for each Scenario (e.g. dwelling_units, employment, and population) | |
* The content of the Example is the Result of the active Scenario. It is used to find the Result of the same key of all Scenarios in the scenarios property | |
* | |
* Naming conventions: | |
* One query column in the series, which has one datapoint per sample, is a 'group' | |
* The default presentation is to stack the bars on top of each other. | |
* The alternative presentation is to stack the series of each sample, which is only enabled when the each series result is related (e.g. a series different types of dwelling_units) | |
* | |
* Example | |
* Query columns: du_house__sum, du_apt__sum, du_condo__sum, i.e. the aggregate dwelling units of each housing type | |
* Samples: 'smart (a)','trend (b)','dumb (c)' | |
* Series: 'sum du house (x)','sum du apt (y)','sum du condo (z)', | |
* | |
* Grouped Presentation (Default): | |
* x y | |
* xx y | |
* xxx yyy zzz | |
* ___________ | |
* abc abc abc | |
* | |
* Note that each series' items are separated and the common series datum of each sample grouped | |
* | |
* Stacked Presentation | |
* | |
* z | |
* z y z | |
* y x y | |
* y x x | |
* x x x | |
* ________ | |
* a b c | |
* | |
* Note that the series' items are together and the series of each sample are stacked. | |
*/ | |
/*** | |
* Create the data structure for the charts. This needs to be recreated when query results change | |
* TODO Eventually we should do transitions when the query data changes. We do this by uniquely identifying | |
* each datum so that d3 knows that it is changing rather than being replaced | |
* @returns {*|Array|Array} The outer array is each series (e.g. du__sum, pop__sum) | |
* Within each series is the datum for each sample (Scenario) of the series, | |
* which is the d3 plot datum containing: | |
* Of these all are required except perhaps the value key, which we might remove. | |
* [ | |
* [(scenarioA/seriesA, scenarioB/seriesA, scenarioC/seriesA, ...)], | |
* [(scenarioA/seriesB, scenarioB/seriesB, scenarioC/seriesB)], | |
* ... | |
* ] | |
* where each (../..) is a datum: | |
* {id: series index, value:scenario/series value, key:series name, x:xpos,y:value} | |
* @private | |
*/ | |
// Create the d3 series of data for the Result. Give each datum a unique id | |
// by index in the series. I'm not sure if this can't just be i | |
// Add an x and y to each datum. x is based on a spacing function and y is simply the datum value | |
var sampledata =[ | |
[ | |
{ "employmenttype":"retail", "dwellingunittype": "single-family","scenarioname":"Base", "x": 0, "y": -5}, | |
{ "employmenttype": "office","dwellingunittype": "attached single-family","scenarioname":"Scenario 1", "x": 1, "y": 29}, | |
{ "employmenttype": "industrial","dwellingunittype": "detached single-family","scenarioname":"Scenario 2","x": 2, "y": 30}, | |
{ "employmenttype": "public","dwellingunittype": "multifamily","scenarioname":"Scenario 3","x": 3, "y": 35}, | |
{ "employmenttype": "civic","dwellingunittype": "yurt","scenarioname":"Scenario 4","x": 4, "y": 10} | |
], | |
[ | |
{ "employmenttype": "retail","dwellingunittype": "single-family","scenarioname":"Base", "x": 0, "y": 25}, | |
{ "employmenttype": "office","dwellingunittype": "attached single-family","scenarioname":"Scenario 1", "x": 1, "y": 30}, | |
{ "employmenttype": "industrial","dwellingunittype": "detached single-family","scenarioname":"Scenario 2","x": 2, "y": 40}, | |
{ "employmenttype": "public","dwellingunittype": "multifamily","scenarioname":"Scenario 3","x": 3, "y": 12}, | |
{ "employmenttype": "civic","dwellingunittype": "yurt","scenarioname":"Scenario 4","x": 4, "y": 12} | |
], | |
[ | |
{ "employmenttype": "retail", "dwellingunittype": "single-family","scenarioname":"Base", "x": 0, "y": 30}, | |
{ "employmenttype": "office","dwellingunittype": "attached single-family","scenarioname":"Scenario 1", "x": 1, "y": 12}, | |
{ "employmenttype": "industrial","dwellingunittype": "detached single-family","scenarioname":"Scenario 2","x": 2, "y": 28}, | |
{ "employmenttype": "public","dwellingunittype": "multifamily","scenarioname":"Scenario 3","x": 3, "y": 16}, | |
{ "employmenttype": "civic","dwellingunittype": "yurt","scenarioname":"Scenario 4","x": 4, "y": 16} | |
], | |
[ | |
{ "employmenttype": "retail", "dwellingunittype": "single-family","scenarioname":"Base", "x": 0, "y": 25}, | |
{ "employmenttype": "office","dwellingunittype": "attached single-family","scenarioname":"Scenario 1", "x": 1, "y": 39}, | |
{ "employmenttype": "industrial","dwellingunittype": "detached single-family","scenarioname":"Scenario 2","x": 2, "y": -30}, | |
{ "employmenttype": "public","dwellingunittype": "multifamily","scenarioname":"Scenario 3","x": 3, "y": 25}, | |
{ "employmenttype": "civic","dwellingunittype": "yurt","scenarioname":"Scenario 4","x": 4, "y": 16} | |
], | |
[ | |
{ "employmenttype": "retail", "dwellingunittype": "single-family","scenarioname":"Base", "x": 0, "y": 6}, | |
{ "employmenttype": "office","dwellingunittype": "attached single-family","scenarioname":"Scenario 1", "x": 1, "y": 29}, | |
{ "employmenttype": "industrial","dwellingunittype": "detached single-family","scenarioname":"Scenario 2","x": 2, "y": 30}, | |
{ "employmenttype": "public","dwellingunittype": "multifamily","scenarioname":"Scenario 3","x": 3, "y": 30}, | |
{ "employmenttype": "civic","dwellingunittype": "yurt","scenarioname":"Scenario 4","x": 4, "y": 40} | |
] | |
]; | |
/*** | |
* The scenarios which form the basis of each sample | |
*/ | |
/*** | |
* The names of the samples used to label the groups or stacked samples | |
*/ | |
var samplenames = sampledata[0].map(function(d){return d.scenarioname}), | |
/*** | |
* Returns a dict that maps result query column names to label names. | |
* (e.g. sum__du: Dwelling Unit Sum) | |
* @returns {*} | |
* @private | |
*/ | |
employmentypelabels = sampledata[0].map(function(d){return d.employmenttype});//returns array of employmenttypes | |
// The data with a stack wrapper for stacking | |
var stack = d3.layout.stack(), | |
layers = stack(sampledata), | |
/*** | |
* Calculates the max y for the grouped version of the chart by iterating over every datum's y | |
* Calculates the max y for the stacked version of the chart by iterating over each sample | |
* and aggregating the series' ys for each to find the greatest stacked height. | |
*/ | |
yStackMax = layers.extent; | |
//Hack for negative numbers | |
barStackedNegative(sampledata); | |
//Modified from http://bl.ocks.org/ZJONSSON/2979974 | |
function barStackedNegative(d) { | |
var l = d[0].length | |
while (l--) { | |
var posBase = 0, negBase = 0; | |
d.forEach(function(d) { | |
d=d[l] | |
d.size = Math.abs(d.y) | |
if (d.y<0) { | |
d.y0 = negBase | |
negBase-=d.size | |
} else | |
{ | |
d.y0 = posBase = posBase + d.size | |
} | |
}) | |
} | |
d.extent= d3.extent(d3.merge(d3.merge(d.map(function(e) { return e.map(function(f) { return [f.y0,f.y0-f.size]})})))) | |
return d | |
} | |
//Modified from Gianluca Bisceglie's solution https://groups.google.com/forum/?fromgroups=#!searchin/d3-js/negative$20barstack$20bargroup/d3-js/gzSkw5skLXo/tQdusn-vLWkJ | |
function barGroupedNegative(d) { | |
var l = d[0].length | |
while (l--) { | |
d.forEach(function(d) { | |
d=d[l] | |
d.size = Math.abs(d.y); | |
d.y0 = Math.max(0, d.y); | |
}) | |
} | |
d.extent = d3.extent(d3.merge(d3.merge(d.map(function(e) { return e.map(function(f) { return [f.y0,f.y0-f.size]})})))) | |
return d; | |
} | |
function returnDomain () { | |
var domain = {}; | |
domain.min = Math.min(d3.min(sampledata.extent),0); | |
domain.max = Math.max(d3.max(sampledata.extent),0); | |
return domain; | |
} | |
/*** | |
* The view containing the chart. The chart is created by using d3 to draw an SVG element | |
* in the view's div | |
*/ | |
var margin = {top: 40, right: 10, bottom: 20, left: 40}, | |
/*** | |
* The calculated graph width, which subtracts the margins | |
* The SVG will still use the full width | |
*/ | |
width = 960 - margin.left - margin.right, | |
/*** | |
* The calculated chart height, which subtracts the margins | |
* The SVG will still use the full height | |
*/ | |
height = 500 - margin.top - margin.bottom; | |
/** | |
* Sets the scale based on the sampleCount an width | |
*/ | |
var x = d3.scale.ordinal() | |
.domain(d3.range(samplenames.length)) | |
// Sets bands for labels and ratio of groups to space between | |
.rangeRoundBands([30, width],.08),//sets bands for labels and ratio of groups to space between | |
/*** | |
* Create the X axis using the sample names | |
*/ | |
//Sets x-scale for labeled axis | |
xlabels = d3.scale.ordinal() | |
.domain(samplenames) | |
.rangeRoundBands([0, width], .08), | |
//Sets x-scale for labeled axis grouped | |
xGroup = d3.scale.ordinal() | |
.domain(d3.range(sampledata.length)) | |
.rangeBands([0, x.rangeBand()]); | |
/** | |
* Sets the scale based on yMax | |
*/ | |
var y = d3.scale.linear() | |
// Map the min y from the max y | |
.domain([returnDomain().min, returnDomain().max]) | |
// to from the bottom of the graph up to the top | |
// (svg puts 0,0 at top-left) | |
.range([height, 0]); | |
//sets y-scale for grouped graph | |
var yGroupedScale = d3.scale.linear() | |
//.domain([-yGroupMax, yGroupMax]) | |
.domain(layers.extent) | |
.range([height, 0]); | |
/*** | |
* Creates the color scale based on the Medium content colorRange, | |
* which is simply an array of two or more colors | |
*/ | |
var color = d3.scale.linear() | |
// The domain is 0 to number of samples | |
.domain([0, layers.length - 1]) | |
// Map the domain to the inclusive range of values between the colors | |
.range(["green", "yellow"]); | |
var xAxis = d3.svg.axis() | |
.scale(xlabels) | |
.tickSize(0) | |
.tickPadding(6) | |
.orient("bottom"); | |
/*** | |
* Create the Y axis based on the yScale | |
* @returns {*} | |
*/ | |
//Sets y-axis for stacked graph | |
var yStackedAxis = d3.svg.axis() | |
.scale(y) | |
.tickSize(5) | |
.tickPadding(0) | |
.tickFormat(d3.format("s")) | |
.orient("left"); | |
//sets y-axis for grouped graph | |
var yGroupedAxis = d3.svg.axis() | |
.scale(yGroupedScale) | |
.tickSize(5) | |
.tickPadding(0) | |
.orient("left"); | |
// Draw the svg and main g elemnent to hold the graph | |
var svg = d3.select("body").append("svg") | |
.attr("width", width + margin.left + margin.right) | |
.attr("height", height + margin.top + margin.bottom) | |
//var graph = svg | |
// Put the top-level g element in. Bars and axes will go in this | |
.append("g") | |
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); | |
// Create a g element for each outer data group | |
// This is the spacing of each group, the domain/range is 0 to the number of samples | |
// The spacing is the chart width divided by the number of samples, plus .2 for buffer. | |
//var sampleSpacer = d3.scale.ordinal().domain(d3.range(scenarionames.length)).rangeRoundBands([30, width],.08); | |
// This is the spacing within each sample group, the domain/range is 0 to the number of series | |
// The spacing is the width of each sample divided by number of series. No buffer between them. | |
//var seriesSpacer = d3.scale.ordinal().domain(d3.range(sampledata.length)).rangeBands([0, sampleSpacer.rangeBand()]); | |
var layer = svg.selectAll(".series") | |
// Bind the 2D matrix. Each outer array is a series. Each series has a datum per Sample | |
.data(layers) | |
.enter() | |
.append("g") | |
// Mark the g with class layer so it's part of our selection set | |
.attr("class", "layer") | |
.style("fill", function(d, i) { | |
return color(i); | |
}); | |
//.attr("transform", function(d, i) { return "translate(" + seriesSpacer(i) + ",0)"; }); | |
/* | |
// Create a div on the svgView to serve as a tooltip | |
var tooltip = d3.select(this.svgView.$()[0]).append("div") | |
.attr("class", "tooltip") | |
.style("opacity", 0); | |
// The svg div so we can get the right mouse coordinates for the tooltip | |
var svgDiv = this.svgView.$()[0]; | |
*/ | |
// Draws rectangles of each group | |
var rect = layer.selectAll("rect") | |
.data(function(d) { return d; }) | |
.enter().append("rect") | |
//.attr("transform", function(d, i) { return "translate(" + sampleSpacer(i) + ",0)"; }) | |
.attr("x", function(d) { return x(d.x); }) | |
.attr("width", x.rangeBand()) | |
.attr("height", 0) // start with 0 height | |
.attr("y", function(d) { | |
// Start all bars at the axis | |
return height; | |
}); | |
/* | |
opening transition, draws stacked bars up from ground | |
this.openingTransition(); | |
*/ | |
/*** | |
* The initial growing of the bars from zero when the chart is created | |
*/ | |
/* | |
rect.transition() | |
.delay(function(d, i) { return i * 100; }) | |
// Set the target bar y (start point from top) to its stacked position of group position | |
.attr("y", function(d) { return y(d.y0); }) | |
// Set the target height | |
.attr("height", function(d) { return y(0)-y(d.size)}) | |
.attr("width",x.rangeBand()); | |
//draw x-Axis | |
svg.append("g") | |
.attr("class", "x axis") | |
.attr("transform", "translate(0," + y(0) + ")") | |
.call(xAxis); | |
//draw y-Axis | |
svg.append("g") | |
.attr("class", "axis y") | |
.attr("transform", "translate("+ margin.left +",0)") | |
.call(yStackedAxis); | |
*/ | |
barGroupedNegative(layers) | |
//y.domain(layers.extent);//sets to grouped domain (rather than stacked) | |
//.attr("transform", function (d, i) { return "translate(" + xlabels(i) + ")" ; }); | |
svg.selectAll("layers") | |
.attr("transform", function (d, i) { return "translate(" + xGroup(i) + ")" ; }); | |
rect.transition()//bartransition | |
.duration(500) | |
//.delay(function(d, i) { return i * 10; }) | |
.attr("x", function(d, i, j) { | |
return x(d.x) + x.rangeBand() / layers.length * j; }) | |
//.attr("x",function(d,i) { return x(i) ; }) | |
//.attr("width", x.rangeBand() / layers.length) | |
//.transition() | |
//.attr("y", function(d) { return y(d.y0); }) | |
.attr("y",function(d,i) { return y(d.y0);}) | |
//.attr("height", function(d) { return height - y(d.size); }) | |
.attr("width", function(d,i) { return xGroup.rangeBand(); }) | |
.attr("height",function(d,i) { return (y(0)-y(d.size)); }) | |
.style("stroke","white") | |
.style("stroke-width", "3") | |
//.attr("transform", function (d, i) { return "translate(" + xlabels(i) + ")" ; }); | |
/*** | |
* Set true during updateData | |
*/ | |
//var updating = false; | |
/*** | |
* Called initially and in observation of changes to the Result query data. This | |
* calls the data property again and applies the data structure to the svg. | |
* This allows us to update the data without recreateing the chart | |
* | |
*/ | |
function updateData(d, i){ | |
var usernumber = prompt("Please enter the value", d);//dialog box defaults to d | |
sampledata[i] = + usernumber; //||https://github.com/mbostock/d3/wiki/CSV#wiki-parse | |
//parseInt(op, 10);//radix (base) 10 | |
rect.transition(); | |
} | |
/* | |
updateData: function() { | |
this.set('updating',true); | |
// Get the new data | |
var sampledata = sampledata1; | |
// Update the seriesSet to the new data | |
this.getPath('chartView.seriesSet').data(sampledata); | |
this.set('updating',false); | |
this.getPath('chartView').openingTransition() | |
}, | |
*/ | |
</script> | |
</body> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment