UNPKG

@obliczeniowo/elementary

Version:
258 lines (252 loc) 11.3 kB
import * as i0 from '@angular/core'; import { input, EventEmitter, effect, ViewChild, Output, Input, Component, NgModule } from '@angular/core'; import * as i1 from '@angular/common'; import { CommonModule, DatePipe } from '@angular/common'; import { TextAlign, DrawingSvgInterface } from '@obliczeniowo/elementary/drawing'; import { keys } from '@obliczeniowo/elementary/objects'; import { Point2D } from '@obliczeniowo/elementary/classes'; class DaypartsStatistics { dc; statistics; datePipe; events; max = 0; fontSize = 12; ray = 12; textOffset = 100; constructor(dc, statistics, datePipe, events) { this.dc = dc; this.statistics = statistics; this.datePipe = datePipe; this.events = events; this.setMax(); } getWidth() { return this.textOffset + 2 * this.ray * 24; } getHeight() { return this.ray * 2 * 7 + this.fontSize * 2; } updateMax(max) { this.max = max; } setStatistics(statistics) { this.statistics = statistics; this.setMax(); this.draw(); } draw(dc = this.dc) { const { statistics, ray, fontSize, textOffset } = this; const offset = new Point2D(textOffset, ray); dc.clear(); keys(statistics).forEach((day) => { keys(statistics[day]).forEach((hour) => { const hourData = statistics[day][hour]; const start = new Point2D(2 * ray * hour, 2 * ray * day); dc .group({ svgOnly: { events: { click: () => this.events.clicked(hourData), mouseover: () => this.events.mouseover && this.events.mouseover(hourData) } } }) .drawRect(start.x + textOffset - ray, start.y, 2 * ray, 2 * ray, 0, 'none', '#ffffff', { svgOnly: { attributes: { opacity: 0.1 }, } }) .drawCircle(start.add(offset), ray * hourData.value / this.max, 0, '#000000', '#00aad4', { svgOnly: { classes: ['obl-dayparts-statistics', 'scale-up'], }, style: { 'transform-origin': `${start.x + offset.x}px ${start.y + offset.y}px` } }) .endGroup(); }); }); const date = new Date(); date.setTime(100); dc.setFontSize(fontSize); dc.setTextAlign(TextAlign.LEFT); for (let day = 0; day < 7; day++) { date.setTime((day + 4) * 24 * 3600 * 1000); dc.drawText(this.datePipe.transform(date, 'EEEE') || '', new Point2D(10, ray + fontSize * 0.3 + day * 2 * ray), 'var(--default-text-color)', 0); } const hoursOffset = new Point2D(offset.x, 7 * 2 * ray + 1.5 * fontSize); dc.setTextAlign(TextAlign.CENTER); for (let hour = 0; hour < 24; hour++) { dc.drawText(hour < 10 ? '0' + hour : hour.toString(), hoursOffset.add(new Point2D(hour * 2 * ray, 0)), 'var(--default-text-color)', 0); } } setMax() { const { statistics } = this; this.max = 0; keys(statistics).forEach((key) => { const value = statistics[key]; keys(value).forEach((key) => { const count = value[key].value; if (this.max < count) { this.max = count; } }); }); } } class DaypartsStatisticsComponent { datePipe; renderer; /** * Do not use this one together with statistics */ data = input([]); /** * Under DaypartsData you can put any kind of key: value fields and now here you can specify field * name to make it representable on diagram */ valueKey = input(); /** * Do not use this one together with data */ set statistics(statistics) { this._statistics = statistics; this._daypartsStatistics?.setStatistics(statistics); } translations = input({ value: 'Value' }); clicked = new EventEmitter(); svg; dc; _statistics = {}; max = 0; _daypartsStatistics; current; constructor(datePipe, renderer) { this.datePipe = datePipe; this.renderer = renderer; effect(() => { if (this.data().length) { this.transform(); } }); effect(() => { this.setMax(); this.draw(); }); } ngAfterViewInit() { this.dc = new DrawingSvgInterface(this.svg.nativeElement, this.renderer); setTimeout(() => this.draw()); } draw() { if (this.dc) { if (!this._daypartsStatistics) { this._daypartsStatistics = new DaypartsStatistics(this.dc, this._statistics, this.datePipe, { clicked: (value) => this.clicked.emit(value), mouseover: (value) => { this.current = value; } }); } this._daypartsStatistics?.setStatistics(this._statistics); } } transform() { this._statistics = {}; const { _statistics: statistics } = this; this.data().forEach(item => { const hours = item.date.getHours(); const day = item.date.getDay(); const data = statistics[day]; const hoursObj = () => ({ data: [item], value: 1 }); if (data) { const hour = data[hours]; if (hour) { hour.value++; hour.data.push(item); } else { data[hours] = hoursObj(); } } else { statistics[day] = { [hours]: hoursObj() }; } }); this.setMax(); this.draw(); } setMax() { const { _statistics: statistics } = this; let max = 0; const valueKey = this.valueKey(); if (valueKey) { keys(statistics).forEach((key) => { const value = statistics[key]; keys(value).forEach((key) => { const data = value[key].data; const v = (valueKey && data?.[0][valueKey] && (data.reduce((p, c) => p += c[valueKey], 0) / data.length)) ?? (data?.length || 0); value[key].value = v; max = max < v ? v : max; }); }); if (max === 0) { max = 1; } this.max = max; this._daypartsStatistics?.updateMax(max); } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: DaypartsStatisticsComponent, deps: [{ token: i1.DatePipe }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.4", type: DaypartsStatisticsComponent, isStandalone: false, selector: "obl-dayparts-statistics", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, valueKey: { classPropertyName: "valueKey", publicName: "valueKey", isSignal: true, isRequired: false, transformFunction: null }, statistics: { classPropertyName: "statistics", publicName: "statistics", isSignal: false, isRequired: false, transformFunction: null }, translations: { classPropertyName: "translations", publicName: "translations", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { clicked: "clicked" }, viewQueries: [{ propertyName: "svg", first: true, predicate: ["svg"], descendants: true }], ngImport: i0, template: "<svg\n [attr.width]=\"_daypartsStatistics?.getWidth() || 0\"\n [attr.height]=\"_daypartsStatistics?.getHeight() || 0\"\n #svg\n></svg>\n<div class=\"current\">\n @if (current) {\n {{ translations().value }}: {{ current.value }}\n }\n</div>\n", styles: ["svg::ng-deep .obl-dayparts-statistics{cursor:pointer;fill:#00aad4;stroke:none}svg::ng-deep .obl-dayparts-statistics:hover{opacity:.8}svg::ng-deep .obl-dayparts-statistics-text{fill:var(--default-text-color)}.current{font-size:12px;padding-left:95px;min-height:20px}\n"] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: DaypartsStatisticsComponent, decorators: [{ type: Component, args: [{ selector: 'obl-dayparts-statistics', standalone: false, template: "<svg\n [attr.width]=\"_daypartsStatistics?.getWidth() || 0\"\n [attr.height]=\"_daypartsStatistics?.getHeight() || 0\"\n #svg\n></svg>\n<div class=\"current\">\n @if (current) {\n {{ translations().value }}: {{ current.value }}\n }\n</div>\n", styles: ["svg::ng-deep .obl-dayparts-statistics{cursor:pointer;fill:#00aad4;stroke:none}svg::ng-deep .obl-dayparts-statistics:hover{opacity:.8}svg::ng-deep .obl-dayparts-statistics-text{fill:var(--default-text-color)}.current{font-size:12px;padding-left:95px;min-height:20px}\n"] }] }], ctorParameters: () => [{ type: i1.DatePipe }, { type: i0.Renderer2 }], propDecorators: { statistics: [{ type: Input }], clicked: [{ type: Output }], svg: [{ type: ViewChild, args: ['svg'] }] } }); class DaypartsStatisticsModule { static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: DaypartsStatisticsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.0.4", ngImport: i0, type: DaypartsStatisticsModule, declarations: [DaypartsStatisticsComponent], imports: [CommonModule], exports: [DaypartsStatisticsComponent] }); static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: DaypartsStatisticsModule, providers: [ DatePipe ], imports: [CommonModule] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: DaypartsStatisticsModule, decorators: [{ type: NgModule, args: [{ declarations: [ DaypartsStatisticsComponent ], providers: [ DatePipe ], imports: [ CommonModule ], exports: [ DaypartsStatisticsComponent ] }] }] }); /** * Generated bundle index. Do not edit. */ export { DaypartsStatistics, DaypartsStatisticsComponent, DaypartsStatisticsModule }; //# sourceMappingURL=obliczeniowo-elementary-dayparts-statistics.mjs.map