diff --git a/Resources/public/heatmap.css b/Resources/public/heatmap.css
index fecfe49..628d548 100644
--- a/Resources/public/heatmap.css
+++ b/Resources/public/heatmap.css
@@ -55,3 +55,25 @@
min-width: 140px;
max-width: 200px;
}
+
+.heatmap-weekend {
+ opacity: 0.8;
+}
+
+.heatmap-weekend:hover {
+ opacity: 0.65;
+}
+
+.heatmap-stats {
+ display: flex;
+ gap: 16px;
+ padding: 8px 0 0;
+ font-size: 0.8125rem;
+ color: var(--tblr-secondary, #6c757d);
+ flex-wrap: wrap;
+}
+
+.heatmap-stats .stat-value {
+ color: var(--tblr-body-color);
+ font-weight: 600;
+}
diff --git a/Resources/public/heatmap.js b/Resources/public/heatmap.js
index 794bf94..bb84665 100644
--- a/Resources/public/heatmap.js
+++ b/Resources/public/heatmap.js
@@ -1,2281 +1 @@
-"use strict";
-var KimaiHeatmap = (() => {
- var __defProp = Object.defineProperty;
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
- var __getOwnPropNames = Object.getOwnPropertyNames;
- var __hasOwnProp = Object.prototype.hasOwnProperty;
- var __export = (target, all) => {
- for (var name in all)
- __defProp(target, name, { get: all[name], enumerable: true });
- };
- var __copyProps = (to, from, except, desc) => {
- if (from && typeof from === "object" || typeof from === "function") {
- for (let key of __getOwnPropNames(from))
- if (!__hasOwnProp.call(to, key) && key !== except)
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
- }
- return to;
- };
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
-
- // assets/src/heatmap.ts
- var heatmap_exports = {};
- __export(heatmap_exports, {
- init: () => init,
- renderHeatmap: () => renderHeatmap
- });
-
- // node_modules/d3-selection/src/namespaces.js
- var xhtml = "http://www.w3.org/1999/xhtml";
- var namespaces_default = {
- svg: "http://www.w3.org/2000/svg",
- xhtml,
- xlink: "http://www.w3.org/1999/xlink",
- xml: "http://www.w3.org/XML/1998/namespace",
- xmlns: "http://www.w3.org/2000/xmlns/"
- };
-
- // node_modules/d3-selection/src/namespace.js
- function namespace_default(name) {
- var prefix = name += "", i = prefix.indexOf(":");
- if (i >= 0 && (prefix = name.slice(0, i)) !== "xmlns") name = name.slice(i + 1);
- return namespaces_default.hasOwnProperty(prefix) ? { space: namespaces_default[prefix], local: name } : name;
- }
-
- // node_modules/d3-selection/src/creator.js
- function creatorInherit(name) {
- return function() {
- var document2 = this.ownerDocument, uri = this.namespaceURI;
- return uri === xhtml && document2.documentElement.namespaceURI === xhtml ? document2.createElement(name) : document2.createElementNS(uri, name);
- };
- }
- function creatorFixed(fullname) {
- return function() {
- return this.ownerDocument.createElementNS(fullname.space, fullname.local);
- };
- }
- function creator_default(name) {
- var fullname = namespace_default(name);
- return (fullname.local ? creatorFixed : creatorInherit)(fullname);
- }
-
- // node_modules/d3-selection/src/selector.js
- function none() {
- }
- function selector_default(selector) {
- return selector == null ? none : function() {
- return this.querySelector(selector);
- };
- }
-
- // node_modules/d3-selection/src/selection/select.js
- function select_default(select) {
- if (typeof select !== "function") select = selector_default(select);
- for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) {
- for (var group = groups[j], n = group.length, subgroup = subgroups[j] = new Array(n), node, subnode, i = 0; i < n; ++i) {
- if ((node = group[i]) && (subnode = select.call(node, node.__data__, i, group))) {
- if ("__data__" in node) subnode.__data__ = node.__data__;
- subgroup[i] = subnode;
- }
- }
- }
- return new Selection(subgroups, this._parents);
- }
-
- // node_modules/d3-selection/src/array.js
- function array(x) {
- return x == null ? [] : Array.isArray(x) ? x : Array.from(x);
- }
-
- // node_modules/d3-selection/src/selectorAll.js
- function empty() {
- return [];
- }
- function selectorAll_default(selector) {
- return selector == null ? empty : function() {
- return this.querySelectorAll(selector);
- };
- }
-
- // node_modules/d3-selection/src/selection/selectAll.js
- function arrayAll(select) {
- return function() {
- return array(select.apply(this, arguments));
- };
- }
- function selectAll_default(select) {
- if (typeof select === "function") select = arrayAll(select);
- else select = selectorAll_default(select);
- for (var groups = this._groups, m = groups.length, subgroups = [], parents = [], j = 0; j < m; ++j) {
- for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) {
- if (node = group[i]) {
- subgroups.push(select.call(node, node.__data__, i, group));
- parents.push(node);
- }
- }
- }
- return new Selection(subgroups, parents);
- }
-
- // node_modules/d3-selection/src/matcher.js
- function matcher_default(selector) {
- return function() {
- return this.matches(selector);
- };
- }
- function childMatcher(selector) {
- return function(node) {
- return node.matches(selector);
- };
- }
-
- // node_modules/d3-selection/src/selection/selectChild.js
- var find = Array.prototype.find;
- function childFind(match) {
- return function() {
- return find.call(this.children, match);
- };
- }
- function childFirst() {
- return this.firstElementChild;
- }
- function selectChild_default(match) {
- return this.select(match == null ? childFirst : childFind(typeof match === "function" ? match : childMatcher(match)));
- }
-
- // node_modules/d3-selection/src/selection/selectChildren.js
- var filter = Array.prototype.filter;
- function children() {
- return Array.from(this.children);
- }
- function childrenFilter(match) {
- return function() {
- return filter.call(this.children, match);
- };
- }
- function selectChildren_default(match) {
- return this.selectAll(match == null ? children : childrenFilter(typeof match === "function" ? match : childMatcher(match)));
- }
-
- // node_modules/d3-selection/src/selection/filter.js
- function filter_default(match) {
- if (typeof match !== "function") match = matcher_default(match);
- for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) {
- for (var group = groups[j], n = group.length, subgroup = subgroups[j] = [], node, i = 0; i < n; ++i) {
- if ((node = group[i]) && match.call(node, node.__data__, i, group)) {
- subgroup.push(node);
- }
- }
- }
- return new Selection(subgroups, this._parents);
- }
-
- // node_modules/d3-selection/src/selection/sparse.js
- function sparse_default(update) {
- return new Array(update.length);
- }
-
- // node_modules/d3-selection/src/selection/enter.js
- function enter_default() {
- return new Selection(this._enter || this._groups.map(sparse_default), this._parents);
- }
- function EnterNode(parent, datum2) {
- this.ownerDocument = parent.ownerDocument;
- this.namespaceURI = parent.namespaceURI;
- this._next = null;
- this._parent = parent;
- this.__data__ = datum2;
- }
- EnterNode.prototype = {
- constructor: EnterNode,
- appendChild: function(child) {
- return this._parent.insertBefore(child, this._next);
- },
- insertBefore: function(child, next) {
- return this._parent.insertBefore(child, next);
- },
- querySelector: function(selector) {
- return this._parent.querySelector(selector);
- },
- querySelectorAll: function(selector) {
- return this._parent.querySelectorAll(selector);
- }
- };
-
- // node_modules/d3-selection/src/constant.js
- function constant_default(x) {
- return function() {
- return x;
- };
- }
-
- // node_modules/d3-selection/src/selection/data.js
- function bindIndex(parent, group, enter, update, exit, data) {
- var i = 0, node, groupLength = group.length, dataLength = data.length;
- for (; i < dataLength; ++i) {
- if (node = group[i]) {
- node.__data__ = data[i];
- update[i] = node;
- } else {
- enter[i] = new EnterNode(parent, data[i]);
- }
- }
- for (; i < groupLength; ++i) {
- if (node = group[i]) {
- exit[i] = node;
- }
- }
- }
- function bindKey(parent, group, enter, update, exit, data, key) {
- var i, node, nodeByKeyValue = /* @__PURE__ */ new Map(), groupLength = group.length, dataLength = data.length, keyValues = new Array(groupLength), keyValue;
- for (i = 0; i < groupLength; ++i) {
- if (node = group[i]) {
- keyValues[i] = keyValue = key.call(node, node.__data__, i, group) + "";
- if (nodeByKeyValue.has(keyValue)) {
- exit[i] = node;
- } else {
- nodeByKeyValue.set(keyValue, node);
- }
- }
- }
- for (i = 0; i < dataLength; ++i) {
- keyValue = key.call(parent, data[i], i, data) + "";
- if (node = nodeByKeyValue.get(keyValue)) {
- update[i] = node;
- node.__data__ = data[i];
- nodeByKeyValue.delete(keyValue);
- } else {
- enter[i] = new EnterNode(parent, data[i]);
- }
- }
- for (i = 0; i < groupLength; ++i) {
- if ((node = group[i]) && nodeByKeyValue.get(keyValues[i]) === node) {
- exit[i] = node;
- }
- }
- }
- function datum(node) {
- return node.__data__;
- }
- function data_default(value, key) {
- if (!arguments.length) return Array.from(this, datum);
- var bind = key ? bindKey : bindIndex, parents = this._parents, groups = this._groups;
- if (typeof value !== "function") value = constant_default(value);
- for (var m = groups.length, update = new Array(m), enter = new Array(m), exit = new Array(m), j = 0; j < m; ++j) {
- var parent = parents[j], group = groups[j], groupLength = group.length, data = arraylike(value.call(parent, parent && parent.__data__, j, parents)), dataLength = data.length, enterGroup = enter[j] = new Array(dataLength), updateGroup = update[j] = new Array(dataLength), exitGroup = exit[j] = new Array(groupLength);
- bind(parent, group, enterGroup, updateGroup, exitGroup, data, key);
- for (var i0 = 0, i1 = 0, previous, next; i0 < dataLength; ++i0) {
- if (previous = enterGroup[i0]) {
- if (i0 >= i1) i1 = i0 + 1;
- while (!(next = updateGroup[i1]) && ++i1 < dataLength) ;
- previous._next = next || null;
- }
- }
- }
- update = new Selection(update, parents);
- update._enter = enter;
- update._exit = exit;
- return update;
- }
- function arraylike(data) {
- return typeof data === "object" && "length" in data ? data : Array.from(data);
- }
-
- // node_modules/d3-selection/src/selection/exit.js
- function exit_default() {
- return new Selection(this._exit || this._groups.map(sparse_default), this._parents);
- }
-
- // node_modules/d3-selection/src/selection/join.js
- function join_default(onenter, onupdate, onexit) {
- var enter = this.enter(), update = this, exit = this.exit();
- if (typeof onenter === "function") {
- enter = onenter(enter);
- if (enter) enter = enter.selection();
- } else {
- enter = enter.append(onenter + "");
- }
- if (onupdate != null) {
- update = onupdate(update);
- if (update) update = update.selection();
- }
- if (onexit == null) exit.remove();
- else onexit(exit);
- return enter && update ? enter.merge(update).order() : update;
- }
-
- // node_modules/d3-selection/src/selection/merge.js
- function merge_default(context) {
- var selection2 = context.selection ? context.selection() : context;
- for (var groups0 = this._groups, groups1 = selection2._groups, m0 = groups0.length, m1 = groups1.length, m = Math.min(m0, m1), merges = new Array(m0), j = 0; j < m; ++j) {
- for (var group0 = groups0[j], group1 = groups1[j], n = group0.length, merge = merges[j] = new Array(n), node, i = 0; i < n; ++i) {
- if (node = group0[i] || group1[i]) {
- merge[i] = node;
- }
- }
- }
- for (; j < m0; ++j) {
- merges[j] = groups0[j];
- }
- return new Selection(merges, this._parents);
- }
-
- // node_modules/d3-selection/src/selection/order.js
- function order_default() {
- for (var groups = this._groups, j = -1, m = groups.length; ++j < m; ) {
- for (var group = groups[j], i = group.length - 1, next = group[i], node; --i >= 0; ) {
- if (node = group[i]) {
- if (next && node.compareDocumentPosition(next) ^ 4) next.parentNode.insertBefore(node, next);
- next = node;
- }
- }
- }
- return this;
- }
-
- // node_modules/d3-selection/src/selection/sort.js
- function sort_default(compare) {
- if (!compare) compare = ascending;
- function compareNode(a, b) {
- return a && b ? compare(a.__data__, b.__data__) : !a - !b;
- }
- for (var groups = this._groups, m = groups.length, sortgroups = new Array(m), j = 0; j < m; ++j) {
- for (var group = groups[j], n = group.length, sortgroup = sortgroups[j] = new Array(n), node, i = 0; i < n; ++i) {
- if (node = group[i]) {
- sortgroup[i] = node;
- }
- }
- sortgroup.sort(compareNode);
- }
- return new Selection(sortgroups, this._parents).order();
- }
- function ascending(a, b) {
- return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
- }
-
- // node_modules/d3-selection/src/selection/call.js
- function call_default() {
- var callback = arguments[0];
- arguments[0] = this;
- callback.apply(null, arguments);
- return this;
- }
-
- // node_modules/d3-selection/src/selection/nodes.js
- function nodes_default() {
- return Array.from(this);
- }
-
- // node_modules/d3-selection/src/selection/node.js
- function node_default() {
- for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) {
- for (var group = groups[j], i = 0, n = group.length; i < n; ++i) {
- var node = group[i];
- if (node) return node;
- }
- }
- return null;
- }
-
- // node_modules/d3-selection/src/selection/size.js
- function size_default() {
- let size = 0;
- for (const node of this) ++size;
- return size;
- }
-
- // node_modules/d3-selection/src/selection/empty.js
- function empty_default() {
- return !this.node();
- }
-
- // node_modules/d3-selection/src/selection/each.js
- function each_default(callback) {
- for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) {
- for (var group = groups[j], i = 0, n = group.length, node; i < n; ++i) {
- if (node = group[i]) callback.call(node, node.__data__, i, group);
- }
- }
- return this;
- }
-
- // node_modules/d3-selection/src/selection/attr.js
- function attrRemove(name) {
- return function() {
- this.removeAttribute(name);
- };
- }
- function attrRemoveNS(fullname) {
- return function() {
- this.removeAttributeNS(fullname.space, fullname.local);
- };
- }
- function attrConstant(name, value) {
- return function() {
- this.setAttribute(name, value);
- };
- }
- function attrConstantNS(fullname, value) {
- return function() {
- this.setAttributeNS(fullname.space, fullname.local, value);
- };
- }
- function attrFunction(name, value) {
- return function() {
- var v = value.apply(this, arguments);
- if (v == null) this.removeAttribute(name);
- else this.setAttribute(name, v);
- };
- }
- function attrFunctionNS(fullname, value) {
- return function() {
- var v = value.apply(this, arguments);
- if (v == null) this.removeAttributeNS(fullname.space, fullname.local);
- else this.setAttributeNS(fullname.space, fullname.local, v);
- };
- }
- function attr_default(name, value) {
- var fullname = namespace_default(name);
- if (arguments.length < 2) {
- var node = this.node();
- return fullname.local ? node.getAttributeNS(fullname.space, fullname.local) : node.getAttribute(fullname);
- }
- return this.each((value == null ? fullname.local ? attrRemoveNS : attrRemove : typeof value === "function" ? fullname.local ? attrFunctionNS : attrFunction : fullname.local ? attrConstantNS : attrConstant)(fullname, value));
- }
-
- // node_modules/d3-selection/src/window.js
- function window_default(node) {
- return node.ownerDocument && node.ownerDocument.defaultView || node.document && node || node.defaultView;
- }
-
- // node_modules/d3-selection/src/selection/style.js
- function styleRemove(name) {
- return function() {
- this.style.removeProperty(name);
- };
- }
- function styleConstant(name, value, priority) {
- return function() {
- this.style.setProperty(name, value, priority);
- };
- }
- function styleFunction(name, value, priority) {
- return function() {
- var v = value.apply(this, arguments);
- if (v == null) this.style.removeProperty(name);
- else this.style.setProperty(name, v, priority);
- };
- }
- function style_default(name, value, priority) {
- return arguments.length > 1 ? this.each((value == null ? styleRemove : typeof value === "function" ? styleFunction : styleConstant)(name, value, priority == null ? "" : priority)) : styleValue(this.node(), name);
- }
- function styleValue(node, name) {
- return node.style.getPropertyValue(name) || window_default(node).getComputedStyle(node, null).getPropertyValue(name);
- }
-
- // node_modules/d3-selection/src/selection/property.js
- function propertyRemove(name) {
- return function() {
- delete this[name];
- };
- }
- function propertyConstant(name, value) {
- return function() {
- this[name] = value;
- };
- }
- function propertyFunction(name, value) {
- return function() {
- var v = value.apply(this, arguments);
- if (v == null) delete this[name];
- else this[name] = v;
- };
- }
- function property_default(name, value) {
- return arguments.length > 1 ? this.each((value == null ? propertyRemove : typeof value === "function" ? propertyFunction : propertyConstant)(name, value)) : this.node()[name];
- }
-
- // node_modules/d3-selection/src/selection/classed.js
- function classArray(string) {
- return string.trim().split(/^|\s+/);
- }
- function classList(node) {
- return node.classList || new ClassList(node);
- }
- function ClassList(node) {
- this._node = node;
- this._names = classArray(node.getAttribute("class") || "");
- }
- ClassList.prototype = {
- add: function(name) {
- var i = this._names.indexOf(name);
- if (i < 0) {
- this._names.push(name);
- this._node.setAttribute("class", this._names.join(" "));
- }
- },
- remove: function(name) {
- var i = this._names.indexOf(name);
- if (i >= 0) {
- this._names.splice(i, 1);
- this._node.setAttribute("class", this._names.join(" "));
- }
- },
- contains: function(name) {
- return this._names.indexOf(name) >= 0;
- }
- };
- function classedAdd(node, names) {
- var list = classList(node), i = -1, n = names.length;
- while (++i < n) list.add(names[i]);
- }
- function classedRemove(node, names) {
- var list = classList(node), i = -1, n = names.length;
- while (++i < n) list.remove(names[i]);
- }
- function classedTrue(names) {
- return function() {
- classedAdd(this, names);
- };
- }
- function classedFalse(names) {
- return function() {
- classedRemove(this, names);
- };
- }
- function classedFunction(names, value) {
- return function() {
- (value.apply(this, arguments) ? classedAdd : classedRemove)(this, names);
- };
- }
- function classed_default(name, value) {
- var names = classArray(name + "");
- if (arguments.length < 2) {
- var list = classList(this.node()), i = -1, n = names.length;
- while (++i < n) if (!list.contains(names[i])) return false;
- return true;
- }
- return this.each((typeof value === "function" ? classedFunction : value ? classedTrue : classedFalse)(names, value));
- }
-
- // node_modules/d3-selection/src/selection/text.js
- function textRemove() {
- this.textContent = "";
- }
- function textConstant(value) {
- return function() {
- this.textContent = value;
- };
- }
- function textFunction(value) {
- return function() {
- var v = value.apply(this, arguments);
- this.textContent = v == null ? "" : v;
- };
- }
- function text_default(value) {
- return arguments.length ? this.each(value == null ? textRemove : (typeof value === "function" ? textFunction : textConstant)(value)) : this.node().textContent;
- }
-
- // node_modules/d3-selection/src/selection/html.js
- function htmlRemove() {
- this.innerHTML = "";
- }
- function htmlConstant(value) {
- return function() {
- this.innerHTML = value;
- };
- }
- function htmlFunction(value) {
- return function() {
- var v = value.apply(this, arguments);
- this.innerHTML = v == null ? "" : v;
- };
- }
- function html_default(value) {
- return arguments.length ? this.each(value == null ? htmlRemove : (typeof value === "function" ? htmlFunction : htmlConstant)(value)) : this.node().innerHTML;
- }
-
- // node_modules/d3-selection/src/selection/raise.js
- function raise() {
- if (this.nextSibling) this.parentNode.appendChild(this);
- }
- function raise_default() {
- return this.each(raise);
- }
-
- // node_modules/d3-selection/src/selection/lower.js
- function lower() {
- if (this.previousSibling) this.parentNode.insertBefore(this, this.parentNode.firstChild);
- }
- function lower_default() {
- return this.each(lower);
- }
-
- // node_modules/d3-selection/src/selection/append.js
- function append_default(name) {
- var create = typeof name === "function" ? name : creator_default(name);
- return this.select(function() {
- return this.appendChild(create.apply(this, arguments));
- });
- }
-
- // node_modules/d3-selection/src/selection/insert.js
- function constantNull() {
- return null;
- }
- function insert_default(name, before) {
- var create = typeof name === "function" ? name : creator_default(name), select = before == null ? constantNull : typeof before === "function" ? before : selector_default(before);
- return this.select(function() {
- return this.insertBefore(create.apply(this, arguments), select.apply(this, arguments) || null);
- });
- }
-
- // node_modules/d3-selection/src/selection/remove.js
- function remove() {
- var parent = this.parentNode;
- if (parent) parent.removeChild(this);
- }
- function remove_default() {
- return this.each(remove);
- }
-
- // node_modules/d3-selection/src/selection/clone.js
- function selection_cloneShallow() {
- var clone = this.cloneNode(false), parent = this.parentNode;
- return parent ? parent.insertBefore(clone, this.nextSibling) : clone;
- }
- function selection_cloneDeep() {
- var clone = this.cloneNode(true), parent = this.parentNode;
- return parent ? parent.insertBefore(clone, this.nextSibling) : clone;
- }
- function clone_default(deep) {
- return this.select(deep ? selection_cloneDeep : selection_cloneShallow);
- }
-
- // node_modules/d3-selection/src/selection/datum.js
- function datum_default(value) {
- return arguments.length ? this.property("__data__", value) : this.node().__data__;
- }
-
- // node_modules/d3-selection/src/selection/on.js
- function contextListener(listener) {
- return function(event) {
- listener.call(this, event, this.__data__);
- };
- }
- function parseTypenames(typenames) {
- return typenames.trim().split(/^|\s+/).map(function(t) {
- var name = "", i = t.indexOf(".");
- if (i >= 0) name = t.slice(i + 1), t = t.slice(0, i);
- return { type: t, name };
- });
- }
- function onRemove(typename) {
- return function() {
- var on = this.__on;
- if (!on) return;
- for (var j = 0, i = -1, m = on.length, o; j < m; ++j) {
- if (o = on[j], (!typename.type || o.type === typename.type) && o.name === typename.name) {
- this.removeEventListener(o.type, o.listener, o.options);
- } else {
- on[++i] = o;
- }
- }
- if (++i) on.length = i;
- else delete this.__on;
- };
- }
- function onAdd(typename, value, options) {
- return function() {
- var on = this.__on, o, listener = contextListener(value);
- if (on) for (var j = 0, m = on.length; j < m; ++j) {
- if ((o = on[j]).type === typename.type && o.name === typename.name) {
- this.removeEventListener(o.type, o.listener, o.options);
- this.addEventListener(o.type, o.listener = listener, o.options = options);
- o.value = value;
- return;
- }
- }
- this.addEventListener(typename.type, listener, options);
- o = { type: typename.type, name: typename.name, value, listener, options };
- if (!on) this.__on = [o];
- else on.push(o);
- };
- }
- function on_default(typename, value, options) {
- var typenames = parseTypenames(typename + ""), i, n = typenames.length, t;
- if (arguments.length < 2) {
- var on = this.node().__on;
- if (on) for (var j = 0, m = on.length, o; j < m; ++j) {
- for (i = 0, o = on[j]; i < n; ++i) {
- if ((t = typenames[i]).type === o.type && t.name === o.name) {
- return o.value;
- }
- }
- }
- return;
- }
- on = value ? onAdd : onRemove;
- for (i = 0; i < n; ++i) this.each(on(typenames[i], value, options));
- return this;
- }
-
- // node_modules/d3-selection/src/selection/dispatch.js
- function dispatchEvent(node, type, params) {
- var window2 = window_default(node), event = window2.CustomEvent;
- if (typeof event === "function") {
- event = new event(type, params);
- } else {
- event = window2.document.createEvent("Event");
- if (params) event.initEvent(type, params.bubbles, params.cancelable), event.detail = params.detail;
- else event.initEvent(type, false, false);
- }
- node.dispatchEvent(event);
- }
- function dispatchConstant(type, params) {
- return function() {
- return dispatchEvent(this, type, params);
- };
- }
- function dispatchFunction(type, params) {
- return function() {
- return dispatchEvent(this, type, params.apply(this, arguments));
- };
- }
- function dispatch_default(type, params) {
- return this.each((typeof params === "function" ? dispatchFunction : dispatchConstant)(type, params));
- }
-
- // node_modules/d3-selection/src/selection/iterator.js
- function* iterator_default() {
- for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) {
- for (var group = groups[j], i = 0, n = group.length, node; i < n; ++i) {
- if (node = group[i]) yield node;
- }
- }
- }
-
- // node_modules/d3-selection/src/selection/index.js
- var root = [null];
- function Selection(groups, parents) {
- this._groups = groups;
- this._parents = parents;
- }
- function selection() {
- return new Selection([[document.documentElement]], root);
- }
- function selection_selection() {
- return this;
- }
- Selection.prototype = selection.prototype = {
- constructor: Selection,
- select: select_default,
- selectAll: selectAll_default,
- selectChild: selectChild_default,
- selectChildren: selectChildren_default,
- filter: filter_default,
- data: data_default,
- enter: enter_default,
- exit: exit_default,
- join: join_default,
- merge: merge_default,
- selection: selection_selection,
- order: order_default,
- sort: sort_default,
- call: call_default,
- nodes: nodes_default,
- node: node_default,
- size: size_default,
- empty: empty_default,
- each: each_default,
- attr: attr_default,
- style: style_default,
- property: property_default,
- classed: classed_default,
- text: text_default,
- html: html_default,
- raise: raise_default,
- lower: lower_default,
- append: append_default,
- insert: insert_default,
- remove: remove_default,
- clone: clone_default,
- datum: datum_default,
- on: on_default,
- dispatch: dispatch_default,
- [Symbol.iterator]: iterator_default
- };
-
- // node_modules/d3-selection/src/select.js
- function select_default2(selector) {
- return typeof selector === "string" ? new Selection([[document.querySelector(selector)]], [document.documentElement]) : new Selection([[selector]], root);
- }
-
- // node_modules/d3-array/src/ascending.js
- function ascending2(a, b) {
- return a == null || b == null ? NaN : a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
- }
-
- // node_modules/d3-array/src/descending.js
- function descending(a, b) {
- return a == null || b == null ? NaN : b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN;
- }
-
- // node_modules/d3-array/src/bisector.js
- function bisector(f) {
- let compare1, compare2, delta;
- if (f.length !== 2) {
- compare1 = ascending2;
- compare2 = (d, x) => ascending2(f(d), x);
- delta = (d, x) => f(d) - x;
- } else {
- compare1 = f === ascending2 || f === descending ? f : zero;
- compare2 = f;
- delta = f;
- }
- function left(a, x, lo = 0, hi = a.length) {
- if (lo < hi) {
- if (compare1(x, x) !== 0) return hi;
- do {
- const mid = lo + hi >>> 1;
- if (compare2(a[mid], x) < 0) lo = mid + 1;
- else hi = mid;
- } while (lo < hi);
- }
- return lo;
- }
- function right(a, x, lo = 0, hi = a.length) {
- if (lo < hi) {
- if (compare1(x, x) !== 0) return hi;
- do {
- const mid = lo + hi >>> 1;
- if (compare2(a[mid], x) <= 0) lo = mid + 1;
- else hi = mid;
- } while (lo < hi);
- }
- return lo;
- }
- function center(a, x, lo = 0, hi = a.length) {
- const i = left(a, x, lo, hi - 1);
- return i > lo && delta(a[i - 1], x) > -delta(a[i], x) ? i - 1 : i;
- }
- return { left, center, right };
- }
- function zero() {
- return 0;
- }
-
- // node_modules/d3-array/src/number.js
- function number(x) {
- return x === null ? NaN : +x;
- }
-
- // node_modules/d3-array/src/bisect.js
- var ascendingBisect = bisector(ascending2);
- var bisectRight = ascendingBisect.right;
- var bisectLeft = ascendingBisect.left;
- var bisectCenter = bisector(number).center;
- var bisect_default = bisectRight;
-
- // node_modules/d3-array/src/ticks.js
- var e10 = Math.sqrt(50);
- var e5 = Math.sqrt(10);
- var e2 = Math.sqrt(2);
- function tickSpec(start, stop, count) {
- const step = (stop - start) / Math.max(0, count), power = Math.floor(Math.log10(step)), error = step / Math.pow(10, power), factor = error >= e10 ? 10 : error >= e5 ? 5 : error >= e2 ? 2 : 1;
- let i1, i2, inc;
- if (power < 0) {
- inc = Math.pow(10, -power) / factor;
- i1 = Math.round(start * inc);
- i2 = Math.round(stop * inc);
- if (i1 / inc < start) ++i1;
- if (i2 / inc > stop) --i2;
- inc = -inc;
- } else {
- inc = Math.pow(10, power) * factor;
- i1 = Math.round(start / inc);
- i2 = Math.round(stop / inc);
- if (i1 * inc < start) ++i1;
- if (i2 * inc > stop) --i2;
- }
- if (i2 < i1 && 0.5 <= count && count < 2) return tickSpec(start, stop, count * 2);
- return [i1, i2, inc];
- }
- function ticks(start, stop, count) {
- stop = +stop, start = +start, count = +count;
- if (!(count > 0)) return [];
- if (start === stop) return [start];
- const reverse = stop < start, [i1, i2, inc] = reverse ? tickSpec(stop, start, count) : tickSpec(start, stop, count);
- if (!(i2 >= i1)) return [];
- const n = i2 - i1 + 1, ticks2 = new Array(n);
- if (reverse) {
- if (inc < 0) for (let i = 0; i < n; ++i) ticks2[i] = (i2 - i) / -inc;
- else for (let i = 0; i < n; ++i) ticks2[i] = (i2 - i) * inc;
- } else {
- if (inc < 0) for (let i = 0; i < n; ++i) ticks2[i] = (i1 + i) / -inc;
- else for (let i = 0; i < n; ++i) ticks2[i] = (i1 + i) * inc;
- }
- return ticks2;
- }
- function tickIncrement(start, stop, count) {
- stop = +stop, start = +start, count = +count;
- return tickSpec(start, stop, count)[2];
- }
- function tickStep(start, stop, count) {
- stop = +stop, start = +start, count = +count;
- const reverse = stop < start, inc = reverse ? tickIncrement(stop, start, count) : tickIncrement(start, stop, count);
- return (reverse ? -1 : 1) * (inc < 0 ? 1 / -inc : inc);
- }
-
- // node_modules/d3-array/src/max.js
- function max(values, valueof) {
- let max2;
- if (valueof === void 0) {
- for (const value of values) {
- if (value != null && (max2 < value || max2 === void 0 && value >= value)) {
- max2 = value;
- }
- }
- } else {
- let index = -1;
- for (let value of values) {
- if ((value = valueof(value, ++index, values)) != null && (max2 < value || max2 === void 0 && value >= value)) {
- max2 = value;
- }
- }
- }
- return max2;
- }
-
- // node_modules/d3-scale/src/init.js
- function initRange(domain, range) {
- switch (arguments.length) {
- case 0:
- break;
- case 1:
- this.range(domain);
- break;
- default:
- this.range(range).domain(domain);
- break;
- }
- return this;
- }
-
- // node_modules/d3-format/src/formatDecimal.js
- function formatDecimal_default(x) {
- return Math.abs(x = Math.round(x)) >= 1e21 ? x.toLocaleString("en").replace(/,/g, "") : x.toString(10);
- }
- function formatDecimalParts(x, p) {
- if (!isFinite(x) || x === 0) return null;
- var i = (x = p ? x.toExponential(p - 1) : x.toExponential()).indexOf("e"), coefficient = x.slice(0, i);
- return [
- coefficient.length > 1 ? coefficient[0] + coefficient.slice(2) : coefficient,
- +x.slice(i + 1)
- ];
- }
-
- // node_modules/d3-format/src/exponent.js
- function exponent_default(x) {
- return x = formatDecimalParts(Math.abs(x)), x ? x[1] : NaN;
- }
-
- // node_modules/d3-format/src/formatGroup.js
- function formatGroup_default(grouping, thousands) {
- return function(value, width) {
- var i = value.length, t = [], j = 0, g = grouping[0], length = 0;
- while (i > 0 && g > 0) {
- if (length + g + 1 > width) g = Math.max(1, width - length);
- t.push(value.substring(i -= g, i + g));
- if ((length += g + 1) > width) break;
- g = grouping[j = (j + 1) % grouping.length];
- }
- return t.reverse().join(thousands);
- };
- }
-
- // node_modules/d3-format/src/formatNumerals.js
- function formatNumerals_default(numerals) {
- return function(value) {
- return value.replace(/[0-9]/g, function(i) {
- return numerals[+i];
- });
- };
- }
-
- // node_modules/d3-format/src/formatSpecifier.js
- var re = /^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;
- function formatSpecifier(specifier) {
- if (!(match = re.exec(specifier))) throw new Error("invalid format: " + specifier);
- var match;
- return new FormatSpecifier({
- fill: match[1],
- align: match[2],
- sign: match[3],
- symbol: match[4],
- zero: match[5],
- width: match[6],
- comma: match[7],
- precision: match[8] && match[8].slice(1),
- trim: match[9],
- type: match[10]
- });
- }
- formatSpecifier.prototype = FormatSpecifier.prototype;
- function FormatSpecifier(specifier) {
- this.fill = specifier.fill === void 0 ? " " : specifier.fill + "";
- this.align = specifier.align === void 0 ? ">" : specifier.align + "";
- this.sign = specifier.sign === void 0 ? "-" : specifier.sign + "";
- this.symbol = specifier.symbol === void 0 ? "" : specifier.symbol + "";
- this.zero = !!specifier.zero;
- this.width = specifier.width === void 0 ? void 0 : +specifier.width;
- this.comma = !!specifier.comma;
- this.precision = specifier.precision === void 0 ? void 0 : +specifier.precision;
- this.trim = !!specifier.trim;
- this.type = specifier.type === void 0 ? "" : specifier.type + "";
- }
- FormatSpecifier.prototype.toString = function() {
- return this.fill + this.align + this.sign + this.symbol + (this.zero ? "0" : "") + (this.width === void 0 ? "" : Math.max(1, this.width | 0)) + (this.comma ? "," : "") + (this.precision === void 0 ? "" : "." + Math.max(0, this.precision | 0)) + (this.trim ? "~" : "") + this.type;
- };
-
- // node_modules/d3-format/src/formatTrim.js
- function formatTrim_default(s) {
- out: for (var n = s.length, i = 1, i0 = -1, i1; i < n; ++i) {
- switch (s[i]) {
- case ".":
- i0 = i1 = i;
- break;
- case "0":
- if (i0 === 0) i0 = i;
- i1 = i;
- break;
- default:
- if (!+s[i]) break out;
- if (i0 > 0) i0 = 0;
- break;
- }
- }
- return i0 > 0 ? s.slice(0, i0) + s.slice(i1 + 1) : s;
- }
-
- // node_modules/d3-format/src/formatPrefixAuto.js
- var prefixExponent;
- function formatPrefixAuto_default(x, p) {
- var d = formatDecimalParts(x, p);
- if (!d) return prefixExponent = void 0, x.toPrecision(p);
- var coefficient = d[0], exponent = d[1], i = exponent - (prefixExponent = Math.max(-8, Math.min(8, Math.floor(exponent / 3))) * 3) + 1, n = coefficient.length;
- return i === n ? coefficient : i > n ? coefficient + new Array(i - n + 1).join("0") : i > 0 ? coefficient.slice(0, i) + "." + coefficient.slice(i) : "0." + new Array(1 - i).join("0") + formatDecimalParts(x, Math.max(0, p + i - 1))[0];
- }
-
- // node_modules/d3-format/src/formatRounded.js
- function formatRounded_default(x, p) {
- var d = formatDecimalParts(x, p);
- if (!d) return x + "";
- var coefficient = d[0], exponent = d[1];
- return exponent < 0 ? "0." + new Array(-exponent).join("0") + coefficient : coefficient.length > exponent + 1 ? coefficient.slice(0, exponent + 1) + "." + coefficient.slice(exponent + 1) : coefficient + new Array(exponent - coefficient.length + 2).join("0");
- }
-
- // node_modules/d3-format/src/formatTypes.js
- var formatTypes_default = {
- "%": (x, p) => (x * 100).toFixed(p),
- "b": (x) => Math.round(x).toString(2),
- "c": (x) => x + "",
- "d": formatDecimal_default,
- "e": (x, p) => x.toExponential(p),
- "f": (x, p) => x.toFixed(p),
- "g": (x, p) => x.toPrecision(p),
- "o": (x) => Math.round(x).toString(8),
- "p": (x, p) => formatRounded_default(x * 100, p),
- "r": formatRounded_default,
- "s": formatPrefixAuto_default,
- "X": (x) => Math.round(x).toString(16).toUpperCase(),
- "x": (x) => Math.round(x).toString(16)
- };
-
- // node_modules/d3-format/src/identity.js
- function identity_default(x) {
- return x;
- }
-
- // node_modules/d3-format/src/locale.js
- var map = Array.prototype.map;
- var prefixes = ["y", "z", "a", "f", "p", "n", "\xB5", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y"];
- function locale_default(locale3) {
- var group = locale3.grouping === void 0 || locale3.thousands === void 0 ? identity_default : formatGroup_default(map.call(locale3.grouping, Number), locale3.thousands + ""), currencyPrefix = locale3.currency === void 0 ? "" : locale3.currency[0] + "", currencySuffix = locale3.currency === void 0 ? "" : locale3.currency[1] + "", decimal = locale3.decimal === void 0 ? "." : locale3.decimal + "", numerals = locale3.numerals === void 0 ? identity_default : formatNumerals_default(map.call(locale3.numerals, String)), percent = locale3.percent === void 0 ? "%" : locale3.percent + "", minus = locale3.minus === void 0 ? "\u2212" : locale3.minus + "", nan = locale3.nan === void 0 ? "NaN" : locale3.nan + "";
- function newFormat(specifier, options) {
- specifier = formatSpecifier(specifier);
- var fill = specifier.fill, align = specifier.align, sign = specifier.sign, symbol = specifier.symbol, zero2 = specifier.zero, width = specifier.width, comma = specifier.comma, precision = specifier.precision, trim = specifier.trim, type = specifier.type;
- if (type === "n") comma = true, type = "g";
- else if (!formatTypes_default[type]) precision === void 0 && (precision = 12), trim = true, type = "g";
- if (zero2 || fill === "0" && align === "=") zero2 = true, fill = "0", align = "=";
- var prefix = (options && options.prefix !== void 0 ? options.prefix : "") + (symbol === "$" ? currencyPrefix : symbol === "#" && /[boxX]/.test(type) ? "0" + type.toLowerCase() : ""), suffix = (symbol === "$" ? currencySuffix : /[%p]/.test(type) ? percent : "") + (options && options.suffix !== void 0 ? options.suffix : "");
- var formatType = formatTypes_default[type], maybeSuffix = /[defgprs%]/.test(type);
- precision = precision === void 0 ? 6 : /[gprs]/.test(type) ? Math.max(1, Math.min(21, precision)) : Math.max(0, Math.min(20, precision));
- function format2(value) {
- var valuePrefix = prefix, valueSuffix = suffix, i, n, c;
- if (type === "c") {
- valueSuffix = formatType(value) + valueSuffix;
- value = "";
- } else {
- value = +value;
- var valueNegative = value < 0 || 1 / value < 0;
- value = isNaN(value) ? nan : formatType(Math.abs(value), precision);
- if (trim) value = formatTrim_default(value);
- if (valueNegative && +value === 0 && sign !== "+") valueNegative = false;
- valuePrefix = (valueNegative ? sign === "(" ? sign : minus : sign === "-" || sign === "(" ? "" : sign) + valuePrefix;
- valueSuffix = (type === "s" && !isNaN(value) && prefixExponent !== void 0 ? prefixes[8 + prefixExponent / 3] : "") + valueSuffix + (valueNegative && sign === "(" ? ")" : "");
- if (maybeSuffix) {
- i = -1, n = value.length;
- while (++i < n) {
- if (c = value.charCodeAt(i), 48 > c || c > 57) {
- valueSuffix = (c === 46 ? decimal + value.slice(i + 1) : value.slice(i)) + valueSuffix;
- value = value.slice(0, i);
- break;
- }
- }
- }
- }
- if (comma && !zero2) value = group(value, Infinity);
- var length = valuePrefix.length + value.length + valueSuffix.length, padding = length < width ? new Array(width - length + 1).join(fill) : "";
- if (comma && zero2) value = group(padding + value, padding.length ? width - valueSuffix.length : Infinity), padding = "";
- switch (align) {
- case "<":
- value = valuePrefix + value + valueSuffix + padding;
- break;
- case "=":
- value = valuePrefix + padding + value + valueSuffix;
- break;
- case "^":
- value = padding.slice(0, length = padding.length >> 1) + valuePrefix + value + valueSuffix + padding.slice(length);
- break;
- default:
- value = padding + valuePrefix + value + valueSuffix;
- break;
- }
- return numerals(value);
- }
- format2.toString = function() {
- return specifier + "";
- };
- return format2;
- }
- function formatPrefix2(specifier, value) {
- var e = Math.max(-8, Math.min(8, Math.floor(exponent_default(value) / 3))) * 3, k = Math.pow(10, -e), f = newFormat((specifier = formatSpecifier(specifier), specifier.type = "f", specifier), { suffix: prefixes[8 + e / 3] });
- return function(value2) {
- return f(k * value2);
- };
- }
- return {
- format: newFormat,
- formatPrefix: formatPrefix2
- };
- }
-
- // node_modules/d3-format/src/defaultLocale.js
- var locale;
- var format;
- var formatPrefix;
- defaultLocale({
- thousands: ",",
- grouping: [3],
- currency: ["$", ""]
- });
- function defaultLocale(definition) {
- locale = locale_default(definition);
- format = locale.format;
- formatPrefix = locale.formatPrefix;
- return locale;
- }
-
- // node_modules/d3-format/src/precisionFixed.js
- function precisionFixed_default(step) {
- return Math.max(0, -exponent_default(Math.abs(step)));
- }
-
- // node_modules/d3-format/src/precisionPrefix.js
- function precisionPrefix_default(step, value) {
- return Math.max(0, Math.max(-8, Math.min(8, Math.floor(exponent_default(value) / 3))) * 3 - exponent_default(Math.abs(step)));
- }
-
- // node_modules/d3-format/src/precisionRound.js
- function precisionRound_default(step, max2) {
- step = Math.abs(step), max2 = Math.abs(max2) - step;
- return Math.max(0, exponent_default(max2) - exponent_default(step)) + 1;
- }
-
- // node_modules/d3-scale/src/tickFormat.js
- function tickFormat(start, stop, count, specifier) {
- var step = tickStep(start, stop, count), precision;
- specifier = formatSpecifier(specifier == null ? ",f" : specifier);
- switch (specifier.type) {
- case "s": {
- var value = Math.max(Math.abs(start), Math.abs(stop));
- if (specifier.precision == null && !isNaN(precision = precisionPrefix_default(step, value))) specifier.precision = precision;
- return formatPrefix(specifier, value);
- }
- case "":
- case "e":
- case "g":
- case "p":
- case "r": {
- if (specifier.precision == null && !isNaN(precision = precisionRound_default(step, Math.max(Math.abs(start), Math.abs(stop))))) specifier.precision = precision - (specifier.type === "e");
- break;
- }
- case "f":
- case "%": {
- if (specifier.precision == null && !isNaN(precision = precisionFixed_default(step))) specifier.precision = precision - (specifier.type === "%") * 2;
- break;
- }
- }
- return format(specifier);
- }
-
- // node_modules/d3-scale/src/linear.js
- function linearish(scale) {
- var domain = scale.domain;
- scale.ticks = function(count) {
- var d = domain();
- return ticks(d[0], d[d.length - 1], count == null ? 10 : count);
- };
- scale.tickFormat = function(count, specifier) {
- var d = domain();
- return tickFormat(d[0], d[d.length - 1], count == null ? 10 : count, specifier);
- };
- scale.nice = function(count) {
- if (count == null) count = 10;
- var d = domain();
- var i0 = 0;
- var i1 = d.length - 1;
- var start = d[i0];
- var stop = d[i1];
- var prestep;
- var step;
- var maxIter = 10;
- if (stop < start) {
- step = start, start = stop, stop = step;
- step = i0, i0 = i1, i1 = step;
- }
- while (maxIter-- > 0) {
- step = tickIncrement(start, stop, count);
- if (step === prestep) {
- d[i0] = start;
- d[i1] = stop;
- return domain(d);
- } else if (step > 0) {
- start = Math.floor(start / step) * step;
- stop = Math.ceil(stop / step) * step;
- } else if (step < 0) {
- start = Math.ceil(start * step) / step;
- stop = Math.floor(stop * step) / step;
- } else {
- break;
- }
- prestep = step;
- }
- return scale;
- };
- return scale;
- }
-
- // node_modules/d3-scale/src/quantize.js
- function quantize() {
- var x0 = 0, x1 = 1, n = 1, domain = [0.5], range = [0, 1], unknown;
- function scale(x) {
- return x != null && x <= x ? range[bisect_default(domain, x, 0, n)] : unknown;
- }
- function rescale() {
- var i = -1;
- domain = new Array(n);
- while (++i < n) domain[i] = ((i + 1) * x1 - (i - n) * x0) / (n + 1);
- return scale;
- }
- scale.domain = function(_) {
- return arguments.length ? ([x0, x1] = _, x0 = +x0, x1 = +x1, rescale()) : [x0, x1];
- };
- scale.range = function(_) {
- return arguments.length ? (n = (range = Array.from(_)).length - 1, rescale()) : range.slice();
- };
- scale.invertExtent = function(y) {
- var i = range.indexOf(y);
- return i < 0 ? [NaN, NaN] : i < 1 ? [x0, domain[0]] : i >= n ? [domain[n - 1], x1] : [domain[i - 1], domain[i]];
- };
- scale.unknown = function(_) {
- return arguments.length ? (unknown = _, scale) : scale;
- };
- scale.thresholds = function() {
- return domain.slice();
- };
- scale.copy = function() {
- return quantize().domain([x0, x1]).range(range).unknown(unknown);
- };
- return initRange.apply(linearish(scale), arguments);
- }
-
- // node_modules/d3-time/src/interval.js
- var t0 = /* @__PURE__ */ new Date();
- var t1 = /* @__PURE__ */ new Date();
- function timeInterval(floori, offseti, count, field) {
- function interval(date) {
- return floori(date = arguments.length === 0 ? /* @__PURE__ */ new Date() : /* @__PURE__ */ new Date(+date)), date;
- }
- interval.floor = (date) => {
- return floori(date = /* @__PURE__ */ new Date(+date)), date;
- };
- interval.ceil = (date) => {
- return floori(date = new Date(date - 1)), offseti(date, 1), floori(date), date;
- };
- interval.round = (date) => {
- const d0 = interval(date), d1 = interval.ceil(date);
- return date - d0 < d1 - date ? d0 : d1;
- };
- interval.offset = (date, step) => {
- return offseti(date = /* @__PURE__ */ new Date(+date), step == null ? 1 : Math.floor(step)), date;
- };
- interval.range = (start, stop, step) => {
- const range = [];
- start = interval.ceil(start);
- step = step == null ? 1 : Math.floor(step);
- if (!(start < stop) || !(step > 0)) return range;
- let previous;
- do
- range.push(previous = /* @__PURE__ */ new Date(+start)), offseti(start, step), floori(start);
- while (previous < start && start < stop);
- return range;
- };
- interval.filter = (test) => {
- return timeInterval((date) => {
- if (date >= date) while (floori(date), !test(date)) date.setTime(date - 1);
- }, (date, step) => {
- if (date >= date) {
- if (step < 0) while (++step <= 0) {
- while (offseti(date, -1), !test(date)) {
- }
- }
- else while (--step >= 0) {
- while (offseti(date, 1), !test(date)) {
- }
- }
- }
- });
- };
- if (count) {
- interval.count = (start, end) => {
- t0.setTime(+start), t1.setTime(+end);
- floori(t0), floori(t1);
- return Math.floor(count(t0, t1));
- };
- interval.every = (step) => {
- step = Math.floor(step);
- return !isFinite(step) || !(step > 0) ? null : !(step > 1) ? interval : interval.filter(field ? (d) => field(d) % step === 0 : (d) => interval.count(0, d) % step === 0);
- };
- }
- return interval;
- }
-
- // node_modules/d3-time/src/duration.js
- var durationSecond = 1e3;
- var durationMinute = durationSecond * 60;
- var durationHour = durationMinute * 60;
- var durationDay = durationHour * 24;
- var durationWeek = durationDay * 7;
- var durationMonth = durationDay * 30;
- var durationYear = durationDay * 365;
-
- // node_modules/d3-time/src/day.js
- var timeDay = timeInterval(
- (date) => date.setHours(0, 0, 0, 0),
- (date, step) => date.setDate(date.getDate() + step),
- (start, end) => (end - start - (end.getTimezoneOffset() - start.getTimezoneOffset()) * durationMinute) / durationDay,
- (date) => date.getDate() - 1
- );
- var timeDays = timeDay.range;
- var utcDay = timeInterval((date) => {
- date.setUTCHours(0, 0, 0, 0);
- }, (date, step) => {
- date.setUTCDate(date.getUTCDate() + step);
- }, (start, end) => {
- return (end - start) / durationDay;
- }, (date) => {
- return date.getUTCDate() - 1;
- });
- var utcDays = utcDay.range;
- var unixDay = timeInterval((date) => {
- date.setUTCHours(0, 0, 0, 0);
- }, (date, step) => {
- date.setUTCDate(date.getUTCDate() + step);
- }, (start, end) => {
- return (end - start) / durationDay;
- }, (date) => {
- return Math.floor(date / durationDay);
- });
- var unixDays = unixDay.range;
-
- // node_modules/d3-time/src/week.js
- function timeWeekday(i) {
- return timeInterval((date) => {
- date.setDate(date.getDate() - (date.getDay() + 7 - i) % 7);
- date.setHours(0, 0, 0, 0);
- }, (date, step) => {
- date.setDate(date.getDate() + step * 7);
- }, (start, end) => {
- return (end - start - (end.getTimezoneOffset() - start.getTimezoneOffset()) * durationMinute) / durationWeek;
- });
- }
- var timeSunday = timeWeekday(0);
- var timeMonday = timeWeekday(1);
- var timeTuesday = timeWeekday(2);
- var timeWednesday = timeWeekday(3);
- var timeThursday = timeWeekday(4);
- var timeFriday = timeWeekday(5);
- var timeSaturday = timeWeekday(6);
- var timeSundays = timeSunday.range;
- var timeMondays = timeMonday.range;
- var timeTuesdays = timeTuesday.range;
- var timeWednesdays = timeWednesday.range;
- var timeThursdays = timeThursday.range;
- var timeFridays = timeFriday.range;
- var timeSaturdays = timeSaturday.range;
- function utcWeekday(i) {
- return timeInterval((date) => {
- date.setUTCDate(date.getUTCDate() - (date.getUTCDay() + 7 - i) % 7);
- date.setUTCHours(0, 0, 0, 0);
- }, (date, step) => {
- date.setUTCDate(date.getUTCDate() + step * 7);
- }, (start, end) => {
- return (end - start) / durationWeek;
- });
- }
- var utcSunday = utcWeekday(0);
- var utcMonday = utcWeekday(1);
- var utcTuesday = utcWeekday(2);
- var utcWednesday = utcWeekday(3);
- var utcThursday = utcWeekday(4);
- var utcFriday = utcWeekday(5);
- var utcSaturday = utcWeekday(6);
- var utcSundays = utcSunday.range;
- var utcMondays = utcMonday.range;
- var utcTuesdays = utcTuesday.range;
- var utcWednesdays = utcWednesday.range;
- var utcThursdays = utcThursday.range;
- var utcFridays = utcFriday.range;
- var utcSaturdays = utcSaturday.range;
-
- // node_modules/d3-time/src/month.js
- var timeMonth = timeInterval((date) => {
- date.setDate(1);
- date.setHours(0, 0, 0, 0);
- }, (date, step) => {
- date.setMonth(date.getMonth() + step);
- }, (start, end) => {
- return end.getMonth() - start.getMonth() + (end.getFullYear() - start.getFullYear()) * 12;
- }, (date) => {
- return date.getMonth();
- });
- var timeMonths = timeMonth.range;
- var utcMonth = timeInterval((date) => {
- date.setUTCDate(1);
- date.setUTCHours(0, 0, 0, 0);
- }, (date, step) => {
- date.setUTCMonth(date.getUTCMonth() + step);
- }, (start, end) => {
- return end.getUTCMonth() - start.getUTCMonth() + (end.getUTCFullYear() - start.getUTCFullYear()) * 12;
- }, (date) => {
- return date.getUTCMonth();
- });
- var utcMonths = utcMonth.range;
-
- // node_modules/d3-time/src/year.js
- var timeYear = timeInterval((date) => {
- date.setMonth(0, 1);
- date.setHours(0, 0, 0, 0);
- }, (date, step) => {
- date.setFullYear(date.getFullYear() + step);
- }, (start, end) => {
- return end.getFullYear() - start.getFullYear();
- }, (date) => {
- return date.getFullYear();
- });
- timeYear.every = (k) => {
- return !isFinite(k = Math.floor(k)) || !(k > 0) ? null : timeInterval((date) => {
- date.setFullYear(Math.floor(date.getFullYear() / k) * k);
- date.setMonth(0, 1);
- date.setHours(0, 0, 0, 0);
- }, (date, step) => {
- date.setFullYear(date.getFullYear() + step * k);
- });
- };
- var timeYears = timeYear.range;
- var utcYear = timeInterval((date) => {
- date.setUTCMonth(0, 1);
- date.setUTCHours(0, 0, 0, 0);
- }, (date, step) => {
- date.setUTCFullYear(date.getUTCFullYear() + step);
- }, (start, end) => {
- return end.getUTCFullYear() - start.getUTCFullYear();
- }, (date) => {
- return date.getUTCFullYear();
- });
- utcYear.every = (k) => {
- return !isFinite(k = Math.floor(k)) || !(k > 0) ? null : timeInterval((date) => {
- date.setUTCFullYear(Math.floor(date.getUTCFullYear() / k) * k);
- date.setUTCMonth(0, 1);
- date.setUTCHours(0, 0, 0, 0);
- }, (date, step) => {
- date.setUTCFullYear(date.getUTCFullYear() + step * k);
- });
- };
- var utcYears = utcYear.range;
-
- // node_modules/d3-time-format/src/locale.js
- function localDate(d) {
- if (0 <= d.y && d.y < 100) {
- var date = new Date(-1, d.m, d.d, d.H, d.M, d.S, d.L);
- date.setFullYear(d.y);
- return date;
- }
- return new Date(d.y, d.m, d.d, d.H, d.M, d.S, d.L);
- }
- function utcDate(d) {
- if (0 <= d.y && d.y < 100) {
- var date = new Date(Date.UTC(-1, d.m, d.d, d.H, d.M, d.S, d.L));
- date.setUTCFullYear(d.y);
- return date;
- }
- return new Date(Date.UTC(d.y, d.m, d.d, d.H, d.M, d.S, d.L));
- }
- function newDate(y, m, d) {
- return { y, m, d, H: 0, M: 0, S: 0, L: 0 };
- }
- function formatLocale(locale3) {
- var locale_dateTime = locale3.dateTime, locale_date = locale3.date, locale_time = locale3.time, locale_periods = locale3.periods, locale_weekdays = locale3.days, locale_shortWeekdays = locale3.shortDays, locale_months = locale3.months, locale_shortMonths = locale3.shortMonths;
- var periodRe = formatRe(locale_periods), periodLookup = formatLookup(locale_periods), weekdayRe = formatRe(locale_weekdays), weekdayLookup = formatLookup(locale_weekdays), shortWeekdayRe = formatRe(locale_shortWeekdays), shortWeekdayLookup = formatLookup(locale_shortWeekdays), monthRe = formatRe(locale_months), monthLookup = formatLookup(locale_months), shortMonthRe = formatRe(locale_shortMonths), shortMonthLookup = formatLookup(locale_shortMonths);
- var formats = {
- "a": formatShortWeekday,
- "A": formatWeekday,
- "b": formatShortMonth,
- "B": formatMonth,
- "c": null,
- "d": formatDayOfMonth,
- "e": formatDayOfMonth,
- "f": formatMicroseconds,
- "g": formatYearISO,
- "G": formatFullYearISO,
- "H": formatHour24,
- "I": formatHour12,
- "j": formatDayOfYear,
- "L": formatMilliseconds,
- "m": formatMonthNumber,
- "M": formatMinutes,
- "p": formatPeriod,
- "q": formatQuarter,
- "Q": formatUnixTimestamp,
- "s": formatUnixTimestampSeconds,
- "S": formatSeconds,
- "u": formatWeekdayNumberMonday,
- "U": formatWeekNumberSunday,
- "V": formatWeekNumberISO,
- "w": formatWeekdayNumberSunday,
- "W": formatWeekNumberMonday,
- "x": null,
- "X": null,
- "y": formatYear,
- "Y": formatFullYear,
- "Z": formatZone,
- "%": formatLiteralPercent
- };
- var utcFormats = {
- "a": formatUTCShortWeekday,
- "A": formatUTCWeekday,
- "b": formatUTCShortMonth,
- "B": formatUTCMonth,
- "c": null,
- "d": formatUTCDayOfMonth,
- "e": formatUTCDayOfMonth,
- "f": formatUTCMicroseconds,
- "g": formatUTCYearISO,
- "G": formatUTCFullYearISO,
- "H": formatUTCHour24,
- "I": formatUTCHour12,
- "j": formatUTCDayOfYear,
- "L": formatUTCMilliseconds,
- "m": formatUTCMonthNumber,
- "M": formatUTCMinutes,
- "p": formatUTCPeriod,
- "q": formatUTCQuarter,
- "Q": formatUnixTimestamp,
- "s": formatUnixTimestampSeconds,
- "S": formatUTCSeconds,
- "u": formatUTCWeekdayNumberMonday,
- "U": formatUTCWeekNumberSunday,
- "V": formatUTCWeekNumberISO,
- "w": formatUTCWeekdayNumberSunday,
- "W": formatUTCWeekNumberMonday,
- "x": null,
- "X": null,
- "y": formatUTCYear,
- "Y": formatUTCFullYear,
- "Z": formatUTCZone,
- "%": formatLiteralPercent
- };
- var parses = {
- "a": parseShortWeekday,
- "A": parseWeekday,
- "b": parseShortMonth,
- "B": parseMonth,
- "c": parseLocaleDateTime,
- "d": parseDayOfMonth,
- "e": parseDayOfMonth,
- "f": parseMicroseconds,
- "g": parseYear,
- "G": parseFullYear,
- "H": parseHour24,
- "I": parseHour24,
- "j": parseDayOfYear,
- "L": parseMilliseconds,
- "m": parseMonthNumber,
- "M": parseMinutes,
- "p": parsePeriod,
- "q": parseQuarter,
- "Q": parseUnixTimestamp,
- "s": parseUnixTimestampSeconds,
- "S": parseSeconds,
- "u": parseWeekdayNumberMonday,
- "U": parseWeekNumberSunday,
- "V": parseWeekNumberISO,
- "w": parseWeekdayNumberSunday,
- "W": parseWeekNumberMonday,
- "x": parseLocaleDate,
- "X": parseLocaleTime,
- "y": parseYear,
- "Y": parseFullYear,
- "Z": parseZone,
- "%": parseLiteralPercent
- };
- formats.x = newFormat(locale_date, formats);
- formats.X = newFormat(locale_time, formats);
- formats.c = newFormat(locale_dateTime, formats);
- utcFormats.x = newFormat(locale_date, utcFormats);
- utcFormats.X = newFormat(locale_time, utcFormats);
- utcFormats.c = newFormat(locale_dateTime, utcFormats);
- function newFormat(specifier, formats2) {
- return function(date) {
- var string = [], i = -1, j = 0, n = specifier.length, c, pad2, format2;
- if (!(date instanceof Date)) date = /* @__PURE__ */ new Date(+date);
- while (++i < n) {
- if (specifier.charCodeAt(i) === 37) {
- string.push(specifier.slice(j, i));
- if ((pad2 = pads[c = specifier.charAt(++i)]) != null) c = specifier.charAt(++i);
- else pad2 = c === "e" ? " " : "0";
- if (format2 = formats2[c]) c = format2(date, pad2);
- string.push(c);
- j = i + 1;
- }
- }
- string.push(specifier.slice(j, i));
- return string.join("");
- };
- }
- function newParse(specifier, Z) {
- return function(string) {
- var d = newDate(1900, void 0, 1), i = parseSpecifier(d, specifier, string += "", 0), week, day;
- if (i != string.length) return null;
- if ("Q" in d) return new Date(d.Q);
- if ("s" in d) return new Date(d.s * 1e3 + ("L" in d ? d.L : 0));
- if (Z && !("Z" in d)) d.Z = 0;
- if ("p" in d) d.H = d.H % 12 + d.p * 12;
- if (d.m === void 0) d.m = "q" in d ? d.q : 0;
- if ("V" in d) {
- if (d.V < 1 || d.V > 53) return null;
- if (!("w" in d)) d.w = 1;
- if ("Z" in d) {
- week = utcDate(newDate(d.y, 0, 1)), day = week.getUTCDay();
- week = day > 4 || day === 0 ? utcMonday.ceil(week) : utcMonday(week);
- week = utcDay.offset(week, (d.V - 1) * 7);
- d.y = week.getUTCFullYear();
- d.m = week.getUTCMonth();
- d.d = week.getUTCDate() + (d.w + 6) % 7;
- } else {
- week = localDate(newDate(d.y, 0, 1)), day = week.getDay();
- week = day > 4 || day === 0 ? timeMonday.ceil(week) : timeMonday(week);
- week = timeDay.offset(week, (d.V - 1) * 7);
- d.y = week.getFullYear();
- d.m = week.getMonth();
- d.d = week.getDate() + (d.w + 6) % 7;
- }
- } else if ("W" in d || "U" in d) {
- if (!("w" in d)) d.w = "u" in d ? d.u % 7 : "W" in d ? 1 : 0;
- day = "Z" in d ? utcDate(newDate(d.y, 0, 1)).getUTCDay() : localDate(newDate(d.y, 0, 1)).getDay();
- d.m = 0;
- d.d = "W" in d ? (d.w + 6) % 7 + d.W * 7 - (day + 5) % 7 : d.w + d.U * 7 - (day + 6) % 7;
- }
- if ("Z" in d) {
- d.H += d.Z / 100 | 0;
- d.M += d.Z % 100;
- return utcDate(d);
- }
- return localDate(d);
- };
- }
- function parseSpecifier(d, specifier, string, j) {
- var i = 0, n = specifier.length, m = string.length, c, parse;
- while (i < n) {
- if (j >= m) return -1;
- c = specifier.charCodeAt(i++);
- if (c === 37) {
- c = specifier.charAt(i++);
- parse = parses[c in pads ? specifier.charAt(i++) : c];
- if (!parse || (j = parse(d, string, j)) < 0) return -1;
- } else if (c != string.charCodeAt(j++)) {
- return -1;
- }
- }
- return j;
- }
- function parsePeriod(d, string, i) {
- var n = periodRe.exec(string.slice(i));
- return n ? (d.p = periodLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
- }
- function parseShortWeekday(d, string, i) {
- var n = shortWeekdayRe.exec(string.slice(i));
- return n ? (d.w = shortWeekdayLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
- }
- function parseWeekday(d, string, i) {
- var n = weekdayRe.exec(string.slice(i));
- return n ? (d.w = weekdayLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
- }
- function parseShortMonth(d, string, i) {
- var n = shortMonthRe.exec(string.slice(i));
- return n ? (d.m = shortMonthLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
- }
- function parseMonth(d, string, i) {
- var n = monthRe.exec(string.slice(i));
- return n ? (d.m = monthLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
- }
- function parseLocaleDateTime(d, string, i) {
- return parseSpecifier(d, locale_dateTime, string, i);
- }
- function parseLocaleDate(d, string, i) {
- return parseSpecifier(d, locale_date, string, i);
- }
- function parseLocaleTime(d, string, i) {
- return parseSpecifier(d, locale_time, string, i);
- }
- function formatShortWeekday(d) {
- return locale_shortWeekdays[d.getDay()];
- }
- function formatWeekday(d) {
- return locale_weekdays[d.getDay()];
- }
- function formatShortMonth(d) {
- return locale_shortMonths[d.getMonth()];
- }
- function formatMonth(d) {
- return locale_months[d.getMonth()];
- }
- function formatPeriod(d) {
- return locale_periods[+(d.getHours() >= 12)];
- }
- function formatQuarter(d) {
- return 1 + ~~(d.getMonth() / 3);
- }
- function formatUTCShortWeekday(d) {
- return locale_shortWeekdays[d.getUTCDay()];
- }
- function formatUTCWeekday(d) {
- return locale_weekdays[d.getUTCDay()];
- }
- function formatUTCShortMonth(d) {
- return locale_shortMonths[d.getUTCMonth()];
- }
- function formatUTCMonth(d) {
- return locale_months[d.getUTCMonth()];
- }
- function formatUTCPeriod(d) {
- return locale_periods[+(d.getUTCHours() >= 12)];
- }
- function formatUTCQuarter(d) {
- return 1 + ~~(d.getUTCMonth() / 3);
- }
- return {
- format: function(specifier) {
- var f = newFormat(specifier += "", formats);
- f.toString = function() {
- return specifier;
- };
- return f;
- },
- parse: function(specifier) {
- var p = newParse(specifier += "", false);
- p.toString = function() {
- return specifier;
- };
- return p;
- },
- utcFormat: function(specifier) {
- var f = newFormat(specifier += "", utcFormats);
- f.toString = function() {
- return specifier;
- };
- return f;
- },
- utcParse: function(specifier) {
- var p = newParse(specifier += "", true);
- p.toString = function() {
- return specifier;
- };
- return p;
- }
- };
- }
- var pads = { "-": "", "_": " ", "0": "0" };
- var numberRe = /^\s*\d+/;
- var percentRe = /^%/;
- var requoteRe = /[\\^$*+?|[\]().{}]/g;
- function pad(value, fill, width) {
- var sign = value < 0 ? "-" : "", string = (sign ? -value : value) + "", length = string.length;
- return sign + (length < width ? new Array(width - length + 1).join(fill) + string : string);
- }
- function requote(s) {
- return s.replace(requoteRe, "\\$&");
- }
- function formatRe(names) {
- return new RegExp("^(?:" + names.map(requote).join("|") + ")", "i");
- }
- function formatLookup(names) {
- return new Map(names.map((name, i) => [name.toLowerCase(), i]));
- }
- function parseWeekdayNumberSunday(d, string, i) {
- var n = numberRe.exec(string.slice(i, i + 1));
- return n ? (d.w = +n[0], i + n[0].length) : -1;
- }
- function parseWeekdayNumberMonday(d, string, i) {
- var n = numberRe.exec(string.slice(i, i + 1));
- return n ? (d.u = +n[0], i + n[0].length) : -1;
- }
- function parseWeekNumberSunday(d, string, i) {
- var n = numberRe.exec(string.slice(i, i + 2));
- return n ? (d.U = +n[0], i + n[0].length) : -1;
- }
- function parseWeekNumberISO(d, string, i) {
- var n = numberRe.exec(string.slice(i, i + 2));
- return n ? (d.V = +n[0], i + n[0].length) : -1;
- }
- function parseWeekNumberMonday(d, string, i) {
- var n = numberRe.exec(string.slice(i, i + 2));
- return n ? (d.W = +n[0], i + n[0].length) : -1;
- }
- function parseFullYear(d, string, i) {
- var n = numberRe.exec(string.slice(i, i + 4));
- return n ? (d.y = +n[0], i + n[0].length) : -1;
- }
- function parseYear(d, string, i) {
- var n = numberRe.exec(string.slice(i, i + 2));
- return n ? (d.y = +n[0] + (+n[0] > 68 ? 1900 : 2e3), i + n[0].length) : -1;
- }
- function parseZone(d, string, i) {
- var n = /^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(string.slice(i, i + 6));
- return n ? (d.Z = n[1] ? 0 : -(n[2] + (n[3] || "00")), i + n[0].length) : -1;
- }
- function parseQuarter(d, string, i) {
- var n = numberRe.exec(string.slice(i, i + 1));
- return n ? (d.q = n[0] * 3 - 3, i + n[0].length) : -1;
- }
- function parseMonthNumber(d, string, i) {
- var n = numberRe.exec(string.slice(i, i + 2));
- return n ? (d.m = n[0] - 1, i + n[0].length) : -1;
- }
- function parseDayOfMonth(d, string, i) {
- var n = numberRe.exec(string.slice(i, i + 2));
- return n ? (d.d = +n[0], i + n[0].length) : -1;
- }
- function parseDayOfYear(d, string, i) {
- var n = numberRe.exec(string.slice(i, i + 3));
- return n ? (d.m = 0, d.d = +n[0], i + n[0].length) : -1;
- }
- function parseHour24(d, string, i) {
- var n = numberRe.exec(string.slice(i, i + 2));
- return n ? (d.H = +n[0], i + n[0].length) : -1;
- }
- function parseMinutes(d, string, i) {
- var n = numberRe.exec(string.slice(i, i + 2));
- return n ? (d.M = +n[0], i + n[0].length) : -1;
- }
- function parseSeconds(d, string, i) {
- var n = numberRe.exec(string.slice(i, i + 2));
- return n ? (d.S = +n[0], i + n[0].length) : -1;
- }
- function parseMilliseconds(d, string, i) {
- var n = numberRe.exec(string.slice(i, i + 3));
- return n ? (d.L = +n[0], i + n[0].length) : -1;
- }
- function parseMicroseconds(d, string, i) {
- var n = numberRe.exec(string.slice(i, i + 6));
- return n ? (d.L = Math.floor(n[0] / 1e3), i + n[0].length) : -1;
- }
- function parseLiteralPercent(d, string, i) {
- var n = percentRe.exec(string.slice(i, i + 1));
- return n ? i + n[0].length : -1;
- }
- function parseUnixTimestamp(d, string, i) {
- var n = numberRe.exec(string.slice(i));
- return n ? (d.Q = +n[0], i + n[0].length) : -1;
- }
- function parseUnixTimestampSeconds(d, string, i) {
- var n = numberRe.exec(string.slice(i));
- return n ? (d.s = +n[0], i + n[0].length) : -1;
- }
- function formatDayOfMonth(d, p) {
- return pad(d.getDate(), p, 2);
- }
- function formatHour24(d, p) {
- return pad(d.getHours(), p, 2);
- }
- function formatHour12(d, p) {
- return pad(d.getHours() % 12 || 12, p, 2);
- }
- function formatDayOfYear(d, p) {
- return pad(1 + timeDay.count(timeYear(d), d), p, 3);
- }
- function formatMilliseconds(d, p) {
- return pad(d.getMilliseconds(), p, 3);
- }
- function formatMicroseconds(d, p) {
- return formatMilliseconds(d, p) + "000";
- }
- function formatMonthNumber(d, p) {
- return pad(d.getMonth() + 1, p, 2);
- }
- function formatMinutes(d, p) {
- return pad(d.getMinutes(), p, 2);
- }
- function formatSeconds(d, p) {
- return pad(d.getSeconds(), p, 2);
- }
- function formatWeekdayNumberMonday(d) {
- var day = d.getDay();
- return day === 0 ? 7 : day;
- }
- function formatWeekNumberSunday(d, p) {
- return pad(timeSunday.count(timeYear(d) - 1, d), p, 2);
- }
- function dISO(d) {
- var day = d.getDay();
- return day >= 4 || day === 0 ? timeThursday(d) : timeThursday.ceil(d);
- }
- function formatWeekNumberISO(d, p) {
- d = dISO(d);
- return pad(timeThursday.count(timeYear(d), d) + (timeYear(d).getDay() === 4), p, 2);
- }
- function formatWeekdayNumberSunday(d) {
- return d.getDay();
- }
- function formatWeekNumberMonday(d, p) {
- return pad(timeMonday.count(timeYear(d) - 1, d), p, 2);
- }
- function formatYear(d, p) {
- return pad(d.getFullYear() % 100, p, 2);
- }
- function formatYearISO(d, p) {
- d = dISO(d);
- return pad(d.getFullYear() % 100, p, 2);
- }
- function formatFullYear(d, p) {
- return pad(d.getFullYear() % 1e4, p, 4);
- }
- function formatFullYearISO(d, p) {
- var day = d.getDay();
- d = day >= 4 || day === 0 ? timeThursday(d) : timeThursday.ceil(d);
- return pad(d.getFullYear() % 1e4, p, 4);
- }
- function formatZone(d) {
- var z = d.getTimezoneOffset();
- return (z > 0 ? "-" : (z *= -1, "+")) + pad(z / 60 | 0, "0", 2) + pad(z % 60, "0", 2);
- }
- function formatUTCDayOfMonth(d, p) {
- return pad(d.getUTCDate(), p, 2);
- }
- function formatUTCHour24(d, p) {
- return pad(d.getUTCHours(), p, 2);
- }
- function formatUTCHour12(d, p) {
- return pad(d.getUTCHours() % 12 || 12, p, 2);
- }
- function formatUTCDayOfYear(d, p) {
- return pad(1 + utcDay.count(utcYear(d), d), p, 3);
- }
- function formatUTCMilliseconds(d, p) {
- return pad(d.getUTCMilliseconds(), p, 3);
- }
- function formatUTCMicroseconds(d, p) {
- return formatUTCMilliseconds(d, p) + "000";
- }
- function formatUTCMonthNumber(d, p) {
- return pad(d.getUTCMonth() + 1, p, 2);
- }
- function formatUTCMinutes(d, p) {
- return pad(d.getUTCMinutes(), p, 2);
- }
- function formatUTCSeconds(d, p) {
- return pad(d.getUTCSeconds(), p, 2);
- }
- function formatUTCWeekdayNumberMonday(d) {
- var dow = d.getUTCDay();
- return dow === 0 ? 7 : dow;
- }
- function formatUTCWeekNumberSunday(d, p) {
- return pad(utcSunday.count(utcYear(d) - 1, d), p, 2);
- }
- function UTCdISO(d) {
- var day = d.getUTCDay();
- return day >= 4 || day === 0 ? utcThursday(d) : utcThursday.ceil(d);
- }
- function formatUTCWeekNumberISO(d, p) {
- d = UTCdISO(d);
- return pad(utcThursday.count(utcYear(d), d) + (utcYear(d).getUTCDay() === 4), p, 2);
- }
- function formatUTCWeekdayNumberSunday(d) {
- return d.getUTCDay();
- }
- function formatUTCWeekNumberMonday(d, p) {
- return pad(utcMonday.count(utcYear(d) - 1, d), p, 2);
- }
- function formatUTCYear(d, p) {
- return pad(d.getUTCFullYear() % 100, p, 2);
- }
- function formatUTCYearISO(d, p) {
- d = UTCdISO(d);
- return pad(d.getUTCFullYear() % 100, p, 2);
- }
- function formatUTCFullYear(d, p) {
- return pad(d.getUTCFullYear() % 1e4, p, 4);
- }
- function formatUTCFullYearISO(d, p) {
- var day = d.getUTCDay();
- d = day >= 4 || day === 0 ? utcThursday(d) : utcThursday.ceil(d);
- return pad(d.getUTCFullYear() % 1e4, p, 4);
- }
- function formatUTCZone() {
- return "+0000";
- }
- function formatLiteralPercent() {
- return "%";
- }
- function formatUnixTimestamp(d) {
- return +d;
- }
- function formatUnixTimestampSeconds(d) {
- return Math.floor(+d / 1e3);
- }
-
- // node_modules/d3-time-format/src/defaultLocale.js
- var locale2;
- var timeFormat;
- var timeParse;
- var utcFormat;
- var utcParse;
- defaultLocale2({
- dateTime: "%x, %X",
- date: "%-m/%-d/%Y",
- time: "%-I:%M:%S %p",
- periods: ["AM", "PM"],
- days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
- shortDays: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
- months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
- shortMonths: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
- });
- function defaultLocale2(definition) {
- locale2 = formatLocale(definition);
- timeFormat = locale2.format;
- timeParse = locale2.parse;
- utcFormat = locale2.utcFormat;
- utcParse = locale2.utcParse;
- return locale2;
- }
-
- // assets/src/heatmap.ts
- var DEFAULT_CONFIG = {
- cellSize: 13,
- cellGap: 2,
- marginTop: 20,
- marginLeft: 30,
- marginBottom: 4
- };
- var FALLBACK_COLORS = ["#9be9a8", "#40c463", "#30a14e", "#216e39"];
- var DAY_LABELS = ["Mon", "", "Wed", "", "Fri", "", ""];
- var MONTH_FORMAT = timeFormat("%b");
- var DATE_FORMAT = timeFormat("%Y-%m-%d");
- var DISPLAY_FORMAT = timeFormat("%a, %b %-d, %Y");
- function resolveColors(container) {
- try {
- const style = getComputedStyle(container);
- const test = style.getPropertyValue("--tblr-bg-surface");
- if (!test) return FALLBACK_COLORS;
- return FALLBACK_COLORS;
- } catch {
- return FALLBACK_COLORS;
- }
- }
- function buildDateMap(days) {
- const map2 = /* @__PURE__ */ new Map();
- for (const d of days) {
- map2.set(d.date, d);
- }
- return map2;
- }
- function generateCells(begin, end, dateMap) {
- const firstMonday = timeMonday.floor(begin);
- const cells = [];
- let current = new Date(begin);
- while (current <= end) {
- const dateStr = DATE_FORMAT(current);
- const weeksSinceStart = timeMonday.count(firstMonday, current);
- const dayOfWeek = (current.getDay() + 6) % 7;
- cells.push({
- date: new Date(current),
- dateStr,
- entry: dateMap.get(dateStr) || null,
- week: weeksSinceStart,
- day: dayOfWeek
- });
- current = timeDay.offset(current, 1);
- }
- return cells;
- }
- function createTooltip() {
- const tip = document.createElement("div");
- tip.className = "heatmap-tooltip";
- tip.style.display = "none";
- return tip;
- }
- function renderHeatmap(container, data, config = DEFAULT_CONFIG, onCellClick, emptyMessage) {
- container.innerHTML = "";
- if (!data.days || data.days.length === 0) {
- const msg = document.createElement("div");
- msg.textContent = emptyMessage || "No tracking data available";
- msg.style.padding = "1rem";
- msg.style.color = "var(--tblr-secondary, #6c757d)";
- container.appendChild(msg);
- return;
- }
- const dateMap = buildDateMap(data.days);
- const begin = new Date(data.range.begin);
- const end = new Date(data.range.end);
- const cells = generateCells(begin, end, dateMap);
- const maxHours = max(data.days, (d) => d.hours) || 1;
- const colors = resolveColors(container);
- const colorScale = quantize().domain([0, maxHours]).range(colors);
- const { cellGap, marginTop, marginLeft, marginBottom } = config;
- const numWeeks = (max(cells, (c) => c.week) ?? 0) + 1;
- const containerWidth = container.clientWidth || 800;
- const maxCellSize = 18;
- const cellSize = Math.min(maxCellSize, Math.max(2, Math.floor((containerWidth - marginLeft) / numWeeks) - cellGap));
- const step = cellSize + cellGap;
- const svgWidth = marginLeft + numWeeks * step;
- const svgHeight = marginTop + 7 * step + marginBottom;
- const wrapper = document.createElement("div");
- wrapper.style.maxWidth = `${svgWidth}px`;
- wrapper.style.margin = "0 auto";
- container.appendChild(wrapper);
- const svg = select_default2(wrapper).append("svg").attr("width", svgWidth).attr("height", svgHeight).attr("class", "heatmap-svg");
- const months = [];
- const firstMonday = timeMonday.floor(begin);
- timeMonth.range(timeMonth.ceil(begin), end).forEach((m) => {
- months.push({
- date: m,
- week: timeMonday.count(firstMonday, m)
- });
- });
- svg.selectAll(".month-label").data(months).join("text").attr("class", "heatmap-label month-label").attr("x", (d) => marginLeft + d.week * step).attr("y", marginTop - 6).text((d) => MONTH_FORMAT(d.date));
- svg.selectAll(".day-label").data(DAY_LABELS).join("text").attr("class", "heatmap-label day-label").attr("x", marginLeft - 6).attr("y", (_d, i) => marginTop + i * step + cellSize - 2).attr("text-anchor", "end").text((d) => d);
- document.querySelectorAll(".heatmap-tooltip").forEach((el) => el.remove());
- const tooltip = createTooltip();
- tooltip.style.position = "fixed";
- document.body.appendChild(tooltip);
- svg.selectAll(".heatmap-cell").data(cells).join("rect").attr(
- "class",
- (d) => d.entry ? "heatmap-cell" : "heatmap-cell heatmap-empty"
- ).attr("x", (d) => marginLeft + d.week * step).attr("y", (d) => marginTop + d.day * step).attr("width", cellSize).attr("height", cellSize).attr("fill", (d) => d.entry ? colorScale(d.entry.hours) : "").on("mouseenter", function(event, d) {
- const hours = d.entry ? d.entry.hours.toFixed(1) : "0.0";
- const count = d.entry ? d.entry.count : 0;
- tooltip.innerHTML = `${DISPLAY_FORMAT(d.date)}
${hours}h (${count} entries)`;
- tooltip.style.display = "block";
- const rect = event.target.getBoundingClientRect();
- tooltip.style.left = `${rect.left + cellSize / 2}px`;
- tooltip.style.top = `${rect.top - tooltip.offsetHeight - 8}px`;
- }).on("mouseleave", function() {
- tooltip.style.display = "none";
- }).on("click", function(_event, d) {
- if (!onCellClick) return;
- onCellClick(d.dateStr);
- });
- }
- function init(container) {
- const baseUrl = container.getAttribute("data-url");
- if (!baseUrl) {
- console.error("KimaiHeatmap: missing data-url attribute");
- return;
- }
- const timesheetUrl = container.getAttribute("data-timesheet-url") || "/en/timesheet/";
- const projectsJson = container.getAttribute("data-projects");
- const projects = projectsJson ? JSON.parse(projectsJson) : [];
- let activeProjectId = null;
- const onCellClick = (dateStr) => {
- const daterange = `${dateStr} - ${dateStr}`;
- let url = `${timesheetUrl}?daterange=${encodeURIComponent(daterange)}`;
- if (activeProjectId) {
- url += `&projects[]=${activeProjectId}`;
- }
- window.location.href = url;
- };
- container.innerHTML = "";
- const wrapper = document.createElement("div");
- wrapper.className = "heatmap-wrapper";
- const svgArea = document.createElement("div");
- svgArea.className = "heatmap-svg-area";
- wrapper.appendChild(svgArea);
- if (projects.length > 0) {
- const filterDiv = document.createElement("div");
- filterDiv.className = "heatmap-filter";
- const select = document.createElement("select");
- select.className = "form-select form-select-sm";
- select.setAttribute("aria-label", "Filter by project");
- const defaultOpt = document.createElement("option");
- defaultOpt.value = "";
- defaultOpt.textContent = "All Projects";
- select.appendChild(defaultOpt);
- for (const p of projects) {
- const opt = document.createElement("option");
- opt.value = String(p.id);
- opt.textContent = p.name;
- select.appendChild(opt);
- }
- select.addEventListener("change", () => {
- const val = select.value;
- activeProjectId = val ? parseInt(val, 10) : null;
- const fetchUrl = val ? `${baseUrl}?project=${val}` : baseUrl;
- fetch(fetchUrl).then((res) => {
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
- return res.json();
- }).then((data) => {
- renderHeatmap(svgArea, data, DEFAULT_CONFIG, onCellClick, "No tracking data for this project");
- }).catch((err) => {
- console.error("KimaiHeatmap: failed to load filtered data", err);
- });
- });
- filterDiv.appendChild(select);
- wrapper.appendChild(filterDiv);
- }
- container.appendChild(wrapper);
- fetch(baseUrl).then((res) => {
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
- return res.json();
- }).then((data) => {
- renderHeatmap(svgArea, data, DEFAULT_CONFIG, onCellClick);
- }).catch((err) => {
- console.error("KimaiHeatmap: failed to load data", err);
- svgArea.textContent = "Failed to load heatmap data";
- });
- }
- return __toCommonJS(heatmap_exports);
-})();
+"use strict";var KimaiHeatmap=(()=>{var Wt=Object.defineProperty;var en=Object.getOwnPropertyDescriptor;var rn=Object.getOwnPropertyNames;var nn=Object.prototype.hasOwnProperty;var on=(t,e)=>{for(var r in e)Wt(t,r,{get:e[r],enumerable:!0})},an=(t,e,r,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of rn(e))!nn.call(t,o)&&o!==r&&Wt(t,o,{get:()=>e[o],enumerable:!(n=en(e,o))||n.enumerable});return t};var un=t=>an(Wt({},"__esModule",{value:!0}),t);var za={};on(za,{calculateStats:()=>Br,calculateStreak:()=>Vr,init:()=>Ra,renderHeatmap:()=>ce});var St="http://www.w3.org/1999/xhtml",Pt={svg:"http://www.w3.org/2000/svg",xhtml:St,xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace",xmlns:"http://www.w3.org/2000/xmlns/"};function Tt(t){var e=t+="",r=e.indexOf(":");return r>=0&&(e=t.slice(0,r))!=="xmlns"&&(t=t.slice(r+1)),Pt.hasOwnProperty(e)?{space:Pt[e],local:t}:t}function sn(t){return function(){var e=this.ownerDocument,r=this.namespaceURI;return r===St&&e.documentElement.namespaceURI===St?e.createElement(t):e.createElementNS(r,t)}}function ln(t){return function(){return this.ownerDocument.createElementNS(t.space,t.local)}}function Dt(t){var e=Tt(t);return(e.local?ln:sn)(e)}function fn(){}function Ct(t){return t==null?fn:function(){return this.querySelector(t)}}function pe(t){typeof t!="function"&&(t=Ct(t));for(var e=this._groups,r=e.length,n=new Array(r),o=0;o=A&&(A=H+1);!(N=W[A])&&++A=0;)(i=n[o])&&(a&&i.compareDocumentPosition(a)^4&&a.parentNode.insertBefore(i,a),a=i);return this}function be(t){t||(t=Sn);function e(p,g){return p&&g?t(p.__data__,g.__data__):!p-!g}for(var r=this._groups,n=r.length,o=new Array(n),a=0;ae?1:t>=e?0:NaN}function Ue(){var t=arguments[0];return arguments[0]=this,t.apply(null,arguments),this}function ke(){return Array.from(this)}function Ae(){for(var t=this._groups,e=0,r=t.length;e1?this.each((e==null?An:typeof e=="function"?Nn:Fn)(t,e,r??"")):Ln(this.node(),t)}function Ln(t,e){return t.style.getPropertyValue(e)||kt(t).getComputedStyle(t,null).getPropertyValue(e)}function Hn(t){return function(){delete this[t]}}function Yn(t,e){return function(){this[t]=e}}function En(t,e){return function(){var r=e.apply(this,arguments);r==null?delete this[t]:this[t]=r}}function Ee(t,e){return arguments.length>1?this.each((e==null?Hn:typeof e=="function"?En:Yn)(t,e)):this.node()[t]}function We(t){return t.trim().split(/^|\s+/)}function Ot(t){return t.classList||new Pe(t)}function Pe(t){this._node=t,this._names=We(t.getAttribute("class")||"")}Pe.prototype={add:function(t){var e=this._names.indexOf(t);e<0&&(this._names.push(t),this._node.setAttribute("class",this._names.join(" ")))},remove:function(t){var e=this._names.indexOf(t);e>=0&&(this._names.splice(e,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(t){return this._names.indexOf(t)>=0}};function Ie(t,e){for(var r=Ot(t),n=-1,o=e.length;++n=0&&(r=e.slice(n+1),e=e.slice(0,n)),{type:e,name:r}})}function to(t){return function(){var e=this.__on;if(e){for(var r=0,n=-1,o=e.length,a;re?1:t>=e?0:NaN}function zt(t,e){return t==null||e==null?NaN:et?1:e>=t?0:NaN}function At(t){let e,r,n;t.length!==2?(e=tt,r=(u,s)=>tt(t(u),s),n=(u,s)=>t(u)-s):(e=t===tt||t===zt?t:io,r=t,n=t);function o(u,s,l=0,c=u.length){if(l>>1;r(u[p],s)<0?l=p+1:c=p}while(l>>1;r(u[p],s)<=0?l=p+1:c=p}while(ll&&n(u[p-1],s)>-n(u[p],s)?p-1:p}return{left:o,center:i,right:a}}function io(){return 0}function $t(t){return t===null?NaN:+t}var er=At(tt),rr=er.right,uo=er.left,so=At($t).center,Vt=rr;var lo=Math.sqrt(50),fo=Math.sqrt(10),co=Math.sqrt(2);function Ft(t,e,r){let n=(e-t)/Math.max(0,r),o=Math.floor(Math.log10(n)),a=n/Math.pow(10,o),i=a>=lo?10:a>=fo?5:a>=co?2:1,u,s,l;return o<0?(l=Math.pow(10,-o)/i,u=Math.round(t*l),s=Math.round(e*l),u/le&&--s,l=-l):(l=Math.pow(10,o)*i,u=Math.round(t/l),s=Math.round(e/l),u*le&&--s),s0))return[];if(t===e)return[t];let n=e=o))return[];let u=a-o+1,s=new Array(u);if(n)if(i<0)for(let l=0;l=n)&&(r=n);else{let n=-1;for(let o of t)(o=e(o,++n,t))!=null&&(r=o)&&(r=o)}return r}function nr(t,e){switch(arguments.length){case 0:break;case 1:this.range(t);break;default:this.range(e).domain(t);break}return this}function or(t){return Math.abs(t=Math.round(t))>=1e21?t.toLocaleString("en").replace(/,/g,""):t.toString(10)}function et(t,e){if(!isFinite(t)||t===0)return null;var r=(t=e?t.toExponential(e-1):t.toExponential()).indexOf("e"),n=t.slice(0,r);return[n.length>1?n[0]+n.slice(2):n,+t.slice(r+1)]}function q(t){return t=et(Math.abs(t)),t?t[1]:NaN}function ar(t,e){return function(r,n){for(var o=r.length,a=[],i=0,u=t[0],s=0;o>0&&u>0&&(s+u+1>n&&(u=Math.max(1,n-s)),a.push(r.substring(o-=u,o+u)),!((s+=u+1)>n));)u=t[i=(i+1)%t.length];return a.reverse().join(e)}}function ir(t){return function(e){return e.replace(/[0-9]/g,function(r){return t[+r]})}}var mo=/^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;function Q(t){if(!(e=mo.exec(t)))throw new Error("invalid format: "+t);var e;return new Lt({fill:e[1],align:e[2],sign:e[3],symbol:e[4],zero:e[5],width:e[6],comma:e[7],precision:e[8]&&e[8].slice(1),trim:e[9],type:e[10]})}Q.prototype=Lt.prototype;function Lt(t){this.fill=t.fill===void 0?" ":t.fill+"",this.align=t.align===void 0?">":t.align+"",this.sign=t.sign===void 0?"-":t.sign+"",this.symbol=t.symbol===void 0?"":t.symbol+"",this.zero=!!t.zero,this.width=t.width===void 0?void 0:+t.width,this.comma=!!t.comma,this.precision=t.precision===void 0?void 0:+t.precision,this.trim=!!t.trim,this.type=t.type===void 0?"":t.type+""}Lt.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(this.width===void 0?"":Math.max(1,this.width|0))+(this.comma?",":"")+(this.precision===void 0?"":"."+Math.max(0,this.precision|0))+(this.trim?"~":"")+this.type};function ur(t){t:for(var e=t.length,r=1,n=-1,o;r0&&(n=0);break}return n>0?t.slice(0,n)+t.slice(o+1):t}var dt;function sr(t,e){var r=et(t,e);if(!r)return dt=void 0,t.toPrecision(e);var n=r[0],o=r[1],a=o-(dt=Math.max(-8,Math.min(8,Math.floor(o/3)))*3)+1,i=n.length;return a===i?n:a>i?n+new Array(a-i+1).join("0"):a>0?n.slice(0,a)+"."+n.slice(a):"0."+new Array(1-a).join("0")+et(t,Math.max(0,e+a-1))[0]}function Zt(t,e){var r=et(t,e);if(!r)return t+"";var n=r[0],o=r[1];return o<0?"0."+new Array(-o).join("0")+n:n.length>o+1?n.slice(0,o+1)+"."+n.slice(o+1):n+new Array(o-n.length+2).join("0")}var Qt={"%":(t,e)=>(t*100).toFixed(e),b:t=>Math.round(t).toString(2),c:t=>t+"",d:or,e:(t,e)=>t.toExponential(e),f:(t,e)=>t.toFixed(e),g:(t,e)=>t.toPrecision(e),o:t=>Math.round(t).toString(8),p:(t,e)=>Zt(t*100,e),r:Zt,s:sr,X:t=>Math.round(t).toString(16).toUpperCase(),x:t=>Math.round(t).toString(16)};function Xt(t){return t}var lr=Array.prototype.map,fr=["y","z","a","f","p","n","\xB5","m","","k","M","G","T","P","E","Z","Y"];function cr(t){var e=t.grouping===void 0||t.thousands===void 0?Xt:ar(lr.call(t.grouping,Number),t.thousands+""),r=t.currency===void 0?"":t.currency[0]+"",n=t.currency===void 0?"":t.currency[1]+"",o=t.decimal===void 0?".":t.decimal+"",a=t.numerals===void 0?Xt:ir(lr.call(t.numerals,String)),i=t.percent===void 0?"%":t.percent+"",u=t.minus===void 0?"\u2212":t.minus+"",s=t.nan===void 0?"NaN":t.nan+"";function l(p,g){p=Q(p);var x=p.fill,w=p.align,_=p.sign,W=p.symbol,P=p.zero,H=p.width,A=p.comma,T=p.precision,N=p.trim,D=p.type;D==="n"?(A=!0,D="g"):Qt[D]||(T===void 0&&(T=12),N=!0,D="g"),(P||x==="0"&&w==="=")&&(P=!0,x="0",w="=");var at=(g&&g.prefix!==void 0?g.prefix:"")+(W==="$"?r:W==="#"&&/[boxX]/.test(D)?"0"+D.toLowerCase():""),I=(W==="$"?n:/[%p]/.test(D)?i:"")+(g&&g.suffix!==void 0?g.suffix:""),B=Qt[D],it=/[defgprs%]/.test(D);T=T===void 0?6:/[gprs]/.test(D)?Math.max(1,Math.min(21,T)):Math.max(0,Math.min(20,T));function K(d){var U=at,h=I,C,ut,Z;if(D==="c")h=B(d)+h,d="";else{d=+d;var R=d<0||1/d<0;if(d=isNaN(d)?s:B(Math.abs(d),T),N&&(d=ur(d)),R&&+d==0&&_!=="+"&&(R=!1),U=(R?_==="("?_:u:_==="-"||_==="("?"":_)+U,h=(D==="s"&&!isNaN(d)&&dt!==void 0?fr[8+dt/3]:"")+h+(R&&_==="("?")":""),it){for(C=-1,ut=d.length;++CZ||Z>57){h=(Z===46?o+d.slice(C+1):d.slice(C))+h,d=d.slice(0,C);break}}}A&&!P&&(d=e(d,1/0));var st=U.length+d.length+h.length,O=st>1)+U+d+h+O.slice(st);break;default:d=O+U+d+h;break}return a(d)}return K.toString=function(){return p+""},K}function c(p,g){var x=Math.max(-8,Math.min(8,Math.floor(q(g)/3)))*3,w=Math.pow(10,-x),_=l((p=Q(p),p.type="f",p),{suffix:fr[8+x/3]});return function(W){return _(w*W)}}return{format:l,formatPrefix:c}}var Ht,Yt,Et;Gt({thousands:",",grouping:[3],currency:["$",""]});function Gt(t){return Ht=cr(t),Yt=Ht.format,Et=Ht.formatPrefix,Ht}function Jt(t){return Math.max(0,-q(Math.abs(t)))}function Kt(t,e){return Math.max(0,Math.max(-8,Math.min(8,Math.floor(q(e)/3)))*3-q(Math.abs(t)))}function jt(t,e){return t=Math.abs(t),e=Math.abs(e)-t,Math.max(0,q(e)-q(t))+1}function te(t,e,r,n){var o=Bt(t,e,r),a;switch(n=Q(n??",f"),n.type){case"s":{var i=Math.max(Math.abs(t),Math.abs(e));return n.precision==null&&!isNaN(a=Kt(o,i))&&(n.precision=a),Et(n,i)}case"":case"e":case"g":case"p":case"r":{n.precision==null&&!isNaN(a=jt(o,Math.max(Math.abs(t),Math.abs(e))))&&(n.precision=a-(n.type==="e"));break}case"f":case"%":{n.precision==null&&!isNaN(a=Jt(o))&&(n.precision=a-(n.type==="%")*2);break}}return Yt(n)}function mr(t){var e=t.domain;return t.ticks=function(r){var n=e();return Nt(n[0],n[n.length-1],r??10)},t.tickFormat=function(r,n){var o=e();return te(o[0],o[o.length-1],r??10,n)},t.nice=function(r){r==null&&(r=10);var n=e(),o=0,a=n.length-1,i=n[o],u=n[a],s,l,c=10;for(u0;){if(l=pt(i,u,r),l===s)return n[o]=i,n[a]=u,e(n);if(l>0)i=Math.floor(i/l)*l,u=Math.ceil(u/l)*l;else if(l<0)i=Math.ceil(i*l)/l,u=Math.floor(u*l)/l;else break;s=l}return t},t}function yt(){var t=0,e=1,r=1,n=[.5],o=[0,1],a;function i(s){return s!=null&&s<=s?o[Vt(n,s,0,r)]:a}function u(){var s=-1;for(n=new Array(r);++s=r?[n[r-1],e]:[n[l-1],n[l]]},i.unknown=function(s){return arguments.length&&(a=s),i},i.thresholds=function(){return n.slice()},i.copy=function(){return yt().domain([t,e]).range(o).unknown(a)},nr.apply(mr(i),arguments)}var ee=new Date,re=new Date;function F(t,e,r,n){function o(a){return t(a=arguments.length===0?new Date:new Date(+a)),a}return o.floor=a=>(t(a=new Date(+a)),a),o.ceil=a=>(t(a=new Date(a-1)),e(a,1),t(a),a),o.round=a=>{let i=o(a),u=o.ceil(a);return a-i(e(a=new Date(+a),i==null?1:Math.floor(i)),a),o.range=(a,i,u)=>{let s=[];if(a=o.ceil(a),u=u==null?1:Math.floor(u),!(a0))return s;let l;do s.push(l=new Date(+a)),e(a,u),t(a);while(lF(i=>{if(i>=i)for(;t(i),!a(i);)i.setTime(i-1)},(i,u)=>{if(i>=i)if(u<0)for(;++u<=0;)for(;e(i,-1),!a(i););else for(;--u>=0;)for(;e(i,1),!a(i););}),r&&(o.count=(a,i)=>(ee.setTime(+a),re.setTime(+i),t(ee),t(re),Math.floor(r(ee,re))),o.every=a=>(a=Math.floor(a),!isFinite(a)||!(a>0)?null:a>1?o.filter(n?i=>n(i)%a===0:i=>o.count(0,i)%a===0):o)),o}var z=F(t=>t.setHours(0,0,0,0),(t,e)=>t.setDate(t.getDate()+e),(t,e)=>(e-t-(e.getTimezoneOffset()-t.getTimezoneOffset())*6e4)/864e5,t=>t.getDate()-1),ho=z.range,gt=F(t=>{t.setUTCHours(0,0,0,0)},(t,e)=>{t.setUTCDate(t.getUTCDate()+e)},(t,e)=>(e-t)/864e5,t=>t.getUTCDate()-1),yo=gt.range,pr=F(t=>{t.setUTCHours(0,0,0,0)},(t,e)=>{t.setUTCDate(t.getUTCDate()+e)},(t,e)=>(e-t)/864e5,t=>Math.floor(t/864e5)),go=pr.range;function rt(t){return F(e=>{e.setDate(e.getDate()-(e.getDay()+7-t)%7),e.setHours(0,0,0,0)},(e,r)=>{e.setDate(e.getDate()+r*7)},(e,r)=>(r-e-(r.getTimezoneOffset()-e.getTimezoneOffset())*6e4)/6048e5)}var nt=rt(0),X=rt(1),hr=rt(2),dr=rt(3),G=rt(4),yr=rt(5),gr=rt(6),xr=nt.range,vo=X.range,Mo=hr.range,wo=dr.range,_o=G.range,So=yr.range,To=gr.range;function ot(t){return F(e=>{e.setUTCDate(e.getUTCDate()-(e.getUTCDay()+7-t)%7),e.setUTCHours(0,0,0,0)},(e,r)=>{e.setUTCDate(e.getUTCDate()+r*7)},(e,r)=>(r-e)/6048e5)}var xt=ot(0),lt=ot(1),vr=ot(2),Mr=ot(3),J=ot(4),wr=ot(5),_r=ot(6),Sr=xt.range,Do=lt.range,Co=vr.range,bo=Mr.range,Uo=J.range,ko=wr.range,Ao=_r.range;var vt=F(t=>{t.setDate(1),t.setHours(0,0,0,0)},(t,e)=>{t.setMonth(t.getMonth()+e)},(t,e)=>e.getMonth()-t.getMonth()+(e.getFullYear()-t.getFullYear())*12,t=>t.getMonth()),Fo=vt.range,Tr=F(t=>{t.setUTCDate(1),t.setUTCHours(0,0,0,0)},(t,e)=>{t.setUTCMonth(t.getUTCMonth()+e)},(t,e)=>e.getUTCMonth()-t.getUTCMonth()+(e.getUTCFullYear()-t.getUTCFullYear())*12,t=>t.getUTCMonth()),No=Tr.range;var $=F(t=>{t.setMonth(0,1),t.setHours(0,0,0,0)},(t,e)=>{t.setFullYear(t.getFullYear()+e)},(t,e)=>e.getFullYear()-t.getFullYear(),t=>t.getFullYear());$.every=t=>!isFinite(t=Math.floor(t))||!(t>0)?null:F(e=>{e.setFullYear(Math.floor(e.getFullYear()/t)*t),e.setMonth(0,1),e.setHours(0,0,0,0)},(e,r)=>{e.setFullYear(e.getFullYear()+r*t)});var Lo=$.range,V=F(t=>{t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0)},(t,e)=>{t.setUTCFullYear(t.getUTCFullYear()+e)},(t,e)=>e.getUTCFullYear()-t.getUTCFullYear(),t=>t.getUTCFullYear());V.every=t=>!isFinite(t=Math.floor(t))||!(t>0)?null:F(e=>{e.setUTCFullYear(Math.floor(e.getUTCFullYear()/t)*t),e.setUTCMonth(0,1),e.setUTCHours(0,0,0,0)},(e,r)=>{e.setUTCFullYear(e.getUTCFullYear()+r*t)});var Ho=V.range;function oe(t){if(0<=t.y&&t.y<100){var e=new Date(-1,t.m,t.d,t.H,t.M,t.S,t.L);return e.setFullYear(t.y),e}return new Date(t.y,t.m,t.d,t.H,t.M,t.S,t.L)}function ae(t){if(0<=t.y&&t.y<100){var e=new Date(Date.UTC(-1,t.m,t.d,t.H,t.M,t.S,t.L));return e.setUTCFullYear(t.y),e}return new Date(Date.UTC(t.y,t.m,t.d,t.H,t.M,t.S,t.L))}function Mt(t,e,r){return{y:t,m:e,d:r,H:0,M:0,S:0,L:0}}function ie(t){var e=t.dateTime,r=t.date,n=t.time,o=t.periods,a=t.days,i=t.shortDays,u=t.months,s=t.shortMonths,l=wt(o),c=_t(o),p=wt(a),g=_t(a),x=wt(i),w=_t(i),_=wt(u),W=_t(u),P=wt(s),H=_t(s),A={a:Z,A:R,b:st,B:O,c:null,d:Ar,e:Ar,f:na,g:pa,G:da,H:ta,I:ea,j:ra,L:Yr,m:oa,M:aa,p:Zr,q:Qr,Q:Lr,s:Hr,S:ia,u:ua,U:sa,V:la,w:fa,W:ca,x:null,X:null,y:ma,Y:ha,Z:ya,"%":Nr},T={a:Xr,A:Gr,b:Jr,B:Kr,c:null,d:Fr,e:Fr,f:Ma,g:Aa,G:Na,H:ga,I:xa,j:va,L:Wr,m:wa,M:_a,p:jr,q:tn,Q:Lr,s:Hr,S:Sa,u:Ta,U:Da,V:Ca,w:ba,W:Ua,x:null,X:null,y:ka,Y:Fa,Z:La,"%":Nr},N={a:it,A:K,b:d,B:U,c:h,d:Ur,e:Ur,f:Go,g:br,G:Cr,H:kr,I:kr,j:Bo,L:Xo,m:Vo,M:Zo,p:B,q:$o,Q:Ko,s:jo,S:Qo,u:Io,U:Oo,V:qo,w:Po,W:Ro,x:C,X:ut,y:br,Y:Cr,Z:zo,"%":Jo};A.x=D(r,A),A.X=D(n,A),A.c=D(e,A),T.x=D(r,T),T.X=D(n,T),T.c=D(e,T);function D(m,y){return function(v){var f=[],L=-1,S=0,Y=m.length,E,j,me;for(v instanceof Date||(v=new Date(+v));++L53)return null;"w"in f||(f.w=1),"Z"in f?(S=ae(Mt(f.y,0,1)),Y=S.getUTCDay(),S=Y>4||Y===0?lt.ceil(S):lt(S),S=gt.offset(S,(f.V-1)*7),f.y=S.getUTCFullYear(),f.m=S.getUTCMonth(),f.d=S.getUTCDate()+(f.w+6)%7):(S=oe(Mt(f.y,0,1)),Y=S.getDay(),S=Y>4||Y===0?X.ceil(S):X(S),S=z.offset(S,(f.V-1)*7),f.y=S.getFullYear(),f.m=S.getMonth(),f.d=S.getDate()+(f.w+6)%7)}else("W"in f||"U"in f)&&("w"in f||(f.w="u"in f?f.u%7:"W"in f?1:0),Y="Z"in f?ae(Mt(f.y,0,1)).getUTCDay():oe(Mt(f.y,0,1)).getDay(),f.m=0,f.d="W"in f?(f.w+6)%7+f.W*7-(Y+5)%7:f.w+f.U*7-(Y+6)%7);return"Z"in f?(f.H+=f.Z/100|0,f.M+=f.Z%100,ae(f)):oe(f)}}function I(m,y,v,f){for(var L=0,S=y.length,Y=v.length,E,j;L=Y)return-1;if(E=y.charCodeAt(L++),E===37){if(E=y.charAt(L++),j=N[E in Dr?y.charAt(L++):E],!j||(f=j(m,v,f))<0)return-1}else if(E!=v.charCodeAt(f++))return-1}return f}function B(m,y,v){var f=l.exec(y.slice(v));return f?(m.p=c.get(f[0].toLowerCase()),v+f[0].length):-1}function it(m,y,v){var f=x.exec(y.slice(v));return f?(m.w=w.get(f[0].toLowerCase()),v+f[0].length):-1}function K(m,y,v){var f=p.exec(y.slice(v));return f?(m.w=g.get(f[0].toLowerCase()),v+f[0].length):-1}function d(m,y,v){var f=P.exec(y.slice(v));return f?(m.m=H.get(f[0].toLowerCase()),v+f[0].length):-1}function U(m,y,v){var f=_.exec(y.slice(v));return f?(m.m=W.get(f[0].toLowerCase()),v+f[0].length):-1}function h(m,y,v){return I(m,e,y,v)}function C(m,y,v){return I(m,r,y,v)}function ut(m,y,v){return I(m,n,y,v)}function Z(m){return i[m.getDay()]}function R(m){return a[m.getDay()]}function st(m){return s[m.getMonth()]}function O(m){return u[m.getMonth()]}function Zr(m){return o[+(m.getHours()>=12)]}function Qr(m){return 1+~~(m.getMonth()/3)}function Xr(m){return i[m.getUTCDay()]}function Gr(m){return a[m.getUTCDay()]}function Jr(m){return s[m.getUTCMonth()]}function Kr(m){return u[m.getUTCMonth()]}function jr(m){return o[+(m.getUTCHours()>=12)]}function tn(m){return 1+~~(m.getUTCMonth()/3)}return{format:function(m){var y=D(m+="",A);return y.toString=function(){return m},y},parse:function(m){var y=at(m+="",!1);return y.toString=function(){return m},y},utcFormat:function(m){var y=D(m+="",T);return y.toString=function(){return m},y},utcParse:function(m){var y=at(m+="",!0);return y.toString=function(){return m},y}}}var Dr={"-":"",_:" ",0:"0"},k=/^\s*\d+/,Yo=/^%/,Eo=/[\\^$*+?|[\]().{}]/g;function M(t,e,r){var n=t<0?"-":"",o=(n?-t:t)+"",a=o.length;return n+(a[e.toLowerCase(),r]))}function Po(t,e,r){var n=k.exec(e.slice(r,r+1));return n?(t.w=+n[0],r+n[0].length):-1}function Io(t,e,r){var n=k.exec(e.slice(r,r+1));return n?(t.u=+n[0],r+n[0].length):-1}function Oo(t,e,r){var n=k.exec(e.slice(r,r+2));return n?(t.U=+n[0],r+n[0].length):-1}function qo(t,e,r){var n=k.exec(e.slice(r,r+2));return n?(t.V=+n[0],r+n[0].length):-1}function Ro(t,e,r){var n=k.exec(e.slice(r,r+2));return n?(t.W=+n[0],r+n[0].length):-1}function Cr(t,e,r){var n=k.exec(e.slice(r,r+4));return n?(t.y=+n[0],r+n[0].length):-1}function br(t,e,r){var n=k.exec(e.slice(r,r+2));return n?(t.y=+n[0]+(+n[0]>68?1900:2e3),r+n[0].length):-1}function zo(t,e,r){var n=/^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(e.slice(r,r+6));return n?(t.Z=n[1]?0:-(n[2]+(n[3]||"00")),r+n[0].length):-1}function $o(t,e,r){var n=k.exec(e.slice(r,r+1));return n?(t.q=n[0]*3-3,r+n[0].length):-1}function Vo(t,e,r){var n=k.exec(e.slice(r,r+2));return n?(t.m=n[0]-1,r+n[0].length):-1}function Ur(t,e,r){var n=k.exec(e.slice(r,r+2));return n?(t.d=+n[0],r+n[0].length):-1}function Bo(t,e,r){var n=k.exec(e.slice(r,r+3));return n?(t.m=0,t.d=+n[0],r+n[0].length):-1}function kr(t,e,r){var n=k.exec(e.slice(r,r+2));return n?(t.H=+n[0],r+n[0].length):-1}function Zo(t,e,r){var n=k.exec(e.slice(r,r+2));return n?(t.M=+n[0],r+n[0].length):-1}function Qo(t,e,r){var n=k.exec(e.slice(r,r+2));return n?(t.S=+n[0],r+n[0].length):-1}function Xo(t,e,r){var n=k.exec(e.slice(r,r+3));return n?(t.L=+n[0],r+n[0].length):-1}function Go(t,e,r){var n=k.exec(e.slice(r,r+6));return n?(t.L=Math.floor(n[0]/1e3),r+n[0].length):-1}function Jo(t,e,r){var n=Yo.exec(e.slice(r,r+1));return n?r+n[0].length:-1}function Ko(t,e,r){var n=k.exec(e.slice(r));return n?(t.Q=+n[0],r+n[0].length):-1}function jo(t,e,r){var n=k.exec(e.slice(r));return n?(t.s=+n[0],r+n[0].length):-1}function Ar(t,e){return M(t.getDate(),e,2)}function ta(t,e){return M(t.getHours(),e,2)}function ea(t,e){return M(t.getHours()%12||12,e,2)}function ra(t,e){return M(1+z.count($(t),t),e,3)}function Yr(t,e){return M(t.getMilliseconds(),e,3)}function na(t,e){return Yr(t,e)+"000"}function oa(t,e){return M(t.getMonth()+1,e,2)}function aa(t,e){return M(t.getMinutes(),e,2)}function ia(t,e){return M(t.getSeconds(),e,2)}function ua(t){var e=t.getDay();return e===0?7:e}function sa(t,e){return M(nt.count($(t)-1,t),e,2)}function Er(t){var e=t.getDay();return e>=4||e===0?G(t):G.ceil(t)}function la(t,e){return t=Er(t),M(G.count($(t),t)+($(t).getDay()===4),e,2)}function fa(t){return t.getDay()}function ca(t,e){return M(X.count($(t)-1,t),e,2)}function ma(t,e){return M(t.getFullYear()%100,e,2)}function pa(t,e){return t=Er(t),M(t.getFullYear()%100,e,2)}function ha(t,e){return M(t.getFullYear()%1e4,e,4)}function da(t,e){var r=t.getDay();return t=r>=4||r===0?G(t):G.ceil(t),M(t.getFullYear()%1e4,e,4)}function ya(t){var e=t.getTimezoneOffset();return(e>0?"-":(e*=-1,"+"))+M(e/60|0,"0",2)+M(e%60,"0",2)}function Fr(t,e){return M(t.getUTCDate(),e,2)}function ga(t,e){return M(t.getUTCHours(),e,2)}function xa(t,e){return M(t.getUTCHours()%12||12,e,2)}function va(t,e){return M(1+gt.count(V(t),t),e,3)}function Wr(t,e){return M(t.getUTCMilliseconds(),e,3)}function Ma(t,e){return Wr(t,e)+"000"}function wa(t,e){return M(t.getUTCMonth()+1,e,2)}function _a(t,e){return M(t.getUTCMinutes(),e,2)}function Sa(t,e){return M(t.getUTCSeconds(),e,2)}function Ta(t){var e=t.getUTCDay();return e===0?7:e}function Da(t,e){return M(xt.count(V(t)-1,t),e,2)}function Pr(t){var e=t.getUTCDay();return e>=4||e===0?J(t):J.ceil(t)}function Ca(t,e){return t=Pr(t),M(J.count(V(t),t)+(V(t).getUTCDay()===4),e,2)}function ba(t){return t.getUTCDay()}function Ua(t,e){return M(lt.count(V(t)-1,t),e,2)}function ka(t,e){return M(t.getUTCFullYear()%100,e,2)}function Aa(t,e){return t=Pr(t),M(t.getUTCFullYear()%100,e,2)}function Fa(t,e){return M(t.getUTCFullYear()%1e4,e,4)}function Na(t,e){var r=t.getUTCDay();return t=r>=4||r===0?J(t):J.ceil(t),M(t.getUTCFullYear()%1e4,e,4)}function La(){return"+0000"}function Nr(){return"%"}function Lr(t){return+t}function Hr(t){return Math.floor(+t/1e3)}var ft,ct,Ir,Or,qr;ue({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});function ue(t){return ft=ie(t),ct=ft.format,Ir=ft.parse,Or=ft.utcFormat,qr=ft.utcParse,ft}var le={cellSize:13,cellGap:2,marginTop:20,marginLeft:30,marginBottom:4},se=["#9be9a8","#40c463","#30a14e","#216e39"],Ha=["Mon","","Wed","","Fri","",""],Ya=["Sun","","Tue","","Thu","","Sat"];function Ea(t){return t==="sunday"?Ya:Ha}var Wa=ct("%b"),fe=ct("%Y-%m-%d"),zr=ct("%a, %b %-d, %Y");function Pa(t){try{return getComputedStyle(t).getPropertyValue("--tblr-bg-surface"),se}catch{return se}}function Ia(t){let e=new Map;for(let r of t)e.set(r.date,r);return e}function $r(t){return t==="sunday"?nt:X}function Oa(t,e,r,n="monday"){let o=$r(n),a=o.floor(t),i=[],u=new Date(t);for(;u<=e;){let s=fe(u),l=o.count(a,u),c=u.getDay(),p=n==="sunday"?c:(c+6)%7,g=c===0||c===6;i.push({date:new Date(u),dateStr:s,entry:r.get(s)||null,week:l,day:p,isWeekend:g}),u=z.offset(u,1)}return i}function qa(){let t=document.createElement("div");return t.className="heatmap-tooltip",t.style.display="none",t}function Vr(t){if(t.length===0)return 0;let e=new Set(t.filter(a=>a.hours>0).map(a=>a.date));if(e.size===0)return 0;let r=new Date;r.setHours(0,0,0,0);let n=new Date(r);e.has(fe(n))||(n=z.offset(n,-1));let o=0;for(;e.has(fe(n));)o++,n=z.offset(n,-1);return o}function Br(t){let e=t.filter(a=>a.hours>0);if(e.length===0)return{totalHours:0,avgHours:0,busiestDay:null};let r=Math.round(e.reduce((a,i)=>a+i.hours,0)*10)/10,n=Math.round(r/e.length*10)/10,o=e.reduce((a,i)=>i.hours>a.hours?i:a);return{totalHours:r,avgHours:n,busiestDay:{date:o.date,hours:o.hours}}}function Rr(t,e){let r=t.querySelector(".heatmap-stats");r&&r.remove();let n=Vr(e),o=Br(e),a=document.createElement("div");a.className="heatmap-stats";let i=[];if(i.push(`\u{1F525} ${n} days`),i.push(`Total: ${o.totalHours}h`),i.push(`Avg: ${o.avgHours}h/day`),o.busiestDay){let u=new Date(o.busiestDay.date+"T00:00:00"),s=zr(u);i.push(`Busiest: ${s} \u2014 ${o.busiestDay.hours.toFixed(1)}h`)}a.innerHTML=i.join(""),t.appendChild(a)}function ce(t,e,r=le,n,o,a="monday"){if(t.innerHTML="",!e.days||e.days.length===0){let h=document.createElement("div");h.textContent=o||"No tracking data available",h.style.padding="1rem",h.style.color="var(--tblr-secondary, #6c757d)",t.appendChild(h);return}let i=Ia(e.days),u=new Date(e.range.begin),s=new Date(e.range.end),l=Oa(u,s,i,a),c=ht(e.days,h=>h.hours)||1,p=Pa(t),g=yt().domain([0,c]).range(p),{cellGap:x,marginTop:w,marginLeft:_,marginBottom:W}=r,P=(ht(l,h=>h.week)??0)+1,H=t.clientWidth||800,T=Math.min(18,Math.max(2,Math.floor((H-_)/P)-x)),N=T+x,D=_+P*N,at=w+7*N+W,I=document.createElement("div");I.style.maxWidth=`${D}px`,I.style.margin="0 auto",t.appendChild(I);let B=Rt(I).append("svg").attr("width",D).attr("height",at).attr("class","heatmap-svg"),it=$r(a),K=[],d=it.floor(u);vt.range(vt.ceil(u),s).forEach(h=>{K.push({date:h,week:it.count(d,h)})}),B.selectAll(".month-label").data(K).join("text").attr("class","heatmap-label month-label").attr("x",h=>_+h.week*N).attr("y",w-6).text(h=>Wa(h.date)),B.selectAll(".day-label").data(Ea(a)).join("text").attr("class","heatmap-label day-label").attr("x",_-6).attr("y",(h,C)=>w+C*N+T-2).attr("text-anchor","end").text(h=>h),document.querySelectorAll(".heatmap-tooltip").forEach(h=>h.remove());let U=qa();U.style.position="fixed",document.body.appendChild(U),B.selectAll(".heatmap-cell").data(l).join("rect").attr("class",h=>{let C="heatmap-cell";return h.entry||(C+=" heatmap-empty"),h.isWeekend&&(C+=" heatmap-weekend"),C}).attr("x",h=>_+h.week*N).attr("y",h=>w+h.day*N).attr("width",T).attr("height",T).attr("fill",h=>h.entry?g(h.entry.hours):"").on("mouseenter",function(h,C){let ut=C.entry?C.entry.hours.toFixed(1):"0.0",Z=C.entry?C.entry.count:0;U.innerHTML=`${zr(C.date)}
${ut}h (${Z} entries)`,U.style.display="block";let R=h.target.getBoundingClientRect();U.style.left=`${R.left+T/2}px`,U.style.top=`${R.top-U.offsetHeight-8}px`}).on("mouseleave",function(){U.style.display="none"}).on("click",function(h,C){n&&n(C.dateStr)})}function Ra(t){let e=t.getAttribute("data-url");if(!e){console.error("KimaiHeatmap: missing data-url attribute");return}let r=t.getAttribute("data-timesheet-url")||"/en/timesheet/",n=t.getAttribute("data-week-start")||"monday",o=t.getAttribute("data-projects"),a=o?JSON.parse(o):[],i=null,u=c=>{let p=`${c} - ${c}`,g=`${r}?daterange=${encodeURIComponent(p)}`;i&&(g+=`&projects[]=${i}`),window.location.href=g};t.innerHTML="";let s=document.createElement("div");s.className="heatmap-wrapper";let l=document.createElement("div");if(l.className="heatmap-svg-area",s.appendChild(l),a.length>0){let c=document.createElement("div");c.className="heatmap-filter";let p=document.createElement("select");p.className="form-select form-select-sm",p.setAttribute("aria-label","Filter by project");let g=document.createElement("option");g.value="",g.textContent="All Projects",p.appendChild(g);for(let x of a){let w=document.createElement("option");w.value=String(x.id),w.textContent=x.name,p.appendChild(w)}p.addEventListener("change",()=>{let x=p.value;i=x?parseInt(x,10):null;let w=x?`${e}?project=${x}`:e;fetch(w).then(_=>{if(!_.ok)throw new Error(`HTTP ${_.status}`);return _.json()}).then(_=>{ce(l,_,le,u,"No tracking data for this project",n),Rr(s,_.days)}).catch(_=>{console.error("KimaiHeatmap: failed to load filtered data",_)})}),c.appendChild(p),s.appendChild(c)}t.appendChild(s),fetch(e).then(c=>{if(!c.ok)throw new Error(`HTTP ${c.status}`);return c.json()}).then(c=>{ce(l,c,le,u,void 0,n),Rr(s,c.days)}).catch(c=>{console.error("KimaiHeatmap: failed to load data",c),l.textContent="Failed to load heatmap data"})}return un(za);})();
diff --git a/Resources/views/widget/heatmap.html.twig b/Resources/views/widget/heatmap.html.twig
index bf09a89..553bc42 100644
--- a/Resources/views/widget/heatmap.html.twig
+++ b/Resources/views/widget/heatmap.html.twig
@@ -8,6 +8,7 @@
data-url="{{ path('heatmap_data') }}"
data-projects="{{ data.projects|json_encode }}"
data-timesheet-url="{{ path('timesheet') }}"
+ data-week-start="{{ data.weekStart }}"
style="min-height: 150px;">
diff --git a/Widget/HeatmapWidget.php b/Widget/HeatmapWidget.php
index c9bcd6c..fb698e2 100644
--- a/Widget/HeatmapWidget.php
+++ b/Widget/HeatmapWidget.php
@@ -43,6 +43,7 @@ class HeatmapWidget extends AbstractWidget
return [
'projects' => $this->service->getUserProjects($user),
+ 'weekStart' => $user->getFirstDayOfWeek(),
];
}
diff --git a/assets/src/heatmap.ts b/assets/src/heatmap.ts
index 96a6253..6d44ab8 100644
--- a/assets/src/heatmap.ts
+++ b/assets/src/heatmap.ts
@@ -1,6 +1,6 @@
import { select } from 'd3-selection';
import { scaleQuantize } from 'd3-scale';
-import { timeMonday, timeDay, timeMonth } from 'd3-time';
+import { timeMonday, timeSunday, timeDay, timeMonth } from 'd3-time';
import { timeFormat } from 'd3-time-format';
import { max } from 'd3-array';
import type { DayEntry, HeatmapData, HeatmapConfig, ProjectOption } from './types';
@@ -14,7 +14,12 @@ const DEFAULT_CONFIG: HeatmapConfig = {
};
const FALLBACK_COLORS = ['#9be9a8', '#40c463', '#30a14e', '#216e39'];
-const DAY_LABELS = ['Mon', '', 'Wed', '', 'Fri', '', ''];
+const DAY_LABELS_MONDAY = ['Mon', '', 'Wed', '', 'Fri', '', ''];
+const DAY_LABELS_SUNDAY = ['Sun', '', 'Tue', '', 'Thu', '', 'Sat'];
+
+function getDayLabels(weekStart: string): string[] {
+ return weekStart === 'sunday' ? DAY_LABELS_SUNDAY : DAY_LABELS_MONDAY;
+}
const MONTH_FORMAT = timeFormat('%b');
const DATE_FORMAT = timeFormat('%Y-%m-%d');
const DISPLAY_FORMAT = timeFormat('%a, %b %-d, %Y');
@@ -25,6 +30,7 @@ interface DayCell {
entry: DayEntry | null;
week: number;
day: number;
+ isWeekend: boolean;
}
function resolveColors(container: HTMLElement): string[] {
@@ -46,19 +52,29 @@ function buildDateMap(days: DayEntry[]): Map {
return map;
}
+function getWeekInterval(weekStart: string) {
+ return weekStart === 'sunday' ? timeSunday : timeMonday;
+}
+
function generateCells(
begin: Date,
end: Date,
dateMap: Map,
+ weekStart: string = 'monday',
): DayCell[] {
- const firstMonday = timeMonday.floor(begin);
+ const weekInterval = getWeekInterval(weekStart);
+ const firstWeekDay = weekInterval.floor(begin);
const cells: DayCell[] = [];
let current = new Date(begin);
while (current <= end) {
const dateStr = DATE_FORMAT(current);
- const weeksSinceStart = timeMonday.count(firstMonday, current);
- const dayOfWeek = (current.getDay() + 6) % 7; // Monday=0, Sunday=6
+ const weeksSinceStart = weekInterval.count(firstWeekDay, current);
+ const jsDay = current.getDay(); // 0=Sunday, 6=Saturday
+ const dayOfWeek = weekStart === 'sunday'
+ ? jsDay // Sunday=0 already first
+ : (jsDay + 6) % 7; // Monday=0, Sunday=6
+ const isWeekend = jsDay === 0 || jsDay === 6;
cells.push({
date: new Date(current),
@@ -66,6 +82,7 @@ function generateCells(
entry: dateMap.get(dateStr) || null,
week: weeksSinceStart,
day: dayOfWeek,
+ isWeekend,
});
current = timeDay.offset(current, 1);
@@ -81,12 +98,88 @@ function createTooltip(): HTMLDivElement {
return tip;
}
+export function calculateStreak(days: DayEntry[]): number {
+ if (days.length === 0) return 0;
+
+ const tracked = new Set(
+ days.filter((d) => d.hours > 0).map((d) => d.date),
+ );
+ if (tracked.size === 0) return 0;
+
+ const today = new Date();
+ today.setHours(0, 0, 0, 0);
+ let current = new Date(today);
+
+ // If today has no entry, start from yesterday
+ if (!tracked.has(DATE_FORMAT(current))) {
+ current = timeDay.offset(current, -1);
+ }
+
+ let streak = 0;
+ while (tracked.has(DATE_FORMAT(current))) {
+ streak++;
+ current = timeDay.offset(current, -1);
+ }
+
+ return streak;
+}
+
+export interface HeatmapStats {
+ totalHours: number;
+ avgHours: number;
+ busiestDay: { date: string; hours: number } | null;
+}
+
+export function calculateStats(days: DayEntry[]): HeatmapStats {
+ const withEntries = days.filter((d) => d.hours > 0);
+ if (withEntries.length === 0) {
+ return { totalHours: 0, avgHours: 0, busiestDay: null };
+ }
+
+ const totalHours = Math.round(withEntries.reduce((sum, d) => sum + d.hours, 0) * 10) / 10;
+ const avgHours = Math.round((totalHours / withEntries.length) * 10) / 10;
+ const busiest = withEntries.reduce((best, d) => (d.hours > best.hours ? d : best));
+
+ return {
+ totalHours,
+ avgHours,
+ busiestDay: { date: busiest.date, hours: busiest.hours },
+ };
+}
+
+function renderStats(container: HTMLElement, days: DayEntry[]): void {
+ // Remove existing stats
+ const existing = container.querySelector('.heatmap-stats');
+ if (existing) existing.remove();
+
+ const streak = calculateStreak(days);
+ const stats = calculateStats(days);
+
+ const statsDiv = document.createElement('div');
+ statsDiv.className = 'heatmap-stats';
+
+ const parts: string[] = [];
+ parts.push(`\u{1F525} ${streak} days`);
+ parts.push(`Total: ${stats.totalHours}h`);
+ parts.push(`Avg: ${stats.avgHours}h/day`);
+
+ if (stats.busiestDay) {
+ const d = new Date(stats.busiestDay.date + 'T00:00:00');
+ const label = DISPLAY_FORMAT(d);
+ parts.push(`Busiest: ${label} \u2014 ${stats.busiestDay.hours.toFixed(1)}h`);
+ }
+
+ statsDiv.innerHTML = parts.join('');
+ container.appendChild(statsDiv);
+}
+
export function renderHeatmap(
container: HTMLElement,
data: HeatmapData,
config: HeatmapConfig = DEFAULT_CONFIG,
onCellClick?: (dateStr: string) => void,
emptyMessage?: string,
+ weekStart: string = 'monday',
): void {
container.innerHTML = '';
@@ -102,7 +195,7 @@ export function renderHeatmap(
const dateMap = buildDateMap(data.days);
const begin = new Date(data.range.begin);
const end = new Date(data.range.end);
- const cells = generateCells(begin, end, dateMap);
+ const cells = generateCells(begin, end, dateMap, weekStart);
const maxHours = max(data.days, (d) => d.hours) || 1;
const colors = resolveColors(container);
@@ -134,12 +227,13 @@ export function renderHeatmap(
.attr('class', 'heatmap-svg');
// Month labels
+ const weekInterval = getWeekInterval(weekStart);
const months: { date: Date; week: number }[] = [];
- const firstMonday = timeMonday.floor(begin);
+ const firstWeekDay = weekInterval.floor(begin);
timeMonth.range(timeMonth.ceil(begin), end).forEach((m) => {
months.push({
date: m,
- week: timeMonday.count(firstMonday, m),
+ week: weekInterval.count(firstWeekDay, m),
});
});
@@ -152,10 +246,10 @@ export function renderHeatmap(
.attr('y', marginTop - 6)
.text((d) => MONTH_FORMAT(d.date));
- // Day labels (Mon, Wed, Fri)
+ // Day labels
svg
.selectAll('.day-label')
- .data(DAY_LABELS)
+ .data(getDayLabels(weekStart))
.join('text')
.attr('class', 'heatmap-label day-label')
.attr('x', marginLeft - 6)
@@ -175,9 +269,12 @@ export function renderHeatmap(
.selectAll('.heatmap-cell')
.data(cells)
.join('rect')
- .attr('class', (d) =>
- d.entry ? 'heatmap-cell' : 'heatmap-cell heatmap-empty',
- )
+ .attr('class', (d) => {
+ let cls = 'heatmap-cell';
+ if (!d.entry) cls += ' heatmap-empty';
+ if (d.isWeekend) cls += ' heatmap-weekend';
+ return cls;
+ })
.attr('x', (d) => marginLeft + d.week * step)
.attr('y', (d) => marginTop + d.day * step)
.attr('width', cellSize)
@@ -210,6 +307,7 @@ export function init(container: HTMLElement): void {
}
const timesheetUrl = container.getAttribute('data-timesheet-url') || '/en/timesheet/';
+ const weekStart = container.getAttribute('data-week-start') || 'monday';
const projectsJson = container.getAttribute('data-projects');
const projects: ProjectOption[] = projectsJson ? JSON.parse(projectsJson) : [];
@@ -265,7 +363,8 @@ export function init(container: HTMLElement): void {
return res.json() as Promise;
})
.then(data => {
- renderHeatmap(svgArea, data, DEFAULT_CONFIG, onCellClick, 'No tracking data for this project');
+ renderHeatmap(svgArea, data, DEFAULT_CONFIG, onCellClick, 'No tracking data for this project', weekStart);
+ renderStats(wrapper, data.days);
})
.catch(err => {
console.error('KimaiHeatmap: failed to load filtered data', err);
@@ -285,7 +384,8 @@ export function init(container: HTMLElement): void {
return res.json() as Promise;
})
.then(data => {
- renderHeatmap(svgArea, data, DEFAULT_CONFIG, onCellClick);
+ renderHeatmap(svgArea, data, DEFAULT_CONFIG, onCellClick, undefined, weekStart);
+ renderStats(wrapper, data.days);
})
.catch(err => {
console.error('KimaiHeatmap: failed to load data', err);
diff --git a/assets/test/heatmap.test.ts b/assets/test/heatmap.test.ts
index 4715ecd..d32ef10 100644
--- a/assets/test/heatmap.test.ts
+++ b/assets/test/heatmap.test.ts
@@ -133,4 +133,40 @@ describe('renderHeatmap', () => {
const rects = container.querySelectorAll('rect.heatmap-cell');
expect(rects.length).toBe(7);
});
+
+ it('applies heatmap-weekend class to Saturday and Sunday cells', () => {
+ // 2025-01-04 is Saturday, 2025-01-05 is Sunday
+ const data: HeatmapData = {
+ days: [
+ { date: '2025-01-04', hours: 2.0, count: 1 },
+ ],
+ range: { begin: '2025-01-01', end: '2025-01-07' },
+ };
+ renderHeatmap(container, data);
+ const weekendRects = container.querySelectorAll('rect.heatmap-weekend');
+ // Jan 1 (Wed) through Jan 7 (Tue): Sat Jan 4 + Sun Jan 5 = 2 weekend cells
+ expect(weekendRects.length).toBe(2);
+ });
+
+ it('does not apply heatmap-weekend class to weekday cells', () => {
+ const data: HeatmapData = {
+ days: [
+ { date: '2025-01-06', hours: 2.0, count: 1 },
+ ],
+ range: { begin: '2025-01-06', end: '2025-01-10' },
+ };
+ renderHeatmap(container, data);
+ const weekendRects = container.querySelectorAll('rect.heatmap-weekend');
+ // Mon-Fri, no weekends
+ expect(weekendRects.length).toBe(0);
+ });
+
+ it('renders day labels for Sunday week start', () => {
+ renderHeatmap(container, makeMockData(), undefined, undefined, undefined, 'sunday');
+ const dayLabels = container.querySelectorAll('text.day-label');
+ const texts = Array.from(dayLabels).map((el) => el.textContent);
+ expect(texts).toContain('Sun');
+ expect(texts).toContain('Tue');
+ expect(texts).toContain('Thu');
+ });
});
diff --git a/assets/test/stats.test.ts b/assets/test/stats.test.ts
new file mode 100644
index 0000000..16d53bc
--- /dev/null
+++ b/assets/test/stats.test.ts
@@ -0,0 +1,128 @@
+import { describe, it, expect } from 'vitest';
+import { timeFormat } from 'd3-time-format';
+import { timeDay } from 'd3-time';
+import { calculateStreak, calculateStats } from '../src/heatmap';
+import type { DayEntry } from '../src/types';
+
+const DATE_FORMAT = timeFormat('%Y-%m-%d');
+
+function daysAgo(n: number): string {
+ const d = new Date();
+ d.setHours(0, 0, 0, 0);
+ return DATE_FORMAT(timeDay.offset(d, -n));
+}
+
+describe('calculateStreak', () => {
+ it('returns 0 for empty data', () => {
+ expect(calculateStreak([])).toBe(0);
+ });
+
+ it('returns 0 when no days have hours > 0', () => {
+ const days: DayEntry[] = [
+ { date: daysAgo(0), hours: 0, count: 0 },
+ ];
+ expect(calculateStreak(days)).toBe(0);
+ });
+
+ it('returns 1 for a single entry today', () => {
+ const days: DayEntry[] = [
+ { date: daysAgo(0), hours: 2.0, count: 1 },
+ ];
+ expect(calculateStreak(days)).toBe(1);
+ });
+
+ it('counts consecutive days ending today', () => {
+ const days: DayEntry[] = [
+ { date: daysAgo(0), hours: 1.0, count: 1 },
+ { date: daysAgo(1), hours: 2.0, count: 1 },
+ { date: daysAgo(2), hours: 3.0, count: 1 },
+ ];
+ expect(calculateStreak(days)).toBe(3);
+ });
+
+ it('starts from yesterday if today has no entry', () => {
+ const days: DayEntry[] = [
+ { date: daysAgo(1), hours: 2.0, count: 1 },
+ { date: daysAgo(2), hours: 3.0, count: 1 },
+ ];
+ expect(calculateStreak(days)).toBe(2);
+ });
+
+ it('breaks streak at gaps', () => {
+ const days: DayEntry[] = [
+ { date: daysAgo(0), hours: 1.0, count: 1 },
+ { date: daysAgo(1), hours: 2.0, count: 1 },
+ // gap at daysAgo(2)
+ { date: daysAgo(3), hours: 3.0, count: 1 },
+ ];
+ expect(calculateStreak(days)).toBe(2);
+ });
+
+ it('does not count days with 0 hours as tracked', () => {
+ const days: DayEntry[] = [
+ { date: daysAgo(0), hours: 1.0, count: 1 },
+ { date: daysAgo(1), hours: 0, count: 0 },
+ { date: daysAgo(2), hours: 2.0, count: 1 },
+ ];
+ expect(calculateStreak(days)).toBe(1);
+ });
+});
+
+describe('calculateStats', () => {
+ it('returns zeros for empty data', () => {
+ const stats = calculateStats([]);
+ expect(stats.totalHours).toBe(0);
+ expect(stats.avgHours).toBe(0);
+ expect(stats.busiestDay).toBeNull();
+ });
+
+ it('returns zeros when all hours are 0', () => {
+ const days: DayEntry[] = [
+ { date: '2025-01-01', hours: 0, count: 0 },
+ ];
+ const stats = calculateStats(days);
+ expect(stats.totalHours).toBe(0);
+ expect(stats.busiestDay).toBeNull();
+ });
+
+ it('computes total hours correctly', () => {
+ const days: DayEntry[] = [
+ { date: '2025-01-01', hours: 2.5, count: 1 },
+ { date: '2025-01-02', hours: 3.5, count: 2 },
+ { date: '2025-01-03', hours: 4.0, count: 1 },
+ ];
+ const stats = calculateStats(days);
+ expect(stats.totalHours).toBe(10);
+ });
+
+ it('computes average over days with entries only', () => {
+ const days: DayEntry[] = [
+ { date: '2025-01-01', hours: 3.0, count: 1 },
+ { date: '2025-01-02', hours: 0, count: 0 },
+ { date: '2025-01-03', hours: 6.0, count: 2 },
+ ];
+ const stats = calculateStats(days);
+ // avg = 9.0 / 2 = 4.5 (not 9/3)
+ expect(stats.avgHours).toBe(4.5);
+ });
+
+ it('identifies the busiest day', () => {
+ const days: DayEntry[] = [
+ { date: '2025-01-01', hours: 2.0, count: 1 },
+ { date: '2025-01-02', hours: 8.5, count: 3 },
+ { date: '2025-01-03', hours: 4.0, count: 2 },
+ ];
+ const stats = calculateStats(days);
+ expect(stats.busiestDay).toEqual({ date: '2025-01-02', hours: 8.5 });
+ });
+
+ it('rounds to one decimal place', () => {
+ const days: DayEntry[] = [
+ { date: '2025-01-01', hours: 1.33, count: 1 },
+ { date: '2025-01-02', hours: 2.67, count: 1 },
+ ];
+ const stats = calculateStats(days);
+ expect(stats.totalHours).toBe(4);
+ expect(stats.avgHours).toBe(2);
+ });
+});