From 787d11cafcb6e58491c636f2201c4ca804d4a61c Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Fri, 24 Jun 2022 00:12:34 +0300 Subject: [PATCH] RED-4365: revert to ngx-charts --- .../combo-chart/combo-chart.component.html | 112 +++++ .../combo-chart/combo-chart.component.scss | 89 ++++ .../combo-chart/combo-chart.component.ts | 393 ++++++++++++++++++ .../combo-series-vertical.component.ts | 199 +++++++++ .../screens/license/combo-chart/index.ts | 2 + .../screens/license/combo-chart/models.ts | 11 + .../google-chart/google-chart.component.ts | 43 -- .../license-chart.component.html | 18 + .../license-chart/license-chart.component.ts | 87 +++- .../license-screen.component.ts | 2 +- .../admin/screens/license/license.module.ts | 16 +- .../admin/screens/license/utils/constants.ts | 16 + apps/red-ui/src/assets/config/config.json | 5 - apps/red-ui/src/assets/i18n/de.json | 1 + apps/red-ui/src/assets/i18n/en.json | 1 + docker/red-ui/docker-entrypoint.sh | 11 - libs/red-domain/src/lib/shared/app-config.ts | 5 - package.json | 2 +- yarn.lock | 167 +++++++- 19 files changed, 1075 insertions(+), 105 deletions(-) create mode 100644 apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-chart.component.html create mode 100644 apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-chart.component.scss create mode 100644 apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-chart.component.ts create mode 100644 apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-series-vertical.component.ts create mode 100644 apps/red-ui/src/app/modules/admin/screens/license/combo-chart/index.ts create mode 100644 apps/red-ui/src/app/modules/admin/screens/license/combo-chart/models.ts delete mode 100644 apps/red-ui/src/app/modules/admin/screens/license/google-chart/google-chart.component.ts create mode 100644 apps/red-ui/src/app/modules/admin/screens/license/license-chart/license-chart.component.html diff --git a/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-chart.component.html b/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-chart.component.html new file mode 100644 index 000000000..f72ec50ed --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-chart.component.html @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-chart.component.scss b/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-chart.component.scss new file mode 100644 index 000000000..7ee754487 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-chart.component.scss @@ -0,0 +1,89 @@ +.ngx-charts { + float: left; + overflow: visible; + + .circle, + .bar, + .arc { + cursor: pointer; + } + + .bar, + .cell, + .arc, + .card { + &.active, + &:hover { + opacity: 0.8; + transition: opacity 100ms ease-in-out; + } + + &:focus { + outline: none; + } + + &.hidden { + display: none; + } + } + + g { + &:focus { + outline: none; + } + } + + .line-series, + .line-series-range, + .area-series { + &.inactive { + transition: opacity 100ms ease-in-out; + opacity: 0.2; + } + } + + .line-highlight { + display: none; + + &.active { + display: block; + } + } + + .area { + opacity: 0.6; + } + + .circle { + &:hover { + cursor: pointer; + } + } + + .label { + font-size: 12px; + font-weight: normal; + } + + .tooltip-anchor { + fill: rgb(0, 0, 0); + } + + .gridline-path { + stroke: #ddd; + stroke-width: 1; + fill: none; + } + + .grid-panel { + rect { + fill: none; + } + + &.odd { + rect { + fill: rgba(0, 0, 0, 0.05); + } + } + } +} diff --git a/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-chart.component.ts b/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-chart.component.ts new file mode 100644 index 000000000..7c5d8cfac --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-chart.component.ts @@ -0,0 +1,393 @@ +import { + Component, + ContentChild, + EventEmitter, + HostListener, + Input, + Output, + TemplateRef, + ViewChild, + ViewEncapsulation, +} from '@angular/core'; + +import { curveLinear } from 'd3-shape'; +import { scaleBand, scaleLinear, scalePoint, scaleTime } from 'd3-scale'; +import { + BaseChartComponent, + calculateViewDimensions, + Color, + ColorHelper, + LegendPosition, + LineSeriesComponent, + Orientation, + ScaleType, + ViewDimensions, +} from '@swimlane/ngx-charts'; +import { ILineChartSeries } from './models'; + +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: 'combo-chart-component', + templateUrl: './combo-chart.component.html', + styleUrls: ['./combo-chart.component.scss'], + encapsulation: ViewEncapsulation.None, +}) +export class ComboChartComponent extends BaseChartComponent { + @Input() curve: any = curveLinear; + @Input() legend = false; + @Input() legendTitle = 'Legend'; + @Input() legendPosition: LegendPosition = LegendPosition.Right; + @Input() xAxis; + @Input() yAxis; + @Input() showXAxisLabel; + @Input() showYAxisLabel; + @Input() showRightYAxisLabel; + @Input() xAxisLabel; + @Input() yAxisLabel; + @Input() yAxisLabelRight; + @Input() tooltipDisabled = false; + @Input() gradient: boolean; + @Input() showGridLines = true; + @Input() activeEntries: any[] = []; + @Input() schemeType: ScaleType; + @Input() yAxisTickFormatting: any; + @Input() yRightAxisTickFormatting: any; + @Input() roundDomains = false; + @Input() colorSchemeLine: Color; + @Input() autoScale; + @Input() lineChart: ILineChartSeries[]; + @Input() yLeftAxisScaleFactor: any; + @Input() yRightAxisScaleFactor: any; + @Input() rangeFillOpacity: number; + @Input() animations = true; + @Input() noBarWhenZero = true; + @Output() activate = new EventEmitter<{ value; entries: unknown[] }>(); + @Output() deactivate = new EventEmitter<{ value; entries: unknown[] }>(); + @ContentChild('tooltipTemplate') tooltipTemplate: TemplateRef; + @ContentChild('seriesTooltipTemplate') seriesTooltipTemplate: TemplateRef; + @ViewChild(LineSeriesComponent) lineSeriesComponent: LineSeriesComponent; + dims: ViewDimensions; + xScale: any; + yScale: any; + xDomain: string[] | number[]; + yDomain: string[] | number[]; + transform: string; + colors: ColorHelper; + colorsLine: ColorHelper; + margin = [10, 20, 10, 20]; + xAxisHeight = 0; + yAxisWidth = 0; + legendOptions: any; + scaleType: ScaleType = ScaleType.Linear; + xScaleLine; + yScaleLine; + xDomainLine; + yDomainLine; + seriesDomain; + combinedSeries: ILineChartSeries[]; + xSet; + filteredDomain; + hoveredVertical; + yOrientLeft: Orientation = Orientation.Left; + yOrientRight: Orientation = Orientation.Right; + legendSpacing = 0; + bandwidth: number; + barPadding = 8; + + @Input() xAxisTickFormatting: any; + + trackBy(index, item): string { + return item.name; + } + + update(): void { + super.update(); + this.dims = calculateViewDimensions({ + width: this.width, + height: this.height, + margins: this.margin, + showXAxis: this.xAxis, + showYAxis: this.yAxis, + xAxisHeight: this.xAxisHeight, + yAxisWidth: this.yAxisWidth, + showXLabel: this.showXAxisLabel, + showYLabel: this.showYAxisLabel, + showLegend: this.legend, + legendType: this.schemeType, + legendPosition: this.legendPosition, + }); + + if (!this.yAxis) { + this.legendSpacing = 0; + } else if (this.showYAxisLabel && this.yAxis) { + this.legendSpacing = 100; + } else { + this.legendSpacing = 40; + } + this.xScale = this.getXScale(); + this.yScale = this.getYScale(); + + // line chart + this.xDomainLine = this.getXDomainLine(); + if (this.filteredDomain) { + this.xDomainLine = this.filteredDomain; + } + + this.yDomainLine = this.getYDomainLine(); + this.seriesDomain = this.getSeriesDomain(); + + this.scaleLines(); + + this.setColors(); + this.legendOptions = this.getLegendOptions(); + + this.transform = `translate(${this.dims.xOffset} , ${this.margin[0]})`; + } + + deactivateAll() { + this.activeEntries = [...this.activeEntries]; + for (const entry of this.activeEntries) { + this.deactivate.emit({ value: entry, entries: [] }); + } + this.activeEntries = []; + } + + @HostListener('mouseleave') + hideCircles(): void { + this.hoveredVertical = null; + this.deactivateAll(); + } + + updateHoveredVertical(item): void { + this.hoveredVertical = item.value; + this.deactivateAll(); + } + + scaleLines() { + this.xScaleLine = this.getXScaleLine(this.xDomainLine, this.dims.width); + this.yScaleLine = this.getYScaleLine(this.yDomainLine, this.dims.height); + } + + getSeriesDomain(): any[] { + this.combinedSeries = this.lineChart.slice(0); + this.combinedSeries.push({ + name: this.yAxisLabel, + series: this.results, + }); + return this.combinedSeries.map(d => d.name); + } + + isDate(value): value is Date { + return value instanceof Date; + } + + getScaleType(values): ScaleType { + let date = true; + let num = true; + + for (const value of values) { + if (!this.isDate(value)) { + date = false; + } + + if (typeof value !== 'number') { + num = false; + } + } + + if (date) { + return ScaleType.Time; + } + if (num) { + return ScaleType.Linear; + } + return ScaleType.Ordinal; + } + + getXDomainLine(): any[] { + let values: number[] = []; + + for (const results of this.lineChart) { + for (const d of results.series) { + if (!values.includes(d.name)) { + values.push(d.name); + } + } + } + + this.scaleType = this.getScaleType(values); + let domain = []; + + if (this.scaleType === 'time') { + const min = Math.min(...values); + const max = Math.max(...values); + domain = [min, max]; + } else if (this.scaleType === 'linear') { + values = values.map(v => Number(v)); + const min = Math.min(...values); + const max = Math.max(...values); + domain = [min, max]; + } else { + domain = values; + } + + this.xSet = values; + return domain; + } + + getYDomainLine(): any[] { + const domain: number[] = []; + + for (const results of this.lineChart) { + for (const d of results.series) { + if (domain.indexOf(d.value) < 0) { + domain.push(d.value); + } + if (d.min !== undefined) { + if (domain.indexOf(d.min) < 0) { + domain.push(d.min); + } + } + if (d.max !== undefined) { + if (domain.indexOf(d.max) < 0) { + domain.push(d.max); + } + } + } + } + + let min = Math.min(...domain); + const max = Math.max(...domain); + if (this.yRightAxisScaleFactor) { + const minMax = this.yRightAxisScaleFactor(min, max); + return [Math.min(0, minMax.min as number), minMax.max]; + } + min = Math.min(0, min); + return [min, max]; + } + + getXScaleLine(domain, width: number): any { + let scale; + if (this.bandwidth === undefined) { + this.bandwidth = width - this.barPadding; + } + const offset = Math.floor((width + this.barPadding - (this.bandwidth + this.barPadding) * domain.length) / 2); + + if (this.scaleType === 'time') { + scale = scaleTime().range([0, width]).domain(domain); + } else if (this.scaleType === 'linear') { + scale = scaleLinear().range([0, width]).domain(domain); + + if (this.roundDomains) { + scale = scale.nice(); + } + } else if (this.scaleType === 'ordinal') { + scale = scalePoint() + .range([offset + this.bandwidth / 2, width - offset - this.bandwidth / 2]) + .domain(domain); + } + + return scale; + } + + getYScaleLine(domain, height): any { + const scale = scaleLinear().range([height, 0]).domain(domain); + + return this.roundDomains ? scale.nice() : scale; + } + + getXScale(): any { + this.xDomain = this.getXDomain(); + const spacing = this.xDomain.length / (this.dims.width / this.barPadding + 1); + return scaleBand().range([0, this.dims.width]).paddingInner(spacing).domain(this.xDomain); + } + + getYScale(): any { + this.yDomain = this.getYDomain(); + const scale = scaleLinear().range([this.dims.height, 0]).domain(this.yDomain); + return this.roundDomains ? scale.nice() : scale; + } + + getXDomain(): any[] { + return this.results.map(d => d.name); + } + + getYDomain() { + const values: number[] = this.results.map(d => d.value); + const min = Math.min(0, ...values); + const max = Math.max(...values); + if (this.yLeftAxisScaleFactor) { + const minMax = this.yLeftAxisScaleFactor(min, max); + return [Math.min(0, minMax.min as number), minMax.max]; + } + return [min, max]; + } + + onClick(data) { + this.select.emit(data); + } + + setColors(): void { + let domain: number[] | string[]; + if (this.schemeType === 'ordinal') { + domain = this.xDomain; + } else { + domain = this.yDomain; + } + this.colors = new ColorHelper(this.scheme, this.schemeType, domain, this.customColors); + this.colorsLine = new ColorHelper(this.colorSchemeLine, this.schemeType, domain, this.customColors); + } + + getLegendOptions() { + const opts = { + scaleType: this.schemeType, + colors: undefined, + domain: [], + title: undefined, + position: this.legendPosition, + }; + if (opts.scaleType === 'ordinal') { + opts.domain = this.seriesDomain; + opts.colors = this.colorsLine; + opts.title = this.legendTitle; + } else { + opts.domain = this.seriesDomain; + opts.colors = this.colors.scale; + } + return opts; + } + + updateLineWidth(width): void { + this.bandwidth = width; + this.scaleLines(); + } + + updateYAxisWidth({ width }: { width: number }): void { + this.yAxisWidth = width + 20; + this.update(); + } + + updateXAxisHeight({ height }): void { + this.xAxisHeight = height; + this.update(); + } + + onActivate(item) { + const idx = this.activeEntries.findIndex(d => d.name === item.name && d.value === item.value && d.series === item.series); + if (idx > -1) { + return; + } + + this.activeEntries = [item, ...this.activeEntries]; + this.activate.emit({ value: item, entries: this.activeEntries }); + } + + onDeactivate(item) { + const idx = this.activeEntries.findIndex(d => d.name === item.name && d.value === item.value && d.series === item.series); + + this.activeEntries.splice(idx, 1); + this.activeEntries = [...this.activeEntries]; + + this.deactivate.emit({ value: item, entries: this.activeEntries }); + } +} diff --git a/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-series-vertical.component.ts b/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-series-vertical.component.ts new file mode 100644 index 000000000..ee6c92cb5 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/combo-series-vertical.component.ts @@ -0,0 +1,199 @@ +import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output } from '@angular/core'; +import { animate, style, transition, trigger } from '@angular/animations'; +import { Bar, BarOrientation, formatLabel, PlacementTypes, StyleTypes } from '@swimlane/ngx-charts'; + +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: 'g[ngx-combo-charts-series-vertical]', + template: ` + + `, + changeDetection: ChangeDetectionStrategy.OnPush, + animations: [ + trigger('animationState', [ + transition('* => void', [ + style({ + opacity: 1, + transform: '*', + }), + animate(500, style({ opacity: 0, transform: 'scale(0)' })), + ]), + ]), + ], +}) +export class ComboSeriesVerticalComponent implements OnChanges { + @Input() dims; + @Input() type = 'standard'; + @Input() series; + @Input() seriesLine; + @Input() xScale; + @Input() yScale; + @Input() colors; + @Input() tooltipDisabled = false; + @Input() gradient: boolean; + @Input() activeEntries: any[]; + @Input() seriesName: string; + @Input() animations = true; + @Input() noBarWhenZero = true; + + @Output() activate = new EventEmitter(); + @Output() deactivate = new EventEmitter(); + @Output() bandwidth = new EventEmitter(); + + bars: any; + x: any; + y: any; + readonly tooltipTypes = StyleTypes; + readonly tooltipPlacements = PlacementTypes; + readonly orientations = BarOrientation; + + ngOnChanges(): void { + this.update(); + } + + update(): void { + let width; + if (this.series.length) { + width = this.xScale.bandwidth(); + this.bandwidth.emit(width); + } + + let d0 = 0; + let total; + if (this.type === 'normalized') { + total = this.series.map(d => d.value).reduce((sum: number, d: number) => sum + d, 0); + } + + this.bars = this.series.map((d, index) => { + let value: number = d.value; + const label = d.name; + const formattedLabel = formatLabel(label); + const roundEdges = this.type === 'standard'; + + const bar: Bar = { + value, + label, + roundEdges, + data: d, + width, + formattedLabel, + height: 0, + x: 0, + y: 0, + ariaLabel: label, + tooltipText: label, + color: undefined, + gradientStops: undefined, + }; + + let offset0 = d0; + let offset1 = offset0 + value; + + if (this.type === 'standard') { + bar.height = Math.abs(this.yScale(value) - this.yScale(0)); + bar.x = this.xScale(label); + + if (value < 0) { + bar.y = this.yScale(0); + } else { + bar.y = this.yScale(value); + } + } else if (this.type === 'stacked') { + d0 += value; + + bar.height = this.yScale(offset0) - this.yScale(offset1); + bar.x = 0; + bar.y = this.yScale(offset1); + // bar.offset0 = offset0; + // bar.offset1 = offset1; + } else if (this.type === 'normalized') { + d0 += value; + + if (total > 0) { + offset0 = (offset0 * 100) / total; + offset1 = (offset1 * 100) / total; + } else { + offset0 = 0; + offset1 = 0; + } + + bar.height = this.yScale(offset0) - this.yScale(offset1); + bar.x = 0; + bar.y = this.yScale(offset1); + // bar.offset0 = offset0; + // bar.offset1 = offset1; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + value = (offset1 - offset0).toFixed(2) + '%'; + } + + if (this.colors.scaleType === 'ordinal') { + bar.color = this.colors.getColor(label); + } else { + if (this.type === 'standard') { + bar.color = this.colors.getColor(value); + bar.gradientStops = this.colors.getLinearGradientStops(value); + } else { + bar.color = this.colors.getColor(offset1); + bar.gradientStops = this.colors.getLinearGradientStops(offset1, offset0); + } + } + + let tooltipLabel = formattedLabel; + if (this.seriesName) { + tooltipLabel = `${this.seriesName} • ${formattedLabel}`; + } + + this.getSeriesTooltips(this.seriesLine, index); + const lineValue: string = this.seriesLine[0].series[index].value; + bar.tooltipText = ` + ${tooltipLabel} + + Y1 - ${value.toLocaleString()} • Y2 - ${lineValue.toLocaleString()}% + + `; + + return bar; + }); + } + + getSeriesTooltips(seriesLine, index) { + return seriesLine.map(d => d.series[index]); + } + + isActive(entry): boolean { + if (!this.activeEntries) { + return false; + } + const item = this.activeEntries.find(d => entry.name === d.name && entry.series === d.series); + return item !== undefined; + } + + trackBy(index, bar): string { + return bar.label; + } +} diff --git a/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/index.ts b/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/index.ts new file mode 100644 index 000000000..2a0ab4f22 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/index.ts @@ -0,0 +1,2 @@ +export * from './combo-chart.component'; +export * from './combo-series-vertical.component'; diff --git a/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/models.ts b/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/models.ts new file mode 100644 index 000000000..7358f8e55 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/license/combo-chart/models.ts @@ -0,0 +1,11 @@ +export interface ISeries { + name: number; + value: number; + min: number; + max: number; +} + +export interface ILineChartSeries { + name: string; + series: ISeries[]; +} diff --git a/apps/red-ui/src/app/modules/admin/screens/license/google-chart/google-chart.component.ts b/apps/red-ui/src/app/modules/admin/screens/license/google-chart/google-chart.component.ts deleted file mode 100644 index 5aa4f2d24..000000000 --- a/apps/red-ui/src/app/modules/admin/screens/license/google-chart/google-chart.component.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; -import { ChartType, Column, Row } from 'angular-google-charts'; -import { TranslateService } from '@ngx-translate/core'; -import ComboChartOptions = google.visualization.ComboChartOptions; - -@Component({ - selector: 'redaction-google-chart', - template: ` `, - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class GoogleChartComponent { - @Input() data: Row[]; - - readonly options: ComboChartOptions; - readonly columns: Column[]; - readonly type = ChartType.ComboChart; - - constructor(translateService: TranslateService) { - const pagesPerMonth = translateService.instant('license-info-screen.chart.pages-per-month'); - const totalPages = translateService.instant('license-info-screen.chart.total-pages'); - const cumulative = translateService.instant('license-info-screen.chart.cumulative'); - - this.options = { - fontName: 'Inter', - fontSize: 13, - vAxis: { title: pagesPerMonth }, - seriesType: 'bars', - vAxes: { 1: { title: totalPages } }, - series: { 1: { type: 'line', targetAxisIndex: 1 }, 2: { type: 'line', targetAxisIndex: 1 } }, - colors: ['#0389ec', '#dd4d50', '#5ce594'], - legend: { position: 'top' }, - }; - - this.columns = ['abc', pagesPerMonth, totalPages, cumulative]; - } -} diff --git a/apps/red-ui/src/app/modules/admin/screens/license/license-chart/license-chart.component.html b/apps/red-ui/src/app/modules/admin/screens/license/license-chart/license-chart.component.html new file mode 100644 index 000000000..8da1b9488 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/license/license-chart/license-chart.component.html @@ -0,0 +1,18 @@ + diff --git a/apps/red-ui/src/app/modules/admin/screens/license/license-chart/license-chart.component.ts b/apps/red-ui/src/app/modules/admin/screens/license/license-chart/license-chart.component.ts index fcf188256..0cafbb004 100644 --- a/apps/red-ui/src/app/modules/admin/screens/license/license-chart/license-chart.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/license/license-chart/license-chart.component.ts @@ -1,24 +1,33 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { LICENSE_STORAGE_KEY } from '../utils/constants'; +import { Component } from '@angular/core'; +import { ComboBarScheme, LICENSE_STORAGE_KEY, LineChartScheme } from '../utils/constants'; import dayjs from 'dayjs'; import { IDateRange, ILicense, ILicenseReport } from '@red/domain'; import { LicenseService } from '../../../../../services/license.service'; import { switchMap, tap } from 'rxjs/operators'; import { List, LoadingService } from '@iqser/common-ui'; import { generateDateRanges, isCurrentMonth, toDate, verboseDate } from '../utils/functions'; -import { Row } from 'angular-google-charts'; +import { TranslateService } from '@ngx-translate/core'; +import { ILineChartSeries } from '../combo-chart/models'; +import { Observable } from 'rxjs'; @Component({ selector: 'redaction-license-chart', - template: '', - changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: './license-chart.component.html', }) export class LicenseChartComponent { - readonly chartData$ = this.#chartData$; + readonly lineChartScheme = LineChartScheme; + readonly comboBarScheme = ComboBarScheme; - constructor(private readonly _licenseService: LicenseService, private readonly _loadingService: LoadingService) {} + lineChartSeries$ = this.#licenseChartSeries$; + barChart = []; - get #chartData$() { + constructor( + private readonly _translateService: TranslateService, + private readonly _licenseService: LicenseService, + private readonly _loadingService: LoadingService, + ) {} + + get #licenseChartSeries$(): Observable { return this._licenseService.selectedLicense$.pipe( tap(() => this._loadingService.start()), switchMap(license => this.#getLicenseData(license)), @@ -26,28 +35,59 @@ export class LicenseChartComponent { ); } - async #getLicenseData(license: ILicense): Promise { + async #getLicenseData(license: ILicense): Promise { const startDate = dayjs(license.validFrom); const endDate = dayjs(license.validUntil); const startMonth: number = startDate.month(); const startYear: number = startDate.year(); - const dateRanges = generateDateRanges(startMonth, startYear, endDate.month() as number, endDate.year() as number); + const dateRanges = generateDateRanges(startMonth, startYear, endDate.month(), endDate.year()); const reports = await this.#getReports(dateRanges, license.id); - return this.#mapRangesToReports(dateRanges, reports); + return this.#mapRangesToReports(startMonth, startYear, dateRanges, reports); } - #mapRangesToReports(dateRanges: List, reports: List): Row[] { - let cumulativePages = 0; - const processingPages = this._licenseService.processingPages; + #mapRangesToReports(month: number, year: number, dateRanges: List, reports: List): ILineChartSeries[] { + return [ + { + name: this._translateService.instant('license-info-screen.chart.total-pages'), + series: this.#totalLicensedPagesSeries(dateRanges), + }, + { + name: this._translateService.instant('license-info-screen.chart.cumulative'), + series: this.#setBar(month, year, reports), + }, + ]; + } - return dateRanges.map((range, index) => [ - verboseDate(range), - reports[index].numberOfAnalyzedPages, - processingPages, - (cumulativePages += reports[index].numberOfAnalyzedPages), - ]); + #setBar(month: number, year: number, reports: List) { + let cumulativePages = 0; + const cumulativePagesSeries = []; + this.barChart = []; + const monthNames = dayjs.monthsShort(); + + for (const report of reports) { + cumulativePages += report.numberOfAnalyzedPages; + + const name = `${monthNames[month]} ${year}`; + this.barChart.push({ + name, + value: report.numberOfAnalyzedPages, + }); + + cumulativePagesSeries.push({ + name, + value: cumulativePages, + }); + + month++; + if (month === 12) { + month = 0; + year++; + } + } + + return cumulativePagesSeries; } #getReports(dateRanges: List, id: string) { @@ -78,4 +118,11 @@ export class LicenseChartComponent { return report; } + + #totalLicensedPagesSeries(dateRanges: List) { + return dateRanges.map(dateRange => ({ + name: verboseDate(dateRange), + value: this._licenseService.processingPages, + })); + } } diff --git a/apps/red-ui/src/app/modules/admin/screens/license/license-screen/license-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/license/license-screen/license-screen.component.ts index 68ab95b66..128915d7e 100644 --- a/apps/red-ui/src/app/modules/admin/screens/license/license-screen/license-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/license/license-screen/license-screen.component.ts @@ -76,7 +76,7 @@ export class LicenseScreenComponent implements OnInit { const licenseCustomer = this.licenseService.selectedLicense.licensedTo; const subject = this._translateService.instant('license-info-screen.email.title', { licenseCustomer, - }); + }) as string; const lineBreak = '%0D%0A'; const body = [ this._translateService.instant('license-info-screen.email.body.analyzed', { diff --git a/apps/red-ui/src/app/modules/admin/screens/license/license.module.ts b/apps/red-ui/src/app/modules/admin/screens/license/license.module.ts index 998a40851..d1dcd8fe9 100644 --- a/apps/red-ui/src/app/modules/admin/screens/license/license.module.ts +++ b/apps/red-ui/src/app/modules/admin/screens/license/license.module.ts @@ -7,9 +7,9 @@ import { RouterModule, Routes } from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; import { MatSelectModule } from '@angular/material/select'; import { IqserListingModule } from '@iqser/common-ui'; +import { NgxChartsModule } from '@swimlane/ngx-charts'; +import { ComboChartComponent, ComboSeriesVerticalComponent } from './combo-chart'; import { FormsModule } from '@angular/forms'; -import { GoogleChartComponent } from './google-chart/google-chart.component'; -import { GoogleChartsModule } from 'angular-google-charts'; import { CommonModule } from '@angular/common'; const routes: Routes = [ @@ -20,15 +20,21 @@ const routes: Routes = [ ]; @NgModule({ - declarations: [LicenseScreenComponent, LicenseSelectComponent, LicenseChartComponent, GoogleChartComponent], + declarations: [ + LicenseScreenComponent, + LicenseSelectComponent, + LicenseChartComponent, + ComboChartComponent, + ComboSeriesVerticalComponent, + ], imports: [ - CommonModule, RouterModule.forChild(routes), + CommonModule, TranslateModule, MatSelectModule, FormsModule, + NgxChartsModule, IqserListingModule, - GoogleChartsModule, ], providers: [LicenseService], }) diff --git a/apps/red-ui/src/app/modules/admin/screens/license/utils/constants.ts b/apps/red-ui/src/app/modules/admin/screens/license/utils/constants.ts index aaffc8d71..a139c9732 100644 --- a/apps/red-ui/src/app/modules/admin/screens/license/utils/constants.ts +++ b/apps/red-ui/src/app/modules/admin/screens/license/utils/constants.ts @@ -1 +1,17 @@ +import { Color, ScaleType } from '@swimlane/ngx-charts'; + +export const ComboBarScheme: Color = { + name: 'Combo bar scheme', + selectable: true, + group: ScaleType.Ordinal, + domain: ['#0389ec'], +}; + +export const LineChartScheme: Color = { + name: 'Line chart scheme', + selectable: true, + group: ScaleType.Ordinal, + domain: ['#dd4d50', '#5ce594', '#0389ec'], +}; + export const LICENSE_STORAGE_KEY = 'redaction-license-reports'; diff --git a/apps/red-ui/src/assets/config/config.json b/apps/red-ui/src/assets/config/config.json index 322276d2f..62578148a 100644 --- a/apps/red-ui/src/assets/config/config.json +++ b/apps/red-ui/src/assets/config/config.json @@ -7,11 +7,6 @@ "BACKEND_APP_VERSION": "4.4.40", "EULA_URL": "EULA_URL", "FRONTEND_APP_VERSION": "1.1", - "LICENSE_CUSTOMER": "Development License", - "LICENSE_EMAIL": "todo-license@email.com", - "LICENSE_END": "31-12-2022", - "LICENSE_PAGE_COUNT": 10000, - "LICENSE_START": "01-01-2022", "MAX_FILE_SIZE_MB": 100, "MAX_RETRIES_ON_SERVER_ERROR": 3, "OAUTH_CLIENT_ID": "redaction", diff --git a/apps/red-ui/src/assets/i18n/de.json b/apps/red-ui/src/assets/i18n/de.json index eb6199f26..906a12370 100644 --- a/apps/red-ui/src/assets/i18n/de.json +++ b/apps/red-ui/src/assets/i18n/de.json @@ -1565,6 +1565,7 @@ "backend-version": "Backend-Version der Anwendung", "chart": { "cumulative": "Seiten insgesamt", + "legend": "", "pages-per-month": "Seiten pro Monat", "total-pages": "Gesamtzahl der Seiten" }, diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json index ad9b7561e..097d884d2 100644 --- a/apps/red-ui/src/assets/i18n/en.json +++ b/apps/red-ui/src/assets/i18n/en.json @@ -1565,6 +1565,7 @@ "backend-version": "Backend Application Version", "chart": { "cumulative": "Cumulative Pages", + "legend": "Legend", "pages-per-month": "Pages per Month", "total-pages": "Total Pages" }, diff --git a/docker/red-ui/docker-entrypoint.sh b/docker/red-ui/docker-entrypoint.sh index 90063fae3..54e57d714 100755 --- a/docker/red-ui/docker-entrypoint.sh +++ b/docker/red-ui/docker-entrypoint.sh @@ -9,12 +9,6 @@ BACKEND_APP_VERSION="${BACKEND_APP_VERSION:-4.7.0}" EULA_URL="${EULA_URL:-}" FRONTEND_APP_VERSION="${FRONTEND_APP_VERSION:-}" -LICENSE_CUSTOMER="${LICENSE_CUSTOMER:-Developement License}" -LICENSE_EMAIL="${LICENSE_EMAIL:-license@iqser.com}" -LICENSE_END="${LICENSE_END:-31-12-2022}" -LICENSE_PAGE_COUNT="${LICENSE_PAGE_COUNT:-1000000}" -LICENSE_START="${LICENSE_START:-01-01-2021}" - MAX_FILE_SIZE_MB="${MAX_FILE_SIZE_MB:-50}" MAX_RETRIES_ON_SERVER_ERROR="${MAX_RETRIES_ON_SERVER_ERROR:-3}" OAUTH_CLIENT_ID="${OAUTH_CLIENT_ID:-gin-client}" @@ -35,11 +29,6 @@ echo '{ "BACKEND_APP_VERSION":"'"$BACKEND_APP_VERSION"'", "EULA_URL":"'"$EULA_URL:"'", "FRONTEND_APP_VERSION":"'"$FRONTEND_APP_VERSION:"'", - "LICENSE_EMAIL":"'"$LICENSE_EMAIL"'", - "LICENSE_CUSTOMER":"'"$LICENSE_CUSTOMER"'", - "LICENSE_END":"'"$LICENSE_END"'", - "LICENSE_PAGE_COUNT":'"$LICENSE_PAGE_COUNT"', - "LICENSE_START":"'"$LICENSE_START"'", "MAX_FILE_SIZE_MB":"'"$MAX_FILE_SIZE_MB"'", "MAX_RETRIES_ON_SERVER_ERROR":"'"$MAX_RETRIES_ON_SERVER_ERROR"'", "OAUTH_CLIENT_ID":"'"$OAUTH_CLIENT_ID"'", diff --git a/libs/red-domain/src/lib/shared/app-config.ts b/libs/red-domain/src/lib/shared/app-config.ts index 32b81a77d..ba047ec67 100644 --- a/libs/red-domain/src/lib/shared/app-config.ts +++ b/libs/red-domain/src/lib/shared/app-config.ts @@ -7,11 +7,6 @@ export interface AppConfig { BACKEND_APP_VERSION: string; EULA_URL: string; FRONTEND_APP_VERSION: string; - LICENSE_CUSTOMER: string; - LICENSE_EMAIL: string; - LICENSE_END: string; - LICENSE_PAGE_COUNT: number; - LICENSE_START: string; MAX_FILE_SIZE_MB: number; MAX_RETRIES_ON_SERVER_ERROR: number; OAUTH_CLIENT_ID: string; diff --git a/package.json b/package.json index b9411e2d6..89d9c5d41 100644 --- a/package.json +++ b/package.json @@ -40,8 +40,8 @@ "@ngx-translate/http-loader": "^7.0.0", "@nrwl/angular": "14.3.2", "@pdftron/webviewer": "8.6.0", + "@swimlane/ngx-charts": "^20.0.1", "@tabuckner/material-dayjs-adapter": "2.0.0", - "angular-google-charts": "^2.2.2", "dayjs": "^1.11.3", "file-saver": "^2.0.5", "jwt-decode": "^3.1.2", diff --git a/yarn.lock b/yarn.lock index d8c6d861e..ffd095625 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2526,6 +2526,25 @@ dependencies: "@sinonjs/commons" "^1.7.0" +"@swimlane/ngx-charts@^20.0.1": + version "20.1.0" + resolved "https://registry.yarnpkg.com/@swimlane/ngx-charts/-/ngx-charts-20.1.0.tgz#c1377adacc835fa35ed0c6cb32a8ec5b43ccfd69" + integrity sha512-PY/X+eW+ZEvF3N1kuUVV5H3NHoFXlIWOvNnCKAs874yye//ttgfL/Qf9haHQpki5WIHQtpwn8xM1ylVEQT98bg== + dependencies: + "@types/d3-shape" "^2.0.0" + d3-array "^2.9.1" + d3-brush "^2.1.0" + d3-color "^2.0.0" + d3-format "^2.0.0" + d3-hierarchy "^2.0.0" + d3-interpolate "^2.0.1" + d3-scale "^3.2.3" + d3-selection "^2.0.0" + d3-shape "^2.0.0" + d3-time-format "^3.0.0" + d3-transition "^2.0.0" + tslib "^2.0.0" + "@tabuckner/material-dayjs-adapter@2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@tabuckner/material-dayjs-adapter/-/material-dayjs-adapter-2.0.0.tgz#e79207232363fca391820c7992f7ed97576d7199" @@ -2624,6 +2643,18 @@ dependencies: "@types/node" "*" +"@types/d3-path@^2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-2.0.2.tgz#6052f38f6186319769dfabab61b5514b0e02c75c" + integrity sha512-3YHpvDw9LzONaJzejXLOwZ3LqwwkoXb9LI2YN7Hbd6pkGo5nIlJ09ul4bQhBN4hQZJKmUpX8HkVqbzgUKY48cg== + +"@types/d3-shape@^2.0.0": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-2.1.3.tgz#35d397b9e687abaa0de82343b250b9897b8cacf3" + integrity sha512-HAhCel3wP93kh4/rq+7atLdybcESZ5bRHDEZUojClyZWsRuEMo3A52NGYJSh48SxfxEU6RZIVbZL2YFZ2OAlzQ== + dependencies: + "@types/d3-path" "^2" + "@types/eslint-scope@^3.7.3": version "3.7.3" resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.3.tgz#125b88504b61e3c8bc6f870882003253005c3224" @@ -2664,11 +2695,6 @@ "@types/qs" "*" "@types/serve-static" "*" -"@types/google.visualization@0.0.58": - version "0.0.58" - resolved "https://registry.yarnpkg.com/@types/google.visualization/-/google.visualization-0.0.58.tgz#eb2aa3cf05d63a9b42ff5cfcab1fc0594fb45a0e" - integrity sha512-ldwrhRvqSlrCYjHELGZNNMP8m5oPLBb6iU7CfKF2C/YpgRTlHihqsrL5M293u0GL7mKfjm0AjSIgEE2LU8fuTw== - "@types/google.visualization@^0.0.68": version "0.0.68" resolved "https://registry.yarnpkg.com/@types/google.visualization/-/google.visualization-0.0.68.tgz#773e908c02e08dffe689844f0972dd481516e704" @@ -3241,14 +3267,6 @@ ajv@^8.0.0, ajv@^8.8.0: require-from-string "^2.0.2" uri-js "^4.2.2" -angular-google-charts@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/angular-google-charts/-/angular-google-charts-2.2.2.tgz#fd26a78a0c44f8bc686832116e7ced9751b46987" - integrity sha512-dbNDqhSfqxv1rMMph2xV55+nsXSREClm03bTRpq4nRjpVghC1hVwsqfeg6p/cngB7WgamQmWj5ExTwN6vBzVAg== - dependencies: - "@types/google.visualization" "0.0.58" - tslib "^2.2.0" - ansi-align@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59" @@ -4427,6 +4445,122 @@ cuint@^0.2.2: resolved "https://registry.yarnpkg.com/cuint/-/cuint-0.2.2.tgz#408086d409550c2631155619e9fa7bcadc3b991b" integrity sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs= +d3-array@2, d3-array@^2.3.0, d3-array@^2.9.1: + version "2.12.1" + resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-2.12.1.tgz#e20b41aafcdffdf5d50928004ececf815a465e81" + integrity sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ== + dependencies: + internmap "^1.0.0" + +d3-brush@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-2.1.0.tgz#adadfbb104e8937af142e9a6e2028326f0471065" + integrity sha512-cHLLAFatBATyIKqZOkk/mDHUbzne2B3ZwxkzMHvFTCZCmLaXDpZRihQSn8UNXTkGD/3lb/W2sQz0etAftmHMJQ== + dependencies: + d3-dispatch "1 - 2" + d3-drag "2" + d3-interpolate "1 - 2" + d3-selection "2" + d3-transition "2" + +"d3-color@1 - 2", d3-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-2.0.0.tgz#8d625cab42ed9b8f601a1760a389f7ea9189d62e" + integrity sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ== + +"d3-dispatch@1 - 2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-2.0.0.tgz#8a18e16f76dd3fcaef42163c97b926aa9b55e7cf" + integrity sha512-S/m2VsXI7gAti2pBoLClFFTMOO1HTtT0j99AuXLoGFKO6deHDdnv6ZGTxSTTUTgO1zVcv82fCOtDjYK4EECmWA== + +d3-drag@2: + version "2.0.0" + resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-2.0.0.tgz#9eaf046ce9ed1c25c88661911c1d5a4d8eb7ea6d" + integrity sha512-g9y9WbMnF5uqB9qKqwIIa/921RYWzlUDv9Jl1/yONQwxbOfszAWTCm8u7HOTgJgRDXiRZN56cHT9pd24dmXs8w== + dependencies: + d3-dispatch "1 - 2" + d3-selection "2" + +"d3-ease@1 - 2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-2.0.0.tgz#fd1762bfca00dae4bacea504b1d628ff290ac563" + integrity sha512-68/n9JWarxXkOWMshcT5IcjbB+agblQUaIsbnXmrzejn2O82n3p2A9R2zEB9HIEFWKFwPAEDDN8gR0VdSAyyAQ== + +"d3-format@1 - 2", d3-format@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-2.0.0.tgz#a10bcc0f986c372b729ba447382413aabf5b0767" + integrity sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA== + +d3-hierarchy@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-2.0.0.tgz#dab88a58ca3e7a1bc6cab390e89667fcc6d20218" + integrity sha512-SwIdqM3HxQX2214EG9GTjgmCc/mbSx4mQBn+DuEETubhOw6/U3fmnji4uCVrmzOydMHSO1nZle5gh6HB/wdOzw== + +"d3-interpolate@1 - 2", "d3-interpolate@1.2.0 - 2", d3-interpolate@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-2.0.1.tgz#98be499cfb8a3b94d4ff616900501a64abc91163" + integrity sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ== + dependencies: + d3-color "1 - 2" + +"d3-path@1 - 2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-2.0.0.tgz#55d86ac131a0548adae241eebfb56b4582dd09d8" + integrity sha512-ZwZQxKhBnv9yHaiWd6ZU4x5BtCQ7pXszEV9CU6kRgwIQVQGLMv1oiL4M+MK/n79sYzsj+gcgpPQSctJUsLN7fA== + +d3-scale@^3.2.3: + version "3.3.0" + resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-3.3.0.tgz#28c600b29f47e5b9cd2df9749c206727966203f3" + integrity sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ== + dependencies: + d3-array "^2.3.0" + d3-format "1 - 2" + d3-interpolate "1.2.0 - 2" + d3-time "^2.1.1" + d3-time-format "2 - 3" + +d3-selection@2, d3-selection@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-2.0.0.tgz#94a11638ea2141b7565f883780dabc7ef6a61066" + integrity sha512-XoGGqhLUN/W14NmaqcO/bb1nqjDAw5WtSYb2X8wiuQWvSZUsUVYsOSkOybUrNvcBjaywBdYPy03eXHMXjk9nZA== + +d3-shape@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-2.1.0.tgz#3b6a82ccafbc45de55b57fcf956c584ded3b666f" + integrity sha512-PnjUqfM2PpskbSLTJvAzp2Wv4CZsnAgTfcVRTwW03QR3MkXF8Uo7B1y/lWkAsmbKwuecto++4NlsYcvYpXpTHA== + dependencies: + d3-path "1 - 2" + +"d3-time-format@2 - 3", d3-time-format@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-3.0.0.tgz#df8056c83659e01f20ac5da5fdeae7c08d5f1bb6" + integrity sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag== + dependencies: + d3-time "1 - 2" + +"d3-time@1 - 2", d3-time@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-2.1.1.tgz#e9d8a8a88691f4548e68ca085e5ff956724a6682" + integrity sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ== + dependencies: + d3-array "2" + +"d3-timer@1 - 2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-2.0.0.tgz#055edb1d170cfe31ab2da8968deee940b56623e6" + integrity sha512-TO4VLh0/420Y/9dO3+f9abDEFYeCUr2WZRlxJvbp4HPTQcSylXNiL6yZa9FIUvV1yRiFufl1bszTCLDqv9PWNA== + +d3-transition@2, d3-transition@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-2.0.0.tgz#366ef70c22ef88d1e34105f507516991a291c94c" + integrity sha512-42ltAGgJesfQE3u9LuuBHNbGrI/AJjNL2OAUdclE70UE6Vy239GCBEYD38uBPoLeNsOhFStGpPI0BAOV+HMxog== + dependencies: + d3-color "1 - 2" + d3-dispatch "1 - 2" + d3-ease "1 - 2" + d3-interpolate "1 - 2" + d3-timer "1 - 2" + data-urls@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" @@ -6351,6 +6485,11 @@ internal-slot@^1.0.3: has "^1.0.3" side-channel "^1.0.4" +internmap@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/internmap/-/internmap-1.0.1.tgz#0017cc8a3b99605f0302f2b198d272e015e5df95" + integrity sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw== + ip@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" @@ -10342,7 +10481,7 @@ tsconfig-paths@^3.9.0: minimist "^1.2.0" strip-bom "^3.0.0" -tslib@2.4.0, tslib@^2.2.0, tslib@^2.4.0: +tslib@2.4.0, tslib@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==