Skip to content

Instantly share code, notes, and snippets.

@ramiroaznar
Last active July 20, 2018 08:08
Show Gist options
  • Save ramiroaznar/3ce20b177abac5634602ed40b3f15f85 to your computer and use it in GitHub Desktop.
Save ramiroaznar/3ce20b177abac5634602ed40b3f15f85 to your computer and use it in GitHub Desktop.
CARTO.js GoT Distance Calculator
html, body {
margin: 0;
padding: 0;
font-family: 'MedievalSharp', sans-serif;
}
.title {
margin: 10px 0;
font-weight: bold;
font-size: 36px;
text-align: center;
}
.text {
font-family: 'Lato', sans-serif;
font-size: 12px;
color: #333;
text-align: center;
}
#map {
position: absolute;
top: 0; left: 0;
width: 100%;
height: 100%;
}
#season-selector{
z-index: 9000;
margin: 30px 30px 24px;
border: none;
border-radius: 3px;
height: 9px;
}
#sidebar {
position: absolute;
bottom: 0;
left: 0;
padding: 20px;
font-size: 2em;
background-color: rgba(255, 255, 255, 0.8);
z-index: 1000;
}
.legend{
max-width: 400px;
}
.ui-widget-content {
background: #D1BDA2;
}
.slider-range {
background: #792427;
border-radius: 3px;
}
.slider-handle {
background:url(https://res.cloudinary.com/dh6mm17sc/image/upload/v1511284538/Bitmap_profpd.png) !important;
border: none !important;
top: -14px !important;
}
.slider-handle:first-of-type {
background:url(https://res.cloudinary.com/dh6mm17sc/image/upload/v1511284542/Group_xmflvz.png) !important;
top: -16px !important;
height: 50px !important;
}
#season-labels {
display: flex;
justify-content: space-between;
margin: 0 30px 20px;
}
.season-label {
font-size: 16px;
font-weight: bold;
}
function main(){
// declare map, query & style
const map = L.map('map').setView([7, 20], 5);
const linesQuery = $('#lines-query').text();
const linesStyle = $('#lines-style').text();
const pointsQuery = $('#points-query').text();
const pointsStyle = $('#points-style').text();
const labelsQuery = $('#labels-query').text();
const labelsStyle = $('#labels-style').text();
// create season selector
const seasonSelector = $("#season-selector");
seasonSelector.slider({
range: true,
min: 1,
max: 7,
step: 1,
values: [ 1, 3 ],
classes: {
"ui-slider": "slider-bar",
"ui-slider-handle": "slider-handle",
"ui-slider-range": "slider-range"
},
labels: ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII'],
});
const labels = $('#season-selector').data().uiSlider.options.labels
for (var label of labels) {
const el = $('<label>').addClass('season-label').text(label);
$( "#season-labels" ).append(el);
}
// add dark GoT basemap
L.tileLayer('https://cartocdn-gusc-d.global.ssl.fastly.net/ramirocartodb/api/v1/map/named/tpl_d44e8b0f_a525_4d23_b93a_71aba54674bc/all/{z}/{x}/{y}.png', {
maxZoom: 18,
attribution: '<a href="https://carto.com/attribution">CARTO</a>'
}).addTo(map);
map.removeControl(map.zoomControl);
// create CARTO client
const client = new carto.Client({
apiKey: 'default_public',
username: 'ramirocartodb'
});
// create a source, style & lines layer
const linesSource = new carto.source.SQL(linesQuery);
const linesCartoCSS = new carto.style.CartoCSS(linesStyle);
const linesLayer = new carto.layer.Layer(linesSource, linesCartoCSS, {
featureOverColumns: ['character', 'distance']
});
// create a source, style & labels layer
const labelsSource = new carto.source.SQL(labelsQuery);
const labelsCartoCSS = new carto.style.CartoCSS(labelsStyle);
const labelsLayer = new carto.layer.Layer(labelsSource, labelsCartoCSS);
// create a source, style & points layer
// GoT emojis source: https://downloademoji.com/game-of-thrones/
const pointsSource = new carto.source.SQL(pointsQuery);
const pointsCartoCSS = new carto.style.CartoCSS(pointsStyle);
const pointsLayer = new carto.layer.Layer(pointsSource, pointsCartoCSS);
// add layers to the client
client.addLayers([linesLayer, labelsLayer, pointsLayer]);
// add the client layer to the map
client.getLeafletLayer().addTo(map);
// add popup
const popup = L.popup({ closeButton: false });
linesLayer.on('featureOver', function (featureEvent) {
popup.setLatLng(featureEvent.latLng);
const name = featureEvent.data.character;
const dist = featureEvent.data.distance.toFixed(0);
popup.setContent(`
<h2 style="font-size: 14px; font-family: 'MedievalSharp', sans-serif;"> ${name} </h2>
<p style="font-size: 10px; font-family: 'MedievalSharp', sans-serif;"> ${dist} Km</p>
`);
popup.openOn(map);
console.log(featureEvent.data, featureEvent.distance);
});
linesLayer.on('featureOut', function (featureEvent) {
popup.removeFrom(map);
});
// add character data view
const charCategory = new carto.dataview.Category(
linesSource, 'character', {
limit: 16,
operation: carto.operation.SUM,
operationColumn: 'distance'
});
const names = [], distances = [];
charCategory.on('dataChanged', function (newData) {
console.log('---Distance by character---');
names.length = 0;
distances.length = 0;
for (category of newData.categories){
names.push(category.name);
distances.push(category.value);
console.log(category.name, category.value);
}
const topBackgrounds = [];
// assign color to character name
for (const name of names) {
if (name =="Theon") {
topBackgrounds.push("#F2B701");
}
else if (name =="Eddard" || name =="Bran" || name =="Arya" || name =="Sansa" || name =="Jon" || name =="Robb") {
topBackgrounds.push("#88CCEE");
}
else if (name =="Jaime" || name =="Cersei" || name =="Tyrion") {
topBackgrounds.push("#882255");
}
else if (name == "Sam") {
topBackgrounds.push("#661100");
}
else if (name == "Brienne") {
topBackgrounds.push("#332288");
}
else if (name == "Davos") {
topBackgrounds.push("#888888");
}
else if (name == "Daenerys") {
topBackgrounds.push("#DDCC77");
}
else {topBackgrounds.push("#117733");
}
}
// add bar chart widget
const widget = document.getElementById("chart").getContext("2d");
const chartOptions = {
legend: {
display:false,
},
title: {
display: true,
text: 'Distance in Kms',
position: 'bottom',
},
scales: {
yAxes: [{
barPercentage: 0.2,
gridLines: {
display: false,
}
}],
xAxes: [{
barPercentage: 0.2,
gridLines: {
display: false,
}
}]
},
elements: {
rectangle: {
borderSkipped: 'left',
}
}
};
Chart.defaults.global.defaultFontFamily = "'MedievalSharp', sans-serif";
Chart.defaults.global.defaultFontSize = 14;
const myChart = new Chart(widget, {
type: 'horizontalBar',
data: {
labels: names,
datasets: [{
label: '',
data: distances,
backgroundColor: topBackgrounds,
borderWidth: 0
}]
},
options: chartOptions
});
console.log(names);
});
client.addDataview(charCategory);
// add bbox filters
const bboxFilter = new carto.filter.BoundingBoxLeaflet(map);
charCategory.addFilter(bboxFilter);
/* Slider on change */
// filter query when selecting season range
seasonSelector.on("slidechange", function( event, ui ) {
let seasons = seasonSelector.slider( "values" );
let rangeSeasons = Array.apply(null, {length: (seasons[1]-seasons[0]+1)}).map(function(value, index){
return index + seasons[0];
});
filterSeason(rangeSeasons);
console.log(rangeSeasons);
});
const filterSeason = function (season) {
let filterLinesQuery = linesQuery;
let filterPointsQuery = pointsQuery;
if (season) {
filterLinesQuery = `
select
row_number() over() as cartodb_id,
array_agg(distinct season) as seasons,
max(color) as color,
st_makeline(the_geom order by cartodb_id asc) as the_geom,
st_length(st_makeline(the_geom order by cartodb_id asc)::geography)*0.138980150292748/1000 as distance,
st_transform(st_makeline(the_geom order by cartodb_id asc), 3857) as the_geom_webmercator,
max(house) as house,
character
from
episodes_locations
where season in (${season})
group by
character`;
filterPointsQuery = `
select
row_number() over() as cartodb_id,
array_agg(distinct season) as seasons,
max(color) as color,
st_endpoint(st_makeline(the_geom order by cartodb_id asc)) as the_geom,
st_transform(st_endpoint(st_makeline(the_geom order by cartodb_id asc)), 3857) as the_geom_webmercator,
max(house) as house,
character
from
episodes_locations
where season in (${season})
group by
character`;
}
linesSource.setQuery(filterLinesQuery);
pointsSource.setQuery(filterPointsQuery);
};
}
window.load = main();
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CARTO.js GoT Distance Calculator</title>
<link rel="shortcut icon" href="egg.ico" />
<!-- family fonts -->
<link href="https://fonts.googleapis.com/css?family=Lato" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=MedievalSharp" rel="stylesheet">
<!-- leaflet + jquery -->
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" />
<script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<!-- chart.js -->
<script src="http://www.chartjs.org/dist/2.7.1/Chart.bundle.js"></script>
<script src="http://www.chartjs.org/samples/latest/utils.js"></script>
<!-- carto.js -->
<script src="https://cdn.rawgit.com/CartoDB/cartodb.js/@4.0.0-alpha.28/carto.js"></script>
<!-- jquery-ui -->
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<link rel="stylesheet" type="text/css" href="app.css">
<script type="text/sql" id="labels-query">
select * from locations
</script>
<script type="text/cartocss" id="labels-style">
Map{
buffer-size: 512;
}
#layer[type='City']{
::inner{
marker-fill-opacity: 1;
marker-fill:#fff;
marker-line-width: 0.7;
marker-line-opacity: 0.8;
marker-placement: point;
marker-type: ellipse;
marker-width: 4;
marker-line-color: fadeout(lighten(#bac9ad,12),70);
marker-allow-overlap: true;
[zoom>=5]{marker-width: 6;}
[zoom>=6]{marker-width: 7;}
[zoom>=7]{marker-width: 10;}
}
::labels {
text-name: [name];
text-face-name: "Lato Bold Italic";
text-size: 13;
text-fill: #fff;
text-halo-fill:fadeout(lighten(#bac9ad,12),70);
text-halo-radius: 1.5;
text-placement-type: simple;
text-placements: "E,W,NW,NE,SE,8";
text-dx:-7;
text-dy:-4;
text-character-spacing: 0;
[zoom>=5]{text-size: 14;}
[zoom>=6]{text-size: 15;}
[zoom>=7]{text-size: 16;}
}
}
</script>
<script type="text/sql" id="lines-query">
select
row_number() over() as cartodb_id,
st_makeline(the_geom order by cartodb_id asc) as the_geom,
st_transform(st_makeline(the_geom order by cartodb_id asc), 3857) as the_geom_webmercator,
array_agg(distinct season) as seasons,
st_length(st_makeline(the_geom order by cartodb_id asc)::geography)*0.138980150292748/1000 as distance,
character,
max(color) as color,
max(house) as house
from
episodes_locations
group by
character
</script>
<script type="text/cartocss" id="lines-style">
#layer {
line-width: 4;
line-opacity: 0.5;
[ house = "Stark" ] {
line-color: fadeout(lighten(#bac9ad,12),70);
}
[ house = "Lannister" ] {
line-color: fadeout(lighten(#DDCC77,12),70);
}
[ house = "Baelish" ] {
line-color: fadeout(lighten(#bac9ad,12),70);
}
[ house = "Greyjoy" ] {
line-color: fadeout(lighten(#DDCC77,12),70);
}
[ house = "Seaworth" ] {
line-color: fadeout(lighten(#bac9ad,12),70);
}
[ house = "Targaryen" ] {
line-color: fadeout(lighten(#332288,12),70);
}
[ house = "Tarly" ] {
line-color: fadeout(lighten(#117733,12),70);
}
[ house = "Tarth" ] {
line-color: fadeout(lighten(#CC6677,12),70);
}
::inner{
line-width: 2;
line-opacity: 1;
line-dasharray: 10, 4;
line-color: [color];
}
}
</script>
<script type="text/sql" id="points-query">
select
row_number() over() as cartodb_id,
st_endpoint(st_makeline(the_geom order by cartodb_id asc)) as the_geom,
st_endpoint(st_transform(st_makeline(the_geom order by cartodb_id asc), 3857)) as the_geom_webmercator,
array_agg(distinct season) as seasons,
character,
max(color) as color,
max(house) as house
from
episodes_locations
group by
character
</script>
<script type="text/cartocss" id="points-style">
#layer{
marker-width: 20;
marker-allow-overlap: true;
marker-file: url('https://s3.amazonaws.com/com.cartodb.users-assets.production/production/ramirocartodb/assets/20171129092439Group_xmflvz.png');
[character = 'Daenerys']{
marker-file: url('https://s3.amazonaws.com/com.cartodb.users-assets.production/production/ramirocartodb/assets/2017112909220702_daenerys.png');
}
[character = 'Jon']{
marker-file: url('https://s3.amazonaws.com/com.cartodb.users-assets.production/production/ramirocartodb/assets/2017112909223403_jonsnow.png');
}
[character = 'Tyrion']{
marker-file: url('https://s3.amazonaws.com/com.cartodb.users-assets.production/production/ramirocartodb/assets/2017112909225804_tyrion.png');
}
[character = 'Jaime']{
marker-file: url('https://s3.amazonaws.com/com.cartodb.users-assets.production/production/ramirocartodb/assets/2017112909245705_jaime.png');
}
[character = 'Arya']{
marker-file: url('https://s3.amazonaws.com/com.cartodb.users-assets.production/production/ramirocartodb/assets/2017112909251508_arya.png');
}
[character = 'Sam']{
marker-file: url('https://s3.amazonaws.com/com.cartodb.users-assets.production/production/ramirocartodb/assets/2017112909252409_sam.png');
}
[character = 'Sansa']{
marker-file: url('https://s3.amazonaws.com/com.cartodb.users-assets.production/production/ramirocartodb/assets/2017112909261616_sansa.png');
}
[character = 'Cersei']{
marker-file: url('https://s3.amazonaws.com/com.cartodb.users-assets.production/production/ramirocartodb/assets/2017112909253711_cersei.png');
}
[character = 'Robb']{
marker-file: url('https://s3.amazonaws.com/com.cartodb.users-assets.production/production/ramirocartodb/assets/2017112909255816_robb.png');
}
[character = 'Littlefinger']{
marker-file: url('https://s3.amazonaws.com/com.cartodb.users-assets.production/production/ramirocartodb/assets/2017112909262717_petyr.png');
}
[character = 'Brienne']{
marker-file: url('https://s3.amazonaws.com/com.cartodb.users-assets.production/production/ramirocartodb/assets/2017112909263720_brienne.png');
}
}
</script>
</head>
<body>
<div id="map"></div>
<div id="sidebar">
<div class="legend">
<h6 class="title">Game of Thrones Distance Calculator</h6>
<p class="text">Is winter coming at the speed of light? Is Littlefinger using teleportation? Are ravens faster than dragons? In order to answer this questions, check the GoT Distance Calculator built with CARTO.js. <strong>Discover which character has traveled the most depending on the season.</strong></p>
</div>
<div id="season-selector"></div>
<div id="season-labels"></div>
<canvas id="chart" width="400" height="400"></canvas>
</div>
<script type="text/javascript" src="app.js"></script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment