Skip to content

Instantly share code, notes, and snippets.

@rgdonohue
Last active June 30, 2019 05:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save rgdonohue/000b9f743327c5c4a45edc3fa1916dbe to your computer and use it in GitHub Desktop.
Save rgdonohue/000b9f743327c5c4a45edc3fa1916dbe to your computer and use it in GitHub Desktop.
CARTO 4.0 with D3 Histogram
<!DOCTYPE html>
<html>
<head>
<title>CARTO 4.0 with D3 Histogram</title>
<meta name="viewport" content="initial-scale=1.0">
<meta charset="utf-8">
<link href="https://unpkg.com/leaflet@1.3.1/dist/leaflet.css" rel="stylesheet">
<link href='https://fonts.googleapis.com/css?family=Lato:300,400,600,700' rel='stylesheet' type='text/css'>
<style>
* {
margin: 0;
padding: 0;
}
/* Mobile first styles b/c that's how we roll */
html {
box-sizing: border-box;
height: 100%;
}
body {
background: #f2f6f9;
color: #374C70;
font-family: Lato, sans-serif;
position: relative;
}
h1, h2 {
font-weight: normal;
margin-bottom: 18px;
}
#map {
width: 100%;
height: 480px;
margin: 12px;
background: rgb(63, 150, 179);
}
#widgets {
margin: 12px;
}
.widget {
background: white;
padding: 10px;
margin-bottom: 10px;
}
#histogram {
width: 100%;
}
div.tooltip {
position: absolute;
min-width: 60px;
min-height: 28px;
padding: 6px 12px;
color: whitesmoke;
background: #374C70;
border-radius: 2px;
pointer-events: none;
z-index: 9999;
}
/* CSS rules for larger devices */
@media (min-width: 900px) {
#container {
display: flex;
}
#map {
flex: 1.618;
margin-right: 0;
}
#widgets {
flex: 1;
}
}
</style>
</head>
<body>
<div id="container">
<div id="map"></div>
<div id="widgets">
<div class="widget">
<h2>2017 uninsured adults by county</h2>
<div id="histogram">
<svg></svg>
</div>
</div>
</div>
</div>
<script src="https://unpkg.com/leaflet@1.3.1/dist/leaflet.js"></script>
<script src="https://cartodb-libs.global.ssl.fastly.net/carto.js/v4.0.0-beta.20/carto.min.js"></script>
<!-- Import babel standalone so the ES6 JS works -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script src="https://d3js.org/d3.v5.js"></script>
<script type="text/babel">
// create a new Leaflet map
const map = L.map('map').setView([-5, 2], 4);
// Create div for the tooltip and hide with opacity
const div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
// position the tooltip when moving over the map
// we'll handle the show/hide stuff with CARTO's method below
d3.select("#map")
.on("mousemove", event => {
div.style("left", (d3.event.pageX + 10) + "px")
.style("top", (d3.event.pageY - 30) + "px");
})
// create new carto client tied to account
const client = new carto.Client({
apiKey: 'default_public',
username: 'rgdonohue'
});
// graticule clipped to North America, reprojected
const graticuleSource = new carto.source.SQL(
`
SELECT ST_Transform(the_geom, 2163)
AS the_geom_webmercator, cartodb_id
FROM na_grat_clipped
`
);
// request the land, reprojecting the geometries
const landSource = new carto.source.SQL(
`
SELECT ST_Transform(the_geom, 2163)
AS the_geom_webmercator, cartodb_id
FROM ne_50m_land
`
);
// request our data layer
const uninsuredSource = new carto.source.SQL(
`
SELECT ST_Transform(the_geom, 2163)
AS the_geom_webmercator, cartodb_id, adults_percent, county
FROM uninsured_counties_2017
`
);
// styles for graticule
const graticuleStyle = new carto.style.CartoCSS(
`
#layer{
line-width: 1.8;
line-color: #c7d4e8;
line-opacity: 0.43;
}
`
);
// styles for land
// based off https://carto.com/learn/guides/styling/styling-a-custom-basemap/#custom-fill-properties
const landStyle = new carto.style.CartoCSS(
`
#layer {
polygon-fill: #d6d4ca;
polygon-opacity: 1;
line-color: #979797;
line-width: 0.25;
line-opacity: 0.5;
::shadow{
polygon-fill: #d6d4ca;
image-filters: agg-stack-blur(10,10);
}
::fill {
line-color: #979797;
line-width: 0.25;
line-opacity: 0.5;
polygon-opacity: 1;
polygon-fill: #d6d4ca;
}
}
`
)
// styles for datalayer
const uninsuredStyle = new carto.style.CartoCSS(
`
#layer {
polygon-fill: ramp([adults_percent], cartocolor(OrYel), jenks(7));
line-width: .7;
line-color: #FFF;
line-opacity: 0.4;
}
`
)
// create the carto layers using the source and styles
const graticuleLayer = new carto.layer.Layer(graticuleSource, graticuleStyle);
const landLayer = new carto.layer.Layer(landSource, landStyle);
const uninsuredLayer = new carto.layer.Layer(uninsuredSource, uninsuredStyle, {
// designate which attribute columns are accessible with interaction
featureOverColumns: ['adults_percent', 'county']
});
// add the layers to the client
client.addLayers([graticuleLayer, landLayer, uninsuredLayer]);
// translate layers to Leaflet map
client.getLeafletLayer().addTo(map);
// are there any errors?
client.on('error', clientError => {
console.error(clientError.message);
});
// display and update tooltip on mouse over
uninsuredLayer.on('featureOver', e => {
div.style("opacity", 1).html(`${e.data.county} County<br> ${e.data.adults_percent} % uninsured`)
});
// hide the tooltip
uninsuredLayer.on('featureOut', e => {
div.style("opacity", 0)
});
// Create a histogram with x bins
const histogram = new carto.dataview.Histogram(uninsuredSource, 'adults_percent', {
bins: 7
});
// Set up a callback to render the histogram data every time new data is obtained
histogram.on('dataChanged', histoData => {
const data = histoData.bins.map(val => {
var d = {};
d.min = +val.min;
d.max = +val.max;
d.freq = +val.freq;
return d;
});
drawHistogramChart(data);
});
// Add the histogram to the client
client.addDataview(histogram);
function drawHistogramChart(data) {
// 7 class color OrYel scheme from
// from https://github.com/CartoDB/CartoColor/blob/master/cartocolor.js
// would be nice to derive colors and class breaks from TurboCARTO results above
const colors = [
"#ecda9a",
"#efc47e",
"#f3ad6a",
"#f7945d",
"#f97b57",
"#f66356",
"#ee4d5a"
]
const margin = {
top: 10,
right: 0,
bottom: 30,
left: 50
}
// select target div
const chartDiv = d3.select("#widgets");
// width and height of chart based on rendered page
const width = chartDiv.node().offsetWidth,
height = chartDiv.node().offsetHeight - 100;
// dynamically provide width/height
const svg = d3.select("#histogram svg")
.style('width', width + 'px')
.style('height', height + margin.bottom + 'px')
// define x axis range
const x = d3.scaleBand()
.domain(data.map(d => d.max))
.range([margin.left, width - margin.right])
.padding(0.1)
// define y axis range
const y = d3.scaleLinear()
.domain([0, d3.max(data, d => d.freq)]).nice()
.range([height - margin.bottom, margin.top])
// draw x axis
const xAxis = g => g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x).tickSizeOuter(0))
// draw y axis
const yAxis = g => g
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y))
.call(g => g.select(".domain").remove())
// draw histogram bars
svg.append("g")
.selectAll("rect")
.data(data).enter()
.append("rect")
.attr("fill", function(d,i) {
return colors[i];
})
.attr("x", d => x(d.max))
.attr("y", d => y(d.freq))
.attr("height", d => y(0) - y(d.freq))
.attr("width", x.bandwidth());
svg.append("g")
.call(xAxis);
svg.append("g")
.call(yAxis);
// x-axis label
svg.append("text")
.attr("transform", `translate(${width/2},${height})`)
.style("text-anchor", "middle")
.attr("font", "16px sans-serif")
.attr("fill", "#374C70")
.attr("dy", "1.3em")
.text("% uninsured in county");
// y-axis label
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0)
.attr("x", -height / 2)
.attr("dy", "0.65em")
.attr("font", "16px sans-serif")
.attr("fill", "#374C70")
.style("text-anchor", "middle")
.text("# of counties");
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment