Published
Edited
Dec 3, 2020
Insert cell
md`# scooter`
Insert cell
geo2square = {
//svg variables
let width = 960;
let height = 480;

//size of transformed rectangle
let squareSize = 50;

//create SVG artboard
let svg = d3
.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("width", width)
.attr("height", height)
.attr('id', 'mapArea');

//create svg background color
let bg = svg
.append('rect')
.attr("x", 0)
.attr("y", 0)
.attr("width", width)
.attr("height", height)
.attr('fill', '#000');

// create Map grouping for asset management
let mapGroup = svg.append("g").attr("id", "map");

// Use Mercator projection - how we convert lon-lat coordinates to svg pixels
var projection = d3
.geoMercator()
//fit the geoJson file in half of the width and height
//the [0,0] are margins.... play with them for styling control!
.fitExtent([[0, 0], [width / 2, height]], areas);

//function to convert svg pixel coordinates to compound path drawing instructions
var path = d3.geoPath().projection(projection);

//function to convert svg pixel coordinates to rectangle drawing instructions
var line = d3
.line()
.x(function(d) {
return d[0];
})
.y(function(d) {
return d[1];
});

//convert area of shapes to 0-1 range, and therefore colors
let areaScale = d3
.scaleLinear()
.domain(
d3.extent(areas.features, d => parseFloat(d.properties.area_numbe))
);

//operate on each geography
for (let i = 0; i < areas.features.length; i++) {
let area = areas.features[i];

//project coordinates to pixels
let projected = area.geometry.coordinates[0][0].map(projection);

//store drawing instruction for the geographic shape
area.properties.geoPath = path(area);

//store drawing instruction for the rectangular shape
area.properties.squarePath = line(
d3.geo2square(projected, squareSize, squareSize)
);

//store the left coordinate of the generated square
area.properties.squareX = d3.geo2square(
projected,
squareSize,
squareSize
)[0][0];

//store the top coordinate of the generated square
area.properties.squareY = d3.geo2square(
projected,
squareSize,
squareSize
)[0][1];

//use the community area number integer divided by 10 as a fake category
area.properties.category = Math.floor(
parseInt(area.properties.area_numbe) / 10
);
}

//we start in geography mode
let toggle = 0;

//draw shapes
mapGroup
.selectAll('.areas')
.data(areas.features)
.enter()
.append('path')
.attr('class', 'areas')
.attr('d', d => d.properties.geoPath)
.attr('opacity',1)
.attr('fill', d =>
d3.interpolateWarm(areaScale(parseFloat(d.properties.area_numbe)))
)

//when clicked....
.on('click', function(d) {
//check if we're in geography mode
if (toggle == 0) {
//if so, transform to square mode
d3.selectAll('.areas')
.data(areas.features)
//transition to transform, recolor, and slide the squares over
.transition()
.duration(1000)
.attr('opacity', function(e){
if (e.properties.area_numbe == 1){return 0}else{return 1}
})
.transition()
.duration(1000)
.attr('fill', d =>
d3.interpolateCool(areaScale(parseFloat(d.properties.area_numbe)))
)
//switch to square paths
.attr('d', function(e){
if (e.properties.area_numbe == 1){return d.properties.geoPath}else{return d.properties.squarePath}

})

//use category and transform to place square on the right half of the artboard
.attr(
'transform',
e =>
'translate(' +
(width / 2 +
(e.properties.category * squareSize - e.properties.squareX)) +
',0)'
)
//trigger a second transition to slide the squares to the vertical center
.transition()
.duration(1000)
//slide to the vertical centerline
.attr(
'transform',
e =>
'translate(' +
(width / 2 +
(e.properties.category * squareSize - e.properties.squareX)) +
',' +
(height / 2 - squareSize / 2 - e.properties.squareY) +
")"
);
//now we're in square mode!
toggle = 1;
} else {
//undo the transformations back to geography mode
mapGroup
.selectAll('.areas')
.data(areas.features)
.transition()
.duration(1500)
.attr('fill', d =>
d3.interpolateCool(areaScale(parseFloat(d.properties.area_numbe)))
)
.attr('d', d => d.properties.geoPath)
.attr('fill', d =>
d3.interpolateWarm(areaScale(parseFloat(d.properties.area_numbe)))
)
.attr('transform', "");
//now we're in geography mode again
toggle = 0;
}
});

return svg.node();
}
Insert cell
//areas = FileAttachment("community-area.json").json()
areas = FileAttachment("community-area@1.json").json()
Insert cell
area1 = d3.csvParse(await FileAttachment("CommAreas.csv").text())
Insert cell
(d3.geo2square = function(coordinates, width, height) {
var centroid = d3.polygonCentroid(coordinates);

width = width ? width : 0;
height = height ? height : 0;

var p = [];
var i = 0;
var length = coordinates.length;
var qtr = ~~(length / 4);

var nScale = d3
.scaleLinear()
.domain([0, qtr])
.range([0, width]);
var sScale = d3
.scaleLinear()
.domain([0, qtr])
.range([width, 0]);
var wScale = d3
.scaleLinear()
.domain([0, qtr])
.range([0, height]);
var eScale = d3
.scaleLinear()
.domain([0, qtr])
.range([height, 0]);

while (i < length) {
if (i <= qtr) {
p.push([centroid[0] + nScale(i), centroid[1]]);
} else if (i <= qtr * 2) {
p.push([centroid[0] + width, centroid[1] + wScale(i - qtr)]);
} else if (i <= qtr * 3) {
p.push([centroid[0] + sScale(i - qtr * 2), centroid[1] + height]);
} else if (i <= qtr * 4) {
p.push([centroid[0], centroid[1] + eScale(i - qtr * 3)]);
}
i++;
}

return p;
})
Insert cell
d3 = require('d3@5')
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