Public
Edited
Jul 24, 2023
Importers
Insert cell
Insert cell
year = 2018
Insert cell
coloring = 'spi'
Insert cell
chart = {
const svg = d3.create("svg")
.attr('width', size)
.attr("viewBox", [-width*0.6, -height*0.6, width*1.2, height*1.2])
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.style('background', background)
const gradient = svg.append('defs')
.append('radialGradient')
.attr('id', 'gradient')
.attr('gradientUnits', 'userSpaceOnUse')
.attr('cx', '0%')
.attr('cy', '0%')
.attr('fr', d3.format('%')(y(-3)/width/1.2))
.attr('r', d3.format('%')(y(3)/width/1.2))
colors.forEach((color, i) => {
gradient.append('stop')
.attr('offset', i/(colors.length-1))
.attr('stop-color', color)
})
svg.selectAll('.line')
.data(data_by_station)
.join("path")
.attr('class', 'line')
.attr("fill", "none")
.attr("stroke", (_, i) => coloring == 'spi' ? 'url(#gradient)' : (coloring == 'basin' ? basin_color(grid.basin_id[i]) : 'steelblue'))
.attr("stroke-width", 0.1)
.attr("d", line);

svg.append("g").selectAll('.xtick')
.data(computed_months)
.join('line')
.attr('class', 'xtick')
.attr('x1', d => y.range()[0]*Math.cos(x(d)-Math.PI/2))
.attr('y1', d => y.range()[0]*Math.sin(x(d)-Math.PI/2))
.attr('x2', d => y.range()[1]*Math.cos(x(d)-Math.PI/2))
.attr('y2', d => y.range()[1]*Math.sin(x(d)-Math.PI/2))
.attr('opacity', 0.2)
.attr('stroke', 'black');

svg.append("g")
.call(yAxis);
svg.append('line')
.datum(d3.max(flat_data, d => d.date))
.attr('class', 'hand')
.attr('x1', d => y.range()[0]*0.95*Math.cos(x(d)-Math.PI/2))
.attr('y1', d => y.range()[0]*0.95*Math.sin(x(d)-Math.PI/2))
.attr('x2', d => y.range()[1]*1.05*Math.cos(x(d)-Math.PI/2))
.attr('y2', d => y.range()[1]*1.05*Math.sin(x(d)-Math.PI/2))
.attr('stroke-width', 1.5)
.attr('stroke', '#777');
svg.append('text')
.text(year)
.attr('dy', '0.2em')
.attr('text-anchor', 'middle')
.attr('font-size', 72)
.attr('font-family', 'sans-serif')
.attr('font-weight', 'bold')
.attr('fill', '#777')
.attr('transform', `translate(0,0)`)
/*svg.append('text')
.text(year-1)
.attr('dy', '0.2em')
.attr('text-anchor', 'middle')
.attr('font-size', 72)
.attr('font-family', 'sans-serif')
.attr('font-weight', 'bold')
.attr('fill', '#777')
.attr('transform', `translate(-400,${-height/2})`)*/
svg.append('text')
.text('INVERNO')
.attr('dy', '.35em')
.attr('text-anchor', 'middle')
.attr('font-size', 42)
.attr('font-family', 'sans-serif')
.attr('font-weight', 'bold')
.attr('fill', '#BBB')
.attr('transform', `rotate(45) translate(0,${-height*0.6})`)
svg.append('text')
.text('PRIMAVERA')
.attr('dy', '.35em')
.attr('text-anchor', 'middle')
.attr('font-size', 42)
.attr('font-family', 'sans-serif')
.attr('font-weight', 'bold')
.attr('fill', '#BBB')
.attr('transform', `rotate(-45) translate(0,${height*0.6})`)
svg.append('text')
.text('ESTATE')
.attr('dy', '.35em')
.attr('text-anchor', 'middle')
.attr('font-size', 42)
.attr('font-family', 'sans-serif')
.attr('font-weight', 'bold')
.attr('fill', '#BBB')
.attr('transform', `rotate(45) translate(0,${height*0.6})`)
svg.append('text')
.text('AUTUNNO')
.attr('dy', '.35em')
.attr('text-anchor', 'middle')
.attr('font-size', 42)
.attr('font-family', 'sans-serif')
.attr('font-weight', 'bold')
.attr('fill', '#BBB')
.attr('transform', `rotate(-45) translate(0,${-height*0.6})`)
svg.selectAll('.monthLabel')
.data(computed_months)
.join('text')
.text(d => d.toLocaleString('it', { month: 'narrow' }))
.attr('class', 'monthLabel')
.attr('text-anchor', 'middle')
.attr('font-family', 'sans-serif')
.attr('font-weight', 'bold')
.attr('fill', d => d == computed_months[computed_months.length-1] ? '#777' : '#BBB')
.attr('font-size', d => d == computed_months[computed_months.length-1] ? 24 : 18)
.attr('dy', '0.35em')
.attr('transform', d => `translate(${y.range()[0]*0.8*Math.cos(x(d)-Math.PI/2)} ${y.range()[0]*0.8*Math.sin(x(d)-Math.PI/2)})`)

return svg.node();
}
Insert cell
background = 'white'
Insert cell
axes = 'black'
Insert cell
size = 680
Insert cell
width = 954
Insert cell
height = width
Insert cell
margin = 10
Insert cell
innerRadius = width / 4
Insert cell
outerRadius = width / 2 - margin
Insert cell
x = d3.scaleUtc()
.domain([Date.UTC(year-1, 0, 1), Date.UTC(year+1, 0, 1) - 1])
.range([-2 * Math.PI, 2 * Math.PI])
Insert cell
y = d3.scaleLinear()
.domain([-2, 2])
.range([innerRadius, outerRadius])
Insert cell
yAxis = g => g
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.call(g => g.selectAll("g")
.data([-2,-1.5,-1,0,1,1.5,2])
.join("g")
.attr("fill", "none")
.call(g => g.append("circle")
.attr("stroke", axes)
.attr("stroke-opacity", d => d != 0 ? 0.2 : 0.8)
.attr("r", y))
.call(g => g.append("text")
.attr("y", d => -y(d))
.attr("dy", "0.35em")
.attr("font-size", 18)
.attr("stroke", background)
.attr("stroke-width", 5)
.text((x, i) => x)
.clone(true)
.attr("y", d => y(d))
.selectAll(function() { return [this, this.previousSibling]; })
.clone(true)
.attr("fill", axes)
.attr("stroke", "none")))
Insert cell
line = d3.lineRadial()
.curve(d3.curveCardinal)
.angle(d => x(d.date))
.radius(d => y(d.value))
Insert cell
area = d3.areaRadial()
.curve(d3.curveCardinal)
.angle(d => x(d.date))
Insert cell
computed_months = d3.timeMonth.range(d3.min(flat_data, d => d.date), d3.max(flat_data, d => d.date))
Insert cell
data_by_station = Array.from(d3.group(
flat_data,
d => d.station
).values())
Insert cell
flat_data = data
.reduce(((a, x) => a.concat(x.values.map((d, i) => ({date: new Date(Date.UTC(x.year, x.month-1)), value: d, station: i}))) ), [])
.sort((a, b) => d3.ascending(a.date, b.date))
Insert cell
data = JSON.parse(await FileAttachment("spi01_2019_2018.json").text()).filter(d => d.year == year || d.year == year+1 && d.month == 1)
Insert cell
grid = JSON.parse(await FileAttachment("grid_cells.json").text())
Insert cell
Insert cell
Insert cell
colors = ['#7e1900','#801c01','#811f02','#822203','#832504','#852705','#862a06','#872d06','#882f07','#8a3108','#8b3409','#8c360a','#8d380b','#8e3b0c','#8f3d0c','#903f0d','#92410e','#93440f','#944610','#954811','#964a12','#974c13','#984e14','#995015','#9a5315','#9b5516','#9c5717','#9d5918','#9e5b19','#9f5d1a','#a05f1b','#a1611c','#a2631c','#a3651d','#a4671e','#a5691f','#a66b20','#a76d21','#a86f21','#a97122','#aa7323','#ab7524','#ac7725','#ad7926','#ae7c27','#af7e27','#b08028','#b18229','#b2842a','#b3862b','#b4882c','#b58a2d','#b68c2e','#b78e2e','#b8902f','#b99230','#ba9431','#bb9632','#bc9933','#bd9b35','#be9d36','#bf9f37','#c0a138','#c1a339','#c2a63b','#c3a83c','#c4aa3e','#c6ac3f','#c7af41','#c8b143','#c9b344','#cab646','#cbb848','#cdba4a','#cebc4d','#cfbf4f','#d0c151','#d2c354','#d3c556','#d4c859','#d5ca5c','#d6cc5e','#d8ce61','#d9d064','#dad267','#dbd36a','#dcd56c','#ddd76f','#ded872','#dfda75','#e0db78','#e0dc7b','#e1de7e','#e2df80','#e3e083','#e3e186','#e4e289','#e4e38b','#e5e48e','#e5e590','#e5e593','#e6e695','#e6e798','#e6e79a','#e6e89d','#e6e89f','#e6e9a1','#e6e9a4','#e6eaa6','#e6eaa8','#e6ebaa','#e5ebad','#e5ebaf','#e4ecb1','#e4ecb3','#e3ecb5','#e2ecb7','#e1edb9','#e0edba','#dfedbc','#deedbe','#ddedc0','#dbedc1','#daeec3','#d8eec4','#d7eec6','#d5eec7','#d3eec9','#d1edca','#cfedcb','#cdedcc','#cbedcd','#c9edcf','#c6edd0','#c4ecd1','#c1ecd1','#bfecd2','#bcebd3','#b9ebd4','#b7ead5','#b4ead5','#b1e9d6','#aee9d7','#abe8d7','#a8e7d8','#a5e7d8','#a2e6d9','#9fe5d9','#9ce4d9','#99e3da','#96e2da','#93e1da','#90e0da','#8ddfda','#8addda','#87dcdb','#84dbda','#81d9da','#7ed8da','#7bd6da','#78d5da','#75d3da','#73d1d9','#70d0d9','#6eced9','#6bccd8','#69cad8','#67c9d7','#65c7d7','#63c5d6','#61c3d6','#5fc1d5','#5ebfd4','#5cbed4','#5abcd3','#59bad2','#58b8d2','#56b6d1','#55b4d0','#54b2d0','#53b0cf','#52afce','#50adcd','#4fabcd','#4ea9cc','#4da7cb','#4da5ca','#4ca4ca','#4ba2c9','#4aa0c8','#499ec7','#489cc7','#479ac6','#4799c5','#4697c4','#4595c3','#4493c3','#4492c2','#4390c1','#428ec0','#418cc0','#418abf','#4089be','#3f87bd','#3e85bd','#3e84bc','#3d82bb','#3c80bb','#3c7eba','#3b7db9','#3a7bb8','#3979b8','#3978b7','#3876b6','#3774b5','#3773b5','#3671b4','#356fb3','#356eb3','#346cb2','#336ab1','#3369b0','#3267b0','#3165af','#3164ae','#3062ae','#2f61ad','#2f5fac','#2e5dac','#2d5cab','#2d5aaa','#2c59a9','#2b57a9','#2b55a8','#2a54a7','#2952a7','#2951a6','#284fa5','#274da5','#274ca4','#264aa3','#2549a3','#2547a2','#2446a1','#2344a1','#2342a0','#22419f','#213f9f','#203e9e','#1f3c9d','#1f3b9c','#1e399c','#1d379b','#1c369a','#1b349a','#1a3399']
Insert cell
d3 = require("d3@6")
Insert cell
msgpack = require('@msgpack/msgpack')
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