summaryrefslogtreecommitdiff
path: root/src/clicks_display.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/clicks_display.ts')
-rw-r--r--src/clicks_display.ts137
1 files changed, 137 insertions, 0 deletions
diff --git a/src/clicks_display.ts b/src/clicks_display.ts
new file mode 100644
index 0000000..ff54705
--- /dev/null
+++ b/src/clicks_display.ts
@@ -0,0 +1,137 @@
+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 data: h337.HeatmapData<h337.DataPoint>
+
+ constructor(url: string, data: h337.DataPoint[]) {
+ this.url = url
+ 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: HTMLIFrameElement
+ private heatmap: HTMLElement
+ private data: PageData[] | undefined
+ private map: h337.Heatmap<"value", "x", "y">
+
+ constructor() {
+ this.status = document.getElementById("status")!!
+ 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 = this.frame.clientWidth
+ const scaleY = data.width
+ let url: string | undefined
+ let clicks: h337.DataPoint[] = []
+ let map = new Map<string, number>()
+ for (const event of data.events) {
+ if ("url" in event) {
+ if (url !== undefined) {
+ console.log(clicks)
+ this.data.push(new PageData(url, 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, 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.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])
+ }
+ })
+}) \ No newline at end of file