]> git.openstreetmap.org Git - rails.git/blob - app/assets/javascripts/heatmap.js
Move directions form buttons closer to edges
[rails.git] / app / assets / javascripts / heatmap.js
1 //= require d3/dist/d3
2 //= require cal-heatmap/dist/cal-heatmap
3 //= require popper
4 //= require cal-heatmap/dist/plugins/Tooltip
5
6 /* global CalHeatmap, Tooltip */
7 document.addEventListener("DOMContentLoaded", () => {
8   const heatmapElement = document.querySelector("#cal-heatmap");
9
10   if (!heatmapElement) {
11     return;
12   }
13
14   const heatmapData = heatmapElement.dataset.heatmap ? JSON.parse(heatmapElement.dataset.heatmap) : [];
15   const displayName = heatmapElement.dataset.displayName;
16   const colorScheme = document.documentElement.getAttribute("data-bs-theme") ?? "auto";
17   const rangeColors = ["#14432a", "#166b34", "#37a446", "#4dd05a"];
18   const startDate = new Date(Date.now() - (365 * 24 * 60 * 60 * 1000));
19   const monthNames = I18n.t("date.abbr_month_names");
20
21   const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
22
23   let cal = new CalHeatmap();
24   let currentTheme = getTheme();
25
26   function renderHeatmap() {
27     cal.destroy();
28     cal = new CalHeatmap();
29
30     cal.paint({
31       itemSelector: "#cal-heatmap",
32       theme: currentTheme,
33       domain: {
34         type: "month",
35         gutter: 4,
36         label: {
37           text: (timestamp) => monthNames[new Date(timestamp).getUTCMonth() + 1],
38           position: "top",
39           textAlign: "middle"
40         },
41         dynamicDimension: true
42       },
43       subDomain: {
44         type: "ghDay",
45         radius: 2,
46         width: 11,
47         height: 11,
48         gutter: 4
49       },
50       date: {
51         start: startDate
52       },
53       range: 13,
54       data: {
55         source: heatmapData,
56         type: "json",
57         x: "date",
58         y: "total_changes"
59       },
60       scale: {
61         color: {
62           type: "threshold",
63           range: currentTheme === "dark" ? rangeColors : Array.from(rangeColors).reverse(),
64           domain: [10, 20, 30, 40]
65         }
66       }
67     }, [
68       [Tooltip, {
69         text: (date, value) => getTooltipText(date, value)
70       }]
71     ]);
72
73     cal.on("mouseover", (event, _timestamp, value) => {
74       if (value) event.target.style.cursor = "pointer";
75     });
76
77     cal.on("click", (_event, timestamp) => {
78       if (!displayName) return;
79       for (const { date, max_id } of heatmapData) {
80         if (!max_id) continue;
81         if (timestamp !== Date.parse(date)) continue;
82         const params = new URLSearchParams([["before", max_id + 1]]);
83         location = `/user/${encodeURIComponent(displayName)}/history?${params}`;
84       }
85     });
86   }
87
88   function getTooltipText(date, value) {
89     const localizedDate = I18n.l("date.formats.long", date);
90
91     if (value > 0) {
92       return I18n.t("javascripts.heatmap.tooltip.contributions", { count: value, date: localizedDate });
93     }
94
95     return I18n.t("javascripts.heatmap.tooltip.no_contributions", { date: localizedDate });
96   }
97
98   function getTheme() {
99     if (colorScheme === "auto") {
100       return mediaQuery.matches ? "dark" : "light";
101     }
102
103     return colorScheme;
104   }
105
106   if (colorScheme === "auto") {
107     mediaQuery.addEventListener("change", (e) => {
108       currentTheme = e.matches ? "dark" : "light";
109       renderHeatmap();
110     });
111   }
112
113   renderHeatmap();
114 });
115
116