RED-3765: Google charts

This commit is contained in:
Adina Țeudan 2022-05-12 20:48:50 +03:00 committed by Dan Percic
parent da1fa1246d
commit e65db170d9
20 changed files with 101 additions and 1083 deletions

View File

@ -1,111 +0,0 @@
<ngx-charts-chart
(legendLabelActivate)="onActivate($event)"
(legendLabelClick)="onClick($event)"
(legendLabelDeactivate)="onDeactivate($event)"
[activeEntries]="activeEntries"
[animations]="animations"
[legendOptions]="legendOptions"
[showLegend]="legend"
[view]="[width + legendSpacing, height]"
>
<svg:g [attr.transform]="transform" class="bar-chart chart">
<svg:g
(dimensionsChanged)="updateXAxisHeight($event)"
*ngIf="xAxis"
[dims]="dims"
[labelText]="xAxisLabel"
[showLabel]="showXAxisLabel"
[tickFormatting]="xAxisTickFormatting"
[xScale]="xScale"
ngx-charts-x-axis
></svg:g>
<svg:g
(dimensionsChanged)="updateYAxisWidth($event)"
*ngIf="yAxis"
[dims]="dims"
[labelText]="yAxisLabel"
[showGridLines]="showGridLines"
[showLabel]="showYAxisLabel"
[tickFormatting]="yAxisTickFormatting"
[yOrient]="yOrientLeft"
[yScale]="yScale"
ngx-charts-y-axis
></svg:g>
<svg:g
(dimensionsChanged)="updateYAxisWidth($event)"
*ngIf="yAxis"
[dims]="dims"
[labelText]="yAxisLabelRight"
[showGridLines]="showGridLines"
[showLabel]="showRightYAxisLabel"
[tickFormatting]="yRightAxisTickFormatting"
[yOrient]="yOrientRight"
[yScale]="yScaleLine"
ngx-charts-y-axis
></svg:g>
<svg:g
(activate)="onActivate($event)"
(bandwidth)="updateLineWidth($event)"
(deactivate)="onDeactivate($event)"
[activeEntries]="activeEntries"
[animations]="animations"
[colors]="colors"
[dims]="dims"
[gradient]="gradient"
[noBarWhenZero]="noBarWhenZero"
[seriesLine]="lineChart"
[series]="results"
[tooltipDisabled]="true"
[xScale]="xScale"
[yScale]="yScale"
ngx-combo-charts-series-vertical
></svg:g>
</svg:g>
<svg:g [attr.transform]="transform" class="line-chart chart">
<svg:g>
<svg:g *ngFor="let series of lineChart; trackBy: trackBy">
<svg:g
[activeEntries]="activeEntries"
[animations]="animations"
[colors]="colorsLine"
[curve]="curve"
[data]="series"
[rangeFillOpacity]="rangeFillOpacity"
[scaleType]="scaleType"
[xScale]="xScaleLine"
[yScale]="yScaleLine"
ngx-charts-line-series
/>
</svg:g>
<svg:g
(hover)="updateHoveredVertical($event)"
*ngIf="!tooltipDisabled"
[colors]="colorsLine"
[dims]="dims"
[results]="combinedSeries"
[tooltipDisabled]="tooltipDisabled"
[xScale]="xScaleLine"
[xSet]="xSet"
[yScale]="yScaleLine"
ngx-charts-tooltip-area
/>
<svg:g *ngFor="let series of lineChart">
<svg:g
(activate)="onActivate($event)"
(deactivate)="onDeactivate($event)"
[activeEntries]="activeEntries"
[colors]="colorsLine"
[data]="series"
[scaleType]="scaleType"
[tooltipDisabled]="tooltipDisabled"
[visibleValue]="hoveredVertical"
[xScale]="xScaleLine"
[yScale]="yScaleLine"
ngx-charts-circle-series
/>
</svg:g>
</svg:g>
</svg:g>
</ngx-charts-chart>

View File

@ -1,89 +0,0 @@
.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);
}
}
}
}

View File

@ -1,405 +0,0 @@
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() xAxisTickFormatting: any;
@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<unknown>;
@ContentChild('seriesTooltipTemplate') seriesTooltipTemplate: TemplateRef<unknown>;
@ViewChild(LineSeriesComponent) lineSeriesComponent: LineSeriesComponent;
dims: ViewDimensions;
xScale: any;
yScale: any;
xDomain: string[] | number[];
yDomain: string[] | number[];
transform: string;
colors: ColorHelper;
colorsLine: ColorHelper;
margin: any[] = [10, 20, 10, 20];
xAxisHeight = 0;
yAxisWidth = 0;
legendOptions: any;
scaleType: ScaleType = ScaleType.Linear;
xScaleLine;
yScaleLine;
xDomainLine;
yDomainLine;
seriesDomain;
scaledAxis;
combinedSeries: ILineChartSeries[];
xSet;
filteredDomain;
hoveredVertical;
yOrientLeft: Orientation = Orientation.Left;
yOrientRight: Orientation = Orientation.Right;
legendSpacing = 0;
bandwidth: number;
barPadding = 8;
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();
}
updateDomain(domain): void {
this.filteredDomain = domain;
this.xDomainLine = this.filteredDomain;
this.xScaleLine = this.getXScaleLine(this.xDomainLine, this.dims.width);
}
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];
} else {
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];
} else {
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 });
}
}

View File

@ -1,199 +0,0 @@
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: `
<svg:g
ngx-charts-bar
*ngFor="let bar of bars; trackBy: trackBy"
[@animationState]="'active'"
[width]="bar.width"
[height]="bar.height"
[x]="bar.x"
[y]="bar.y"
[fill]="bar.color"
[stops]="bar.gradientStops"
[data]="bar.data"
[orientation]="orientations.Vertical"
[roundEdges]="bar.roundEdges"
[gradient]="gradient"
[isActive]="isActive(bar.data)"
[animations]="animations"
[noBarWhenZero]="noBarWhenZero"
(activate)="activate.emit($event)"
(deactivate)="deactivate.emit($event)"
ngx-tooltip
[tooltipDisabled]="tooltipDisabled"
[tooltipPlacement]="tooltipPlacements.Top"
[tooltipType]="tooltipTypes.tooltip"
[tooltipTitle]="bar.tooltipText"
></svg:g>
`,
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 = this.seriesLine[0].series[index].value;
bar.tooltipText = `
<span class="tooltip-label">${tooltipLabel}</span>
<span class="tooltip-val">
Y1 - ${value.toLocaleString()} Y2 - ${lineValue.toLocaleString()}%
</span>
`;
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;
}
}

View File

@ -1,2 +0,0 @@
export * from './combo-chart.component';
export * from './combo-series-vertical.component';

View File

@ -1,11 +0,0 @@
export interface ISeries {
name: number;
value: number;
min: number;
max: number;
}
export interface ILineChartSeries {
name: string;
series: ISeries[];
}

View File

@ -0,0 +1,8 @@
<google-chart
[columns]="['abc', options.vAxis.title, options.vAxes[1].title, 'license-info-screen.chart.cumulative' | translate]"
[data]="data"
[height]="300"
[options]="options"
[type]="type"
[width]="1000"
></google-chart>

View File

@ -0,0 +1,36 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { ChartType } from 'angular-google-charts';
import { TranslateService } from '@ngx-translate/core';
import { GoogleChartData } from './models';
@Component({
selector: 'redaction-google-chart',
templateUrl: './google-chart.component.html',
styleUrls: ['./google-chart.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class GoogleChartComponent {
@Input() data: GoogleChartData;
readonly options = {
fontName: 'Inter',
fontSize: 13,
vAxis: {
title: this._translateService.instant('license-info-screen.chart.pages-per-month'),
},
seriesType: 'bars',
vAxes: {
1: {
title: this._translateService.instant('license-info-screen.chart.total-pages'),
},
},
series: { 1: { type: 'line', targetAxisIndex: 1 }, 2: { type: 'line', targetAxisIndex: 1 } },
colors: ['#0389ec', '#dd4d50', '#5ce594'],
legend: { position: 'top' },
};
readonly type = ChartType.ComboChart;
readonly width = 1000;
readonly height = 300;
constructor(private readonly _translateService: TranslateService) {}
}

View File

@ -0,0 +1 @@
export type GoogleChartData = [string, number, number, number][];

View File

@ -1,18 +1 @@
<combo-chart-component
*ngIf="lineChartSeries$ | async as lineChartSeries"
[animations]="true"
[colorSchemeLine]="lineChartScheme"
[legendTitle]="'license-info-screen.chart.legend' | translate"
[legend]="true"
[lineChart]="lineChartSeries"
[results]="barChart"
[scheme]="comboBarScheme"
[showGridLines]="true"
[showRightYAxisLabel]="true"
[showYAxisLabel]="true"
[view]="[1000, 300]"
[xAxis]="true"
[yAxisLabelRight]="'license-info-screen.chart.total-pages' | translate"
[yAxisLabel]="'license-info-screen.chart.pages-per-month' | translate"
[yAxis]="true"
></combo-chart-component>
<redaction-google-chart [data]="chartData$ | async"></redaction-google-chart>

View File

@ -1,15 +1,14 @@
import { Component } from '@angular/core';
import { ComboBarScheme, LICENSE_STORAGE_KEY, LineChartScheme } from '../utils/constants';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { LICENSE_STORAGE_KEY } from '../utils/constants';
import dayjs from 'dayjs';
import { TranslateService } from '@ngx-translate/core';
import { ILicenseReport } from '@red/domain';
import { LicenseService } from '../services/license.service';
import { IDateRange } from '../utils/date-range';
import { ILicense } from '../utils/license';
import { switchMap, tap } from 'rxjs/operators';
import { ILineChartSeries } from '../combo-chart/models';
import { LoadingService } from '@iqser/common-ui';
import { generateDateRanges, isCurrentMonth, toDate } from '../utils/functions';
import { GoogleChartData } from '../google-chart/models';
const monthNames = dayjs.monthsShort();
@ -17,76 +16,38 @@ const monthNames = dayjs.monthsShort();
selector: 'redaction-license-chart',
templateUrl: './license-chart.component.html',
styleUrls: ['./license-chart.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class LicenseChartComponent {
readonly lineChartScheme = LineChartScheme;
readonly comboBarScheme = ComboBarScheme;
readonly chartData$ = this.#chartData$;
lineChartSeries$ = this.#licenseChartSeries$;
barChart: any[];
constructor(private readonly _licenseService: LicenseService, private readonly _loadingService: LoadingService) {}
constructor(
private readonly _translateService: TranslateService,
private readonly _licenseService: LicenseService,
private readonly _loadingService: LoadingService,
) {}
get #licenseChartSeries$() {
get #chartData$() {
return this._licenseService.selectedLicense$.pipe(
tap(() => this._loadingService.start()),
switchMap(license => this.#setMonthlyStats(license)),
switchMap(license => this.#chartData(license)),
tap(() => this._loadingService.stop()),
);
}
async #setMonthlyStats(licence: ILicense): Promise<ILineChartSeries[]> {
const startDate = dayjs(licence.validFrom);
const endDate = dayjs(licence.validUntil);
async #chartData(license: ILicense): Promise<GoogleChartData> {
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(), endDate.year());
const dateRanges = generateDateRanges(startMonth, startYear, endDate.month() as number, endDate.year() as number);
const reports = await this.#getReports(dateRanges);
return [
{
name: this._translateService.instant('license-info-screen.chart.licensed-total'),
series: this.#totalLicensedPagesSeries(dateRanges),
},
{
name: this._translateService.instant('license-info-screen.chart.cumulative'),
series: this.#setBarChartAndGetCumulativePageSeries(startMonth, startYear, reports),
},
];
}
#setBarChartAndGetCumulativePageSeries(month: number, year: number, reports: ILicenseReport[]) {
let cumulativePages = 0;
const cumulativePagesSeries = [];
this.barChart = [];
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;
return dateRanges.map((range, index) => [
`${monthNames[range.startMonth]} ${range.startYear}`,
reports[index].numberOfAnalyzedPages,
this._licenseService.totalLicensedNumberOfPages,
(cumulativePages += reports[index].numberOfAnalyzedPages),
]);
}
#getReports(dateRanges: IDateRange[]) {
@ -117,11 +78,4 @@ export class LicenseChartComponent {
return report;
}
#totalLicensedPagesSeries(dateRanges: IDateRange[]) {
return dateRanges.map(dateRange => ({
name: `${monthNames[dateRange.startMonth]} ${dateRange.startYear}`,
value: this._licenseService.totalLicensedNumberOfPages,
}));
}
}

View File

@ -40,7 +40,7 @@
}
}
combo-chart-component {
redaction-license-chart {
margin-bottom: 50px;
}
}

View File

@ -9,7 +9,7 @@ import { LicenseService } from '../services/license.service';
import { ILicenseReport } from '@red/domain';
import dayjs from 'dayjs';
import { ILicense } from '../utils/license';
import { UserPreferenceService } from '../../../../../services/user-preference.service';
import { UserPreferenceService } from '@services/user-preference.service';
@Component({
templateUrl: './license-screen.component.html',

View File

@ -7,9 +7,10 @@ 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 = [
{
@ -19,14 +20,16 @@ const routes: Routes = [
];
@NgModule({
declarations: [
LicenseScreenComponent,
LicenseSelectComponent,
LicenseChartComponent,
ComboChartComponent,
ComboSeriesVerticalComponent,
declarations: [LicenseScreenComponent, LicenseSelectComponent, LicenseChartComponent, GoogleChartComponent],
imports: [
CommonModule,
RouterModule.forChild(routes),
TranslateModule,
MatSelectModule,
FormsModule,
IqserListingModule,
GoogleChartsModule,
],
imports: [RouterModule.forChild(routes), TranslateModule, MatSelectModule, FormsModule, NgxChartsModule, IqserListingModule],
providers: [LicenseService],
})
export class LicenseModule {}

View File

@ -1,17 +1 @@
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';

View File

@ -25,7 +25,7 @@ export const LICENSE_DATA: ILicenses = {
licenses: [
{
id: 'guid-1',
name: '1 Year comulative (2022)',
name: '1 Year cumulative (2022)',
product: 'RedactManager',
licensedTo: 'Customer company name 1',
licensedToEmail: 'customer@example.com',
@ -46,7 +46,7 @@ export const LICENSE_DATA: ILicenses = {
},
{
id: 'guid-2',
name: '2 Year comulative (2021)',
name: '2 Year cumulative (2021)',
product: 'RedactManager',
licensedTo: 'Customer company name 2',
licensedToEmail: 'customer@example.com',

View File

@ -11,8 +11,8 @@
"LICENSE_CUSTOMER": "Development License",
"LICENSE_EMAIL": "todo-license@email.com",
"LICENSE_END": "31-12-2022",
"LICENSE_PAGE_COUNT": 1000,
"LICENSE_START": "01-01-2021",
"LICENSE_PAGE_COUNT": 10000,
"LICENSE_START": "01-01-2022",
"MAX_FILE_SIZE_MB": 100,
"MAX_RETRIES_ON_SERVER_ERROR": 3,
"OAUTH_CLIENT_ID": "redaction",

View File

@ -42,8 +42,8 @@
"@ngx-translate/http-loader": "^7.0.0",
"@nrwl/angular": "13.9.5",
"@pdftron/webviewer": "8.3.3",
"@swimlane/ngx-charts": "^20.0.1",
"@tabuckner/material-dayjs-adapter": "2.0.0",
"angular-google-charts": "^2.2.2",
"dayjs": "^1.11.0",
"file-saver": "^2.0.5",
"jwt-decode": "^3.1.2",

170
yarn.lock
View File

@ -2150,25 +2150,6 @@
"@swc/core-win32-ia32-msvc" "1.2.155"
"@swc/core-win32-x64-msvc" "1.2.155"
"@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"
@ -2267,18 +2248,6 @@
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"
@ -2324,6 +2293,11 @@
"@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/graceful-fs@^4.1.2":
version "4.1.5"
resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15"
@ -2905,6 +2879,14 @@ 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"
@ -4049,122 +4031,6 @@ 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"
@ -6322,11 +6188,6 @@ 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.0, ip@^1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a"
@ -10461,6 +10322,11 @@ tslib@^1.8.1, tslib@^1.9.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tslib@^2.2.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"
integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==
tsutils@^3.21.0:
version "3.21.0"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"