Public
Edited
Jan 16, 2024
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function normalizeData(data, keysToNormalize) {
// Create a D3 linear scale for each key to be normalized
const scales = keysToNormalize.map((key) =>
d3
.scaleLinear()
.domain(d3.extent(data, (d) => d[key])) // Set domain based on actual data range
.range([0, 100])
);

// Normalize the data using the scales
const normalizedData = data.map((d) => ({
...d, // Keep original properties
...keysToNormalize.reduce(
(acc, key) => ({
...acc,
[key]: scales[keysToNormalize.indexOf(key)](d[key])
}),
{}
)
}));

return normalizedData;
}
Insert cell
normalisedData = normalizeData(selected_data, selected_variables)
Insert cell
Insert cell
Insert cell
Insert cell
{
await visibility();
return glyphMap({
data: normalisedData,
getLocationFn: (row) => [row.long, row.lat],
cellSize: 30,
mapType: "CartoPositron",
discretisationShape: "grid",

width: width,
height: width * 0.5,
greyscale: true,
tileWidth: 150,

glyph: {
aggrFn: appendRecordsAggrFn,
postAggrFn: summariseVariablesPostAggr(selected_variables),
drawFn: drawFnSquares,
postDrawFn: drawLegend,
tooltipTextFn: (cell) => {
// console.log(cell.records[0]);
const textBuilder = [];
for (const variable of selected_variables) {
const average = cell.averages[variable] ?? "-";
const percentage = average ? Math.round(average) + "%" : "-";
textBuilder.push(`${variable}=${percentage}; <br>`);
}
const text = textBuilder.join("").slice(0, -4); // Remove trailing "; "

return text;
}
}
});
}
Insert cell
Insert cell
Insert cell
Insert cell
// (Use d3) to find the average for each cell (storing as a property of "cell") and the max of
// all cells (storing as a propery of "global")
// Store averages and maxes as key-value pairs as objects
function summariseVariablesPostAggr(listOfVariables) {
return function postAggrFn(cells, cellSize, global, panel) {
for (const cell of cells) {
cell.averages = {};
for (const variable of listOfVariables) {
if (cell.records) {
cell.averages[variable] = d3.mean(
cell.records.map((row) => +row[variable])
);
}
}
}
global.maxes = {};
for (const variable of listOfVariables) {
global.maxes[variable] = d3.max(
cells.map((row) => row.averages[variable])
);
}
};
}
Insert cell
Insert cell
//draw a little barchart of each variable
function drawFnSquares(cell, x, y, cellSize, ctx, global, panel) {
if (!cell) return;
const padding = 2;
// ctx.globalAlpha = 0.5;

var grid_long = cellSize - padding * 2;
var grid_wide = cellSize - padding * 2;
//draw cell background
const boundary = cell.getBoundary(padding);
// console.log("boundary: ", boundary);
ctx.fillStyle = "#cccb";
ctx.beginPath();
ctx.moveTo(boundary[0][0], boundary[0][1]);
for (let i = 1; i < boundary.length; i++)
ctx.lineTo(boundary[i][0], boundary[i][1]);
ctx.closePath();
ctx.fill();

var cellWidth = (cellSize - 2 * padding) / 3;
var cellHeight = (cellSize - 2 * padding) / 2;

for (const rec of cell.records) {
// console.log("size: ", rec);
// console.log(rec[selected_variables[0]]);
// const sizes = [rec[p] for p in selected_variables];
const sizes = [];
for (const key of selected_variables) {
sizes.push(rec[key]);
}

// ctx.beginPath();
// ctx.fillRect(
// x - cellSize / 2 + padding + row,
// y - cellSize / 2 + padding + col,
// 10,
// 10
// );


for (var row = 0; row < 2; row++) {
for (var col = 0; col < 3; col++) {
// Calculate the index of the square
var index = row * 3 + col;
// Calculate the size and position of the square
var size = (sizes[index] / 100) * Math.min(cellWidth, cellHeight);
// console.log(colours[index]);
var centerX = col * cellWidth + cellWidth / 2 - size / 2;
var centerY = row * cellHeight + cellHeight / 2 - size / 2;
// Draw the square
ctx.beginPath();
ctx.fillRect(
centerX + x - cellSize / 2 + padding,
centerY + y - cellSize / 2 + padding,
size,
size
);
ctx.fillStyle = colours[index];
ctx.fill();
ctx.lineWidth = 2;
ctx.strokeStyle = "black";
ctx.stroke();
}
} // inner cell iteration
}
}
Insert cell
colours = [
"#2e8b57ba",
"#ffa500ba",
"#00ff00ba",
"#0000ffba",
"#1e90ffba",
"#ff1493ba"
]
Insert cell
function drawLegend(grid, cellSize, ctx, global, panel) {
ctx.font = "10px sans-serif";
ctx.textAlign = "left";
ctx.textBaseline = "middle";

const maxTextWidth = d3.max(
selected_variables.map((item) => ctx.measureText(item).width)
);

const x = panel.getWidth() - maxTextWidth - 20;
let y = panel.getHeight() - selected_variables.length * 15;

ctx.fillStyle = "#fff8";
ctx.fillRect(x, y, maxTextWidth + 15, selected_variables.length * 15);

for (let i = 0; i < selected_variables.length; i++) {
ctx.fillStyle = colours[i];
ctx.fillRect(x, y, 10, 10);
ctx.fillStyle = "#333";
ctx.fillText(selected_variables[i], x + 15, y + 5);
y += 15;
}
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
await visibility();
return glyphMap({
data: selected_data,
getLocationFn: (row) => [row.long, row.lat],
cellSize: 20,
discretisationShape: "grid",

width: 600,
height: 300,
greyscale: true,
mapType: "StamenTonerLite",
tileWidth: 150,

glyph: {
aggrFn: appendRecordsAggrFn,
postAggrFn: summariseVariablesPostAggr_weighted(weighted_variables),
drawFn: drawFnBarChart
}
});

// invalidation.then({});
}
Insert cell
Insert cell
Insert cell
function summariseVariablesPostAggr_weighted(input_obj) {
return function postAggrFn(cells, cellSize, global, panel) {
for (const cell of cells) {
cell.averages = {};
for (const obj of input_obj) {
if (cell.records) {
cell.averages[obj.layer] = d3.mean(
cell.records.map((row) => +row[obj.layer] * obj.weight)
);
}
}
}
global.maxes = {};
for (const obj of input_obj) {
global.maxes[obj.layer] = d3.max(
cells.map((row) => row.averages[obj.layer])
);
}
};
}
Insert cell
Insert cell
Insert cell
Insert cell
// Glyph design is WIP
Insert cell
Insert cell
Insert cell
Insert cell
vb_with_weight = _.filter(import_vb, function (obj) {
return _.includes(selected_variables, obj.layer);
})
Insert cell
all_data
Insert cell
Insert cell
import_vb
Insert cell
import { variables as import_vb } from "684573e863e1549b"
Insert cell
Insert cell
import { poissonDisc2d, hexgrid2d, grid2d } from "@fil/2d-point-distributions"
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