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==