Public
Edited
May 15, 2024
1 fork
Importers
3 stars
Insert cell
Insert cell
Insert cell
{
play
const width = 500
const height = 270
const squareSize = 50

// Create svg container
const svg = d3.create("svg")
.attr("viewBox", [-20, -10, width + 20, height + 20]);
// create chart svg elements
svg.append("g")
.selectAll('path')
.data(data)
.join('path')
.attr("fill", "#e4d96f")
.attr("fill-opacity", 0.5)
.attr("stroke", "white")
.attr("stroke-width", 1)
.attr("d", d => geoPath(data, width, height).path(d))
// define interpolator function
let interpolator = function (d) {
return flubber.toRect(
d3.select(this).attr('d'),
d.x * squareSize, d.y * squareSize,
squareSize, squareSize
);
}
// animate chart type change transition
svg.selectAll("path")
.transition()
.delay((d, i) => 200 + i * 20) // different delay for different squares
.duration(2000)
.attrTween('d', interpolator);

return svg.node()
}
Insert cell
{
const width = 530
const height = 330
const svg = d3
.create("svg")
.attr("viewBox", [-10, -10, width + 20, height + 20]);

// Append Ukraine regions polygons
svg.append("g")
.selectAll('path')
.data(data)
.join('path')
.attr("fill", "#e4d96f")
.attr("fill-opacity", 0.5)
.attr("stroke", "white")
.attr("stroke-width", 1)
.attr("d", d => geoPath(data, width, height).path(d))
.on('mousemove', function(e,d) {
d3.select(this)
.attr("fill", "blue")
})
.on('mouseleave', function(e,d) {
d3.select(this)
.attr("fill", "#e4d96f")
})

return svg.node()
}
Insert cell
{
const width = 530
const height = 270
const squareSize = 50

const svg = d3
.create("svg")
.attr("viewBox", [-10, -10, width + 20, height + 20]);

svg.append("g")
.selectAll('path')
.data(data)
.enter().append('path')
.attr('d', d => convertRectPath(d.x*squareSize, d.y*squareSize, squareSize, squareSize))
.attr('fill', "#e4d96f")
.attr("fill-opacity", 0.5)
.attr("stroke", "white")
.attr("stroke-width", 1)

return svg.node()
}
Insert cell
data = ukraine.features
.map(d => ({...d, name: name_mapping[d.properties.name]}))
.map(d => ({...d,
x: grid.filter(D => D.name == d.name).map(d => d.col)[0],
y: grid.filter(D => D.name == d.name).map(d => d.row)[0],
// sort polygons in multi-polygon geometries by size
// important for flubber to work properly as it only morphs the first polygon in array
geometry:({
type: d.geometry.type,
coordinates: d.geometry.coordinates.sort((a,b) => b[0].length - a[0].length)
})
})
)
Insert cell
// mapping between different region name spellings
name_mapping = {
const reg_new = ukraine.features.map(d => d.properties.name).sort()
const reg_old = ["Cherkasy", "Chernihiv", "Chernivtsi", "Crimea", "Dnipropetrovsk", "Donetsk", "Ivano-Frankivsk", "Kharkiv", "Kherson", "Khmelnytskiy", "Kyiv", "Kirovohrad", "Lviv", "Luhansk", "Mykolayiv", "Odesa", "Poltava", "Rivne", "Sumy", "Ternopil", "Zakarpattya", "Vinnytsya", "Volyn", "Zaporizhzhya", "Zhytomyr"]
return _.zipObject(reg_new, reg_old) // merge two arrays into key-value pairs
}
Insert cell
// define projection and scale
function geoPath(data, width, height) {
const projection = d3.geoMercator().scale(1).translate([0,0]);
const path = d3.geoPath().projection(projection);
const bounds = path.bounds({ type: 'FeatureCollection', features: data });
const s = 0.95 / Math.max((bounds[1][0] - bounds[0][0]) / width, (bounds[1][1] - bounds[0][1]) / height);
const t = [(width - s * (bounds[1][0] + bounds[0][0])) / 2, (height - s * (bounds[1][1] + bounds[0][1])) / 2];
projection.scale(s).translate(t);
return {
projection: projection,
path: path,
bounds: bounds,
s: s,
t: t
};
}
Insert cell
grid = FileAttachment("ukr_grid@6.csv").csv({typed:true})
Insert cell
Insert cell
Insert cell
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