]> git.openstreetmap.org Git - rails.git/blob - app/assets/javascripts/heatmap.js
b96a47a2208ac3e0797b31bc863f33782358bb32
[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).getMonth() + 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("click", (_event, timestamp) => {
74       if (!displayName) return;
75       for (const { date, max_id } of heatmapData) {
76         if (!max_id) continue;
77         if (timestamp !== Date.parse(date)) continue;
78         const params = new URLSearchParams([["before", max_id + 1]]);
79         location = `/user/${encodeURIComponent(displayName)}/history?${params}`;
80       }
81     });
82   }
83
84   function getTooltipText(date, value) {
85     const localizedDate = I18n.l("date.formats.long", date);
86
87     if (value > 0) {
88       return I18n.t("javascripts.heatmap.tooltip.contributions", { count: value, date: localizedDate });
89     }
90
91     return I18n.t("javascripts.heatmap.tooltip.no_contributions", { date: localizedDate });
92   }
93
94   function getTheme() {
95     if (colorScheme === "auto") {
96       return mediaQuery.matches ? "dark" : "light";
97     }
98
99     return colorScheme;
100   }
101
102   if (colorScheme === "auto") {
103     mediaQuery.addEventListener("change", (e) => {
104       currentTheme = e.matches ? "dark" : "light";
105       renderHeatmap();
106     });
107   }
108
109   renderHeatmap();
110 });
111
112