Published
Edited
Aug 15, 2019
Insert cell
md`# Interactions with Lines 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-temperature">
Temperatura Maxima: <span id="temperature"></span>
</div>
</div>
</div>`;
drawLineChart();
}
Insert cell
async function drawLineChart() {
// Datos
let dataset = await d3.json("https://gist.githubusercontent.com/chekos/7ee802ef53ba4bbd10a3b8161116d638/raw/e0d655473a57fae5ba54e648429bfd01ca698e12/tijuana_weather_data.json")
const yAccessor = d => d.temperatureMax
const dateParser = d3.timeParse("%Y-%m-%d")
const xAccessor = d => dateParser(d.date)
dataset = dataset.sort((a,b) => xAccessor(a) - xAccessor(b)).slice(0, 100)
// Dimensiones
let dimensions = {
width: width * 0.90,
height: 400,
margin: {
top: 15,
right: 15,
bottom: 40,
left: 60,
},
}
dimensions.boundedWidth = dimensions.width - dimensions.margin.left - dimensions.margin.right
dimensions.boundedHeight = dimensions.height - dimensions.margin.top - dimensions.margin.bottom
// Lienzo pa dibujar los datos
const wrapper = d3.select("#wrapper")
.append("svg")
.attr("width", dimensions.width)
.attr("height", dimensions.height)
const bounds = wrapper.append("g")
.attr("transform", `translate(${dimensions.margin.left}, ${dimensions.margin.top})`)
bounds.append("defs").append("clipPath")
.attr("id", "bounds-clip-path")
.append("rect")
.attr("width", dimensions.boundedWidth)
.attr("height", dimensions.boundedHeight)
const clip = bounds.append("g")
.attr("clip-path", "url(#bounds-clip-path)")
// escalas
const yScale = d3.scaleLinear()
.domain(d3.extent(dataset, yAccessor))
.range([dimensions.boundedHeight, 0])
.nice()
const freezingTemperaturePlacement = yScale(32)
const freezingTemperatures = clip.append("rect")
.attr("class", "freezing")
.attr("x", 0)
.attr("width", d3.max([0, dimensions.boundedWidth]))
.attr("y", freezingTemperaturePlacement)
.attr("height", d3.max([0, dimensions.boundedHeight - freezingTemperaturePlacement]))
const xScale = d3.scaleTime()
.domain(d3.extent(dataset, xAccessor))
.range([0, dimensions.boundedWidth])
.nice()
// Dibujar datos
const lineGenerator = d3.line()
.x(d => xScale(xAccessor(d)))
.y(d => yScale(yAccessor(d)))
const line = clip.append("path")
.attr("class", "line")
.attr("d", lineGenerator(dataset))
// Periferales y detalles
const yAxisGenerator = d3.axisLeft()
.scale(yScale)
const yAxis = bounds.append("g")
.attr("class", "y-axis")
.call(yAxisGenerator)
const yAxisLabel = yAxis.append("text")
.attr("class", "y-axis-label")
.attr("x", -dimensions.boundedHeight / 2)
.attr("y", -dimensions.margin.left + 10)
.html("Temperatura Maxima (&deg; F)")
const xAxisGenerator = d3.axisBottom()
.scale(xScale)
const xAxis = bounds.append("g")
.attr("class", "x-axis")
.style("transform", `translateY(${dimensions.boundedHeight}px)`)
.call(xAxisGenerator)
// Interacciones
const listentingRect = bounds.append("rect")
.attr("class", "listening-rect")
.attr("width", dimensions.boundedWidth)
.attr("height", dimensions.boundedHeight)
.on("mousemove", onMouseMove)
.on("mouseleave", onMouseLeave)
const tooltip = d3.select("#tooltip")
const formatDate = d3.timeFormat("%B %A %-d, %Y")
const formatTemperature = d => `${d3.format(".1f")(d)} °F`
const tooltipCircle = bounds.append("circle")
.attr("r", 4)
.attr("stroke", "#AF9358")
.attr("fill", "white")
.attr("stroke-width", 2)
.attr("opacity", 0)
function onMouseMove() {
const mousePosition = d3.mouse(this)
const hoveredDate = xScale.invert(mousePosition[0])
const getDistanceFromHoveredDate = d => Math.abs(xAccessor(d) - hoveredDate)
const closestIndex = d3.scan(dataset, (a, b) => (
getDistanceFromHoveredDate(a) - getDistanceFromHoveredDate(b)
))
const closestDataPoint = dataset[closestIndex]
const closestXValue = xAccessor(closestDataPoint)
const closestYValue = yAccessor(closestDataPoint)
tooltip.select("#date")
.text(formatDate(closestXValue))
tooltip.select("#temperature")
.text(formatTemperature(closestYValue))
const x = xScale(closestXValue) + dimensions.margin.left
const y = yScale(closestYValue) + dimensions.margin.top
tooltip.style("transform", `translate( calc(-50% + ${x}px), calc(-100% + ${y}px))`)
tooltip.style("opacity", 1)
tooltipCircle
.attr("cx", xScale(closestXValue))
.attr("cy", yScale(closestYValue))
.style("opacity", 1)
}
function onMouseLeave() {
tooltip.style("opacity", 0)
tooltipCircle.style("opacity", 0)
}
}
Insert cell
mx_ES = ({
"decimal": ",",
"thousands": ".",
"grouping": [3],
"currency": ["€", ""],
"dateTime": "%a %b %e %X %Y",
"date": "%d/%m/%Y",
"time": "%H:%M:%S",
"periods": ["AM", "PM"],
"days": ["Domingo", "Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado"],
"shortDays": ["Dom", "Lun", "Mar", "Mi", "Jue", "Vie", "Sab"],
"months": ["Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"],
"shortMonths": ["Ene", "Feb", "Mar", "Abr", "May", "Jun", "Jul", "Ago", "Sep", "Oct", "Nov", "Dic"]
})
Insert cell
d3.timeFormatDefaultLocale(mx_ES);
Insert cell
html`<style>
.wrapper {
position: relative;
}

.freezing {
fill: #e0f3f3;
}

.line {
fill: none;
stroke: #af9358;
stroke-width: 2;
}

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

.listening-rect {
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