Skip to content

Instantly share code, notes, and snippets.

@Zhenmao
Created November 11, 2017 08:38
Show Gist options
  • Save Zhenmao/34ac7f6c3056f3818330417fb9c450e0 to your computer and use it in GitHub Desktop.
Save Zhenmao/34ac7f6c3056f3818330417fb9c450e0 to your computer and use it in GitHub Desktop.
World fastest lifts -- Remake of a Financial Time data visualization
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name='viewport' content='width=device-width,initial-scale=1'>
<title>World’s fastest lifts race to the top of the tallest buildings</title>
<style>
body {
font-family: sans-serif;
font-size: 12px;
}
#container{
position: relative;
}
#restart {
border: solid 1px #43423e;
position: absolute;
top: 0px;
left: 910px;
font-size: 14px;
color: #74736c;
text-decoration: none;
padding: 4px 8px;
border-radius: 3px;
transition: 0.3s;
}
#restart:hover {
color: white;
background-color: #74736c;
}
.axistitle {
font-size: 14px;
fill: #74736c;
}
.axis text{
fill: #74736c;
}
.axis line,
.axis path {
display: none;
}
.horizontalGrid {
fill: none;
stroke: #cec6b9;
stroke-width: 1px;
stroke-dasharray: 1 1;
}
#horizontalGridBaseline {
stroke-dasharray: none;
}
.towershape {
fill: #cec6b9;
}
.liftline {
stroke-width: 2px;
stroke: #bb6d82;
fill: none;
}
.liftblock {
fill: #9e2f50;
stroke: #9e2f50;
}
.towername,
.location,
.year {
text-anchor: middle;
fill: #74736c;
}
.liftspeed,
.totalDistance,
.totalLoops {
text-anchor: middle;
fill: #9e2f50;
}
</style>
<script src='https://d3js.org/d3.v4.min.js'></script>
</head>
<body>
<h1>World’s fastest lifts race to the top of the tallest buildings</h1>
<p>Remake of Financial Times' visualization for <a href="https://ig.ft.com/worlds-fastest-lifts/">World’s fastest lifts race to the top of the tallest buildings</a>.</p>
<script type="text/javascript">
/* jshint esversion: 6 */
const data = [
{
towername: 'CTF Finance Centre',
location: 'Guangzhou',
year: 2016,
speed: 20,
height: 560,
liftHeight: 495,
},
{
towername: 'Shanghai Tower',
location: 'Shanghai',
year: 2015,
speed: 18,
height: 655,
liftHeight: 561,
},
{
towername: 'Burj Khalifa',
location: 'Dubai',
year: 2010,
speed: 10,
height: 835,
liftHeight: 585,
},
{
towername: 'One World Trade Center',
location: 'New York City',
year: 2014,
speed: 10.2,
height: 562,
liftHeight: 387
},
{
towername: 'Willis Tower',
location: 'Chicago',
year: 1974,
speed: 8.1,
height: 556,
liftHeight: 413,
},
{
towername: 'Taipei 101',
location: 'Taipei',
year: 2004,
speed: 16.8,
height: 525,
liftHeight: 438,
},
{
towername: 'Empire State Building',
location: 'New York City',
year: 1931,
speed: 7.1,
height: 430,
liftHeight: 373,
},
{
towername: 'Chrysler Building',
location: 'New York City',
year: 1930,
speed: 4.5,
height: 330,
liftHeight: 252,
},
];
const width = 972,
height = 670;
const numberTextFormat = d3.format(',');
d3.select('body').append('div')
.attr('id', 'container')
.style('width', width)
.style('height', height)
const svg = d3.select('div#container').append('svg')
.attr('width', width)
.attr('height', height);
// Set up scales and axes
const yticks = d3.range(9).map(d => d * 100);
const yScale = d3.scaleLinear()
.domain(d3.extent(yticks))
.range([540, 40]);
const yAxis1 = d3.axisLeft()
.scale(yScale)
.tickValues(yticks);
const yAxis2 = d3.axisRight()
.scale(yScale)
.tickValues(yticks);
svg.append('g')
.classed('axis', true)
.attr('id', 'yaxistower1')
.attr('transform', 'translate(32, 0)')
.call(yAxis1);
svg.append('g')
.classed('axis', true)
.attr('id', 'yaxistower2')
.attr('transform', 'translate(940, 0)')
.call(yAxis2);
svg.append('text')
.classed('axistitle', true)
.attr('x', '0')
.attr('y', '15')
.text('Height (m)');
svg.selectAll('line.horizontalGrid')
.data(yticks)
.enter()
.append('line')
.classed('horizontalGrid', true)
.attr('x1', 32)
.attr('x2', 940)
.attr('y1', d => yScale(d))
.attr('y2', d => yScale(d));
svg.selectAll('line.horizontalGrid')
.filter(d => d === 0)
.attr('id', 'horizontalGridBaseline');
// Add tower shapes
svg.append('polygon')
.classed('towershape', true)
.attr('id','ctf')
.attr('transform', 'translate(-73,-64) scale(1.32)')
.attr('points','85.029,457.425 85.267,292.262 90.028,292.262 90.028,199.189 111.332,209.782 111.332,238.971 116.33,238.942 116.568,449.364 120.213,449.364 124.542,445.196 134.897,445.196 134.897,447.979 136.481,447.979 136.503,457.333 ');
svg.append('path')
.classed('towershape', true)
.attr('id','shanghai')
.attr('transform','translate(-118,-64) scale(1.32)')
.attr('d','M174.956,457.703v-4h3.645v-7.218h11.718c0,0,0.168-6.785,3.025-6.689 c2.858,0.093,11.051-0.026,11.051-0.026s-0.285-45.229,0.858-61.808c1.144-16.577,6.765-84.791,7.336-87.457 c0.573-2.669,2.097-32.395,2.287-40.586c0.189-8.193,0.572-32.583,0.38-37.727c-0.191-5.146-0.38-54.878-0.38-54.878 s-0.263-1.643,1.31-1.929c1.572-0.286,10.932-1.859,13.361-1.644l-0.143,6.001c0,0,6.788,1.144,8.217,1.858s2,0.43,2.073,4.931 c0.071,4.501,1.643,34.296,1.643,34.296l2.001,35.371c0,0,1.072,38.799,0.929,42.229s0.523,31.894,0.523,45.992 c0,14.101-0.189,39.061,0.191,48.588c0.381,9.528,0.571,23.629,2.097,38.682c1.524,15.051,2.667,28.773,2.667,28.773l0.809,2.096 l5.86,0.142l-0.43,9.003l5.431,0.428l0.285,1.285h9.29l-0.287,4.144L174.956,457.703z');
svg.append('polyline')
.classed('towershape', true)
.attr('id','khalifa')
.attr('transform','translate(-147,-64) scale(1.32)')
.attr('points','339.16,94.438 339.61,94.438 339.61,112.671 340.107,112.671 340.107,121.751 341.415,121.751 341.415,139.946 341.54,139.946 341.574,149.072 342.938,149.072 342.938,158.188 343.021,158.188 343.021,176.397 344.332,176.397 344.332,203.049 346.099,203.049 346.099,243.069 349.301,243.069 349.301,310.072 353.821,310.072 353.821,365.087 357.066,365.087 357.066,408.307 360.601,408.307 360.601,439.848 364.831,439.848 364.831,443.382 368.818,443.382 368.818,446.966 374.536,446.966 375.263,447.267 375.263,457.512 303.449,457.512 303.449,446.997 308.543,446.997 308.543,443.382 312.527,443.382 312.527,439.848 316.677,439.848 316.677,395.897 320.295,395.897 320.295,348.163 323.54,348.163 323.54,289.897 328.019,289.897 328.019,214.428 331.269,214.428 331.269,184.818 332.987,184.818 332.987,167.271 334.306,167.271 334.306,158.188 334.433,158.188 334.433,130.875 335.949,130.875 335.949,103.509 337.716,103.509 337.716,89.912 338.166,89.912 338.166,85.971 338.341,85.971 338.341,76.855 338.42,76.855 338.42,66.779 338.341,66.779 338.341,66.748 338.375,66.748 338.375,66.585 338.213,66.585 338.213,66.458 338.166,66.458 338.166,66.213 339.16,66.213 339.16,66.585 338.986,66.585 338.986,66.748 339.028,66.748 339.028,66.779 338.947,66.779 338.947,76.855 339.028,76.855 339.028,85.971 339.16,85.971 339.16,94.438 ');
svg.append('polygon')
.classed('towershape', true)
.attr('id','oneworldtrade')
.attr('transform','translate(-159.5,-64) scale(1.32)')
.attr('points','453.501,458.042 453.501,258.943 448.501,258.943 448.509,257.997 449.578,258.006 449.595,253.531 439.65,253.531 439.65,241.08 440.49,241.08 440.49,238.912 439.65,238.91 439.65,234.107 440.49,234.121 440.49,232.623 439.65,232.609 439.65,228.427 440.49,228.413 440.49,227.143 439.65,227.155 439.65,222.702 440.49,222.692 440.49,221.518 439.65,221.518 439.65,217.46 440.49,217.46 440.49,216.094 439.65,216.094 439.65,211.204 440.49,211.209 440.49,209.773 439.65,209.782 439.65,197.869 438.237,197.869 438.237,209.782 437.397,209.773 437.397,211.209 438.237,211.204 438.237,216.094 437.397,216.094 437.397,217.46 438.237,217.46 438.237,221.518 437.397,221.518 437.397,222.692 438.237,222.702 438.237,227.155 437.397,227.143 437.397,228.413 438.237,228.427 438.237,232.609 437.397,232.623 437.397,234.121 438.237,234.107 438.237,238.91 437.397,238.912 437.397,241.08 438.237,241.08 438.237,253.531 428.293,253.531 428.312,258.006 429.379,257.997 429.387,258.943 424.387,258.943 424.387,458.042 ');
svg.append('polyline')
.classed('towershape', true)
.attr('id','willis')
.attr('transform','translate(-155,-64) scale(1.32)')
.attr('points','549.077,284.294 549.077,457.592 515.227,457.592 515.227,246.013 517.276,246.013 517.276,231.055 517.521,231.055 517.521,228.181 517.784,228.181 517.784,231.055 518.095,231.055 518.095,246.013 519.34,246.013 519.34,223.821 519.587,223.821 519.587,212.177 519.904,212.177 519.904,202.123 520.898,202.123 520.898,212.177 521.149,212.177 521.149,223.821 521.709,223.821 521.709,246.013 522.46,246.013 522.46,241.893 530.496,241.893 530.496,246.013 531.25,246.013 531.25,220.949 531.495,220.949 531.495,212.177 531.741,212.177 531.741,204.941 532.545,204.941 532.545,212.177 532.81,212.177 532.81,220.949 533.302,220.949 533.302,246.013 534.367,246.013 534.367,231.055 534.612,231.055 534.612,228.181 534.855,228.181 534.855,231.055 535.103,231.055 535.103,246.013 537.729,246.013 537.729,284.294 549.077,284.294 ');
svg.append('polyline')
.classed('towershape', true)
.attr('id','taipei')
.attr('transform','translate(-184,-64) scale(1.32)')
.attr('points','650.683,400.56 654.978,457.592 592.334,457.592 592.334,434.214 599.18,434.214 599.18,428.349 603.296,426.054 607.852,424.495 612.473,423.742 617.271,424.232 621.875,425.79 625.927,428.05 628.235,400.56 626.428,384.53 627.989,384.53 626.428,368.509 627.989,368.509 626.428,352.435 628.235,352.435 626.746,336.409 628.235,336.409 626.746,320.384 628.235,320.384 626.428,304.307 627.989,304.307 626.746,288.286 628.235,288.286 626.428,272.258 633.101,272.258 633.101,266.39 636.396,266.39 635.099,251.877 638.396,251.877 638.396,249.562 637.149,248.32 637.149,247.268 638.396,247.268 638.706,214.416 639.703,214.416 640.214,247.268 641.458,247.268 641.458,248.32 639.951,249.562 640.214,251.877 643.503,251.877 642.267,266.39 645.571,266.39 645.571,272.258 651.929,272.258 650.683,288.286 652.178,288.286 650.683,304.307 652.178,304.307 650.683,320.384 651.929,320.384 650.683,336.409 651.929,336.409 650.683,352.435 652.178,352.435 650.932,368.509 652.178,368.509 650.683,384.53 651.929,384.53 650.683,400.56 ');
svg.append('polyline')
.classed('towershape', true)
.attr('id','empirestate')
.attr('transform','translate(-206,-64) scale(1.32)')
.attr('points','781.202,451.36 781.202,457.592 713.254,457.592 713.254,451.36 718.562,451.36 718.562,426.786 726.04,426.786 726.04,408.277 728.911,408.277 728.911,328.054 730.958,328.109 730.958,308.915 734.393,308.915 734.393,296.761 736.825,296.761 736.825,294.569 738.631,294.569 738.631,293.013 739.505,293.013 739.505,292.328 742.377,292.328 742.377,290.334 742.943,290.334 742.943,287.335 743.253,287.335 743.253,284.66 743.621,284.66 743.621,281.979 743.936,281.979 743.936,268.826 744.305,268.826 744.305,268.076 744.688,268.076 744.688,267.339 745.111,267.339 745.111,266.586 746.738,266.586 746.738,257.479 747.107,257.479 747.369,245.45 747.369,260.791 747.736,260.791 747.736,266.586 749.367,266.586 749.367,267.339 749.731,267.339 749.731,268.076 750.1,268.076 750.1,268.826 750.54,268.826 750.54,281.979 750.853,281.979 750.853,284.66 751.222,284.66 751.222,287.335 751.54,287.335 751.54,290.334 752.096,290.334 752.096,292.328 754.968,292.328 754.968,293.013 755.844,293.013 755.844,294.569 757.649,294.569 757.649,296.761 760.015,296.761 760.015,308.915 763.515,308.915 763.515,328.109 765.562,328.054 765.562,408.277 768.437,408.277 768.437,426.786 775.916,426.786 775.916,451.36 781.202,451.36 ');
svg.append('polygon')
.classed('towershape', true)
.attr('id','chrysler')
.attr('transform','translate(-227,-64) scale(1.32)')
.attr('points','853.979,305.161 854.222,315.574 855.223,315.574 855.223,318.432 855.904,318.432 855.904,322.298 856.655,322.298 856.655,326.098 857.338,326.098 857.338,328.038 858.076,328.038 858.076,329.966 858.774,329.966 858.774,331.837 859.509,331.837 859.509,333.766 860.456,333.766 860.456,337.632 860.942,337.632 860.942,353.385 863.308,353.385 863.308,355.331 862.817,355.331 862.817,358.182 863.308,358.182 863.308,360.124 862.817,360.124 862.817,406.6 865.246,406.6 865.246,408.034 864.254,408.034 864.254,413.268 867.174,413.268 867.174,418.062 867.612,418.062 867.612,430.53 871.48,430.53 871.48,457.763 836.018,457.763 836.018,430.53 839.885,430.53 839.885,418.062 840.326,418.062 840.326,413.268 843.25,413.268 843.25,408.034 842.249,408.034 842.249,406.6 844.681,406.6 844.681,360.124 844.193,360.124 844.193,358.182 844.681,358.182 844.681,355.331 844.193,355.331 844.193,353.385 846.555,353.385 846.555,337.632 847.047,337.632 847.047,333.766 847.991,333.766 847.991,331.837 848.742,331.837 848.742,329.966 849.43,329.966 849.43,328.038 850.163,328.038 850.163,326.098 850.86,326.098 850.86,322.298 851.599,322.298 851.599,318.432 852.277,318.432 852.277,315.574 853.278,315.574 853.54,305.161 ');
// Plot lifts
svg.selectAll('.liftline')
.data(data)
.enter()
.append('line')
.classed('liftline', true)
.attr('x1', (d, i) => 60 + i * 120)
.attr('y1', yScale(0))
.attr('x2', (d, i) => 60 + i * 120)
.attr('y2', d => yScale(d.liftHeight));
svg.selectAll('.liftblock')
.data(data)
.enter()
.append('rect')
.classed('liftblock', true)
.attr('id', (d, i) => `liftblock${i}`)
.attr('width', 4)
.attr('height', 8)
.attr('x', (d, i) => 58 + i * 120)
.attr('y', yScale(8));
// Labels
svg.selectAll('.liftspeed')
.data(data)
.enter()
.append('text')
.classed('liftspeed', true)
.text(d => `${d.speed} m/s`)
.attr('x', (d, i) => 60 + i * 120)
.attr('y', d => yScale(d.height));
svg.selectAll('.towername')
.data(data)
.enter()
.append('text')
.classed('towername', true)
.text(d => d.towername)
.attr('x', (d, i) => 60 + i * 120)
.attr('y', yScale(-130));
svg.selectAll('.location')
.data(data)
.enter()
.append('text')
.classed('location', true)
.text(d => d.location)
.attr('x', (d, i) => 60 + i * 120)
.attr('y', yScale(-160));
svg.selectAll('.year')
.data(data)
.enter()
.append('text')
.classed('year', true)
.text(d => d.year)
.attr('x', (d, i) => 60 + i * 120)
.attr('y', yScale(-190));
svg.selectAll('.totalLoops')
.data(data)
.enter()
.append('text')
.classed('totalLoops', true)
.attr('id', (d, i) => `totalLoops${i}`)
.attr('x', (d, i) => 60 + i * 120)
.attr('y', yScale(-55));
svg.selectAll('.totalDistance')
.data(data)
.enter()
.append('text')
.classed('totalDistance', true)
.attr('id', (d, i) => `totalDistance${i}`)
.attr('x', (d, i) => 60 + i * 120)
.attr('y', yScale(-90));
// Animation
const animate = function() {
svg.selectAll('.liftblock')
.transition()
.duration(d => d.liftHeight / d.speed * 1000)
.on('start', function repeat(d, i) {
d3.active(this)
.attr('y', d => yScale(d.liftHeight + 8))
.transition()
.attr('y', yScale(8))
.transition()
.on('start', repeat);
});
svg.selectAll('.totalLoops')
.text('0 round trips')
.transition()
.delay(d => d.liftHeight / d.speed * 1000 * 2)
.text('1 round trip')
.transition()
.delay(d => d.liftHeight / d.speed * 1000 * 2)
.on('start', function repeat(d) {
const numberOfLoops = Number(d3.select(this).text().split(' ')[0]);
d3.active(this)
.text(`${numberOfLoops + 1} round trips`)
.transition()
.delay(d => d.liftHeight / d.speed * 1000 * 2)
.on('start', repeat);
});
svg.selectAll('.totalDistance')
.text('0 m')
.transition()
.duration(d => d.liftHeight / d.speed * 1000)
.on('start', function repeat(d, i) {
d3.active(this)
.tween('text', function(d, i) {
const node = this;
const startDistance = Number(node.textContent.split(' ')[0].replace(',', ''));
const endDistance = Number(d3.select(`#totalLoops${i}`).text().split(' ')[0].replace(',', '')) * d.liftHeight * 2 + d.liftHeight;
const iterpolator = d3.interpolate(startDistance, endDistance);
return function(t) {
node.textContent = `${numberTextFormat(Math.round(iterpolator(t)))} m`;
};
})
.transition()
.tween('text', function(d, i) {
const node = this;
const startDistance = Number(node.textContent.split(' ')[0].replace(',', ''));
const endDistance = Number(d3.select(`#totalLoops${i}`).text().split(' ')[0].replace(',', '')) * d.liftHeight * 2 + d.liftHeight * 2;
const iterpolator = d3.interpolate(startDistance, endDistance);
return function(t) {
node.textContent = `${numberTextFormat(Math.round(iterpolator(t)))} m`;
};
})
.transition()
.on('start', repeat);
});
};
// Add restart button
d3.select('div#container').append('a')
.attr('href', '#')
.attr('id', 'restart')
.text('Restart');
d3.select('#restart').on('click', function() {
svg.selectAll('.liftblock')
.transition()
.attr('y', yScale(8))
.on('end', animate);
});
animate();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment