UNPKG

@obliczeniowo/elementary

Version:
354 lines (347 loc) 24.7 kB
import * as i0 from '@angular/core'; import { input, EventEmitter, effect, computed, HostBinding, HostListener, Output, Input, Component, LOCALE_ID, ContentChild, Inject, NgModule } from '@angular/core'; import { lastValueFrom, map } from 'rxjs'; import { keys } from '@obliczeniowo/elementary/objects'; import * as i1 from '@obliczeniowo/elementary/loading'; import { LoadingModule } from '@obliczeniowo/elementary/loading'; import { ElementaryMath } from '@obliczeniowo/elementary/math'; import { Dates } from '@obliczeniowo/elementary/classes'; import * as i1$1 from '@angular/common'; import { CommonModule } from '@angular/common'; import * as i2 from '@obliczeniowo/elementary/year-picker'; import { YearPickerModule } from '@obliczeniowo/elementary/year-picker'; class DailyStatisticComponent { loading = false; selected = false; /** day statistic day */ statistics = input.required(); /** */ settings = input.required(); get x() { return this.statistics()?.x ?? 0; } get y() { return this.statistics()?.y ?? 0; } clicked = new EventEmitter(); async onClick() { if (this.loading) { return; } const { date, count, data } = this.statistics(); const settings = this.settings(); if (!data && settings.getExtData) { this.loading = true; const data = await lastValueFrom(settings.getExtData(this.statistics())); this.loading = false; if (data) { this.statistics().data = data; } } this.clicked.emit({ date, count, data: data || this.statistics().data }); } hovered = new EventEmitter(); hover() { this.hovered.emit(this.statistics()); } leave() { this.hovered.emit(undefined); } constructor() { effect(() => { this.setCurrentColor(); }); } setCurrentColor() { if (this.settings() && this.statistics()) { const { count } = this.statistics(); const { colors, colorsStats } = this.settings(); const key = (keys(colorsStats || {})).map(num => +num).sort((p, c) => p - c).find((item) => item >= count); this.currentColor = count === 0 || !key ? colors[0] : colorsStats?.[key]; } } currentColor = 'rgba(200, 200, 200, 0.5)'; get width() { return this.settings().size || 10; } get height() { return this.settings().size || 10; } size = computed(() => this.settings().size || 10); static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: DailyStatisticComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.4", type: DailyStatisticComponent, isStandalone: false, selector: "obl-daily-statistic", inputs: { selected: { classPropertyName: "selected", publicName: "selected", isSignal: false, isRequired: false, transformFunction: null }, statistics: { classPropertyName: "statistics", publicName: "statistics", isSignal: true, isRequired: true, transformFunction: null }, settings: { classPropertyName: "settings", publicName: "settings", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { clicked: "clicked", hovered: "hovered" }, host: { listeners: { "click": "onClick()", "mouseover": "hover()", "mouseleave": "leave()" }, properties: { "class.selected": "this.selected", "style.left.px": "this.x", "style.top.px": "this.y", "style.background-color": "this.currentColor", "style.width.px": "this.width", "style.height.px": "this.height" } }, ngImport: i0, template: "@if (loading) {\n <obl-spinner\n [style.width.px]=\"size() * 0.8\"\n [style.height.px]=\"size() * 0.8\"\n [style.margin.px]=\"size() * 0.2\"\n ></obl-spinner>\n}\n", styles: [":host{position:absolute;overflow:hidden;display:flex;justify-content:center;align-items:center;cursor:pointer;box-sizing:border-box}:host:hover{opacity:.8;border:var(--daily-statistic-border, 1px) solid var(--daily-statistic-border-hover-color, gray)}:host.selected{border:var(--daily-statistic-border, 1px) solid var(--daily-statistic-border-selected-color, gray)}obl-spinner{--obl-spinner-color: black}\n"], dependencies: [{ kind: "component", type: i1.SpinnerComponent, selector: "obl-spinner" }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: DailyStatisticComponent, decorators: [{ type: Component, args: [{ selector: 'obl-daily-statistic', standalone: false, template: "@if (loading) {\n <obl-spinner\n [style.width.px]=\"size() * 0.8\"\n [style.height.px]=\"size() * 0.8\"\n [style.margin.px]=\"size() * 0.2\"\n ></obl-spinner>\n}\n", styles: [":host{position:absolute;overflow:hidden;display:flex;justify-content:center;align-items:center;cursor:pointer;box-sizing:border-box}:host:hover{opacity:.8;border:var(--daily-statistic-border, 1px) solid var(--daily-statistic-border-hover-color, gray)}:host.selected{border:var(--daily-statistic-border, 1px) solid var(--daily-statistic-border-selected-color, gray)}obl-spinner{--obl-spinner-color: black}\n"] }] }], ctorParameters: () => [], propDecorators: { selected: [{ type: HostBinding, args: ['class.selected'] }, { type: Input }], x: [{ type: HostBinding, args: ['style.left.px'] }], y: [{ type: HostBinding, args: ['style.top.px'] }], clicked: [{ type: Output }], onClick: [{ type: HostListener, args: ['click'] }], hovered: [{ type: Output }], hover: [{ type: HostListener, args: ['mouseover'] }], leave: [{ type: HostListener, args: ['mouseleave'] }], currentColor: [{ type: HostBinding, args: ['style.background-color'] }], width: [{ type: HostBinding, args: ['style.width.px'] }], height: [{ type: HostBinding, args: ['style.height.px'] }] } }); class DailyLegendComponent { settings = input.required(); sort = (p, c) => (+p.key) - (+c.key); static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: DailyLegendComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.4", type: DailyLegendComponent, isStandalone: false, selector: "obl-daily-legend", inputs: { settings: { classPropertyName: "settings", publicName: "settings", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<span>{{ settings().text?.less || 'less' }}</span>\n\n@for (item of (settings().colorsStats || {}) | keyvalue: sort; track item.key) {\n <div\n [style.background]=\"item.value\"\n [style.width.px]=\"settings().size\"\n [style.height.px]=\"settings().size\"\n [style.margin.px]=\"settings().margin\"\n ></div>\n}\n\n<span>{{ settings().text?.more || 'more' }}</span>\n", styles: [":host{display:flex;align-items:center;font-size:12px;color:var(--default-text-color)}:host span{margin:5px}\n"], dependencies: [{ kind: "pipe", type: i1$1.KeyValuePipe, name: "keyvalue" }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: DailyLegendComponent, decorators: [{ type: Component, args: [{ selector: 'obl-daily-legend', standalone: false, template: "<span>{{ settings().text?.less || 'less' }}</span>\n\n@for (item of (settings().colorsStats || {}) | keyvalue: sort; track item.key) {\n <div\n [style.background]=\"item.value\"\n [style.width.px]=\"settings().size\"\n [style.height.px]=\"settings().size\"\n [style.margin.px]=\"settings().margin\"\n ></div>\n}\n\n<span>{{ settings().text?.more || 'more' }}</span>\n", styles: [":host{display:flex;align-items:center;font-size:12px;color:var(--default-text-color)}:host span{margin:5px}\n"] }] }] }); class DailyStatisticsComponent { locale; statistics = []; _settings; max = 0; daysHeaders = []; monthHeaders = []; selected; hovered; total; selectedIndex; default = { min: new Date().getFullYear() - 20, max: new Date().getFullYear() }; totalRef; selectedRef; /** settings for all view components */ set settings(value) { const dateChanged = !this._settings || !Dates.equalToDayLevel(this._settings.end, value.end) || !Dates.equalToDayLevel(this._settings.start, value.start); this._settings = value; const first = Dates.getWeekDate(value.start, 'first', this.weakDayOffset); const date = new Date(first); this.daysHeaders = []; for (let i = 0; i < 7; i += 2) { date.setDate(date.getDate() + (i ? 2 : 0)); this.daysHeaders.push(new Date(date)); } if (dateChanged || !this.statistics) { this.selected = undefined; this.selectedIndex = undefined; this.selectionChanged.emit(this.selected); value.get(value.start, value.end).pipe(map((statistics) => { const { start, end } = value; this.total = statistics.map(v => v.count).reduce((p, c) => (p += c, p), 0); const iterator = new Date(start); this.setColors(statistics); while (!Dates.equalToDayLevel(iterator, end)) { if (statistics.findIndex(v => Dates.equalToDayLevel(v.date, iterator)) === -1) { statistics.push({ date: new Date(iterator.getTime()), count: 0, }); } iterator.setDate(iterator.getDate() + 1); } return statistics; }), map((statistics) => { this.max = 0; this.monthHeaders = []; return statistics.map(v => { const days = Dates.countDays(first, v.date); const x = Math.floor(days / 7) * (value.size + this.margin); if (this.max < x) { this.max = x; } if (v.date.getDate() === 1 && !this.monthHeaders.find(d => Dates.equalToDayLevel(d.date, v.date))) { this.monthHeaders.push({ date: new Date(v.date), x }); } return { ...v, x, y: (Math.floor(days) % 7) * (value.size + this.margin) }; }); })).subscribe(v => this.statistics = v); } } _weakDayOffset = 0; /** * Set weak day offset to for example start weak not from Sunday but from Monday you should set it to 6 * * Do not use values less then 0 */ set weakDayOffset(value) { this._weakDayOffset = value; if (this.statistics && this.settings) { const { size, start } = this.settings; const first = Dates.getWeekDate(start, 'first', value); const date = new Date(first); this.daysHeaders = []; for (let i = 0; i < 7; i += 2) { date.setDate(date.getDate() + (i ? 2 : 0)); this.daysHeaders.push(new Date(date)); } this.max = 0; this.monthHeaders = []; this.statistics = this.statistics.map(v => { const days = Dates.countDays(first, v.date); const x = Math.floor(days / 7) * (size + this.margin); if (this.max < x) { this.max = x; } if (v.date.getDate() === 1) { this.monthHeaders.push({ date: new Date(v.date), x }); } return { ...v, x, y: (Math.floor(days) % 7) * (size + this.margin) }; }); } } get weakDayOffset() { return this._weakDayOffset; } // eslint-disable-next-line @typescript-eslint/adjacent-overload-signatures get settings() { return this._settings; } get margin() { return this._settings?.margin ?? 5; } get height() { return (this.size + this.margin) * 7; } get width() { return this.max + this.fontSize + this.margin; } get size() { return this._settings?.size ?? 10; } get fontSize() { return this.size * 0.5; } clicked = new EventEmitter(); selectionChanged = new EventEmitter(); constructor(locale) { this.locale = locale; } changed(year) { this.settings = { ...this._settings, start: new Date(year, 0, 1), end: new Date(year + 1, 0, 1) }; } onClicked(statistic, index) { this.selected = statistic; this.selectedIndex = index; this.clicked.emit(statistic); this.selectionChanged.emit(statistic); } onHovered(statistics) { this.hovered = statistics; } setColors(statistics) { const elements = statistics.map(item => item.count).filter(v => v); const { min, max } = elements.length && ElementaryMath.getMinMax(elements) || { min: 0, max: 0 }; const mid = (max - min) / ((this._settings.colors.length - 1) || 1); const colorsStats = this._settings.colors.map((color, index) => { return { [index === 0 ? 0 : mid * index + min]: color }; }).reduce((p, c) => ({ ...p, ...c }), {}); this._settings = { ...this._settings, colorsStats }; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: DailyStatisticsComponent, deps: [{ token: LOCALE_ID }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.4", type: DailyStatisticsComponent, isStandalone: false, selector: "obl-daily-statistics", inputs: { settings: "settings", weakDayOffset: "weakDayOffset" }, outputs: { clicked: "clicked", selectionChanged: "selectionChanged" }, queries: [{ propertyName: "totalRef", first: true, predicate: ["totalRef"], descendants: true }, { propertyName: "selectedRef", first: true, predicate: ["selectedRef"], descendants: true }], ngImport: i0, template: "<div class=\"months-header\">\n @for (month of monthHeaders; track month) {\n <div\n class=\"month-header\"\n [style.left.px]=\"month.x\"\n >\n {{ month.date | date : 'LLL' : undefined : locale }}\n </div>\n }\n</div>\n\n<div class=\"days-section\">\n <div class=\"days-header\" [style.lineHeight.px]=\"size\">\n @for (date of daysHeaders; track date) {\n <div\n class=\"day\"\n [style.top.px]=\"$index * 2 * (settings.size + settings.margin)\"\n >\n {{ date | date : 'EEEE' : undefined : locale }}\n </div>\n }\n </div>\n <div class=\"days\" [style.width.px]=\"width\" [style.height.px]=\"height\">\n @for (statistic of statistics; track statistic) {\n <obl-daily-statistic\n [settings]=\"settings\"\n [statistics]=\"statistic\"\n (clicked)=\"onClicked($event, $index)\"\n (hovered)=\"onHovered($event)\"\n [selected]=\"selectedIndex === $index\"\n ></obl-daily-statistic>\n }\n </div>\n</div>\n\n<div class=\"legend\" [style.width.px]=\"width\">\n <div>\n @if (selected || hovered) {\n <ng-container\n *ngTemplateOutlet=\"\n selectedRef || defaultSelected;\n context: { $implicit: hovered || selected }\n \"\n >\n </ng-container>\n }\n @if (total) {\n <ng-container\n *ngTemplateOutlet=\"\n totalRef || defaultTotal;\n context: { $implicit: total }\n \"\n >\n </ng-container>\n }\n </div>\n <obl-daily-legend [settings]=\"settings\"></obl-daily-legend>\n</div>\n\n@if (settings.yearSwitcher) {\n <div class=\"year-picker\" [style.width.px]=\"width\">\n <obl-year-picker\n [display]=\"{ nextPrevious: false }\"\n [current]=\"settings.start.getFullYear()\"\n [start]=\"settings.minYear || default.min\"\n [end]=\"settings.maxYear || default.max\"\n [picker]=\"{\n className: 'obl-daily-picker'\n }\"\n (changed)=\"changed($event)\"\n ></obl-year-picker>\n </div>\n}\n\n<ng-template let-selected #defaultSelected>\n {{ selected.date | date : 'dd-MM-yyyy' : locale }} &rarr;\n {{ selected.count }}\n</ng-template>\n\n<ng-template let-total #defaultTotal> Total: {{ total }} </ng-template>\n", styles: [":host{width:fit-content;color:var(--default-text-color)}.days-section{display:flex;overflow:hidden}.months-header,.legend{margin-left:var(--days-space-size, 80px)}.days-header{position:relative;font-size:12px;width:var(--days-space-size, 80px);min-width:var(--days-space-size, 80px)}.days-header .day{position:absolute}.days{position:relative;display:block}.legend{display:flex;justify-content:space-between;align-items:center;min-width:fit-content;font-size:12px}.months-header{position:relative;font-size:12px;height:20px;overflow:hidden}.month-header{position:absolute}obl-year-picker{display:block;width:305px;max-width:100%;margin-left:auto}.year-picker{margin-left:var(--days-space-size, 80px);--obl-font-size: 14px}::ng-deep obl-values-set.obl-daily-picker{--obl-font-size: 14px}\n"], dependencies: [{ kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i2.YearPickerComponent, selector: "obl-year-picker", inputs: ["current", "start", "end", "placeholder", "disabled", "display", "picker"], outputs: ["changed"] }, { kind: "component", type: DailyStatisticComponent, selector: "obl-daily-statistic", inputs: ["selected", "statistics", "settings"], outputs: ["clicked", "hovered"] }, { kind: "component", type: DailyLegendComponent, selector: "obl-daily-legend", inputs: ["settings"] }, { kind: "pipe", type: i1$1.DatePipe, name: "date" }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: DailyStatisticsComponent, decorators: [{ type: Component, args: [{ selector: 'obl-daily-statistics', standalone: false, template: "<div class=\"months-header\">\n @for (month of monthHeaders; track month) {\n <div\n class=\"month-header\"\n [style.left.px]=\"month.x\"\n >\n {{ month.date | date : 'LLL' : undefined : locale }}\n </div>\n }\n</div>\n\n<div class=\"days-section\">\n <div class=\"days-header\" [style.lineHeight.px]=\"size\">\n @for (date of daysHeaders; track date) {\n <div\n class=\"day\"\n [style.top.px]=\"$index * 2 * (settings.size + settings.margin)\"\n >\n {{ date | date : 'EEEE' : undefined : locale }}\n </div>\n }\n </div>\n <div class=\"days\" [style.width.px]=\"width\" [style.height.px]=\"height\">\n @for (statistic of statistics; track statistic) {\n <obl-daily-statistic\n [settings]=\"settings\"\n [statistics]=\"statistic\"\n (clicked)=\"onClicked($event, $index)\"\n (hovered)=\"onHovered($event)\"\n [selected]=\"selectedIndex === $index\"\n ></obl-daily-statistic>\n }\n </div>\n</div>\n\n<div class=\"legend\" [style.width.px]=\"width\">\n <div>\n @if (selected || hovered) {\n <ng-container\n *ngTemplateOutlet=\"\n selectedRef || defaultSelected;\n context: { $implicit: hovered || selected }\n \"\n >\n </ng-container>\n }\n @if (total) {\n <ng-container\n *ngTemplateOutlet=\"\n totalRef || defaultTotal;\n context: { $implicit: total }\n \"\n >\n </ng-container>\n }\n </div>\n <obl-daily-legend [settings]=\"settings\"></obl-daily-legend>\n</div>\n\n@if (settings.yearSwitcher) {\n <div class=\"year-picker\" [style.width.px]=\"width\">\n <obl-year-picker\n [display]=\"{ nextPrevious: false }\"\n [current]=\"settings.start.getFullYear()\"\n [start]=\"settings.minYear || default.min\"\n [end]=\"settings.maxYear || default.max\"\n [picker]=\"{\n className: 'obl-daily-picker'\n }\"\n (changed)=\"changed($event)\"\n ></obl-year-picker>\n </div>\n}\n\n<ng-template let-selected #defaultSelected>\n {{ selected.date | date : 'dd-MM-yyyy' : locale }} &rarr;\n {{ selected.count }}\n</ng-template>\n\n<ng-template let-total #defaultTotal> Total: {{ total }} </ng-template>\n", styles: [":host{width:fit-content;color:var(--default-text-color)}.days-section{display:flex;overflow:hidden}.months-header,.legend{margin-left:var(--days-space-size, 80px)}.days-header{position:relative;font-size:12px;width:var(--days-space-size, 80px);min-width:var(--days-space-size, 80px)}.days-header .day{position:absolute}.days{position:relative;display:block}.legend{display:flex;justify-content:space-between;align-items:center;min-width:fit-content;font-size:12px}.months-header{position:relative;font-size:12px;height:20px;overflow:hidden}.month-header{position:absolute}obl-year-picker{display:block;width:305px;max-width:100%;margin-left:auto}.year-picker{margin-left:var(--days-space-size, 80px);--obl-font-size: 14px}::ng-deep obl-values-set.obl-daily-picker{--obl-font-size: 14px}\n"] }] }], ctorParameters: () => [{ type: undefined, decorators: [{ type: Inject, args: [LOCALE_ID] }] }], propDecorators: { totalRef: [{ type: ContentChild, args: ['totalRef'] }], selectedRef: [{ type: ContentChild, args: ['selectedRef'] }], settings: [{ type: Input }], weakDayOffset: [{ type: Input }], clicked: [{ type: Output }], selectionChanged: [{ type: Output }] } }); class DailyStatisticsModule { static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: DailyStatisticsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.0.4", ngImport: i0, type: DailyStatisticsModule, declarations: [DailyStatisticsComponent, DailyStatisticComponent, DailyLegendComponent], imports: [CommonModule, LoadingModule, YearPickerModule], exports: [DailyStatisticsComponent, DailyStatisticComponent, DailyLegendComponent] }); static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: DailyStatisticsModule, imports: [CommonModule, LoadingModule, YearPickerModule] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: DailyStatisticsModule, decorators: [{ type: NgModule, args: [{ declarations: [ DailyStatisticsComponent, DailyStatisticComponent, DailyLegendComponent ], imports: [ CommonModule, LoadingModule, YearPickerModule ], exports: [ DailyStatisticsComponent, DailyStatisticComponent, DailyLegendComponent ] }] }] }); /** * Generated bundle index. Do not edit. */ export { DailyLegendComponent, DailyStatisticComponent, DailyStatisticsComponent, DailyStatisticsModule }; //# sourceMappingURL=obliczeniowo-elementary-daily-statistics.mjs.map