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); + }); +});