diff --git a/apps/red-ui/src/app/app.module.ts b/apps/red-ui/src/app/app.module.ts
index 907a54f0d..65081d387 100644
--- a/apps/red-ui/src/app/app.module.ts
+++ b/apps/red-ui/src/app/app.module.ts
@@ -112,6 +112,8 @@ import { DownloadsListScreenComponent } from './screens/downloads-list-screen/do
import { DigitalSignatureScreenComponent } from './screens/admin/digital-signature-screen/digital-signature-screen.component';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { RemoveAnnotationsDialogComponent } from './dialogs/remove-annotations-dialog/remove-annotations-dialog.component';
+import { NgxChartsModule } from '@swimlane/ngx-charts';
+import { ComboChartComponent, ComboSeriesVerticalComponent } from './screens/admin/license-information-screen/combo-chart';
export function HttpLoaderFactory(httpClient: HttpClient) {
return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json');
@@ -371,7 +373,9 @@ const matImports = [
EditColorDialogComponent,
DownloadsListScreenComponent,
DigitalSignatureScreenComponent,
- RemoveAnnotationsDialogComponent
+ RemoveAnnotationsDialogComponent,
+ ComboChartComponent,
+ ComboSeriesVerticalComponent
],
imports: [
BrowserModule,
@@ -382,6 +386,7 @@ const matImports = [
AuthModule,
IconsModule,
ApiModule,
+ NgxChartsModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
diff --git a/apps/red-ui/src/app/screens/admin/license-information-screen/combo-chart/combo-chart.component.html b/apps/red-ui/src/app/screens/admin/license-information-screen/combo-chart/combo-chart.component.html
new file mode 100644
index 000000000..5cffc2f32
--- /dev/null
+++ b/apps/red-ui/src/app/screens/admin/license-information-screen/combo-chart/combo-chart.component.html
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/red-ui/src/app/screens/admin/license-information-screen/combo-chart/combo-chart.component.scss b/apps/red-ui/src/app/screens/admin/license-information-screen/combo-chart/combo-chart.component.scss
new file mode 100644
index 000000000..708586e9e
--- /dev/null
+++ b/apps/red-ui/src/app/screens/admin/license-information-screen/combo-chart/combo-chart.component.scss
@@ -0,0 +1,88 @@
+.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/screens/admin/license-information-screen/combo-chart/combo-chart.component.ts b/apps/red-ui/src/app/screens/admin/license-information-screen/combo-chart/combo-chart.component.ts
new file mode 100644
index 000000000..47e88c180
--- /dev/null
+++ b/apps/red-ui/src/app/screens/admin/license-information-screen/combo-chart/combo-chart.component.ts
@@ -0,0 +1,388 @@
+import { Component, Input, ViewEncapsulation, Output, EventEmitter, ViewChild, HostListener, ContentChild, TemplateRef } from '@angular/core';
+
+import { curveLinear } from 'd3-shape';
+import { scaleBand, scaleLinear, scalePoint, scaleTime } from 'd3-scale';
+import { BaseChartComponent, LineSeriesComponent, ViewDimensions, ColorHelper, calculateViewDimensions } from '@swimlane/ngx-charts';
+
+@Component({
+ // tslint:disable-next-line: 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 = '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: string;
+ @Input() xAxisTickFormatting: any;
+ @Input() yAxisTickFormatting: any;
+ @Input() yRightAxisTickFormatting: any;
+ @Input() roundDomains = false;
+ @Input() colorSchemeLine: any;
+ @Input() autoScale;
+ @Input() lineChart: any;
+ @Input() yLeftAxisScaleFactor: any;
+ @Input() yRightAxisScaleFactor: any;
+ @Input() rangeFillOpacity: number;
+ @Input() animations = true;
+ @Input() noBarWhenZero = true;
+
+ @Output() activate: EventEmitter = new EventEmitter();
+ @Output() deactivate: EventEmitter = new EventEmitter();
+
+ @ContentChild('tooltipTemplate') tooltipTemplate: TemplateRef;
+ @ContentChild('seriesTooltipTemplate') seriesTooltipTemplate: TemplateRef;
+
+ @ViewChild(LineSeriesComponent) lineSeriesComponent: LineSeriesComponent;
+
+ dims: ViewDimensions;
+ xScale: any;
+ yScale: any;
+ xDomain: any;
+ yDomain: any;
+ transform: string;
+ colors: ColorHelper;
+ colorsLine: ColorHelper;
+ margin: any[] = [10, 20, 10, 20];
+ xAxisHeight = 0;
+ yAxisWidth = 0;
+ legendOptions: any;
+ scaleType = 'linear';
+ xScaleLine;
+ yScaleLine;
+ xDomainLine;
+ yDomainLine;
+ seriesDomain;
+ scaledAxis;
+ combinedSeries;
+ xSet;
+ filteredDomain;
+ hoveredVertical;
+ yOrientLeft = 'left';
+ yOrientRight = 'right';
+ legendSpacing = 0;
+ bandwidth;
+ 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): boolean {
+ if (value instanceof Date) {
+ return true;
+ }
+
+ return false;
+ }
+
+ getScaleType(values): string {
+ 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 'time';
+ if (num) return 'linear';
+ return 'ordinal';
+ }
+
+ getXDomainLine(): any[] {
+ let values = [];
+
+ 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 = [];
+
+ 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), minMax.max];
+ } else {
+ min = Math.min(0, min);
+ return [min, max];
+ }
+ }
+
+ getXScaleLine(domain, width): 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 = 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), minMax.max];
+ } else {
+ return [min, max];
+ }
+ }
+
+ onClick(data) {
+ this.select.emit(data);
+ }
+
+ setColors(): void {
+ let domain;
+ 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 }): void {
+ this.yAxisWidth = width + 20;
+ this.update();
+ }
+
+ updateXAxisHeight({ height }): void {
+ this.xAxisHeight = height;
+ this.update();
+ }
+
+ onActivate(item) {
+ const idx = this.activeEntries.findIndex((d) => {
+ return 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) => {
+ return 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/screens/admin/license-information-screen/combo-chart/combo-series-vertical.component.ts b/apps/red-ui/src/app/screens/admin/license-information-screen/combo-chart/combo-series-vertical.component.ts
new file mode 100644
index 000000000..0646b3eab
--- /dev/null
+++ b/apps/red-ui/src/app/screens/admin/license-information-screen/combo-chart/combo-series-vertical.component.ts
@@ -0,0 +1,189 @@
+import { Component, Input, Output, EventEmitter, OnChanges, ChangeDetectionStrategy } from '@angular/core';
+import { trigger, style, animate, transition } from '@angular/animations';
+import { formatLabel } from '@swimlane/ngx-charts';
+
+@Component({
+ // tslint:disable-next-line: 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;
+
+ ngOnChanges(changes): 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, d) => sum + d, 0);
+ }
+
+ this.bars = this.series.map((d, index) => {
+ let value = d.value;
+ const label = d.name;
+ const formattedLabel = formatLabel(label);
+ const roundEdges = this.type === 'standard';
+
+ const bar: any = {
+ value,
+ label,
+ roundEdges,
+ data: d,
+ width,
+ formattedLabel,
+ height: 0,
+ x: 0,
+ y: 0
+ };
+
+ 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') {
+ const offset0 = d0;
+ const offset1 = offset0 + value;
+ 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') {
+ let offset0 = d0;
+ let offset1 = offset0 + value;
+ 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;
+ 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(bar.offset1);
+ bar.gradientStops = this.colors.getLinearGradientStops(bar.offset1, bar.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 = `
+ ${tooltipLabel}
+ Y1 - ${value.toLocaleString()} • Y2 - ${lineValue.toLocaleString()}%
+ `;
+
+ return bar;
+ });
+ }
+ getSeriesTooltips(seriesLine, index) {
+ return seriesLine.map((d) => {
+ return d.series[index];
+ });
+ }
+ isActive(entry): boolean {
+ if (!this.activeEntries) return false;
+ const item = this.activeEntries.find((d) => {
+ return 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/screens/admin/license-information-screen/combo-chart/index.ts b/apps/red-ui/src/app/screens/admin/license-information-screen/combo-chart/index.ts
new file mode 100644
index 000000000..2a0ab4f22
--- /dev/null
+++ b/apps/red-ui/src/app/screens/admin/license-information-screen/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/screens/admin/license-information-screen/license-information-screen.component.html b/apps/red-ui/src/app/screens/admin/license-information-screen/license-information-screen.component.html
index f9a399fd3..d1122b62e 100644
--- a/apps/red-ui/src/app/screens/admin/license-information-screen/license-information-screen.component.html
+++ b/apps/red-ui/src/app/screens/admin/license-information-screen/license-information-screen.component.html
@@ -94,6 +94,26 @@
+
+
+
diff --git a/apps/red-ui/src/app/screens/admin/license-information-screen/license-information-screen.component.scss b/apps/red-ui/src/app/screens/admin/license-information-screen/license-information-screen.component.scss
index 31c51d932..4e11ec7da 100644
--- a/apps/red-ui/src/app/screens/admin/license-information-screen/license-information-screen.component.scss
+++ b/apps/red-ui/src/app/screens/admin/license-information-screen/license-information-screen.component.scss
@@ -4,35 +4,46 @@
.left-container {
width: 100vw;
@include inset-shadow;
-}
+ overflow: auto;
+ @include scroll-bar;
-.grid-container {
- display: grid;
- grid-template-columns: 1fr 2fr;
- margin: 20px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
- .row {
- display: contents;
+ .grid-container {
+ width: calc(100vw - 40px);
+ display: grid;
+ grid-template-columns: 1fr 2fr;
+ margin: 20px 20px 50px 20px;
- > div {
- padding: 8px 20px;
+ .row {
+ display: contents;
- &:first-of-type {
- font-weight: 600;
- }
- }
-
- &:hover {
> div {
- background-color: $grey-2;
+ padding: 8px 20px;
+
+ &:first-of-type {
+ font-weight: 600;
+ }
}
+
+ &:hover {
+ > div {
+ background-color: $grey-2;
+ }
+ }
+ }
+
+ .section-title {
+ grid-column: span 2;
+ padding: 20px 20px 8px;
+ margin-bottom: 8px;
+ border-bottom: 1px solid $separator;
}
}
- .section-title {
- grid-column: span 2;
- padding: 20px 20px 8px;
- margin-bottom: 8px;
- border-bottom: 1px solid $separator;
+ combo-chart-component {
+ margin-bottom: 50px;
}
}
diff --git a/apps/red-ui/src/app/screens/admin/license-information-screen/license-information-screen.component.ts b/apps/red-ui/src/app/screens/admin/license-information-screen/license-information-screen.component.ts
index 90998b290..263d57718 100644
--- a/apps/red-ui/src/app/screens/admin/license-information-screen/license-information-screen.component.ts
+++ b/apps/red-ui/src/app/screens/admin/license-information-screen/license-information-screen.component.ts
@@ -1,16 +1,27 @@
-import { Component } from '@angular/core';
+import { Component, OnInit } from '@angular/core';
import { PermissionsService } from '../../../common/service/permissions.service';
import { LicenseReport, LicenseReportControllerService } from '@redaction/red-ui-http';
import { AppConfigService } from '../../../app-config/app-config.service';
import * as moment from 'moment';
import { TranslateService } from '@ngx-translate/core';
+import { Moment } from 'moment';
@Component({
selector: 'redaction-license-information-screen',
templateUrl: './license-information-screen.component.html',
styleUrls: ['./license-information-screen.component.scss']
})
-export class LicenseInformationScreenComponent {
+export class LicenseInformationScreenComponent implements OnInit {
+ constructor(
+ public readonly permissionsService: PermissionsService,
+ private readonly _licenseReportController: LicenseReportControllerService,
+ public readonly appConfigService: AppConfigService,
+ private readonly _translateService: TranslateService
+ ) {}
+
+ get currentYear(): number {
+ return new Date().getFullYear();
+ }
public currentInfo: LicenseReport = {};
public totalInfo: LicenseReport = {};
public unlicensedInfo: LicenseReport = {};
@@ -18,15 +29,31 @@ export class LicenseInformationScreenComponent {
public analysisPercentageOfLicense = 100;
public viewReady = false;
- constructor(
- public readonly permissionsService: PermissionsService,
- private readonly _licenseReportController: LicenseReportControllerService,
- public readonly appConfigService: AppConfigService,
- private readonly _translateService: TranslateService
- ) {
+ barChart: any[] = [];
+ lineChartSeries: any[] = [];
+
+ yAxisLabel = this._translateService.instant('license-info-screen.chart.pages-per-month');
+ yAxisLabelRight = this._translateService.instant('license-info-screen.chart.total-pages');
+
+ lineChartScheme = {
+ selectable: true,
+ group: 'Ordinal',
+ domain: ['#dd4d50', '#5ce594', '#0389ec']
+ };
+
+ comboBarScheme = {
+ selectable: true,
+ group: 'Ordinal',
+ domain: ['#0389ec']
+ };
+
+ public async ngOnInit() {
this.totalLicensedNumberOfPages = this.appConfigService.getConfig('LICENSE_PAGE_COUNT', 0);
const startDate = moment(this.appConfigService.getConfig('LICENSE_START'), 'DD-MM-YYYY');
const endDate = moment(this.appConfigService.getConfig('LICENSE_END'), 'DD-MM-YYYY');
+
+ await this._setMonthlyStats(startDate, endDate);
+
const currentConfig = {
startDate: startDate.toDate(),
endDate: endDate.toDate()
@@ -48,8 +75,75 @@ export class LicenseInformationScreenComponent {
});
}
- get currentYear(): number {
- return new Date().getFullYear();
+ private async _setMonthlyStats(startDate: Moment, endDate: Moment) {
+ const [startMonth, startYear] = [startDate.month(), startDate.year()];
+ const [endMonth, endYear] = [endDate.month(), endDate.year()];
+ moment.locale(this._translateService.currentLang);
+
+ let m = startMonth;
+ let y = startYear;
+ const totalLicensedSeries = [];
+ const cumulativePagesSeries = [];
+ const promises = [];
+
+ while (m <= endMonth && y <= endYear) {
+ totalLicensedSeries.push({
+ name: `${moment.localeData().monthsShort(moment([y, m]))} ${y}`,
+ value: this.totalLicensedNumberOfPages
+ });
+
+ let nm = m + 1;
+ let ny = y;
+ if (nm === 12) {
+ nm = 0;
+ ny++;
+ }
+
+ promises.push(
+ this._licenseReportController
+ .licenseReport({
+ startDate: moment(`01-${m + 1}-${y}`, 'DD-MM-YYYY').toDate(),
+ endDate: moment(`01-${nm + 1}-${ny}`, 'DD-MM-YYYY').toDate()
+ })
+ .toPromise()
+ );
+
+ y = ny;
+ m = nm;
+ }
+
+ const reports = await Promise.all(promises);
+
+ m = startMonth;
+ y = startYear;
+ let cumulativePages = 0;
+ for (const report of reports) {
+ cumulativePages += report.numberOfAnalyzedPages;
+ this.barChart.push({
+ name: `${moment.localeData().monthsShort(moment([y, m]))} ${y}`,
+ value: report.numberOfAnalyzedPages
+ });
+ cumulativePagesSeries.push({
+ name: `${moment.localeData().monthsShort(moment([y, m]))} ${y}`,
+ value: cumulativePages
+ });
+ m++;
+ if (m === 12) {
+ m = 0;
+ y++;
+ }
+ }
+
+ this.lineChartSeries = [
+ {
+ name: this._translateService.instant('license-info-screen.chart.licensed-total'),
+ series: totalLicensedSeries
+ },
+ {
+ name: this._translateService.instant('license-info-screen.chart.cumulative'),
+ series: cumulativePagesSeries
+ }
+ ];
}
sendMail(): void {
diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json
index 0711b5924..8ce911a7d 100644
--- a/apps/red-ui/src/assets/i18n/en.json
+++ b/apps/red-ui/src/assets/i18n/en.json
@@ -775,6 +775,13 @@
"analyzed": "Total Analyzed Pages in current license period: {{pages}}.",
"licensed": "Licensed Pages: {{pages}}."
}
+ },
+ "chart": {
+ "licensed-total": "Licensed Total",
+ "cumulative": "Cumulative Pages",
+ "pages-per-month": "Pages per Month",
+ "total-pages": "Total Pages",
+ "legend": "Legend"
}
},
"default-colors": "Default Colors",
diff --git a/package.json b/package.json
index 79999da15..8c745497b 100644
--- a/package.json
+++ b/package.json
@@ -1,107 +1,108 @@
{
- "name": "redaction",
- "version": "1.0.15",
- "private": true,
- "license": "MIT",
- "scripts": {
- "affected": "nx affected",
- "affected:apps": "nx affected:apps",
- "affected:build": "nx affected:build",
- "affected:dep-graph": "nx affected:dep-graph",
- "affected:e2e": "nx affected:e2e",
- "affected:libs": "nx affected:libs",
- "affected:lint": "nx affected:lint",
- "affected:test": "nx affected:test",
- "build": "nx build",
- "build-lint-all": "ng lint --project=red-ui-http --fix && ng build --project=red-ui-http && ng lint --project=red-ui --fix && ng build --project=red-ui --prod",
- "dep-graph": "nx dep-graph",
- "e2e": "nx e2e",
- "format": "nx format:write",
- "format:check": "nx format:check",
- "format:write": "nx format:write",
- "help": "nx help",
- "postinstall": "ngcc --properties es2015 browser module main --first-only --create-ivy-entry-points",
- "lint": "nx workspace-lint && nx lint",
- "nx": "nx",
- "start": "nx serve",
- "test": "nx test",
- "update": "nx migrate latest",
- "workspace-schematic": "nx workspace-schematic"
- },
- "husky": {
- "hooks": {
- "pre-commit": "pretty-quick --staged && ng lint --project=red-ui-http && ng lint --project=red-ui --fix"
+ "name": "redaction",
+ "version": "1.0.15",
+ "private": true,
+ "license": "MIT",
+ "scripts": {
+ "affected": "nx affected",
+ "affected:apps": "nx affected:apps",
+ "affected:build": "nx affected:build",
+ "affected:dep-graph": "nx affected:dep-graph",
+ "affected:e2e": "nx affected:e2e",
+ "affected:libs": "nx affected:libs",
+ "affected:lint": "nx affected:lint",
+ "affected:test": "nx affected:test",
+ "build": "nx build",
+ "build-lint-all": "ng lint --project=red-ui-http --fix && ng build --project=red-ui-http && ng lint --project=red-ui --fix && ng build --project=red-ui --prod",
+ "dep-graph": "nx dep-graph",
+ "e2e": "nx e2e",
+ "format": "nx format:write",
+ "format:check": "nx format:check",
+ "format:write": "nx format:write",
+ "help": "nx help",
+ "postinstall": "ngcc --properties es2015 browser module main --first-only --create-ivy-entry-points",
+ "lint": "nx workspace-lint && nx lint",
+ "nx": "nx",
+ "start": "nx serve",
+ "test": "nx test",
+ "update": "nx migrate latest",
+ "workspace-schematic": "nx workspace-schematic"
+ },
+ "husky": {
+ "hooks": {
+ "pre-commit": "pretty-quick --staged && ng lint --project=red-ui-http && ng lint --project=red-ui --fix"
+ }
+ },
+ "dependencies": {
+ "@angular/animations": "~11.0.1",
+ "@angular/cdk": "~11.0.1",
+ "@angular/common": "~11.0.1",
+ "@angular/compiler": "~11.0.1",
+ "@angular/core": "~11.0.1",
+ "@angular/forms": "~11.0.1",
+ "@angular/material": "~11.0.1",
+ "@angular/material-moment-adapter": "^11.0.2",
+ "@angular/platform-browser": "~11.0.1",
+ "@angular/platform-browser-dynamic": "~11.0.1",
+ "@angular/router": "~11.0.1",
+ "@angular/service-worker": "~11.0.1",
+ "@ngx-translate/core": "^13.0.0",
+ "@ngx-translate/http-loader": "^6.0.0",
+ "@nrwl/angular": "^10.2.0",
+ "@pdftron/webviewer": "^7.2.0",
+ "@swimlane/ngx-charts": "^17.0.0",
+ "file-saver": "^2.0.2",
+ "jwt-decode": "^3.0.0",
+ "keycloak-angular": "^8.0.1",
+ "keycloak-js": "10.0.2",
+ "lint-staged": "^10.5.0",
+ "ng2-ace-editor": "^0.3.9",
+ "ngp-sort-pipe": "^0.0.4",
+ "ngx-color-picker": "^10.1.0",
+ "ngx-dropzone": "^2.2.2",
+ "ngx-toastr": "^13.0.0",
+ "rxjs": "~6.6.0",
+ "scroll-into-view-if-needed": "^2.2.26",
+ "streamsaver": "^2.0.5",
+ "tslib": "^2.0.0",
+ "zone.js": "~0.10.2"
+ },
+ "devDependencies": {
+ "@angular-devkit/build-angular": "~0.1100.2",
+ "@angular-devkit/build-ng-packagr": "~0.1002.0",
+ "@angular/cli": "~11.0.2",
+ "@angular/compiler": "~11.0.1",
+ "@angular/compiler-cli": "~11.0.1",
+ "@angular/language-service": "~11.0.2",
+ "@nrwl/cypress": "10.2.0",
+ "@nrwl/jest": "10.2.0",
+ "@nrwl/workspace": "10.2.0",
+ "@types/cypress": "^1.1.3",
+ "@types/jasmine": "~3.6.0",
+ "@types/jest": "26.0.8",
+ "@types/node": "^12.11.1",
+ "codelyzer": "^6.0.0",
+ "cypress": "^5.6.0",
+ "cypress-file-upload": "^4.1.1",
+ "cypress-keycloak": "^1.5.0",
+ "cypress-keycloak-commands": "^1.2.0",
+ "cypress-localstorage-commands": "^1.2.4",
+ "dotenv": "6.2.0",
+ "eslint": "6.8.0",
+ "google-translate-api-browser": "^1.1.71",
+ "husky": "^4.3.0",
+ "jest": "26.2.2",
+ "jest-preset-angular": "8.2.1",
+ "lodash": "^4.17.20",
+ "moment": "^2.29.1",
+ "ng-packagr": "^10.1.2",
+ "prettier": "2.0.4",
+ "pretty-quick": "^3.1.0",
+ "superagent": "^6.1.0",
+ "superagent-promise": "^1.1.0",
+ "ts-jest": "26.1.4",
+ "ts-node": "~8.3.0",
+ "tslint": "~6.1.0",
+ "typescript": "~4.0.2"
}
- },
- "dependencies": {
- "@angular/animations": "~11.0.1",
- "@angular/cdk": "~11.0.1",
- "@angular/common": "~11.0.1",
- "@angular/compiler": "~11.0.1",
- "@angular/core": "~11.0.1",
- "@angular/forms": "~11.0.1",
- "@angular/material": "~11.0.1",
- "@angular/material-moment-adapter": "^11.0.2",
- "@angular/platform-browser": "~11.0.1",
- "@angular/platform-browser-dynamic": "~11.0.1",
- "@angular/router": "~11.0.1",
- "@angular/service-worker": "~11.0.1",
- "@ngx-translate/core": "^13.0.0",
- "@ngx-translate/http-loader": "^6.0.0",
- "@nrwl/angular": "^10.2.0",
- "@pdftron/webviewer": "^7.2.0",
- "file-saver": "^2.0.2",
- "jwt-decode": "^3.0.0",
- "keycloak-angular": "^8.0.1",
- "keycloak-js": "10.0.2",
- "lint-staged": "^10.5.0",
- "ng2-ace-editor": "^0.3.9",
- "ngp-sort-pipe": "^0.0.4",
- "ngx-color-picker": "^10.1.0",
- "ngx-dropzone": "^2.2.2",
- "ngx-toastr": "^13.0.0",
- "rxjs": "~6.6.0",
- "scroll-into-view-if-needed": "^2.2.26",
- "tslib": "^2.0.0",
- "zone.js": "~0.10.2",
- "streamsaver": "^2.0.5"
- },
- "devDependencies": {
- "@angular-devkit/build-angular": "~0.1100.2",
- "@angular-devkit/build-ng-packagr": "~0.1002.0",
- "@angular/cli": "~11.0.2",
- "@angular/compiler": "~11.0.1",
- "@angular/compiler-cli": "~11.0.1",
- "@angular/language-service": "~11.0.2",
- "@nrwl/cypress": "10.2.0",
- "@nrwl/jest": "10.2.0",
- "@nrwl/workspace": "10.2.0",
- "@types/cypress": "^1.1.3",
- "@types/jasmine": "~3.6.0",
- "@types/jest": "26.0.8",
- "@types/node": "^12.11.1",
- "codelyzer": "^6.0.0",
- "cypress": "^5.6.0",
- "cypress-file-upload": "^4.1.1",
- "cypress-keycloak": "^1.5.0",
- "cypress-keycloak-commands": "^1.2.0",
- "cypress-localstorage-commands": "^1.2.4",
- "dotenv": "6.2.0",
- "eslint": "6.8.0",
- "google-translate-api-browser": "^1.1.71",
- "husky": "^4.3.0",
- "jest": "26.2.2",
- "jest-preset-angular": "8.2.1",
- "lodash": "^4.17.20",
- "moment": "^2.29.1",
- "ng-packagr": "^10.1.2",
- "prettier": "2.0.4",
- "pretty-quick": "^3.1.0",
- "superagent": "^6.1.0",
- "superagent-promise": "^1.1.0",
- "ts-jest": "26.1.4",
- "ts-node": "~8.3.0",
- "tslint": "~6.1.0",
- "typescript": "~4.0.2"
- }
}
diff --git a/yarn.lock b/yarn.lock
index 773e0ae04..6ae583b27 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2343,6 +2343,24 @@
dependencies:
"@sinonjs/commons" "^1.7.0"
+"@swimlane/ngx-charts@^17.0.0":
+ version "17.0.0"
+ resolved "https://registry.yarnpkg.com/@swimlane/ngx-charts/-/ngx-charts-17.0.0.tgz#77b0b4277f7b75f9f26bbd7f464b5a6099fe944a"
+ integrity sha512-NsjDBWeizvWrDq6W/ZImBGL9/RERsW+2qLR30xJg/oNbIrSqtLty7Scmah3b9lgyCH00c0pK1DMYUak1QoMBJQ==
+ dependencies:
+ 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"
+
"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7":
version "7.1.9"
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.9.tgz#77e59d438522a6fb898fa43dc3455c6e72f3963d"
@@ -4636,6 +4654,120 @@ cypress@*, cypress@^5.6.0:
url "^0.11.0"
yauzl "^2.10.0"
+d3-array@^2.3.0, d3-array@^2.9.1:
+ version "2.11.0"
+ resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-2.11.0.tgz#5ed6a2869bc7d471aec8df9ff6ed9fef798facc4"
+ integrity sha512-26clcwmHQEdsLv34oNKq5Ia9tQ26Y/4HqS3dQzF42QBUqymZJ+9PORcN1G52bt37NsL2ABoX4lvyYZc+A9Y0zw==
+ 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.2.3"
+ resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-3.2.3.tgz#be380f57f1f61d4ff2e6cbb65a40593a51649cfd"
+ integrity sha512-8E37oWEmEzj57bHcnjPVOBS3n4jqakOeuv1EDdQSiSrYnMCBdMd3nc4HtKk7uia8DUHcY/CGuJ42xxgtEYrX0g==
+ dependencies:
+ d3-array "^2.3.0"
+ d3-format "1 - 2"
+ d3-interpolate "1.2.0 - 2"
+ d3-time "1 - 2"
+ 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.0.0"
+ resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-2.0.0.tgz#2331b62fa784a2a1daac47a7233cfd69301381fd"
+ integrity sha512-djpGlA779ua+rImicYyyjnOjeubyhql1Jyn1HK0bTyawuH76UQRWXd+pftr67H6Fa8hSwetkgb/0id3agKWykw==
+ 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":
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-2.0.0.tgz#ad7c127d17c67bd57a4c61f3eaecb81108b1e0ab"
+ integrity sha512-2mvhstTFcMvwStWd9Tj3e6CEqtOivtD8AUiHT8ido/xmzrI9ijrUUihZ6nHuf/vsScRBonagOdj0Vv+SEL5G3Q==
+
+"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"
+
d@1, d@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a"
@@ -6602,6 +6734,11 @@ internal-ip@^4.3.0:
default-gateway "^4.2.0"
ipaddr.js "^1.9.0"
+internmap@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/internmap/-/internmap-1.0.0.tgz#3c6bf0944b0eae457698000412108752bbfddb56"
+ integrity sha512-SdoDWwNOTE2n4JWUsLn4KXZGuZPjPF9yyOGc8bnfWnBQh7BD/l80rzSznKc/r4Y0aQ7z3RTk9X+tV4tHBpu+dA==
+
invariant@^2.2.2, invariant@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"