Published
Edited
Aug 15, 2019
Insert cell
md`# Interactions with Scatterplots from Fullstack DataViz`
Insert cell
d3 = require("d3@5")
Insert cell
{
yield html`
<div id="wrapper" class="wrapper">
<div id="tooltip" class="tooltip">
<div class="tooltip-date">
<span id="date"></span>
</div>
<div class="tooltip-humidity">
Humidity: <span id="humidity"></span>
</div>
<div class="tooltip-dew-point">
Dew Point: <span id="dew-point"></span>
</div>
</div>
</div>`;
drawScatter();
}
Insert cell
async function drawScatter() {
// datos
const dataset = await d3.json("https://gist.githubusercontent.com/chekos/7ee802ef53ba4bbd10a3b8161116d638/raw/e0d655473a57fae5ba54e648429bfd01ca698e12/tijuana_weather_data.json")
const xAccessor = d => d.dewPoint
const yAccessor = d => d.humidity
// dimensiones
let dimensions = {
width: width * .75,
height: width * .75,
margin: {
top: 40,
right: 10,
bottom: 50,
left: 50,
},
}
dimensions.boundedWidth = dimensions.width - dimensions.margin.left - dimensions.margin.right
dimensions.boundedHeight = dimensions.height - dimensions.margin.top - dimensions.margin.bottom
// linezo pa dibujar
const wrapper = d3.select("#wrapper")
.append("svg")
.attr("width", dimensions.width)
.attr("height", dimensions.height)
const bounds = wrapper.append("g")
.style("transform", `translate(${dimensions.margin.left}px, ${dimensions.margin.top}px)`)
// escalas
const xScale = d3.scaleLinear()
.domain(d3.extent(dataset, xAccessor))
.range([0, dimensions.boundedWidth])
.nice()
const yScale = d3.scaleLinear()
.domain(d3.extent(dataset, yAccessor))
.range([dimensions.boundedHeight, 0])
.nice()
const drawDots = (dataset) => {
// dibujar datos
const dots = bounds.selectAll("circle")
.data(dataset, d => d[0])
const newDots = dots.enter().append("circle")
const allDots = newDots.merge(dots)
.attr("cx", d => xScale(xAccessor(d)))
.attr("cy", d => yScale(yAccessor(d)))
.attr("r", 4)
const oldDots = dots.exit()
.remove()
}
drawDots(dataset)
// voronoi
const voronoiGenerator = d3.voronoi()
.x(d => xScale(xAccessor(d)))
.y(d => yScale(yAccessor(d)))
.extent([ [0,0], [dimensions.boundedWidth, dimensions.boundedHeight]])
const voronoiPolygons = voronoiGenerator.polygons(dataset)
bounds.selectAll(".voronoi")
.data(voronoiPolygons)
.enter().append("polygon")
.attr("class", "voronoi")
.attr("points", (d = []) => (
d.map(point => (
point.join(",")
)).join(" ")
))
//.attr("stroke", "salmon")
.on("mouseenter", onMouseEnter)
.on("mouseleave", onMouseLeave)
// Agrega los detalles periferales
const xAxisGenerator = d3.axisBottom()
.scale(xScale)
const xAxis = bounds.append("g")
.call(xAxisGenerator)
.style("transform", `translateY(${dimensions.boundedHeight}px)`)
const xAxisLabel = xAxis.append("text")
.attr("class", "x-axis-label")
.attr("x", dimensions.boundedWidth / 2)
.attr("y", dimensions.margin.bottom - 10)
.html("dew point (&deg; F)")
const yAxisGenerator = d3.axisLeft()
.scale(yScale)
.ticks(4)
const yAxis = bounds.append("g")
.call(yAxisGenerator)
const yAxisLabel = yAxis.append("text")
.attr("class", "y-axis-label")
.attr("x", -dimensions.boundedHeight / 2)
.attr("y", -dimensions.margin.left + 10)
.text("relative humidity")
// interacciones
bounds.selectAll("circle")
.on("mouseenter", onMouseEnter)
.on("mouseleave", onMouseLeave)
const tooltip = d3.select("#tooltip")
const formatHumidity = d3.format(".2f")
const formatDewPoint = d3.format(".2f")
const dateParser = d3.timeParse("%Y-%m-%d")
const formatDate = d3.timeFormat("%B %A %-d, %Y")
function onMouseEnter(voronoiDatum, index) {
const datum = voronoiDatum.data
const dayDot = bounds.append("circle")
.attr("class", "tooltipDot")
.attr("cx", d => xScale(xAccessor(datum)))
.attr("cy", d => yScale(yAccessor(datum)))
.attr("r", 7)
.style("fill", "maroon")
.style("pointer-events", "none")
tooltip.select("#humidity")
.text(formatHumidity(xAccessor(datum)))
tooltip.select("#dew-point")
.text(formatDewPoint(yAccessor(datum)))
tooltip.select("#date")
.text(formatDate(dateParser(datum.date)))
const x = xScale(xAccessor(datum)) + dimensions.margin.left
const y = yScale(yAccessor(datum)) + dimensions.margin.top
tooltip.style("transform", `translate( calc(-50% + ${x}px), calc( -100% + ${y}px))`)
tooltip.style("opacity", 1)
}
function onMouseLeave() {
tooltip.style("opacity", 0)
d3.selectAll(".tooltipDot")
.remove()
}
}
Insert cell
html`<style>
.wrapper {
position: relative;
}

.x-axis-label {
fill: black;
font-size: 1.4em;
text-transform: capitalize;
}

.y-axis-label {
fill: black;
font-size: 1.4em;
text-anchor: middle;
transform: rotate(-90deg);
}

circle {
fill: cornflowerblue;
}

.voronoi {
fill: transparent;
}

body {
font-family: sans-serif;
}

.tooltip {
opacity: 0;
position: absolute;
top: -14px;
left: 0;
padding: 0.6em 1em;
background: #fff;
text-align: center;
line-height: 1.4em;
font-size: 0.9em;
border: 1px solid #ddd;
z-index: 10;
transition: all 0.1s ease-out;
pointer-events: none;
}

.tooltip:before {
content: '';
position: absolute;
bottom: 0;
left: 50%;
width: 12px;
height: 12px;
background: white;
border: 1px solid #ddd;
border-top-color: transparent;
border-left-color: transparent;
transform: translate(-50%, 50%) rotate(45deg);
transform-origin: center center;
z-index: 10;
}

.tooltip-date {
margin-bottom: 0.2em;
font-weight: 600;
font-size: 1.1em;
line-height: 1.4em;
}
</style>`
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more