Skip to content

Instantly share code, notes, and snippets.

@chris-creditdesign
Last active August 29, 2015 14:19
Show Gist options
  • Save chris-creditdesign/7279a12809269293f069 to your computer and use it in GitHub Desktop.
Save chris-creditdesign/7279a12809269293f069 to your computer and use it in GitHub Desktop.
Impact of terror groups 1970 to 2013
BuildWidget.prototype.buildAxes = function () {
var self = this;
this.yAxis = d3.svg.axis()
.scale(this.yScale)
.tickSize(3,0)
.orient("left");
this.yBrushAxis = d3.svg.axis()
.scale(this.yBrushScale)
.tickSize(3,0)
.orient("left");
this.xAxis = d3.svg.axis()
.scale(this.xScale)
.tickSize(3,0)
.ticks(6)
.orient("bottom");
this.xBrushAxis = d3.svg.axis()
.scale(this.xBrushScale)
.tickSize(3,0)
.orient("bottom");
/* Prepare the y axis */
this.svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + (this.params.margin.left + this.params.brushThickness + this.params.margin.mid) + "," + this.params.margin.top + ")")
.call(this.yAxis);
this.svg.append("g")
.attr("class", "y-brush axis")
.attr("transform", "translate(" + this.params.margin.left + "," + this.params.margin.top + ")")
.call(this.yBrushAxis)
.append("g")
.attr("class", "axisLabel")
.append("text")
.attr("transform", "translate(" + -(this.params.margin.left * 0.8) + "," + (this.params.height / 2) + "), rotate(-90)")
.style("text-anchor", "middle")
.text(this.params.key.yAxisLabel);
/* Prepare the x axis */
this.svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(" + (this.params.margin.left + this.params.brushThickness + this.params.margin.mid) + "," + (this.params.margin.top + this.params.height) + ")")
.call(this.xAxis)
this.svg.append("g")
.attr("class", "x-brush axis")
.attr("transform", "translate(" + (this.params.margin.left + this.params.brushThickness + this.params.margin.mid) + "," + (this.params.margin.top + this.params.height + this.params.margin.mid + this.params.brushThickness) + ")")
.call(this.xBrushAxis)
.append("g")
.attr("class","axisLabel")
.append("text")
.attr("transform", "translate(" + (this.params.width / 2) + "," + (this.params.margin.bottom * 0.7) + ")")
.style("text-anchor","middle")
.text(self.params.key.xAxisLabel);
};
BuildWidget.prototype.buildBrush = function () {
var self = this;
var xDisplay = function () {
var extent = self.xBrush.extent();
self.xScale.domain(extent);
self.updateView();
};
var xBrushend = function () {
if (self.xBrush.extent()[0] === self.xBrush.extent()[1]) {
d3.select(this).call(self.xBrush.extent(self.xExtent));
self.xScale.domain(self.xExtent);
self.updateView();
}
};
var yDisplay = function () {
var extent = self.yBrush.extent();
self.yScale.domain(extent);
self.updateView();
};
var yBrushend = function () {
if (self.yBrush.extent()[0] === self.yBrush.extent()[1]) {
d3.select(this).call(self.yBrush.extent(self.yExtent));
self.yScale.domain(self.yExtent);
self.updateView();
}
};
var buildHandles = function (target, horizontal) {
var handleGroup = target.selectAll(".resize").append("g")
.attr("transform", function(d, i) {
if (horizontal) {
return i ? "translate(" + -(self.params.handleWidth) + ",0)" : "translate(0,0)";
} else {
return i ? "translate(0,0)" : "translate(0," + -(self.params.handleWidth) + ")";
}
});
handleGroup.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("fill", self.params.uiColour.grey)
.attr("width", horizontal ? self.params.handleWidth : self.params.brushThickness)
.attr("height", horizontal ? self.params.brushThickness : self.params.handleWidth);
handleGroup.append("line")
.attr("x1", horizontal ? (self.params.handleWidth * 0.65) : (self.params.brushThickness * 0.2))
.attr("y1", horizontal ? (self.params.brushThickness * 0.2) : (self.params.handleWidth * 0.65))
.attr("x2", horizontal ? (self.params.handleWidth * 0.65) : (self.params.brushThickness * 0.8))
.attr("y2", horizontal ? (self.params.brushThickness * 0.8): (self.params.handleWidth * 0.65))
.attr("stroke", self.params.uiColour.veryLightGrey)
.attr("stroke-width", 1);
handleGroup.append("line")
.attr("x1", horizontal ? (self.params.handleWidth * 0.35) : (self.params.brushThickness * 0.2))
.attr("y1", horizontal ? (self.params.brushThickness * 0.2) : (self.params.handleWidth * 0.35))
.attr("x2", horizontal ? (self.params.handleWidth * 0.35) : (self.params.brushThickness * 0.8))
.attr("y2", horizontal ? (self.params.brushThickness * 0.8) : (self.params.handleWidth * 0.35))
.attr("stroke", self.params.uiColour.veryLightGrey)
.attr("stroke-width", 1);
};
this.xBrush = d3.svg.brush()
.x(this.xBrushScale)
.extent(this.xExtent)
.on("brush", xDisplay)
.on("brushend", xBrushend);
this.yBrush = d3.svg.brush()
.y(this.yBrushScale)
.extent(this.yExtent)
.on("brush", yDisplay)
.on("brushend", yBrushend);
this.xBrushGroup.append("g")
.attr("class", "brush")
.call(this.xBrush)
.selectAll("rect")
.attr("fill",this.params.uiColour.veryLightGrey)
.attr("height", this.params.brushThickness );
buildHandles(this.xBrushGroup, true);
this.yBrushGroup.append("g")
.attr("class", "brush")
.call(this.yBrush)
.selectAll("rect")
.attr("fill", this.params.uiColour.veryLightGrey)
.attr("width", this.params.brushThickness);
buildHandles(this.yBrushGroup, false);
};
function buildData (data) {
data.forEach(function(d) {
d.id = d.gname;
d.x = +d.count;
d.y = +d["sum(nkill)"];
});
return data;
}
BuildWidget.prototype.buildGraphic = function () {
this.svg = d3.select(this.target).append("svg")
.attr("width", this.params.width + this.params.margin.left + this.params.brushThickness + this.params.margin.mid + this.params.margin.right)
.attr("height", this.params.height + this.params.margin.top + this.params.brushThickness + this.params.margin.mid + this.params.margin.bottom);
var clip = this.svg.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", this.params.width)
.attr("height", this.params.height);
var miniClip = this.svg.append("defs").append("svg:clipPath")
.attr("id", "mini-clip")
.append("svg:rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", this.params.miniMapThickness)
.attr("height", this.params.miniMapThickness);
this.scatterGroup = this.svg.append("g")
.attr("class","scatterGroup")
.attr("clip-path", "url(#clip)")
.attr("transform","translate(" + (this.params.margin.left + this.params.brushThickness + this.params.margin.mid) + "," + this.params.margin.top + ")");
this.scatterGroup.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", this.params.width)
.attr("height", this.params.height)
.attr("fill", this.params.uiColour.offWhite);
this.keyGroup = this.svg.append("g")
.attr("class","keyGroup")
.attr("transform","translate(" + (this.params.margin.left + this.params.brushThickness + this.params.margin.mid) + "," + this.params.margin.top + ")");
this.xBrushGroup = this.svg.append("g")
.attr("class","xBrushGroup")
.attr("transform","translate(" + (this.params.margin.left + this.params.brushThickness + this.params.margin.mid) + "," + (this.params.margin.top + this.params.height + this.params.margin.mid)+ ")");
this.yBrushGroup = this.svg.append("g")
.attr("class","yBrushGroup")
.attr("transform","translate(" + this.params.margin.left + "," + this.params.margin.top + ")");
this.miniMapGroup = this.svg.append("g")
.attr("class","miniMapGroup")
.attr("clip-path", "url(#mini-clip)")
.attr("transform","translate(" + this.params.miniMapMargin + "," + (this.params.margin.top + this.params.height + this.params.margin.mid) + ")");
this.mapperGroup = this.svg.append("g")
.attr("class","miniMapGroup")
.attr("transform","translate(" + this.params.miniMapMargin + "," + (this.params.margin.top + this.params.height + this.params.margin.mid) + ")");
};
BuildWidget.prototype.buildMiniMap = function () {
var self = this;
this.miniMapGroup.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", this.params.miniMapThickness)
.attr("height", this.params.miniMapThickness)
.attr("fill", this.params.uiColour.veryLightGrey)
.attr("stroke","none");
this.mapper = this.mapperGroup.append("rect")
.attr("x", function () {
return self.xMiniMapScale(self.xExtent[0])
})
.attr("y", function () {
var top = self.yExtent[1] - self.yExtent[1];
return self.yMiniMapInvertScale(top);
})
.attr("width", function () {
var width = self.xExtent[1] - self.xExtent[0];
return self.xMiniMapScale(width);
})
.attr("height", function () {
var height = self.yExtent[1] - self.yExtent[0];
return self.yMiniMapInvertScale(height);
})
.attr("fill","none")
.attr("stroke", this.params.uiColour.darkGrey)
.attr("storke-width","1px");
};
BuildWidget.prototype.updateMiniMap = function(x, y) {
var self = this;
this.mapper
.attr("x", function() {
return self.xMiniMapScale(x[0]);
})
.attr("y", function() {
var top = self.yExtent[1] - y[1];
return self.yMiniMapInvertScale(top);
})
.attr("width", function () {
var width = x[1] - x[0];
return self.xMiniMapScale(width);
})
.attr("height", function () {
var height = y[1] - y[0];
return self.yMiniMapInvertScale(height);
});
};
function buildParams () {
var params = {};
params.uiColour = {
offWhite: "#efefef",
veryLightGrey: "#ddd",
lightGrey: "#999",
grey: "#666",
darkGrey: "#333"
};
params.key = {
xAxisLabel: "Number of attacks",
yAxisLabel: "Number of fatalities"
};
/* Margin, Width and height */
params.margin = {top: 10, right: 10, mid: 50, bottom: 60, left: 60};
params.brushThickness = 30;
params.handleWidth = 10;
params.miniMapMargin = 10;
params.radius = 3;
params.radiusSmall = 1;
params.fill = "darkred";
params.opacity = 0.6;
params.miniMapThickness = params.margin.left + params.brushThickness - params.miniMapMargin;
params.width = 500 - params.margin.left - params.brushThickness - params.margin.mid - params.margin.right;
params.height = 500 - params.margin.top - params.margin.mid - params.brushThickness - params.margin.bottom;
params.format = d3.format("0,000");
return params;
}
BuildWidget.prototype.buildScales = function () {
this.xExtent = d3.extent(this.data, function (d) {
return d.x;
});
this.xExtent[1] *= 1.1;
this.yExtent = d3.extent(this.data, function (d) {
return d.y;
});
this.yExtent[1] *= 1.1;
this.yExtentInvert = d3.extent(this.data, function (d) {
return d.y;
}).reverse();
this.yExtentInvert[0] *= 1.1;
this.yScale = d3.scale.linear()
.range([this.params.height, 0])
.domain(this.yExtent);
this.yBrushScale = d3.scale.linear()
.range([this.params.height, 0])
.domain(this.yExtent);
this.xScale = d3.scale.linear()
.range([0, this.params.width])
.domain(this.xExtent);
this.xBrushScale = d3.scale.linear()
.range([0, this.params.width])
.domain(this.xExtent);
this.yMiniMapScale = d3.scale.linear()
.range([this.params.miniMapThickness, 0])
.domain(this.yExtent);
this.yMiniMapInvertScale = d3.scale.linear()
.range([this.params.miniMapThickness, 0])
.domain(this.yExtentInvert);
// Reverse this one
this.xMiniMapScale = d3.scale.linear()
.range([0, this.params.miniMapThickness])
.domain(this.xExtent);
};
BuildWidget.prototype.enterScatterPlot = function (target, main) {
var self = this;
target.selectAll("circle")
.data(this.data, function (d) {
return d.id;
})
.enter()
.append("circle")
.attr("cx", function (d) {
return main ? self.xScale(d.x) : self.xMiniMapScale(d.x);
})
.attr("cy", function (d) {
return main ? self.yScale(d.y) : self.yMiniMapScale(d.y);
})
.attr("r", 0)
.attr("opacity", this.params.opacity)
.attr("fill", this.params.fill)
.attr("stroke", self.params.uiColour.darkGrey)
.attr("stroke-width", 0)
.attr("r", function () {
return main ? self.params.radius : self.params.radiusSmall;
});
};
BuildWidget.prototype.updateScatterPlot = function () {
var self = this;
this.scatterGroup.selectAll("circle")
.data(this.data, function (d) {
return d.id;
})
.attr("cx", function (d) {
return self.xScale(d.x);
})
.attr("cy", function (d) {
return self.yScale(d.y);
});
};
BuildWidget.prototype.buildTooltip = function () {
var self = this;
this.scatterGroup.selectAll("circle")
.on("mouseover", function (d) {
var myCircle = d3.select(this);
myCircle.attr("stroke-width", 3);
var tooltipWidth = parseInt(d3.select("#widget-tooltip").style("padding-left"),10) + parseInt(d3.select("#widget-tooltip").style("width"),10) + parseInt(d3.select("#widget-tooltip").style("padding-right"),10);
var top = (parseFloat(myCircle.attr("cy")) + self.params.margin.top);
var left = (parseFloat(myCircle.attr("cx")) + self.params.margin.left + self.params.brushThickness + self.params.margin.mid + 30);
d3.select("#widget-tooltip")
.style("top", top + "px")
.style("left", left + "px")
.classed("hidden", false);
d3.select("#id").text(d.id);
d3.select("#y").text(self.params.format(d.y));
d3.select("#x").text(self.params.format(d.x));
}).on("mouseout", function () {
d3.select(this).attr("stroke-width", 0);
self.hideTooltip();
});
};
BuildWidget.prototype.hideTooltip = function () {
d3.select("#widget-tooltip").classed("hidden", true);
};
function BuildWidget (target, params, data) {
this.target = target;
this.params = params;
this.data = data;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Global Terrorism Database</title>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="./buildWidget.js" charset="utf-8"></script>
<script src="./buildTooltip.js" charset="utf-8"></script>
<script src="./updateView.js" charset="utf-8"></script>
<script src="./buildScatterPlot.js" charset="utf-8"></script>
<script src="./buildMiniMap.js" charset="utf-8"></script>
<script src="./buildBrush.js" charset="utf-8"></script>
<script src="./buildAxes.js" charset="utf-8"></script>
<script src="./buildScales.js" charset="utf-8"></script>
<script src="./buildGraphic.js" charset="utf-8"></script>
<script src="./buildData.js" charset="utf-8"></script>
<script src="./buildParams.js" charset="utf-8"></script>
<script src="./index.js" charset="utf-8"></script>
<style>
body {
color: #333;
font-family: helvetica;
}
h1 {
margin-bottom: 0;
}
.outer-wrapper {
width: 940px;
height: 500px;
padding: 5px 10px;
position: relative;
}
.axis {
font-size: 12px;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.widget-tooltip {
position: absolute;
height: auto;
padding: 6px 10px;
color: #ccc;
background-color: #333;
pointer-events: none;
font-size: 0.8em;
line-height: 1.4em;
opacity: 1;
transition: opacity 0.3s ease;
}
.widget-tooltip:before {
content: "";
position:absolute;
top: 5px;
left: -20px;
border-right: 0;
border-top: 10px solid #333;
border-bottom: 0;
border-left: 20px solid transparent;
}
p {
padding: 0;
margin: 0;
}
span {
font-weight: bold;
color: #fff;
}
.hidden {
opacity: 0;
pointer-events: none;
}
</style>
</head>
<body>
<div class="outer-wrapper">
<div id="scatterplot">
<div class="widget-tooltip hidden" id="widget-tooltip">
<p id="id">Hello Mum!</p>
<p>Fatalities: <span id="y"></span></p>
<p>Attacks: <span id="x"></span></p>
</div>
</div>
</div>
function init () {
d3.csv("/chris-creditdesign/raw/e07ff4ab3d879396f2a1/gtd_gname_count.csv", function(d) {
drawScatterPlot(d);
});
}
function drawScatterPlot (d) {
var params = buildParams();
var data = buildData(d);
var scatterplot = new BuildWidget("#scatterplot", params, data);
scatterplot.buildGraphic();
scatterplot.buildScales();
scatterplot.buildAxes();
scatterplot.buildBrush();
scatterplot.buildMiniMap();
scatterplot.enterScatterPlot(scatterplot.scatterGroup, true);
scatterplot.buildTooltip();
scatterplot.enterScatterPlot(scatterplot.miniMapGroup, false);
}
window.onload = init;
BuildWidget.prototype.updateView = function () {
this.updateScatterPlot();
this.svg.selectAll(".x.axis").call(this.xAxis);
this.svg.selectAll(".y.axis").call(this.yAxis);
this.updateMiniMap(this.xBrush.extent(), this.yBrush.extent());
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment