Skip to content

Instantly share code, notes, and snippets.

@And-How
Last active December 17, 2015 15:29
Show Gist options
  • Save And-How/5631985 to your computer and use it in GitHub Desktop.
Save And-How/5631985 to your computer and use it in GitHub Desktop.
Working on Update/Refresh
<!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