import h337 from "heatmap.js" namespace Clicks { export type ClickEvent = { x: number y: number tag: string } export type UrlEvent = { url: string } export type Event = ClickEvent | UrlEvent export interface Format { format: string version: string width: number height: number brands: string events: Event[] } } class PageData { readonly url: string readonly width: number readonly data: h337.HeatmapData constructor(url: string, width: number, data: h337.DataPoint[]) { this.url = url this.width = width let maxValue = 0 for (const d of data) { if (d.value > maxValue) maxValue = d.value } this.data = { data: data, min: 0, max: maxValue, } } } class Display { private status: HTMLElement private frame_container: HTMLElement private frame: HTMLIFrameElement private heatmap: HTMLElement private data: PageData[] | undefined private map: h337.Heatmap<"value", "x", "y"> constructor() { this.status = document.getElementById("status")!! this.frame_container = document.getElementById("frame_container")!! this.frame = document.getElementById("frame")!! as HTMLIFrameElement this.heatmap = document.getElementById("heatmap")!! this.map = h337.create({ container: this.heatmap, }) } loadFile(file: File) { this.setStatus("Loading...") const reader = new FileReader() reader.addEventListener("load", () => { const data = JSON.parse(reader.result as string) as Clicks.Format if (data.format !== "clicks" || data.version !== "1.0") { this.setStatus("Unknown format") return } this.data = [] const scaleX = data.width const scaleY = data.width let url: string | undefined let clicks: h337.DataPoint[] = [] let map = new Map() for (const event of data.events) { if ("url" in event) { if (url !== undefined) { console.log(clicks) this.data.push(new PageData(url, data.width, clicks)) } url = event.url clicks = [] map.clear() } else { const ce = event as Clicks.ClickEvent const x = Math.round(ce.x * scaleX) const y = Math.round(ce.y * scaleY) const pos = `${x}x${y}` const index = map.get(pos) if (index === undefined) { map.set(pos, clicks!!.length) clicks.push({ x: x, y: y, value: 1 }) } else { clicks[index].value++ } } } if (url !== undefined) { console.log(clicks) this.data.push(new PageData(url, data.width, clicks)) } this.setStatus("Loaded") if (this.data.length > 0) this.show(this.data[0]) }) reader.readAsText(file) } private setStatus(newStatus: string) { this.status.innerText = newStatus } private show(data: PageData) { this.frame_container.style.width = `${data.width}px` this.frame.src = data.url this.heatmap.style.transform = "translateY(0px)" this.frame.addEventListener("load", () => { this.frame.contentDocument?.addEventListener("scroll", () => { const y = this.frame.contentDocument!!.documentElement.scrollTop this.heatmap.style.transform = `translateY(${-y}px)` }) }) this.map.setData(data.data) } } document.addEventListener("DOMContentLoaded", () => { const file = document.getElementById("file") const display = new Display() file?.addEventListener("change", (event: Event) => { const files = (event.target as HTMLInputElement | null)!!.files if (files === null) return if (files.length > 0) { display.loadFile(files[0]) } }) })