Public
Edited
Oct 4, 2024
Importers
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
everyBike2 = glyphMap({
data: data,
getLocationFn: (row) => [row.lon, row.lat],
cellSize: 35,
discretisationShape: "grid",
//discretisationMode: "relativeToMouse",
showLegend: false,
interactiveCellSize: true,
//interactiveKernelBW: true,
//kernelBW: 2,
interactiveZoomPan: true,
mapType: tile,
// greyscale: true,
tileWidth: 150,

width: 600,
height: 400,

customMap: {
scaleParams: [
_setupParamFns({
name: "heightMaxV",
decKey: "LEFT",
decFn: (v) => v - v / 10,
incKey: "RIGHT",
incFn: (v) => v + v / 10,
resetKey: "s",
autoscale: true,
resetFn: (cells, global, panel) =>
d3.max(
cells.map((cell) =>
cell && cell.ts ? d3.max(cell.ts.map((ts) => d3.max(ts))) : 0
)
)
})
],

//kernelSmoothProperties: ["ts", "count"],
//kernelSmoothPropertyTypes: ["array", "value"],

initFn: (cells, cellSize, global, panel) => {
global.numT = d3.max(panel.data.map((row) => row.ts.length));
},

preAggrFn: (cells, cellSize, global, panel) => {},

aggrFn: (cell, row, weight, global, panel) => {
if (!cell.ts) cell.ts = [];
const sums = [];
const cs = [];
cell.ts.push(row.ts);
if (!cell.stations) cell.stations = [];
if (!cell.stations.includes(row.name)) cell.stations.push(row.name);
},

postAggrFn: (cells, cellSize, global, panel) => {},

preDrawFn: (cells, cellSize, ctx, global, panel) => {
// ctx.fillStyle = "#fff9";
// ctx.fillRect(0, 0, panel.getWidth(), panel.getHeight());

global.heightScale = d3
.scaleLinear()
.domain([0, global.heightMaxV])
.range([0, cellSize])
.clamp(true);
},

drawFn: (cell, x, y, cellSize, ctx, global, panel) => {
//console.log(x, y);
if (cell && cell.ts && !cell.new) {
const padding = 4;
const incX = (cellSize - padding * 2) / global.numT;

// get current zoom
let zoom = Math.log2(panel.getTransform().k).toFixed(2);
const mouse = [global.mouseX, global.mouseY];
// console.log("locafn", panel.screenToCoord(mouse));
// console.log("locafn", global.mouseCell.getXCentre());
// console.log(zoom);

//test screen coord
// const scoord = panel.screenToCoord([0, cellSize]);
// console.log("screencord", scoord);

//draw cell background
const boundary = cell.getBoundary(padding);
ctx.fillStyle = "#cccb";
if (zoom >= 12) ctx.fillStyle = "black";
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();

for (const ts of cell.ts) {
ctx.beginPath();
ctx.strokeStyle = "#8557";
if (zoom >= 12) ctx.strokeStyle = "red";
for (let i = 0; i < ts.length; i++) {
ctx.lineTo(
x - cellSize / 2 + padding + incX * i,
y + cellSize / 2 - padding - global.heightScale(ts[i])
);
}
ctx.stroke();
}
}
},
postDrawFn: (cells, cellSize, ctx, global, panel) => {},
tooltipTextFn: (cell) => {
return cell.stations ? cell.stations : "";
}
}
})
Insert cell
Insert cell
Insert cell
// glyphMap({
// data: data,
// getLocationFn: (row) => [row.lon, row.lat],
// cellSize: 3,
// interactiveCellSize: true,
// interactiveKernelBW: true,
// kernelBW: 5,
// useBlur: true,

// width: 600,
// height: 300,
// tileWidth: 150,
// greyscale: true,

// glyph: heatmapGlyph({
// valueFn: (row, global) => row.ts[row.ts.length - 1],
// type: "mean",
// colourScheme: d3.scaleSequential(d3.interpolatePurples),
// colourAutoscale: true,
// useTransparency: true,
// showLegend: true,
// numberFormatFn: (value) => +value.toFixed(3)
// })
// })
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function _blur(options) {
const grid = options.grid;
const discretiser = options.discretiser;
const properties = options.properties;
const propertyTypes = options.propertyTypes;
if (properties === undefined || propertyTypes === undefined) {
console.log("Need to define properties and propertyTypes for smoothing");
return;
}
const kernelRadius = options.kernelRadius;

const numCols = grid.length + kernelRadius;
const numRows =
Math.max(...Object.values(grid.map((cols) => cols.length))) + kernelRadius;

{
for (let j = 0; j < properties.length; j++) {
if (propertyTypes[j] === "value") {
const vs = [];
for (let y = 0; y < numRows; y++) {
for (let x = 0; x < numCols; x++) {
vs.push(
grid[x] && grid[x][y] && grid[x][y][properties[j]]
? grid[x][y][properties[j]]
: 0
);
}
}
d3.blur2({ data: vs, width: numCols, height: numRows }, kernelRadius);
for (let x = 0; x < numCols; x++) {
for (let y = 0; y < numRows; y++) {
const v = vs[x + y * numCols];
if (v > 0) {
if (!grid[x]) grid[x] = [];
if (!grid[x][y])
grid[x][y] = {
getBoundary: () => discretiser.getBoundary(x, y),
getXCentre: () =>
discretiser.getXYCentre(x, y)[0] + grid.xOffset,
getYCentre: () =>
discretiser.getXYCentre(x, y)[1] + grid.yOffset,
getCellSize: () => discretiser.getCellSize(),
new: true
};
grid[x][y][properties[j]] = v;
} else {
if (grid[x] && grid[x][y] && grid[x][y][properties[j]])
grid[x][y][properties[j]] = v;
}
}
}
}
}
}
return grid;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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