Skip to content

Instantly share code, notes, and snippets.

@darosh
Last active February 3, 2018 12:54
Show Gist options
  • Save darosh/383197ed997156ac9fd5 to your computer and use it in GitHub Desktop.
Save darosh/383197ed997156ac9fd5 to your computer and use it in GitHub Desktop.
Countries & Events
  • Space/Enter keys: Pause/Play
  • Left/Right keys: Previous/Next
  • Pan & Zoom: when paused
function initData(data, colors) {
var max = 0;
data.forEach(function (v) {
v[2] = new Date(v[0]);
v[3] = d3.keys(v[1]);
v[3].sort(function (a, b) {
return v[1][b] - v[1][a];
});
var y0 = 0;
var i = 0;
var minMax = getValuesMinMax(v[1]);
v[6] = {};
v[4] = v[3].map(function (id) {
var color = colors(normalize(v[1][id], minMax));
var r = {
id: id,
i: i++,
v: v[1][id],
y0: y0,
y1: y0 += v[1][id],
color: color
};
v[6][id] = r;
return r;
});
v[5] = minMax;
max = Math.max(max, y0);
});
return max;
}
function getValuesMinMax(values) {
var v = [];
var s = 0;
for (var k in values) {
v.push(values[k]);
s += values[k];
}
var r = {
min: Math.min.apply(this, v),
max: Math.max.apply(this, v)
};
r.diff = r.max - r.min;
r.sum = s;
return r;
}
function normalize(v, minMax) {
return minMax.diff ? (v - minMax.min) / minMax.diff : 0.5;
}
function limitBounds(b, maxWidth, maxHeight) {
var w = b[1][0] - b[0][0];
if (w > maxWidth) {
var c = (b[1][0] + b[0][0]) / 2;
maxWidth /= 2;
b[0][0] = c - maxWidth;
b[1][0] = c + maxWidth;
}
var h = b[1][1] - b[0][1];
if (h > maxHeight) {
var ch = (b[1][1] + b[0][1]) / 2;
maxHeight /= 2;
b[0][1] = ch - maxHeight;
b[1][1] = ch + maxHeight;
}
}
function visualBounds(path, feature) {
if (feature._visualBounds) {
return feature._visualBounds;
}
var bb = feature._bounds || (feature._bounds = path.bounds(feature));
var c = feature._centroid || (feature._centroid = path.centroid(feature));
var d = 0.366;
var b = [[bb[0][0], bb[0][1]], [bb[1][0], bb[1][1]]];
var sx = d3.scale.linear().domain([b[0][0], b[1][0]]).range([0, 1]);
var sy = d3.scale.linear().domain([b[0][1], b[1][1]]).range([0, 1]);
if (Math.abs(0.5 - sx(c[0])) > d || Math.abs(0.5 - sy(c[1])) > d) {
var h = Math.min(c[0] - b[0][0], b[1][0] - c[0], c[1] - b[0][1], b[1][1] - c[1]);
b[0][0] = c[0] - h;
b[1][0] = c[0] + h;
b[0][1] = c[1] - h;
b[1][1] = c[1] + h;
}
if (feature.id === 'AUS') {
b[1][1] = d3.interpolate(b[0][1], b[1][1])(0.75);
} else if (feature.id === 'USA') {
b[1][0] = d3.interpolate(b[0][0], b[1][0])(0.28);
}
feature._visualBounds = b;
return b;
}
function groupBounds(path, features, width, height, maxWidth, maxHeight) {
var r = features.length ? [[[], []], [[], []]] : [[[0], [0]], [[width], [height]]];
features.forEach(function (feature) {
var b = visualBounds(path, feature);
//limitBounds(b, maxWidth, maxHeight);
r[0][0].push(b[0][0]);
r[0][1].push(b[0][1]);
r[1][0].push(b[1][0]);
r[1][1].push(b[1][1]);
});
r[0][0] = Math.min.apply(this, r[0][0]);
r[0][1] = Math.min.apply(this, r[0][1]);
r[1][0] = Math.max.apply(this, r[1][0]);
r[1][1] = Math.max.apply(this, r[1][1]);
return r;
}
function getScale(size, width, height, scaleMargin, maxScale) {
var marginSize = [(size[0] + width * scaleMargin * 2), (size[1] + width * scaleMargin * 2)];
var sizeRatio = marginSize[0] / marginSize[1];
var boxRatio = width / height;
var r;
if (sizeRatio >= boxRatio) {
r = width / marginSize[0];
} else {
r = height / marginSize[1];
}
if (r < 1) {
r = 1;
} else if (r > maxScale) {
r = maxScale;
}
return r;
}
<!DOCTYPE html>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1">
<title>Countries &amp; Events</title>
<link rel="stylesheet" href="style.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/topojson/1.6.19/topojson.min.js"></script>
<script src="geo.js"></script>
<script src="data.js"></script>
<script src="labels.js"></script>
<script src="svg-chart.js"></script>
<script src="svg-player.js"></script>
<script src="svg-scale-legend.js"></script>
<script src="svg-bar-legend.js"></script>
<script src="svg-info-legend.js"></script>
<body></body>
<script>
var width = 960;
var height = 410;
var shift = (480 - height) / 2;
var canvasScale = ((document.body.clientWidth > width / 3) ? document.body.clientWidth : width) / width;
width *= canvasScale;
height *= canvasScale;
var maxScale = 3;
var scaleMargin = 0.02;
var maxCountry = 1;
var svgMargin = 10;
var labelMargin = 1;
var gridModule = 10;
var scaleHeight = 20;
var svgLine = 10;
var chartHeight = gridModule * 5;
var infoRealHeight;
var legendItems;
var lastSelectedFeatures;
var lastColors;
var lastEvent;
var valueInterpolation = d3.interpolate('#f8af00', '#f80000');
d3.select(self.frameElement).style('height', (height + chartHeight + scaleHeight + 2 * svgMargin) + 'px');
var config = {
durationMultiplier: 1.5,
durationMin: 750,
durationMax: 1500,
durationColor: 350,
tracePast: false,
fontSize: 16,
fontShift: 4,
font: 'Calibri, Arial, Helvetica, sans-serif',
land: {
fillStyle: '#fff'
},
border: {
lineWidth: 0.444,
strokeStyle: '#666'
},
past: {
fillStyle: '#ddd'
},
simpleArea: 8,
baseArea: 8 / canvasScale / canvasScale
};
var currentScale = 1;
var realCenter = [width / 2, height / 2];
var currentTranslate = realCenter;
var pastCountries = {};
var pastColors = {};
var events;
var index = -1;
var auto = true;
var pending = false;
var borders, countries;
var svgHeader, svgs, message, chart, header, infoLegend, barLegend, scaleLegendData, barLegendData, chartData, playerData;
var path, ctx, projection, trans;
var mouseWasDown;
var zoomBehavior = d3.behavior.zoom()
.scaleExtent([1, 8])
.on('zoom', zoomed);
initItems();
initCanvas();
initSvg();
loadData();
function clamp(t, a, b) {
return Math.max(a, Math.min(b, t));
}
function zoomed() {
if (!auto) {
mouseWasDown = false;
requestAnimationFrame(drawManual);
}
}
function initItems() {
var infoMaxHeight = height - scaleHeight - 9 * svgMargin;
legendItems = Math.floor((infoMaxHeight - svgLine) / 20);
infoRealHeight = legendItems * 20 + svgLine;
}
function loaded() {
if (countries && events) {
initKeys();
update();
setTimeout(function () {
message.style('display', 'none');
}, 500);
}
}
function loadData() {
d3.json('topo.json', function (t) {
initTopo(t);
loaded();
});
d3.json('/darosh/raw/baf7dd8d481d83b7f37e/events.json', function (e) {
var max = initData(e, valueInterpolation);
events = e;
chartData = makeChart(chart, events, width - svgMargin * 2, chartHeight, max, setIndex);
playerData.updatePlayer(events, chartData.band, setIndex);
barLegendData = makeBarLegend(barLegend, gridModule * 32, max);
loaded();
});
}
function initCanvas() {
var canvas = d3.select('body').append('canvas').attr('width', width).attr('height', height);
ctx = canvas.node().getContext('2d');
ctx.textAlign = 'center';
ctx.lineJoin = 'round';
message = d3.select('body').append('div').html('Loading&hellip;');
var simplify = d3.geo.transform({
point: function (x, y, z) {
if (z >= projection.area && (!projection.clip || (projection.clip[0] <= x && x <= projection.clip[2] &&
projection.clip[1] <= y && y <= projection.clip[3]))) {
this.stream.point(x * canvasScale, (y + 20) * canvasScale);
}
}
});
projection = {
stream: function (s) {
return simplify.stream(s);
},
clip: false,
area: config.simpleArea
};
path = d3.geo.path()
.context(ctx)
.projection(projection);
canvas.on('mousedown', mouseDown);
canvas.call(zoomBehavior);
canvas.on('mouseup', mouseUp);
}
function mouseDown() {
mouseWasDown = true;
}
function mouseUp() {
if (mouseWasDown) {
clicked();
}
}
function clicked() {
auto = !auto;
if (pending) {
trans();
}
if (auto && !pending) {
index++;
index = index % events.length;
update();
}
}
function initSvg() {
svgHeader = d3.select('body').append('svg')
.attr('width', width)
.attr('height', scaleHeight + 2 * svgMargin);
var scaleLegend = svgHeader.append('g')
.attr('class', 'scale-legend')
.attr('transform', 'translate(' + (width - gridModule * 32 - svgMargin) + ',' + svgMargin + ')');
scaleLegendData = makeScaleLegend(svgHeader, scaleLegend, gridModule * 32, valueInterpolation);
var svgBarScale = d3.select('body').append('svg')
.style('left', svgMargin + 'px')
.style('top', (height - 2 * svgMargin - gridModule * 32 - svgLine * .5) + 'px')
.attr('width', scaleHeight + 3 * svgMargin)
.attr('height', gridModule * 32 + svgMargin + svgMargin);
barLegend = svgBarScale.append('g')
.attr('class', 'scale-legend')
.attr('transform', 'translate(' + 0 + ',' + svgMargin + ')');
var svgInfo = d3.select('body').append('svg')
.style('left', (width - 12 * gridModule - svgMargin) + 'px')
.style('top', (height - infoRealHeight - svgLine * 0.5) + 'px')
.attr('width', (12 * gridModule))
.attr('height', infoRealHeight);
infoLegend = svgInfo.append('g');
var svgChart = d3.select('body').append('svg')
.style('top', (height + svgMargin) + 'px')
.attr('width', width)
.attr('height', chartHeight + scaleHeight + svgMargin);
chart = svgChart.append('g')
.attr('class', 'chart')
.attr('transform', 'translate(' + svgMargin + ',' + 0 + ')');
var player = svgChart.append('g')
.attr('class', 'player')
.attr('transform', 'translate(' + svgMargin + ',' + (chartHeight) + ')');
playerData = makePlayer(player, width - 2 * svgMargin);
header = svgHeader.append('text')
.attr('dy', svgLine + svgMargin)
.attr('dx', svgMargin);
svgs = d3.selectAll('svg');
svgHeader.call(zoomBehavior);
svgBarScale.call(zoomBehavior);
svgInfo.call(zoomBehavior);
svgHeader.on('mousedown', mouseDown);
svgHeader.on('mouseup', mouseUp);
svgBarScale.on('mousedown', mouseDown);
svgBarScale.on('mouseup', mouseUp);
svgInfo.on('mousedown', mouseDown);
svgInfo.on('mouseup', mouseUp);
}
function stop() {
auto = false;
}
function initKeys() {
if (self.frameElement) {
self.frameElement.focus();
}
d3.select('body').on('keydown', function () {
if (d3.event.keyCode === 32 || d3.event.keyCode === 13) {
clicked();
} else {
if (auto && (d3.event.keyCode === 37 || d3.event.keyCode === 39 )) {
stop();
}
if (d3.event.keyCode === 37) {
setIndex(index - 1);
} else if (d3.event.keyCode === 39) {
setIndex(index + 1);
}
}
});
}
function setIndex(i) {
stop();
if (pending) {
trans(true, function () {
done();
});
} else {
done();
}
function done() {
var n = (i + events.length) % events.length;
if (index !== n) {
index = n;
update();
}
}
}
function reset() {
pastCountries = {};
}
function update() {
var event = events[index];
if (index === 0) {
reset();
} else if (index === (events.length - 1)) {
stop();
}
d3.select('body').attr('class', null);
if (event) {
svgs.style('display', 'block');
scaleLegendData.update(event[5], config.durationMin);
barLegendData.update(event[4], config.durationMin);
updateInfoLegend(infoLegend, event, infoRealHeight, gridModule * 12, countries, legendItems);
chartData.updateCursor(index);
header.text((event[2].getMonth() + 1) + '/' + event[2].getFullYear() + ' — ' +
event[5].sum + ' events in ' + event[3].length + ' countries');
trans = transition(event);
} else if (index >= -1 && index <= events.length) {
svgs.style('display', 'none');
trans = transition();
}
}
function done() {
pending = false;
if (auto) {
index++;
if (index < events.length) {
update();
} else {
index--;
stop();
}
} else {
d3.select('body').attr('class', 'paused');
}
}
function initTopo(topo) {
topojson.presimplify(topo);
countries = {};
var tempArcs = null;
var tempObj = {};
topo.objects.countries.geometries.forEach(function (country) {
if (country.id === 'RUS') {
tempObj = country;
var merged = topojson.mergeArcs(topo, [country]);
tempArcs = country.arcs;
country.arcs = merged.arcs;
}
});
borders = topojson.mesh(topo, topo.objects.countries, function (a) {
return a.id !== 'ATA';
});
tempObj.arcs = tempArcs;
topojson.feature(topo, topo.objects.countries).features.forEach(function (v) {
countries[v.id] = v;
});
}
function transition(event) {
event = event || [{}, {}, {}, {}, []];
pending = true;
var colors = {};
var selectedFeatures = event[4].map(function (v) {
colors[v.id] = d3.interpolate(pastColors[v.id] || config.land.fillStyle, event[6][v.id].color);
return countries[v.id];
});
pastColors = {};
projection.clip = false;
projection.area = config.simpleArea;
var bound = groupBounds(path, selectedFeatures, width, height, maxCountry * width, maxCountry * height);
var size = [bound[1][0] - bound[0][0], bound[1][1] - bound[0][1]];
var targetScale = getScale(size, width, height, scaleMargin, maxScale);
var targetCenter = [(bound[0][0] + bound[1][0]) / 2, (bound[0][1] + bound[1][1]) / 2];
targetCenter[1] = (targetCenter[1] < realCenter[1] / targetScale) ? realCenter[1] / targetScale : targetCenter[1];
targetCenter[0] = (targetCenter[0] < realCenter[0] / targetScale) ? realCenter[0] / targetScale : targetCenter[0];
var zoomInterpolation = d3.interpolateZoom([currentTranslate[0], currentTranslate[1], width * currentScale],
[targetCenter[0], targetCenter[1], width * targetScale]);
var duration = clamp(zoomInterpolation.duration * config.durationMultiplier, config.durationMin, config.durationMax);
var stop = false;
var stopped = false;
var forced = false;
var cb;
var dScale = d3.scale.linear().domain([0, duration]).range([0, 1])(config.durationColor);
var cScale = d3.scale.linear().domain([0, dScale]).range([0, 1]).clamp(true);
var ease = d3.ease('cubic-out');
function tScale(x) {
return ease(cScale(x));
}
d3.transition()
.duration(duration)
.tween('tween', function getTween() {
return drawTween;
})
.each('end', done);
return function (force, done) {
cb = cb || done;
if (force) {
forced = true;
} else {
stop = true;
}
};
function drawTween(t) {
if (cb && (t === 1 || stopped)) {
cb();
}
if (stopped) {
return;
} else if (stop) {
stopped = true;
t = 1;
} else if (forced) {
stopped = true;
}
var tt;
if (forced) {
tt = 1;
} else {
tt = tScale(t);
}
var zoom = zoomInterpolation(t);
currentTranslate = [zoom[0], zoom[1]];
currentScale = zoom[2] / width;
var box = [currentTranslate[0] - realCenter[0] / currentScale, currentTranslate[1] - realCenter[1] / currentScale];
var translate = [-box[0] * currentScale, -box[1] * currentScale];
zoomBehavior.scale(currentScale);
zoomBehavior.translate(translate);
lastSelectedFeatures = selectedFeatures;
lastColors = colors;
lastEvent = event;
drawMap(selectedFeatures, event, translate, currentScale, colors, tt);
// Labels
if (t === 1 && !auto) {
var labels = getLabels(selectedFeatures, currentScale);
arrangeLabels(labels, 1.25 * config.fontSize / currentScale, selectedFeatures.length * 2);
drawLabels(labels, currentScale);
}
}
}
function invertTranslate(t, currentScale) {
var box = [-t[0] / currentScale, -t[1] / currentScale];
return [box[0] + realCenter[0] / currentScale, box[1] + realCenter[1] / currentScale]
}
function drawManual() {
var scale = zoomBehavior.scale();
var translate = zoomBehavior.translate();
var tt = 1;
currentScale = scale;
currentTranslate = invertTranslate(translate, currentScale);
drawMap(lastSelectedFeatures, lastEvent, translate, scale, lastColors, tt);
var labels = getLabels(lastSelectedFeatures, scale);
arrangeLabels(labels, 1.25 * config.fontSize / scale, lastSelectedFeatures.length * 2);
drawLabels(labels, scale);
}
function setContextStyle(ctx, opt) {
ctx.fillStyle = opt.fillStyle;
ctx.strokeStyle = opt.strokeStyle;
}
function roundRect(ctx, x, y, width, height, radius) {
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
ctx.lineTo(x + width, y + height - radius);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
ctx.lineTo(x + radius, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
ctx.closePath();
}
function drawLabels(labels, currentScale) {
labels.forEach(function (label) {
addMargin(label, -labelMargin / currentScale);
ctx.fillStyle = 'rgba(0,0,0,0.87)';
roundRect(ctx, label.left, label.top, label.width, label.height, label.radius / 2);
ctx.fill();
ctx.fillStyle = '#fff';
ctx.fillText(label.text, label.cx, label.cy + label.shift);
}
);
}
function getLabels(selectedFeatures, currentScale) {
projection.area = config.simpleArea;
var labels = [];
ctx.font = config.fontSize / currentScale + 'px ' + config.font;
selectedFeatures.forEach(function (f) {
var x = f._centroid || path.centroid(f);
var n = (f.properties.name.length >= 20) ? f.id : f.properties.name;
var m = ctx.measureText(n);
var l = {
id: f.id,
text: n,
cx: x[0],
cy: x[1],
width: m.width + config.fontSize / currentScale,
height: 1.25 * config.fontSize / currentScale,
shift: 0.25 * config.fontSize / currentScale,
radius: config.fontSize / 2 / currentScale
};
l.left = l.cx - l.width / 2;
l.right = l.cx + l.width / 2;
l.top = l.cy - l.height / 2;
l.bottom = l.cy + l.height / 2;
addMargin(l, labelMargin / currentScale);
labels.push(l);
});
return labels;
}
function drawMap(selectedFeatures, event, translate, currentScale, colors, tt) {
// Clear
ctx.globalAlpha = 1;
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, width, height);
// Transform
ctx.translate(translate[0], translate[1]);
ctx.scale(currentScale, currentScale);
projection.area = config.baseArea / currentScale / currentScale;
projection.clip = false;
if (config.tracePast) {
// Past
ctx.beginPath();
setContextStyle(ctx, config.past);
Object.keys(pastCountries).forEach(function fillPast(id) {
if (!event[1][id]) {
path(countries[id]);
pastColors[id] = config.past.fillStyle;
}
});
ctx.fill();
}
// Current
selectedFeatures.forEach(function fillNow(f) {
ctx.beginPath();
pastColors[f.id] = ctx.fillStyle = colors[f.id](tt);
path(f);
ctx.fill();
pastCountries[f.id] = true;
});
projection.clip = [-translate[0] / canvasScale / currentScale,
-translate[1] / canvasScale / currentScale - 20,
-translate[0] / canvasScale / currentScale + width / currentScale,
-translate[1] / canvasScale / currentScale + height / currentScale];
// Borders
ctx.beginPath();
setContextStyle(ctx, config.border);
ctx.lineWidth = config.border.lineWidth / currentScale;
path(borders);
ctx.stroke();
projection.clip = false;
}
</script>
function updateLabel(label, tx, ty) {
label.left += tx;
label.cx += tx;
label.right += tx;
label.top += ty;
label.cy += ty;
label.bottom += ty;
}
function colision(a, b) {
return a.left < b.right &&
a.right > b.left &&
a.top < b.bottom &&
a.bottom > b.top;
}
// based on http://bl.ocks.org/larskotthoff/11406992
function arrangeLabels(labels, module, loops) {
var move = true;
module /= labels.length;
while (move && loops--) {
move = false;
labels.forEach(function (a) {
labels.forEach(function (b) {
if (b !== a) {
if (colision(a, b)) {
move = true;
updateLabel(b, -module * Math.sign(a.cx - b.cx), -module * Math.sign(a.cy - b.cy));
updateLabel(a, module * Math.sign(a.cx - b.cx), module * Math.sign(a.cy - b.cy));
}
}
});
});
}
}
function addMargin(label, margin) {
label.left -= margin;
label.right += margin;
label.top -= margin;
label.bottom += margin;
label.width += 2 * margin;
label.height += 2 * margin;
}
* {
line-height: 20px;
font-family: Calibri, Arial, Helvetica, sans-serif;
color: #999;
margin: 0;
padding: 0;
text-align: center;
text-rendering: optimizelegibility;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
canvas {
display: block;
border-bottom: 1px solid #DEDEDE;
cursor: pointer;
}
svg {
position: absolute;
left: 0;
top: 0;
display: none;
cursor: pointer;
}
.paused canvas,
.paused svg {
cursor: move;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
stroke-opacity: 0.25;
stroke-width: 1;
shape-rendering: crispEdges;
}
.chart line {
stroke-opacity: 1;
stroke: #ddd;
}
.axis text {
font: 16px Calibri, Arial, Helvetica, sans-serif;
fill: #000;
fill-opacity: 0.87;
}
.click, .chart g {
cursor: crosshair;
}
.chart rect {
stroke-width: 0;
}
.chart g rect {
fill-opacity: 0.22;
}
.chart:hover g rect {
fill-opacity: 0.38;
}
.chart g.active rect {
fill-opacity: 1;
}
.player circle {
fill-opacity: 0.87;
fill: #000;
}
.player text {
cursor: pointer;
}
function makeBarLegend(root, height, max) {
var band = 6;
var scale = d3.scale.linear()
.domain([0, max])
.range([height, 0]);
var axis = d3.svg.axis()
.scale(scale)
.ticks(8)
.tickSize(6)
.orient('right');
var group = root.append('g');
root.append('g')
.attr('transform', 'translate(6,0)')
.attr('class', 'axis')
.call(axis);
function update(event, duration) {
var r = group.selectAll('rect')
.data(event);
r.enter().append('rect')
.attr('width', band)
.attr('height', 0)
.attr('y', height)
.style('fill', function (d) {
return d.color;
});
r.exit().remove();
group.selectAll('rect').transition().duration(duration)
.attr('y', function (d) {
return scale(d.y1);
})
.attr('height', function (d) {
return scale(d.y0) - scale(d.y1);
})
.style('fill', function (d) {
return d.color;
});
}
return {
update: update
};
}
function makeChart(root, data, width, height, max, clicked) {
var band = (width + 1.5) / data.length - 1.5;
band = Math.min(band, 6);
var bandHalf = band / 2;
var x = d3.time.scale()
.range([0, width - band])
.domain([data[0][2], data[data.length - 1][2]]);
var y = d3.scale.linear()
.range([height, 0])
.domain([0, max]);
var line = root.append('line')
.style('stroke-width', band)
.attr('x1', bandHalf)
.attr('x2', bandHalf)
.attr('y2', height);
var group = root.selectAll('.group')
.data(data)
.enter().append('g')
.attr('class', 'group')
.attr('transform', function (d) {
return 'translate(' + x(d[2]) + ',0)';
});
group.selectAll('rect')
.data(function (d) {
return d[4];
})
.enter().append('rect')
.attr('width', band)
.attr('y', function (d) {
return y(d.y1);
})
.attr('height', function (d) {
return y(d.y0) - y(d.y1);
})
.style('fill', function (d) {
return d.color;
});
root.append('rect')
.attr('class', 'click')
.attr('width', width)
.attr('height', height)
.style('fill', 'rgba(255,255,255,0)')
.on('click', function () {
d3.event.stopPropagation();
var i = Math.round((d3.mouse(this)[0] / width) * (data.length - 1));
clicked(i);
});
function updateCursor(index) {
group
.attr('class', function (d) {
return data[index] === d ? 'active' : '';
});
var p = x(data[index][2]) + bandHalf;
line.transition()
.attr('x1', p)
.attr('x2', p);
}
return {
updateCursor: updateCursor,
band: band
};
}
function updateInfoLegend(list, event, height, width, countries, items) {
var radius = 5;
var charsLimit = 13;
items--;
var display = event[3].slice(0, Math.min(items, event[3].length));
var other = {
color: 'transparent',
id: 'other'
};
if (event[3].length === (display.length + 1)) {
display.push(event[3].length - 1);
} else if (event[3].length > display.length) {
other.v = 0;
for (var i = display.length; i < event[3].length; i++) {
other.v += event[1][event[3][i]];
}
other.title = (event[3].length - display.length) + ' other';
other.i = display.length;
display.push(other.id);
}
var z = list.selectAll('g').data(display, function (d) {
return d;
});
z.exit().remove();
z.transition().attr('opacity', 1).attr('transform', function (d, i) {
return 'translate(0,' + (height - i * 20 - 10) + ')'
});
var g = z.enter().append('g');
g.append('circle');
g.append('text')
.attr('class', 'name')
.attr('transform', 'translate(' + radius * 3 + ',0)');
g.append('text')
.attr('class', 'value')
.attr('dx', width)
.attr('text-anchor', 'end');
g.attr('opacity', 0)
.attr('transform', function (d, i) {
return 'translate(' + (width / 2) + ',' + (height - i * 20 - 10) + ')'
})
.transition().duration(200).delay(200)
.attr('opacity', 1).attr('transform', function (d, i) {
return 'translate(0,' + (height - i * 20 - 10) + ')'
});
list.selectAll('text.name')
.text(function (d) {
return !countries[d] ? other.title : ((countries[d].properties.name.length > charsLimit) ? d : countries[d].properties.name);
});
list.selectAll('text.value')
.text(function (d) {
return !event[1][d] ? other.v : event[1][d];
});
list.selectAll('circle')
.attr('cy', -radius)
.attr('cx', radius)
.attr('r', radius)
.style('fill', function (d) {
var i = event[3].indexOf(d);
return i === -1 ? other.color : event[4][i].color;
});
}
function makePlayer(root, width) {
var scale = d3.time.scale().range([0, width]);
var axis = d3.svg.axis()
.scale(scale)
.ticks(width / 80)
.tickSize(6)
.orient('bottom');
var timeLine = root.append('g').attr('class', 'axis');
function updatePlayer(events, band, clicked) {
scale.domain([new Date(events[0][0]), new Date(events[events.length - 1][0])]);
scale.range([0, width - band]);
scale.domain([new Date(events[0][0]), scale.invert(width)]);
scale.range([0, width]);
timeLine.call(axis)
.selectAll('text')
.style('text-anchor', 'start');
timeLine.selectAll('text').on('click', function (d) {
for (var i = 0; i < events.length; i++) {
if (events[i][2] >= d) {
d3.event.stopPropagation();
clicked(i);
break;
}
}
});
}
return {
updatePlayer: updatePlayer
};
}
function makeScaleLegend(svg, root, width, colors) {
var gradient = svg.append('svg:defs')
.append('svg:linearGradient')
.attr('id', 'gradient')
.attr('x1', '0%')
.attr('y1', '0%')
.attr('x2', '100%')
.attr('y2', '0%')
.attr('spreadMethod', 'pad');
gradient.append('svg:stop')
.attr('offset', '0%')
.attr('stop-color', colors(0))
.attr('stop-opacity', 1);
gradient.append('svg:stop')
.attr('offset', '100%')
.attr('stop-color', colors(1))
.attr('stop-opacity', 1);
root.append('svg:rect')
.attr('width', width)
.attr('height', 6)
.style('stroke-width', 0)
.style('fill', 'url(#gradient)');
var scale = d3.scale.linear()
.domain([1, 2])
.range([0, width]);
var axis = d3.svg.axis()
.scale(scale)
.ticks(1)
.tickSize(6)
.orient('bottom');
var legend = root.append('g')
.attr('transform', 'translate(0,6)')
.attr('class', 'axis')
.call(axis);
function update(minMax, duration) {
scale.domain(!(minMax.max - minMax.min) ? [minMax.min - 0.5, minMax.max + 0.5] : [minMax.min, minMax.max]);
var ticks = Math.min(8, (minMax.max - minMax.min) || 1);
axis.ticks(ticks);
legend.transition().duration(duration).call(axis);
}
return {
update: update
};
}
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment