Skip to content

Commit 61371a0

Browse files
committed
update timeseries example
1 parent a43de81 commit 61371a0

File tree

5 files changed

+154
-684
lines changed

5 files changed

+154
-684
lines changed

examples/example.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ScreenGridLayerGL } from "./screengrid.js";
1+
import { ScreenGridLayerGL } from "../src/index.js";
22

33
const map = new maplibregl.Map({
44
container: "map",

examples/timeseries.html

Lines changed: 141 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@
77
<link href="https://unpkg.com/maplibre-gl@^4/dist/maplibre-gl.css" rel="stylesheet" />
88
<script src="https://unpkg.com/maplibre-gl@^4/dist/maplibre-gl.js"></script>
99
<script type="module">
10-
import { ScreenGridLayerGL, GlyphUtilities } from '../src/screengrid.js';
10+
import { ScreenGridLayerGL, GlyphUtilities } from '../src/index.js';
11+
12+
// Global state for hovered year reference
13+
let hoveredYear = null;
14+
let gridLayer = null;
1115

1216
const map = new maplibregl.Map({
1317
container: 'map',
@@ -26,7 +30,6 @@
2630
{ id: 'dark', type: 'raster', source: 'dark', paint: { 'raster-opacity': 1.0 } }
2731
]
2832
},
29-
// center: [-1.25, 51.75],
3033
// center shows cambridge, uk
3134
center: [0.1278, 52.2053],
3235
zoom: 12
@@ -35,6 +38,7 @@
3538
/**
3639
* Custom glyph function for time series visualization
3740
* Groups data by year and aggregates ashp_carbonsaved values
41+
* Also draws reference line for hovered year if applicable
3842
*/
3943
function drawTimeSeriesGlyph(ctx, x, y, normVal, cellInfo) {
4044
const { cellData, cellSize } = cellInfo;
@@ -71,7 +75,14 @@
7175

7276
if (timeSeriesData.length === 0) return;
7377

78+
// Get year range for this cell
79+
const years = timeSeriesData.map(d => d.year);
80+
const minYear = Math.min(...years);
81+
const maxYear = Math.max(...years);
82+
const yearRange = maxYear - minYear || 1;
83+
7484
// Draw time series chart
85+
const padding = 0.15;
7586
GlyphUtilities.drawTimeSeriesGlyph(
7687
ctx,
7788
x,
@@ -86,52 +97,59 @@
8697
showPoints: true,
8798
showArea: true,
8899
areaColor: 'rgba(46, 204, 113, 0.15)',
89-
padding: 0.15
100+
padding: padding
90101
}
91102
);
92-
}
93103

94-
/**
95-
* Alternative: Bar chart over time
96-
*/
97-
function drawTimeSeriesBarGlyph(ctx, x, y, normVal, cellInfo) {
98-
const { cellData, cellSize } = cellInfo;
99-
100-
if (!cellData || cellData.length === 0) return;
101-
102-
// Group by year
103-
const yearData = {};
104-
cellData.forEach(item => {
105-
const year = item.data.year;
106-
const carbonSaved = item.data.ashp_carbonsaved;
104+
// Draw reference line for hovered year if it exists in this cell's data
105+
if (hoveredYear !== null && hoveredYear >= minYear && hoveredYear <= maxYear) {
106+
const chartWidth = cellSize * (1 - 2 * padding);
107+
const chartHeight = cellSize * (1 - 2 * padding);
108+
const chartX = x - chartWidth / 2;
109+
const chartY = y - chartHeight / 2;
107110

108-
if (carbonSaved == null || isNaN(carbonSaved)) return;
111+
// Calculate x position for the hovered year
112+
const yearX = chartX + ((hoveredYear - minYear) / yearRange) * chartWidth;
109113

110-
if (!yearData[year]) {
111-
yearData[year] = { total: 0, count: 0 };
114+
// Draw vertical reference line
115+
ctx.strokeStyle = 'rgba(150, 150, 150, 0.7)';
116+
ctx.lineWidth = 1.5;
117+
ctx.setLineDash([3, 3]); // Dashed line
118+
ctx.beginPath();
119+
ctx.moveTo(yearX, chartY);
120+
ctx.lineTo(yearX, chartY + chartHeight);
121+
ctx.stroke();
122+
ctx.setLineDash([]); // Reset dash
123+
124+
// Draw a small circle at the intersection with the line (if data exists for this year)
125+
const yearValue = timeSeriesData.find(d => d.year === hoveredYear);
126+
if (yearValue) {
127+
const values = timeSeriesData.map(d => d.value);
128+
const minValue = Math.min(...values);
129+
const maxValue = Math.max(...values);
130+
const valueRange = maxValue - minValue || 1;
131+
132+
const valueY = chartY + chartHeight - ((yearValue.value - minValue) / valueRange) * chartHeight;
133+
134+
// Draw circle marker
135+
ctx.fillStyle = 'rgba(150, 150, 150, 0.9)';
136+
ctx.beginPath();
137+
ctx.arc(yearX, valueY, 3.5, 0, 2 * Math.PI);
138+
ctx.fill();
139+
140+
// Draw white outline for better visibility
141+
ctx.strokeStyle = 'rgba(255, 255, 255, 0.8)';
142+
ctx.lineWidth = 1;
143+
ctx.stroke();
112144
}
113-
yearData[year].total += carbonSaved;
114-
yearData[year].count += 1;
115-
});
116-
117-
// Convert to sorted array
118-
const sortedYears = Object.keys(yearData)
119-
.map(y => parseInt(y))
120-
.sort((a, b) => a - b);
121-
122-
const values = sortedYears.map(year => yearData[year].total);
123-
const maxValue = Math.max(...values, 1);
124-
125-
// Draw bar chart
126-
GlyphUtilities.drawBarGlyph(
127-
ctx,
128-
x,
129-
y,
130-
values,
131-
maxValue,
132-
cellSize,
133-
['#3498db', '#2ecc71', '#e74c3c', '#f39c12', '#9b59b6']
134-
);
145+
146+
// Draw year label at the bottom of the chart
147+
ctx.fillStyle = 'rgba(150, 150, 150, 0.9)';
148+
ctx.font = 'bold 9px Arial';
149+
ctx.textAlign = 'center';
150+
ctx.textBaseline = 'top';
151+
ctx.fillText(hoveredYear.toString(), yearX, chartY + chartHeight + 2);
152+
}
135153
}
136154

137155
map.on('load', async () => {
@@ -151,9 +169,7 @@
151169

152170
console.log(`Using ${validData.length} valid data points`);
153171

154-
let useTimeSeries = true;
155-
156-
const gridLayer = new ScreenGridLayerGL({
172+
gridLayer = new ScreenGridLayerGL({
157173
id: 'cambridge-timeseries',
158174
data: validData,
159175
getPosition: (d) => [d.lon, d.lat],
@@ -162,7 +178,7 @@
162178
colorScale: (v) => [255 * v, 200 * (1 - v), 50, 180],
163179
enableGlyphs: true,
164180
glyphSize: 0.9,
165-
onDrawCell: useTimeSeries ? drawTimeSeriesGlyph : drawTimeSeriesBarGlyph,
181+
onDrawCell: drawTimeSeriesGlyph,
166182
onAggregate: (grid) => {
167183
console.log('Grid aggregated:', {
168184
cols: grid.cols,
@@ -171,30 +187,82 @@
171187
maxValue: Math.max(...grid.grid)
172188
});
173189
},
174-
onHover: ({ cell }) => {
175-
if (cell.cellData && cell.cellData.length > 0) {
176-
// Group by year for display
177-
const yearGroups = {};
178-
cell.cellData.forEach(item => {
179-
const year = item.data.year;
180-
if (!yearGroups[year]) {
181-
yearGroups[year] = {
182-
total: 0,
183-
count: 0
184-
};
185-
}
186-
if (item.data.ashp_carbonsaved != null) {
187-
yearGroups[year].total += item.data.ashp_carbonsaved;
188-
yearGroups[year].count += 1;
189-
}
190-
});
190+
onHover: ({ cell, event }) => {
191+
// Clear reference line if hovering over cell without data
192+
if (!cell || !cell.cellData || cell.cellData.length === 0) {
193+
if (hoveredYear !== null) {
194+
hoveredYear = null;
195+
gridLayer.render();
196+
}
197+
return;
198+
}
199+
200+
// Group by year for display
201+
const yearGroups = {};
202+
cell.cellData.forEach(item => {
203+
const year = item.data.year;
204+
if (!yearGroups[year]) {
205+
yearGroups[year] = {
206+
total: 0,
207+
count: 0
208+
};
209+
}
210+
if (item.data.ashp_carbonsaved != null) {
211+
yearGroups[year].total += item.data.ashp_carbonsaved;
212+
yearGroups[year].count += 1;
213+
}
214+
});
215+
216+
// Calculate which year the mouse is over based on x position within cell
217+
const cellSize = gridLayer.config.cellSizePixels;
218+
const mouseX = event.point.x;
219+
220+
// Calculate relative position within the cell (0 to 1)
221+
// cell.x is the left edge of the cell in canvas coordinates
222+
const relativeX = (mouseX - cell.x) / cellSize;
223+
224+
// Get year range for this cell
225+
const years = Object.keys(yearGroups).map(y => parseInt(y)).sort((a, b) => a - b);
226+
if (years.length > 0) {
227+
const minYear = Math.min(...years);
228+
const maxYear = Math.max(...years);
229+
const yearRange = maxYear - minYear || 1;
191230

192-
const yearSummary = Object.keys(yearGroups)
193-
.sort()
194-
.map(year => `${year}: ${yearGroups[year].total.toFixed(1)} kg CO₂`)
195-
.join('\n');
231+
// Calculate year based on mouse x position within the chart area
232+
// Account for padding: chart starts at padding, ends at (1 - padding)
233+
const padding = 0.15;
234+
const chartStart = padding;
235+
const chartEnd = 1 - padding;
236+
const chartWidth = chartEnd - chartStart;
196237

197-
console.log(`Cell at [${cell.col}, ${cell.row}]:\n${yearSummary}`);
238+
// Normalize relativeX to chart area
239+
const normalizedX = (relativeX - chartStart) / chartWidth;
240+
const clampedX = Math.max(0, Math.min(1, normalizedX));
241+
242+
const calculatedYear = Math.round(minYear + clampedX * yearRange);
243+
244+
// Only update if year is valid and in range
245+
if (calculatedYear >= minYear && calculatedYear <= maxYear) {
246+
hoveredYear = calculatedYear;
247+
// Trigger re-render to show reference lines
248+
gridLayer.render();
249+
}
250+
} else {
251+
// No valid years found, clear reference line
252+
if (hoveredYear !== null) {
253+
hoveredYear = null;
254+
gridLayer.render();
255+
}
256+
}
257+
258+
const yearSummary = Object.keys(yearGroups)
259+
.sort()
260+
.map(year => `${year}: ${yearGroups[year].total.toFixed(1)} kg CO₂`)
261+
.join('\n');
262+
263+
// console.log(`Cell at [${cell.col}, ${cell.row}]:\n${yearSummary}`);
264+
if (hoveredYear !== null) {
265+
console.log(`Hovered year: ${hoveredYear}`);
198266
}
199267
},
200268
onClick: ({ cell }) => {
@@ -223,44 +291,11 @@
223291

224292
map.addLayer(gridLayer);
225293

226-
// Add controls
227-
const controls = document.createElement('div');
228-
controls.style.cssText = `
229-
position: absolute;
230-
top: 10px;
231-
right: 10px;
232-
background: white;
233-
padding: 15px;
234-
border-radius: 8px;
235-
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
236-
z-index: 1000;
237-
font-family: Arial, sans-serif;
238-
font-size: 14px;
239-
`;
240-
241-
controls.innerHTML = `
242-
<h3 style="margin-top: 0; margin-bottom: 10px;">Visualization Options</h3>
243-
<button id="toggleGlyph" style="padding: 8px 12px; margin-bottom: 10px; cursor: pointer;">
244-
Switch to Bar Chart
245-
</button>
246-
<div style="font-size: 12px; color: #666;">
247-
Showing: <span id="currentMode">Time Series Line</span>
248-
</div>
249-
`;
250-
251-
document.body.appendChild(controls);
252-
253-
const toggleBtn = document.getElementById('toggleGlyph');
254-
const modeSpan = document.getElementById('currentMode');
255-
256-
toggleBtn.onclick = () => {
257-
useTimeSeries = !useTimeSeries;
258-
gridLayer.setConfig({
259-
onDrawCell: useTimeSeries ? drawTimeSeriesGlyph : drawTimeSeriesBarGlyph
260-
});
261-
toggleBtn.textContent = useTimeSeries ? 'Switch to Bar Chart' : 'Switch to Line Chart';
262-
modeSpan.textContent = useTimeSeries ? 'Time Series Line' : 'Time Series Bar';
263-
};
294+
// Clear hovered year when mouse leaves the map
295+
map.on('mouseout', () => {
296+
hoveredYear = null;
297+
gridLayer.render();
298+
});
264299
});
265300
</script>
266301
<style>

src/canvas/Renderer.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@ export class Renderer {
3131
return;
3232
}
3333

34-
console.log('Rendering grid:', {
35-
cols,
36-
rows,
37-
maxVal,
38-
cellsWithData: grid.filter((v) => v > 0).length,
39-
enableGlyphs,
40-
});
34+
// console.log('Rendering grid:', {
35+
// cols,
36+
// rows,
37+
// maxVal,
38+
// cellsWithData: grid.filter((v) => v > 0).length,
39+
// enableGlyphs,
40+
// });
4141

4242
// Clear canvas
4343
const dpr = window.devicePixelRatio || 1;

src/core/Aggregator.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@ export class Aggregator {
4848
}
4949

5050
const cellsWithData = grid.filter((v) => v > 0).length;
51-
console.log('Grid aggregation complete:', {
52-
cellsWithData,
53-
maxValue: Math.max(...grid),
54-
totalValue: grid.reduce((sum, v) => sum + v, 0),
55-
});
51+
// console.log('Grid aggregation complete:', {
52+
// cellsWithData,
53+
// maxValue: Math.max(...grid),
54+
// totalValue: grid.reduce((sum, v) => sum + v, 0),
55+
// });
5656

5757
return {
5858
grid,

0 commit comments

Comments
 (0)