Skip to content
GitLab
Menu
Why GitLab
Pricing
Contact Sales
Explore
Why GitLab
Pricing
Contact Sales
Explore
Sign in
Get free trial
Commits on Source (2)
Analytics v2 finetune
· b3044bc3
Olivia Madrid
authored
Nov 04, 2019
and
Mark Harding
committed
Nov 04, 2019
b3044bc3
Merge branch 'analytics-v2-finetune-2089' into 'master'
· f03e30f6
Mark Harding
authored
Nov 04, 2019
Analytics v2 finetune Closes ux#3,
#1007
,
#2127
,
#2054
,
#2126
, and
#2089
See merge request
!614
f03e30f6
Hide whitespace changes
Inline
Side-by-side
src/app/common/common.module.ts
View file @
f03e30f6
...
...
@@ -113,6 +113,9 @@ import { MarketingComponent } from './components/marketing/marketing.component';
import
{
MarketingFooterComponent
}
from
'
./components/marketing/footer.component
'
;
import
{
ToggleComponent
}
from
'
./components/toggle/toggle.component
'
;
import
{
MarketingAsFeaturedInComponent
}
from
'
./components/marketing/as-featured-in.component
'
;
import
{
SidebarMenuComponent
}
from
'
./components/sidebar-menu/sidebar-menu.component
'
;
import
{
ChartV2Component
}
from
'
./components/chart-v2/chart-v2.component
'
;
import
{
MiniChartComponent
}
from
'
./components/mini-chart/mini-chart.component
'
;
@
NgModule
({
imports
:
[
...
...
@@ -215,6 +218,9 @@ import { MarketingAsFeaturedInComponent } from './components/marketing/as-featur
MarketingComponent
,
MarketingFooterComponent
,
MarketingAsFeaturedInComponent
,
SidebarMenuComponent
,
ChartV2Component
,
MiniChartComponent
,
],
exports
:
[
MINDS_PIPES
,
...
...
@@ -305,6 +311,7 @@ import { MarketingAsFeaturedInComponent } from './components/marketing/as-featur
ToggleComponent
,
MarketingComponent
,
MarketingAsFeaturedInComponent
,
SidebarMenuComponent
,
],
providers
:
[
SiteService
,
...
...
src/app/common/components/chart-v2/chart-v2.component.html
0 → 100644
View file @
f03e30f6
<!-- <div
#chartContainer
class="m-chartV2__chartContainer"
[ngClass]="{ isTouchDevice: isTouchDevice }"
>
<plotly-plot
*ngIf="init"
#graphDiv
id="graphDiv"
[data]="data"
[layout]="layout"
[config]="config"
[useResizeHandler]="true"
[style]="{ position: 'relative' }"
(hover)="onHover($event)"
(unhover)="onUnhover($event)"
(plotly_click)="onClick($event)"
>
</plotly-plot>
</div>
<div #hoverInfoDiv id="hoverInfoDiv" class="m-chartV2__hoverInfoDiv">
<i *ngIf="isTouchDevice" class="material-icons" (click)="onUnhover($event)"
>close</i
>
<div class="m-chartV2__hoverInfo__row">
{{ hoverInfo.date | utcDate | date: datePipe }}
</div>
<div
[ngSwitch]="selectedMetric?.unit"
class="m-chartV2__hoverInfo__row--primary"
>
<ng-template ngSwitchCase="number">
{{ hoverInfo.value | number }} {{ selectedMetric.label | lowercase }}
</ng-template>
<ng-template ngSwitchCase="usd">
{{ hoverInfo.value | currency }} USD
</ng-template>
<ng-template ngSwitchDefault>
{{ hoverInfo.value | number: '1.1-3' }} {{ selectedMetric?.unit }}
</ng-template>
</div>
<div class="m-chartV2__hoverInfo__row" *ngIf="isComparison">
vs
<ng-container
[ngSwitch]="selectedMetric?.unit"
class="m-chartV2__hoverInfo__row"
>
<ng-template ngSwitchCase="number">
{{ hoverInfo.comparisonValue | number }}
</ng-template>
<ng-template ngSwitchCase="usd">
{{ hoverInfo.comparisonValue | currency }}
</ng-template>
<ng-template ngSwitchDefault>
{{ hoverInfo.comparisonValue | number: '1.1-3' }}
</ng-template>
</ng-container>
on {{ hoverInfo.comparisonDate | utcDate | date: datePipe }}
</div>
</div> -->
src/app/
modules/analytics/v2/layouts/layout-table/table
.component.scss
→
src/app/
common/components/chart-v2/chart-v2
.component.scss
View file @
f03e30f6
File moved
src/app/
modules/analytics/v2/components/menu/menu
.component.spec.ts
→
src/app/
common/components/chart-v2/chart-v2
.component.spec.ts
View file @
f03e30f6
import
{
async
,
ComponentFixture
,
TestBed
}
from
'
@angular/core/testing
'
;
import
{
AnalyticsMenu
Component
}
from
'
./
menu
.component
'
;
import
{
ChartV2
Component
}
from
'
./
chart-v2
.component
'
;
describe
(
'
AnalyticsMenu
Component
'
,
()
=>
{
let
component
:
AnalyticsMenu
Component
;
let
fixture
:
ComponentFixture
<
AnalyticsMenu
Component
>
;
describe
(
'
ChartV2
Component
'
,
()
=>
{
let
component
:
ChartV2
Component
;
let
fixture
:
ComponentFixture
<
ChartV2
Component
>
;
beforeEach
(
async
(()
=>
{
TestBed
.
configureTestingModule
({
declarations
:
[
AnalyticsMenu
Component
],
declarations
:
[
ChartV2
Component
],
}).
compileComponents
();
}));
beforeEach
(()
=>
{
fixture
=
TestBed
.
createComponent
(
AnalyticsMenu
Component
);
fixture
=
TestBed
.
createComponent
(
ChartV2
Component
);
component
=
fixture
.
componentInstance
;
fixture
.
detectChanges
();
});
x
it
(
'
should create
'
,
()
=>
{
it
(
'
should create
'
,
()
=>
{
expect
(
component
).
toBeTruthy
();
});
});
src/app/common/components/chart-v2/chart-v2.component.ts
0 → 100644
View file @
f03e30f6
import
{
Component
,
OnInit
,
OnDestroy
,
HostListener
,
ChangeDetectionStrategy
,
ChangeDetectorRef
,
Input
,
ViewChild
,
ElementRef
,
}
from
'
@angular/core
'
;
// import { Subscription } from 'rxjs';
// import { map } from 'rxjs/operators';
// import {
// AnalyticsDashboardService,
// Timespan as TimespanBase,
// Buckets,
// } from '../../dashboard.service';
// import * as Plotly from 'plotly.js';
// import chartPalette from '../../chart-palette.default';
// import { ThemeService } from '../../../../../common/services/theme.service';
// import isMobileOrTablet from '../../../../../helpers/is-mobile-or-tablet';
@
Component
({
selector
:
'
m-chartV2
'
,
templateUrl
:
'
./chart-v2.component.html
'
,
})
export
class
ChartV2Component
implements
OnInit
{
constructor
()
{}
ngOnInit
()
{}
}
// -------------------------------------------------------------------------
// interface TimespanExtended extends TimespanBase {
// xTickFormat?: string;
// datePipe?: string;
// }
// export { TimespanExtended as Timespan };
// @Component({
// selector: 'm-analytics__chart',
// templateUrl: 'chart.component.html',
// changeDetection: ChangeDetectionStrategy.OnPush,
// })
// export class AnalyticsChartComponent implements OnDestroy, OnInit {
// @ViewChild('graphDiv', { static: true }) graphDiv;
// @ViewChild('hoverInfoDiv', { static: true }) hoverInfoDivEl: ElementRef;
// @ViewChild('chartContainer', { static: true }) chartContainer: ElementRef;
// isTouchDevice: boolean;
// init: boolean = false;
// hoverInfoDiv: any;
// hoverInfo: any = {};
// metricSubscription: Subscription;
// selectedMetric$ = this.analyticsService.metrics$.pipe(
// map(metrics => {
// return metrics.find(metric => metric.visualisation !== null);
// })
// );
// selectedMetric;
// timespansSubscription: Subscription;
// selectedTimespan;
// themeSubscription: Subscription;
// isDark: boolean = false;
// segments: Buckets[];
// isComparison: boolean = false;
// data = [];
// layout;
// config = {
// displayModeBar: false,
// // responsive: true,
// };
// pointsPerSegment = 1;
// hoverPoint: number;
// lineRange: Array<any>;
// newLineRange = true;
// markerFills;
// shapes = [];
// timespanFormats = [
// { interval: 'day', xTickFormat: '%m/%d', datePipe: 'EEE MMM d, y' },
// { interval: 'month', xTickFormat: '%m/%Y', datePipe: 'MMM y' },
// ];
// datePipe: string = this.timespanFormats[0].datePipe;
// xTickFormat: string = this.timespanFormats[0].xTickFormat;
// // yTickPrefix: string = '';
// yTickFormat: string = '';
// // ***********************************************************
// constructor(
// private analyticsService: AnalyticsDashboardService,
// private themeService: ThemeService,
// protected cd: ChangeDetectorRef
// ) {}
// ngOnInit() {
// this.isTouchDevice = isMobileOrTablet();
// this.hoverInfoDiv = this.hoverInfoDivEl.nativeElement;
// this.timespansSubscription = this.analyticsService.timespans$.subscribe(
// timespans => {
// this.selectedTimespan = timespans.find(
// timespan => timespan.selected === true
// );
// const timespanFormat =
// this.timespanFormats.find(
// t => t.interval === this.selectedTimespan.interval
// ) || this.timespanFormats[0];
// this.xTickFormat = timespanFormat.xTickFormat;
// this.datePipe = timespanFormat.datePipe;
// if (this.init) {
// this.layout.xaxis.tickformat = this.xTickFormat;
// }
// this.detectChanges();
// }
// );
// this.themeSubscription = this.themeService.isDark$.subscribe(isDark => {
// this.isDark = isDark;
// if (this.init) {
// this.getData();
// this.getLayout();
// }
// this.detectChanges();
// });
// this.metricSubscription = this.selectedMetric$.subscribe(metric => {
// this.init = false;
// this.selectedMetric = metric;
// if (metric.unit && metric.unit === 'usd') {
// this.yTickFormat = '$.2f';
// }
// try {
// this.initPlot();
// } catch (err) {
// console.log(err);
// }
// this.detectChanges();
// });
// }
// swapSegmentColors() {
// const tempPaletteItem = chartPalette.segmentColorIds[0];
// chartPalette.segmentColorIds[0] = chartPalette.segmentColorIds[1];
// chartPalette.segmentColorIds[1] = tempPaletteItem;
// }
// initPlot() {
// this.segments = this.selectedMetric.visualisation.segments;
// if (this.segments.length === 2) {
// this.isComparison = true;
// // Reverse the segments so comparison line is layered behind current line
// this.segments.reverse();
// console.log(this.segments);
// // Current line should be blue
// this.swapSegmentColors();
// }
// this.pointsPerSegment = this.segments[0].buckets.length;
// for (let i = 0; i < this.pointsPerSegment; i++) {
// this.shapes[i] = {
// type: 'line',
// layer: 'below',
// x0: this.segments[0].buckets[i].date,
// y0: 0,
// x1: this.segments[0].buckets[i].date,
// y1: 0,
// line: {
// color: 'rgba(0, 0, 0, 0)',
// width: 2,
// },
// };
// }
// this.getData();
// this.getLayout();
// this.init = true;
// this.detectChanges();
// }
// getData() {
// this.markerFills = [];
// this.segments.forEach((segment, index) => {
// const segmentMarkerFills = [];
// for (let i = 0; i < this.pointsPerSegment; i++) {
// segmentMarkerFills[i] = this.getColor('m-white');
// }
// this.markerFills.push(segmentMarkerFills);
// });
// const globalSegmentSettings = {
// type: 'scatter',
// mode: 'lines+markers',
// line: {
// width: 1,
// dash: 'solid',
// },
// marker: {
// size: 7,
// },
// showlegend: false,
// hoverinfo: 'text',
// x: this.unpack(this.segments[0].buckets, 'date'),
// };
// this.segments.forEach((s, i) => {
// const segment = {
// ...globalSegmentSettings,
// line: {
// ...globalSegmentSettings.line,
// color: this.getColor(chartPalette.segmentColorIds[i]),
// },
// marker: {
// ...globalSegmentSettings.marker,
// color: this.markerFills[i],
// line: {
// color: this.getColor(chartPalette.segmentColorIds[i]),
// width: 1,
// },
// },
// y: this.unpack(this.segments[i].buckets, 'value'),
// };
// this.data[i] = segment;
// });
// if (this.isComparison) {
// this.data[0].line.dash = 'dot';
// }
// }
// getLayout() {
// this.layout = {
// width: 0,
// height: 0,
// autoexpand: 'true',
// autosize: 'true',
// hovermode: 'x',
// paper_bgcolor: this.getColor('m-white'),
// plot_bgcolor: this.getColor('m-white'),
// font: {
// family: 'Roboto',
// },
// xaxis: {
// tickformat: this.xTickFormat,
// tickmode: 'array',
// tickson: 'labels',
// tickcolor: this.getColor('m-grey-130'),
// tickangle: -45,
// tickfont: {
// color: this.getColor('m-grey-130'),
// },
// showgrid: false,
// showline: true,
// linecolor: this.getColor('m-grey-300'),
// linewidth: 1,
// zeroline: false,
// fixedrange: true,
// // automargin: true,
// // rangemode: 'nonnegative',
// },
// yaxis: {
// ticks: '',
// tickformat: this.yTickFormat,
// tickmode: 'array',
// tickson: 'labels',
// showgrid: true,
// gridcolor: this.getColor('m-grey-70'),
// zeroline: false,
// visible: true,
// side: 'right',
// tickfont: {
// color: this.getColor('m-grey-130'),
// },
// fixedrange: true,
// // automargin: true,
// // rangemode: 'nonnegative',
// },
// margin: {
// t: 16,
// b: 80,
// l: 0,
// r: 80,
// pad: 16,
// },
// shapes: this.shapes,
// };
// }
// onHover($event) {
// console.log($event);
// this.hoverPoint = $event.points[0].pointIndex;
// this.addMarkerFill();
// this.showShape($event);
// this.positionHoverInfo($event);
// this.populateHoverInfo();
// this.hoverInfoDiv.style.opacity = 1;
// this.detectChanges();
// }
// onUnhover($event) {
// this.emptyMarkerFill();
// this.hideShape();
// this.hoverInfoDiv.style.opacity = 0;
// this.detectChanges();
// }
// onClick($event) {
// if (!this.isTouchDevice) {
// return;
// }
// // TODO: use this for non-hover devices
// }
// addMarkerFill() {
// this.data.forEach((segment, i) => {
// this.markerFills[i][this.hoverPoint] = this.getColor(
// chartPalette.segmentColorIds[i]
// );
// });
// }
// emptyMarkerFill() {
// this.data.forEach((segment, i) => {
// this.markerFills[i][this.hoverPoint] = this.getColor('m-white');
// segment.marker.color = this.markerFills[i];
// });
// }
// showShape($event) {
// const hoverLine = this.shapes[this.hoverPoint];
// // Without this, entire graph resizes on every hover
// if (this.newLineRange) {
// this.newLineRange = false;
// this.lineRange = $event.yaxes[0].range;
// }
// hoverLine.y0 = this.lineRange[0];
// hoverLine.y1 = this.lineRange[1] * 0.99;
// hoverLine.line.color = this.getColor('m-grey-70');
// this.layout.shapes = this.shapes;
// }
// hideShape() {
// this.shapes[this.hoverPoint].line.color = 'rgba(0, 0, 0, 0)';
// this.layout.shapes = this.shapes;
// }
// populateHoverInfo() {
// const pt = this.isComparison ? 1 : 0;
// // TODO: format value strings here and remove ngSwitch from template?
// this.hoverInfo['date'] = this.segments[pt].buckets[this.hoverPoint].date;
// this.hoverInfo['value'] =
// this.selectedMetric.unit !== 'usd'
// ? this.segments[pt].buckets[this.hoverPoint].value
// : this.segments[pt].buckets[this.hoverPoint].value / 100;
// if (this.isComparison && this.segments[1]) {
// this.hoverInfo['comparisonValue'] =
// this.selectedMetric.unit !== 'usd'
// ? this.segments[0].buckets[this.hoverPoint].value
// : this.segments[0].buckets[this.hoverPoint].value / 100;
// this.hoverInfo['comparisonDate'] = this.segments[0].buckets[
// this.hoverPoint
// ].date;
// }
// }
// positionHoverInfo($event) {
// const pad = 16,
// pt = this.isComparison ? 1 : 0,
// xAxis = $event.points[pt].xaxis,
// yAxis = $event.points[pt].yaxis,
// pointXDist = xAxis.d2p($event.points[pt].x) + xAxis._offset,
// pointYDist = yAxis.d2p($event.points[pt].y) + yAxis._offset,
// plotRect = document
// .querySelector('.js-plotly-plot')
// .getBoundingClientRect(),
// hoverInfoRect = this.hoverInfoDiv.getBoundingClientRect();
// if (pointYDist < plotRect.height / 2) {
// // If point is in top half of plot, hoverinfo should go beneath it
// this.hoverInfoDiv.style.top = pointYDist + pad + 'px';
// } else {
// this.hoverInfoDiv.style.top =
// pointYDist - pad - hoverInfoRect.height + 'px';
// }
// if (pointXDist < plotRect.width / 2) {
// // If point is in left half of plot, hoverinfo should go on the right
// this.hoverInfoDiv.style.left = pointXDist + pad + 'px';
// } else {
// this.hoverInfoDiv.style.left =
// pointXDist - pad - hoverInfoRect.width + 'px';
// }
// }
// @HostListener('window:resize')
// applyDimensions() {
// if (this.init) {
// console.log(
// 'chartcontainer: W ' +
// this.chartContainer.nativeElement.clientWidth +
// ', H ' +
// this.chartContainer.nativeElement.clientHeight
// );
// this.layout.width = this.chartContainer.nativeElement.clientWidth - 56; //- 32;
// this.layout.height = this.chartContainer.nativeElement.clientHeight;
// // this.layout = {
// // ...this.layout,
// // width: this.chartContainer.nativeElement.clientWidth, // - 32,
// // height: this.chartContainer.nativeElement.clientHeight, // - 32,
// // };
// this.newLineRange = true;
// this.detectChanges();
// }
// }
// // * UTILITY -----------------------------------
// unpack(rows, key) {
// return rows.map(row => {
// if (key === 'date') {
// return row[key].slice(0, 10);
// } else if (this.selectedMetric.unit === 'usd') {
// return row[key] / 100;
// } else {
// return row[key];
// }
// });
// }
// getColor(colorId) {
// const palette = chartPalette.themeMaps;
// let colorCode = '#607d8b';
// if (palette.find(color => color.id === colorId)) {
// colorCode = palette.find(color => color.id === colorId).themeMap[
// +this.isDark
// ];
// }
// return colorCode;
// }
// detectChanges() {
// this.cd.markForCheck();
// this.cd.detectChanges();
// }
// }
/////////////////////////////////////////////////////////////////////
// @Component({
// selector: 'm-graph',
// template: `
// <plotly-plot
// [data]="data"
// [layout]="_layout"
// [config]="_config"
// ></plotly-plot>
// `,
// })
// export class Graph {
// @Input() data;
// @Input() layout;
// @Input() config;
// get _config() {
// return {
// ...this.config,
// ...{
// displayModeBar: false,
// },
// };
// }
// get _layout() {
// return {
// ...this.layout,
// /*...{
// margin: {
// t: 0,
// b: 0,
// },
// },*/
// };
// }
src/app/common/components/mini-chart/mini-chart.component.html
0 → 100644
View file @
f03e30f6
<p>
mini-chart works!
</p>
src/app/common/components/mini-chart/mini-chart.component.scss
0 → 100644
View file @
f03e30f6
src/app/
modules/analytics/v2/layouts/layout-table/layout-table
.component.spec.ts
→
src/app/
common/components/mini-chart/mini-chart
.component.spec.ts
View file @
f03e30f6
import
{
async
,
ComponentFixture
,
TestBed
}
from
'
@angular/core/testing
'
;
import
{
AnalyticsLayoutTable
Component
}
from
'
./
layout-table
.component
'
;
import
{
MiniChart
Component
}
from
'
./
mini-chart
.component
'
;
describe
(
'
AnalyticsLayoutTable
Component
'
,
()
=>
{
let
component
:
AnalyticsLayoutTable
Component
;
let
fixture
:
ComponentFixture
<
AnalyticsLayoutTable
Component
>
;
describe
(
'
MiniChart
Component
'
,
()
=>
{
let
component
:
MiniChart
Component
;
let
fixture
:
ComponentFixture
<
MiniChart
Component
>
;
beforeEach
(
async
(()
=>
{
TestBed
.
configureTestingModule
({
declarations
:
[
AnalyticsLayoutTable
Component
],
declarations
:
[
MiniChart
Component
],
}).
compileComponents
();
}));
beforeEach
(()
=>
{
fixture
=
TestBed
.
createComponent
(
AnalyticsLayoutTable
Component
);
fixture
=
TestBed
.
createComponent
(
MiniChart
Component
);
component
=
fixture
.
componentInstance
;
fixture
.
detectChanges
();
});
x
it
(
'
should create
'
,
()
=>
{
it
(
'
should create
'
,
()
=>
{
expect
(
component
).
toBeTruthy
();
});
});
src/app/
modules/analytics/v2/layouts/layout-table/layout-table
.component.ts
→
src/app/
common/components/mini-chart/mini-chart
.component.ts
View file @
f03e30f6
import
{
Component
,
OnInit
}
from
'
@angular/core
'
;
@
Component
({
selector
:
'
m-
analytics__layout--table
'
,
templateUrl
:
'
./
layout-table
.component.html
'
,
selector
:
'
m-
miniChart
'
,
templateUrl
:
'
./
mini-chart
.component.html
'
,
})
export
class
AnalyticsLayoutTable
Component
implements
OnInit
{
export
class
MiniChart
Component
implements
OnInit
{
constructor
()
{}
ngOnInit
()
{}
...
...
src/app/common/components/sidebar-menu/categories.default.ts
0 → 100644
View file @
f03e30f6
const
sidebarMenuCategories
=
[
{
category
:
{
id
:
'
analytics
'
,
label
:
'
Analytics
'
,
path
:
'
/analytics/dashboard/
'
,
permissions
:
[
'
admin
'
,
'
user
'
],
},
subcategories
:
[
// {
// id: 'summary',
// label: 'Summary',
// permissions: ['admin', 'user'],
// },
{
id
:
'
traffic
'
,
label
:
'
Traffic
'
,
permissions
:
[
'
admin
'
,
'
user
'
],
},
{
id
:
'
earnings
'
,
label
:
'
Earnings
'
,
permissions
:
[
'
admin
'
,
'
user
'
],
},
// {
// id: 'engagement',
// label: 'Engagement',
// permissions: ['admin', 'user'],
// },
{
id
:
'
trending
'
,
label
:
'
Trending
'
,
permissions
:
[
'
admin
'
,
'
user
'
],
},
// {
// id: 'referrers',
// label: 'Referrers',
// permissions: ['admin', 'user'],
// },
// {
// id: 'plus',
// label: 'Plus',
// permissions: ['admin'],
// },
// {
// id: 'pro',
// label: 'Pro',
// permissions: ['admin'],
// {
// id: 'boost',
// label: 'Boost',
// permissions: ['admin'],
// },
// {
// id: 'nodes',
// label: 'Nodes',
// permissions: ['admin'],
// },
],
},
// {
// category: {
// id: 'test1',
// label: 'Test1',
// permissions: ['admin', 'user'],
// path: '/somepath/bork',
// },
// subcategories: [
// {
// id: 'nodes',
// label: 'Nodes',
// permissions: ['admin'],
// },
// {
// id: 'nodes2',
// label: 'Nodes2',
// permissions: ['admin'],
// },
// ],
// },
// {
// category: {
// id: 'test2',
// label: 'Test2 no subcats',
// path: '/anotherpath/test2',
// },
// },
];
export
default
sidebarMenuCategories
;
src/app/common/components/sidebar-menu/sidebar-menu.component.html
0 → 100644
View file @
f03e30f6
<section
class=
"m-sidebarMenu"
>
<div
class=
"m-sidebarMenu__topbar"
>
<i
class=
"material-icons"
(click)=
"mobileMenuExpanded = true"
>
menu
</i>
<div
class=
"m-sidebarMenu__topbarCatLabel"
*ngIf=
"activeCat"
>
{{ activeCat.category.label }}
</div>
</div>
<div
class=
"m-sidebarMenu__overlay"
[ngClass]=
"{ mobileMenuExpanded: mobileMenuExpanded }"
(click)=
"mobileMenuExpanded = false"
></div>
<div
class=
"m-sidebarMenu__sidebar"
[ngClass]=
"{ mobileMenuExpanded: mobileMenuExpanded }"
>
<a
class=
"m-sidebarMenu__userWrapper"
[routerLink]=
"['/', user.username]"
>
<img
class=
"m-sidebarMenu__userAvatar"
[src]=
"minds.cdn_url + 'icon/' + user.guid + '/small/' + user.icontime"
/>
<div
class=
"m-sidebarMenu__userDetails"
>
<div
class=
"m-sidebarMenu__userDetails__name"
>
{{ user.name }}
</div>
<div
class=
"m-sidebarMenu__userDetails__username"
>
@{{ user.username }}
</div>
<!-- TODO: get subscriberCount and remove username -->
<!-- <div class="m-sidebarMenu__userDetails__subscribers">
{{ user.subscribers_count | abbr }} subscribers
</div> -->
</div>
</a>
<ng-container
*ngFor=
"let cat of cats"
>
<div
class=
"m-sidebarMenu__catContainer"
*ngIf=
"cat.category.permissionGranted"
[ngClass]=
"{ expanded: cat.category.expanded }"
>
<div
class=
"m-sidebarMenu__catLabel"
>
<h3>
{{ cat.category.label }}
</h3>
<i
class=
"material-icons"
*ngIf=
"cat.category.expanded && cat.subcategories"
(click)=
"cat.category.expanded = false"
>
keyboard_arrow_up
</i
>
<i
class=
"material-icons"
*ngIf=
"!cat.category.expanded && cat.subcategories"
(click)=
"cat.category.expanded = true"
>
keyboard_arrow_down
</i
>
</div>
<div
class=
"m-sidebarMenu__subcatContainer"
*ngIf=
"cat.subcategories"
>
<div
class=
"m-sidebarMenu__subcat"
*ngFor=
"let subcat of cat.subcategories"
>
<a
*ngIf=
"subcat.permissionGranted"
class=
"m-sidebarMenu__subcatLabel"
(click)=
"mobileMenuExpanded = false"
[routerLink]=
"'../' + subcat.id"
routerLinkActive=
"selected"
>
{{ subcat.label }}
</a
>
</div>
</div>
</div>
</ng-container>
</div>
</section>
src/app/
modules/analytics/v2/components/menu/
menu.component.scss
→
src/app/
common/components/sidebar-menu/sidebar-
menu.component.scss
View file @
f03e30f6
// .m-sidebarMarkers__container,
// m-v2-topbar {
// display: none;
// }
m-sidebarMenu
{
display
:
block
;
// min-width: 180px;
// padding: 16px 16px 16px 80px;
// flex: 1 1 0px;
.m-sidebarMenu
{
padding
:
16px
16px
16px
80px
;
}
i
{
display
:
none
;
cursor
:
pointer
;
}
.m-sidebarMenu__topbar
,
.m-sidebarMenu__userWrapper
{
display
:
none
;
}
.m-sidebarMenu__catContainer
{
.m-sidebarMenu__subcatContainer
{
display
:
block
;
cursor
:
pointer
;
}
}
// .m-sidebarMenu__sidebar {
// position: relative;
// position: -webkit-sticky;
// position: sticky;
// top: 0;
// }
.page.isMobile
m-analytics__menu
{
margin-right
:
-32px
;
flex
:
0
1
0px
;
}
.m-sidebarMenu__catLabel
{
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
i
{
display
:
none
;
}
}
m-analytics__menu
{
display
:
block
;
max-width
:
160px
;
// ----------------------------------------
// MOBILE
.m-sidebarMenu__subcatContainer
{
cursor
:
pointer
;
display
:
none
;
.m-sidebarMenu__subcat
{
a
{
display
:
block
;
padding
:
8px
0
;
text-decoration
:
none
;
font-weight
:
400
;
@include
m-theme
()
{
color
:
themed
(
$m-grey-300
);
}
}
a
.selected
,
&
:hover
a
{
@include
m-theme
()
{
color
:
themed
(
$m-blue
);
}
}
}
}
}
// --------------------------------------------------
// TABLET & MOBILE
// --------------------------------------------------
@media
screen
and
(
max-width
:
$min-tablet
)
{
m-sidebarMenu
{
margin-right
:
-32px
;
flex
:
0
1
0px
;
padding
:
0
;
.m-sidebarMenu
{
padding
:
0
8px
;
}
.
isMobile
{
.topbar
{
.
m-sidebarMenu__topbar
{
display
:
block
;
z-index
:
99999
;
position
:
fixed
;
top
:
0
;
...
...
@@ -24,7 +80,7 @@ m-analytics__menu {
padding
:
16px
;
text-align
:
center
;
@include
m-theme
()
{
background-color
:
themed
(
$m-grey-
10
0
);
background-color
:
themed
(
$m-grey-
5
0
);
color
:
themed
(
$m-grey-800
);
}
...
...
@@ -35,34 +91,32 @@ m-analytics__menu {
left
:
16px
;
transform
:
translateY
(
-50%
);
@include
m-theme
()
{
background-color
:
themed
(
$m-grey-100
);
color
:
themed
(
$m-grey-700
);
color
:
themed
(
$m-grey-300
);
}
}
.
pageTitle
{
.
m-sidebarMenu__topbarCatLabel
{
font-size
:
20px
;
margin
:
0
;
margin
:
0
0
0
-24px
;
min-height
:
20px
;
}
}
.overlay
{
.
m-sidebarMenu__
overlay
{
position
:
fixed
;
top
:
0
;
bottom
:
0
;
left
:
0
;
right
:
0
;
z-index
:
-1
;
// display: none;
background-color
:
transparent
;
transition
:
background-color
0
.5s
cubic-bezier
(
0
.075
,
0
.82
,
0
.165
,
1
);
&
.expanded
{
// display: block;
&
.mobileMenuExpanded
{
z-index
:
999998
;
@include
m-theme
()
{
background-color
:
rgba
(
themed
(
$m-grey-700
)
,
0
.2
);
}
}
}
.sidebar
{
.
m-sidebarMenu__
sidebar
{
z-index
:
999999
;
position
:
fixed
;
top
:
0
;
...
...
@@ -76,50 +130,66 @@ m-analytics__menu {
@include
m-theme
()
{
background-color
:
themed
(
$m-white
);
}
&
.
e
xpanded
{
&
.
mobileMenuE
xpanded
{
left
:
0
;
}
.sidebarTitle
{
.m-sidebarMenu__catContainer
{
.m-sidebarMenu__subcatContainer
{
display
:
none
;
}
&
.expanded
{
.m-sidebarMenu__subcatContainer
{
display
:
block
;
.m-sidebarMenu__subcat
{
a
{
padding
:
6px
0
;
}
}
}
}
}
.m-sidebarMenu__catLabel
{
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
h3
{
font-size
:
20px
;
margin
:
0
;
margin
:
16px
0
;
}
i
{
font-size
:
20px
;
display
:
inline-block
;
font-size
:
26px
;
@include
m-theme
()
{
color
:
themed
(
$m-grey-200
);
}
}
}
.
profile
{
.
m-sidebarMenu__userWrapper
{
display
:
flex
;
text-decoration
:
none
;
margin
:
24px
0
;
@include
m-theme
()
{
color
:
themed
(
$m-grey-800
);
}
.
a
vatar
{
.
m-sidebarMenu__userA
vatar
{
border-radius
:
50%
;
margin-right
:
16px
;
}
.
d
etails
{
.
m-sidebarMenu__userD
etails
{
&
>
{
padding
:
8px
0
;
}
.name
{
.
m-sidebarMenu__userDetails__
name
{
font-weight
:
bold
;
}
.username
{
.
m-sidebarMenu__userDetails__
username
{
@include
m-theme
()
{
color
:
themed
(
$m-grey-200
);
}
}
.subscribers
{
.
m-sidebarMenu__userDetails__
subscribers
{
font-size
:
11px
;
@include
m-theme
()
{
color
:
themed
(
$m-grey-200
);
...
...
@@ -129,32 +199,4 @@ m-analytics__menu {
}
}
}
// ----------------------------------------
padding
:
16px
16px
16px
16px
;
flex
:
1
1
0px
;
i
{
display
:
none
;
}
.catContainer
{
cursor
:
pointer
;
.cat
{
a
{
display
:
block
;
padding
:
6px
0
;
text-decoration
:
none
;
font-weight
:
400
;
@include
m-theme
()
{
color
:
themed
(
$m-grey-200
);
}
}
a
.selected
,
&
:hover
a
{
@include
m-theme
()
{
color
:
themed
(
$m-blue
);
}
}
}
}
}
src/app/common/components/sidebar-menu/sidebar-menu.component.spec.ts
0 → 100644
View file @
f03e30f6
import
{
async
,
ComponentFixture
,
TestBed
}
from
'
@angular/core/testing
'
;
import
{
RouterTestingModule
}
from
'
@angular/router/testing
'
;
import
{
Session
}
from
'
../../../services/session
'
;
import
{
sessionMock
}
from
'
../../../../tests/session-mock.spec
'
;
import
{
SidebarMenuComponent
}
from
'
./sidebar-menu.component
'
;
describe
(
'
SidebarMenuComponent
'
,
()
=>
{
let
component
:
SidebarMenuComponent
;
let
fixture
:
ComponentFixture
<
SidebarMenuComponent
>
;
beforeEach
(
async
(()
=>
{
TestBed
.
configureTestingModule
({
declarations
:
[
SidebarMenuComponent
],
imports
:
[
RouterTestingModule
],
providers
:
[{
provide
:
Session
,
useValue
:
sessionMock
}],
}).
compileComponents
();
}));
beforeEach
(()
=>
{
fixture
=
TestBed
.
createComponent
(
SidebarMenuComponent
);
component
=
fixture
.
componentInstance
;
// component.user = sessionMock.user;
fixture
.
detectChanges
();
});
it
(
'
should create
'
,
()
=>
{
expect
(
component
).
toBeTruthy
();
});
});
src/app/common/components/sidebar-menu/sidebar-menu.component.ts
0 → 100644
View file @
f03e30f6
import
{
Component
,
OnInit
}
from
'
@angular/core
'
;
import
{
ActivatedRoute
}
from
'
@angular/router
'
;
import
{
Session
}
from
'
../../../services/session
'
;
import
menuCategories
from
'
./categories.default
'
;
interface
MenuCategory
{
category
:
MenuLink
;
subcategories
?:
MenuLink
[];
expanded
?:
boolean
;
}
export
{
MenuCategory
};
interface
MenuLink
{
id
:
string
;
label
:
string
;
permissions
?:
string
[];
permissionGranted
?:
boolean
;
path
?:
string
;
}
export
{
MenuLink
};
@
Component
({
selector
:
'
m-sidebarMenu
'
,
templateUrl
:
'
./sidebar-menu.component.html
'
,
})
export
class
SidebarMenuComponent
implements
OnInit
{
cats
:
MenuCategory
[]
=
menuCategories
;
mobileMenuExpanded
=
false
;
activeCat
;
minds
:
Minds
;
user
;
userRoles
:
string
[]
=
[
'
user
'
];
constructor
(
public
route
:
ActivatedRoute
,
public
session
:
Session
)
{}
ngOnInit
()
{
this
.
minds
=
window
.
Minds
;
this
.
user
=
this
.
session
.
getLoggedInUser
();
this
.
getUserRoles
();
this
.
grantPermissionsAndFindActiveCat
();
}
getUserRoles
()
{
if
(
this
.
session
.
isAdmin
())
{
this
.
userRoles
.
push
(
'
admin
'
);
}
// TODO: define & handle other userRole options, e.g. pro, loggedIn
}
grantPermissionsAndFindActiveCat
()
{
this
.
cats
.
forEach
(
catObj
=>
{
catObj
.
category
[
'
permissionGranted
'
]
=
catObj
.
category
.
permissions
?
this
.
checkForRoleMatch
(
catObj
.
category
.
permissions
)
:
true
;
if
(
catObj
.
subcategories
)
{
catObj
.
subcategories
.
forEach
(
subCat
=>
{
subCat
[
'
permissionGranted
'
]
=
subCat
.
permissions
?
this
.
checkForRoleMatch
(
subCat
.
permissions
)
:
true
;
});
}
if
(
location
.
pathname
.
indexOf
(
catObj
.
category
.
path
)
!==
-
1
)
{
catObj
.
category
[
'
expanded
'
]
=
true
;
this
.
activeCat
=
catObj
;
}
else
{
catObj
.
category
[
'
expanded
'
]
=
false
;
}
});
}
checkForRoleMatch
(
permissionsArray
)
{
return
permissionsArray
.
some
(
role
=>
this
.
userRoles
.
includes
(
role
));
}
}
src/app/modules/analytics/analytics.module.ts
View file @
f03e30f6
...
...
@@ -51,7 +51,6 @@ import { PageviewsCardComponent } from './components/cards/pageviews/pageviews.c
import
{
PageviewsChartComponent
}
from
'
./components/charts/pageviews/pageviews.component
'
;
import
{
AnalyticsDashboardComponent
}
from
'
./v2/dashboard.component
'
;
import
{
AnalyticsLayoutChartComponent
}
from
'
./v2/layouts/layout-chart/layout-chart.component
'
;
import
{
AnalyticsLayoutTableComponent
}
from
'
./v2/layouts/layout-table/layout-table.component
'
;
import
{
AnalyticsLayoutSummaryComponent
}
from
'
./v2/layouts/layout-summary/layout-summary.component
'
;
import
{
AnalyticsMetricsComponent
}
from
'
./v2/components/metrics/metrics.component
'
;
import
{
AnalyticsFiltersComponent
}
from
'
./v2/components/filters/filters.component
'
;
...
...
@@ -63,7 +62,6 @@ import { SearchModule } from '../search/search.module';
import
{
AnalyticsSearchComponent
}
from
'
./v2/components/search/search.component
'
;
import
{
FormsModule
}
from
'
@angular/forms
'
;
import
{
AnalyticsSearchSuggestionsComponent
}
from
'
./v2/components/search-suggestions/search-suggestions.component
'
;
import
{
AnalyticsMenuComponent
}
from
'
./v2/components/menu/menu.component
'
;
PlotlyModule
.
plotlyjs
=
PlotlyJS
;
...
...
@@ -163,7 +161,6 @@ const routes: Routes = [
Graph
,
AnalyticsDashboardComponent
,
AnalyticsLayoutChartComponent
,
AnalyticsLayoutTableComponent
,
AnalyticsLayoutSummaryComponent
,
AnalyticsMetricsComponent
,
AnalyticsFiltersComponent
,
...
...
@@ -172,7 +169,6 @@ const routes: Routes = [
AnalyticsTableComponent
,
AnalyticsSearchComponent
,
AnalyticsSearchSuggestionsComponent
,
AnalyticsMenuComponent
,
],
providers
:
[
AnalyticsDashboardService
],
})
...
...
src/app/modules/analytics/v2/categories.default.ts
deleted
100644 → 0
View file @
409ccf04
const
categories
:
Array
<
any
>
=
[
// {
// id: 'summary',
// label: 'Summary',
// permissions: ['admin', 'user'],
// metrics: [],
// },
{
id
:
'
traffic
'
,
label
:
'
Traffic
'
,
permissions
:
[
'
admin
'
,
'
user
'
],
metrics
:
[
'
active_users
'
,
'
signups
'
,
'
unique_visitors
'
,
'
pageviews
'
,
'
impressions
'
,
'
retention
'
,
],
},
{
id
:
'
earnings
'
,
label
:
'
Earnings
'
,
permissions
:
[
'
admin
'
,
'
user
'
],
metrics
:
[
'
total
'
,
'
pageviews
'
,
'
active_referrals
'
,
'
customers
'
],
},
// {
// id: 'engagement',
// label: 'Engagement',
// permissions: ['admin', 'user'],
// metrics: ['posts', 'votes', 'comments', 'reminds', 'subscribers', 'tags'],
// },
{
id
:
'
trending
'
,
label
:
'
Trending
'
,
permissions
:
[
'
admin
'
,
'
user
'
],
metrics
:
[
'
top_content
'
,
'
top_channels
'
],
},
// {
// id: 'referrers',
// label: 'Referrers',
// permissions: ['admin', 'user'],
// metrics: ['top_referrers'],
// },
// {
// id: 'plus',
// label: 'Plus',
// permissions: ['admin'],
// metrics: ['transactions', 'users', 'revenue_usd', 'revenue_tokens'],
// },
// {
// id: 'pro',
// label: 'Pro',
// permissions: ['admin'],
// metrics: ['transactions', 'users', 'revenue_usd', 'revenue_tokens'],
// },
// {
// id: 'boost',
// label: 'Boost',
// permissions: ['admin'],
// metrics: ['transactions', 'users', 'revenue_tokens'],
// },
// {
// id: 'nodes',
// label: 'Nodes',
// permissions: ['admin'],
// metrics: ['transactions', 'users', 'revenue_usd', 'revenue_tokens'],
// },
];
export
default
categories
;
src/app/modules/analytics/v2/chart-palette.default.ts
View file @
f03e30f6
const
chartPalette
:
Array
<
any
>
=
[
{
id
:
'
m-white
'
,
themeMap
:
[
'
#fff
'
,
'
#161616
'
],
},
{
id
:
'
m-transparent
'
,
themeMap
:
[
'
rgba(0,0,0,0)
'
,
'
rgba(0,0,0,0)
'
],
},
{
id
:
'
m-grey-50
'
,
themeMap
:
[
'
rgba(232,232,232,1)
'
,
'
rgba(53,53,53,1)
'
],
},
{
id
:
'
m-grey-70
'
,
themeMap
:
[
'
#eee
'
,
'
#333
'
],
},
{
id
:
'
m-grey-130
'
,
themeMap
:
[
'
#ccc
'
,
'
#555
'
],
},
{
id
:
'
m-grey-160
'
,
themeMap
:
[
'
#bbb
'
,
'
#555
'
],
},
{
id
:
'
m-grey-300
'
,
themeMap
:
[
'
#999
'
,
'
#666
'
],
},
{
id
:
'
m-blue
'
,
themeMap
:
[
'
#4690df
'
,
'
#44aaff
'
],
},
{
id
:
'
m-red-dark
'
,
themeMap
:
[
'
#c62828
'
,
'
#e57373
'
],
},
{
id
:
'
m-amber-dark
'
,
themeMap
:
[
'
#ffa000
'
,
'
#ffecb3
'
],
},
{
id
:
'
m-green-dark
'
,
themeMap
:
[
'
#388e3c
'
,
'
#8bc34a
'
],
},
{
id
:
'
m-blue-grey-500
'
,
themeMap
:
[
'
#607d8b
'
,
'
#607d8b
'
],
},
];
const
chartPalette
=
{
segmentColorIds
:
[
// Colors for up to 6 segments
'
m-blue
'
,
'
m-grey-160
'
,
'
m-amber-dark
'
,
'
m-green-dark
'
,
'
m-red-dark
'
,
'
m-blue-grey-500
'
,
],
themeMaps
:
[
{
id
:
'
m-white
'
,
themeMap
:
[
'
#fff
'
,
'
#232323
'
],
},
{
id
:
'
m-grey-50
'
,
themeMap
:
[
'
rgba(232,232,232,1)
'
,
'
rgba(47,47,47,1)
'
],
// 222
},
{
id
:
'
m-grey-70
'
,
themeMap
:
[
'
#eee
'
,
'
#404040
'
],
// 333 before 5% lighten
},
{
id
:
'
m-grey-130
'
,
themeMap
:
[
'
#ccc
'
,
'
#515151
'
],
// 444
},
{
id
:
'
m-grey-160
'
,
themeMap
:
[
'
#bbb
'
,
'
#626262
'
],
// 555
},
{
id
:
'
m-grey-300
'
,
themeMap
:
[
'
#999
'
,
'
#737373
'
],
// 666
},
{
id
:
'
m-blue
'
,
themeMap
:
[
'
#4690df
'
,
'
#5db6ff
'
],
// 44aaff
},
{
id
:
'
m-red-dark
'
,
themeMap
:
[
'
#c62828
'
,
'
#e98989
'
],
// e57373
},
{
id
:
'
m-amber-dark
'
,
themeMap
:
[
'
#ffa000
'
,
'
#fff2cc
'
],
// ffecb3
},
{
id
:
'
m-green-dark
'
,
themeMap
:
[
'
#388e3c
'
,
'
#97c95d
'
],
// 8bc34a
},
{
id
:
'
m-blue-grey-500
'
,
themeMap
:
[
'
#607d8b
'
,
'
#6b8a99
'
],
// 607d8b
},
],
};
export
default
chartPalette
;
src/app/modules/analytics/v2/components/chart/chart.component.html
View file @
f03e30f6
<!-- TODO: Make this into a different component -->
<!-- <m-chart [buckets]="(vm$.thecurrentvisualisation" | async)></m-chart> -->
<!-- TODO: then all this becomes m-plotlyChart -->
<!-- <div *ngIf="vm$ | async as vm"> -->
<div>
<div
#graphDiv
id=
"graphDiv"
></div>
<!-- <plotly-plot
<div
#chartContainer
class=
"m-analyticsChart__chartContainer"
[ngClass]=
"{ isTouchDevice: isTouchDevice }"
>
<plotly-plot
*ngIf=
"init"
#graphDiv
id=
"graphDiv"
[divId]="graphDiv"
[data]=
"data"
[layout]=
"layout"
[config]=
"config"
[useResizeHandler]="true"
[style]=
"{ position: 'relative' }"
[useResizeHandler]=
"true"
(hover)=
"onHover($event)"
(unhover)=
"onUnhover($event)"
(
afterPlot)="afterPlot(
)"
(
plotly_click)=
"onClick($event
)"
>
</plotly-plot> -->
<!-- <div class="hoverInfo__row">
{{ hoverInfo.date | date: selectedTimespan.datePipe }}
</div> -->
</plotly-plot>
</div>
<div
#hoverInfoDiv
id=
"hoverInfoDiv"
class=
"hoverInfoDiv"
>
<div
class=
"hoverInfo__row"
>
{{ hoverInfo.date | utcDate | date: datePipe }}
</div>
<div
[ngSwitch]=
"selectedMetric.unit"
class=
"hoverInfo__row--primary"
>
<div
#hoverInfoDiv
id=
"hoverInfoDiv"
class=
"m-analyticsChart__hoverInfoDiv"
>
<i
*ngIf=
"isTouchDevice"
class=
"material-icons"
(click)=
"onUnhover($event)"
>
close
</i
>
<div
class=
"m-analyticsChart__hoverInfo__row"
>
{{ hoverInfo.date | utcDate | date: datePipe }}
</div>
<div
[ngSwitch]=
"selectedMetric?.unit"
class=
"m-analyticsChart__hoverInfo__row--primary"
>
<ng-template
ngSwitchCase=
"number"
>
{{ hoverInfo.value | number }} {{ selectedMetric.label | lowercase }}
</ng-template>
<ng-template
ngSwitchCase=
"usd"
>
{{ hoverInfo.value | currency }} USD
</ng-template>
<ng-template
ngSwitchDefault
>
{{ hoverInfo.value | number: '1.1-3' }} {{ selectedMetric?.unit }}
</ng-template>
</div>
<div
class=
"m-analyticsChart__hoverInfo__row"
*ngIf=
"isComparison"
>
vs
<ng-container
[ngSwitch]=
"selectedMetric?.unit"
class=
"m-analyticsChart__hoverInfo__row"
>
<ng-template
ngSwitchCase=
"number"
>
{{ hoverInfo.
v
alue | number }}
{{ selectedMetric.label | lowercase }}
{{ hoverInfo.
comparisonV
alue | number }}
</ng-template>
<ng-template
ngSwitchCase=
"usd"
>
{{ hoverInfo.
v
alue | currency }}
USD
{{ hoverInfo.
comparisonV
alue | currency }}
</ng-template>
<ng-template
ngSwitchDefault
>
{{ hoverInfo.
v
alue | number: '1.1-3' }}
{{ selectedMetric.unit }}
{{ hoverInfo.
comparisonV
alue | number: '1.1-3' }}
</ng-template>
</div>
<div
class=
"hoverInfo__row"
*ngIf=
"isComparison"
>
vs
<ng-container
[ngSwitch]=
"selectedMetric.unit"
class=
"hoverInfo__row"
>
<ng-template
ngSwitchCase=
"number"
>
{{ hoverInfo.comparisonValue | number }}
</ng-template>
<ng-template
ngSwitchCase=
"usd"
>
{{ hoverInfo.comparisonValue | currency }} USD
</ng-template>
<ng-template
ngSwitchDefault
>
{{ hoverInfo.comparisonValue | number: '1.1-3' }}
{{ selectedMetric.unit }}
</ng-template>
</ng-container>
on {{ hoverInfo.comparisonDate | utcDate | date: datePipe }}
</div>
</ng-container>
on {{ hoverInfo.comparisonDate | utcDate | date: datePipe }}
</div>
</div>
src/app/modules/analytics/v2/components/chart/chart.component.scss
View file @
f03e30f6
m-analytics__chart
{
display
:
block
;
position
:
relative
;
margin-left
:
40px
;
.js-plotly-plot
,
.plot-container
{
height
:
44vh
;
min-height
:
44vh
;
display
:
block
;
}
}
#graphDiv
{
display
:
block
;
position
:
relative
;
g
,
g
>
*
{
...
...
@@ -24,7 +28,7 @@ m-analytics__chart {
}
}
.hoverInfoDiv
{
.
m-analyticsChart__
hoverInfoDiv
{
width
:
160px
;
padding
:
12px
;
position
:
absolute
;
...
...
@@ -37,20 +41,49 @@ m-analytics__chart {
@include
m-theme
()
{
background-color
:
themed
(
$m-white
);
box-shadow
:
0
0
4px
rgba
(
themed
(
$m-black
)
,
0
.3
);
color
:
themed
(
$m-grey-
2
00
);
color
:
themed
(
$m-grey-
3
00
);
}
[
class
*=
'hoverInfo__row'
]
{
[
class
*=
'
m-analyticsChart__
hoverInfo__row'
]
{
padding-bottom
:
4px
;
font-weight
:
300
;
&
:last-of-type
{
padding-top
:
2px
;
}
}
.hoverInfo__row--primary
{
.m-analyticsChart__hoverInfo__row--primary
{
font-weight
:
400
;
font-size
:
15px
;
// font-weight: bold;
@include
m-theme
()
{
color
:
themed
(
$m-grey-600
);
}
}
i
{
display
:
none
;
font-size
:
15px
;
position
:
absolute
;
cursor
:
pointer
;
top
:
10px
;
right
:
10px
;
transition
:
color
0
.3s
cubic-bezier
(
0
.23
,
1
,
0
.32
,
1
);
@include
m-theme
()
{
color
:
themed
(
$m-grey-300
);
}
&
:hover
{
@include
m-theme
()
{
color
:
themed
(
$m-grey-500
);
}
}
}
}
.isTouchDevice
.m-analyticsChart__hoverInfoDiv
i
{
display
:
block
;
}
@media
screen
and
(
max-width
:
$min-tablet
)
{
m-analytics__chart
{
margin-left
:
16px
;
}
}
src/app/modules/analytics/v2/components/chart/chart.component.ts
View file @
f03e30f6
// Working version: https://codepen.io/omadrid/pen/NWKZYrV?editors=0010
import
{
Component
,
OnInit
,
...
...
@@ -22,9 +20,10 @@ import {
import
*
as
Plotly
from
'
plotly.js
'
;
import
chartPalette
from
'
../../chart-palette.default
'
;
import
{
ThemeService
}
from
'
../../../../../common/services/theme.service
'
;
import
isMobileOrTablet
from
'
../../../../../helpers/is-mobile-or-tablet
'
;
interface
TimespanExtended
extends
TimespanBase
{
t
ickFormat
?:
string
;
xT
ickFormat
?:
string
;
datePipe
?:
string
;
}
export
{
TimespanExtended
as
Timespan
};
...
...
@@ -34,11 +33,14 @@ export { TimespanExtended as Timespan };
templateUrl
:
'
chart.component.html
'
,
changeDetection
:
ChangeDetectionStrategy
.
OnPush
,
})
export
class
AnalyticsChartComponent
implements
On
Init
,
OnDestroy
{
@
ViewChild
(
'
graphDiv
'
,
{
static
:
true
})
graphDiv
El
:
ElementRef
;
export
class
AnalyticsChartComponent
implements
On
Destroy
,
OnInit
{
@
ViewChild
(
'
graphDiv
'
,
{
static
:
true
})
graphDiv
;
@
ViewChild
(
'
hoverInfoDiv
'
,
{
static
:
true
})
hoverInfoDivEl
:
ElementRef
;
@
ViewChild
(
'
chartContainer
'
,
{
static
:
true
})
chartContainer
:
ElementRef
;
isTouchDevice
:
boolean
;
init
:
boolean
=
false
;
graphDiv
:
any
;
hoverInfoDiv
:
any
;
hoverInfo
:
any
=
{};
...
...
@@ -59,48 +61,41 @@ export class AnalyticsChartComponent implements OnInit, OnDestroy {
segments
:
Buckets
[];
isComparison
:
boolean
=
false
;
data
:
Array
<
any
>
=
[];
layout
:
any
;
config
:
any
=
{
data
=
[];
layout
;
config
=
{
displayModeBar
:
false
,
// responsive: true,
};
segmentLength
:
number
;
pointsPerSegment
=
1
;
hoverPoint
:
number
;
lineRange
:
Array
<
any
>
;
newLineRange
=
true
;
markerOpacities
:
Array
<
number
>
=
[];
shapes
:
Array
<
any
>
=
[];
markerFills
;
shapes
=
[];
timespanFormats
=
[
{
interval
:
'
day
'
,
t
ickFormat
:
'
%m/%d
'
,
datePipe
:
'
EEE MMM d, y
'
},
{
interval
:
'
month
'
,
t
ickFormat
:
'
%m/%
y
'
,
datePipe
:
'
MMM y
'
},
{
interval
:
'
day
'
,
xT
ickFormat
:
'
%m/%d
'
,
datePipe
:
'
EEE MMM d, y
'
},
{
interval
:
'
month
'
,
xT
ickFormat
:
'
%m/%
Y
'
,
datePipe
:
'
MMM y
'
},
];
datePipe
:
string
=
this
.
timespanFormats
[
0
].
datePipe
;
tickFormat
:
string
=
this
.
timespanFormats
[
0
].
tickFormat
;
xTickFormat
:
string
=
this
.
timespanFormats
[
0
].
xTickFormat
;
// yTickPrefix: string = '';
yTickFormat
:
string
=
''
;
// ***********************************************************
************************
// ***********************************************************
constructor
(
private
analyticsService
:
AnalyticsDashboardService
,
private
themeService
:
ThemeService
,
protected
cd
:
ChangeDetectorRef
protected
cd
:
ChangeDetectorRef
,
private
hostElement
:
ElementRef
)
{}
ngOnInit
()
{
this
.
graphDiv
=
this
.
graphDivEl
.
nativeElement
;
this
.
isTouchDevice
=
isMobileOrTablet
()
;
this
.
hoverInfoDiv
=
this
.
hoverInfoDivEl
.
nativeElement
;
this
.
metricSubscription
=
this
.
selectedMetric$
.
subscribe
(
metric
=>
{
this
.
selectedMetric
=
metric
;
try
{
this
.
initPlot
();
}
catch
(
err
)
{
console
.
log
(
err
);
}
this
.
detectChanges
();
});
this
.
timespansSubscription
=
this
.
analyticsService
.
timespans$
.
subscribe
(
timespans
=>
{
this
.
selectedTimespan
=
timespans
.
find
(
...
...
@@ -112,33 +107,60 @@ export class AnalyticsChartComponent implements OnInit, OnDestroy {
t
=>
t
.
interval
===
this
.
selectedTimespan
.
interval
)
||
this
.
timespanFormats
[
0
];
this
.
t
ickFormat
=
timespanFormat
.
t
ickFormat
;
this
.
xT
ickFormat
=
timespanFormat
.
xT
ickFormat
;
this
.
datePipe
=
timespanFormat
.
datePipe
;
if
(
this
.
init
)
{
this
.
layout
.
xaxis
.
tickformat
=
this
.
xTickFormat
;
}
this
.
detectChanges
();
}
);
this
.
themeSubscription
=
this
.
themeService
.
isDark$
.
subscribe
(
isDark
=>
{
this
.
isDark
=
isDark
;
this
.
applyDimensions
();
// this.relayout(this.getLayout());
// this.restyle(this.getData());
if
(
this
.
init
)
{
this
.
getData
();
this
.
getLayout
();
}
this
.
detectChanges
();
});
this
.
metricSubscription
=
this
.
selectedMetric$
.
subscribe
(
metric
=>
{
this
.
init
=
false
;
this
.
selectedMetric
=
metric
;
if
(
metric
.
unit
&&
metric
.
unit
===
'
usd
'
)
{
this
.
yTickFormat
=
'
$.2f
'
;
}
try
{
this
.
initPlot
();
}
catch
(
err
)
{
console
.
log
(
err
);
}
this
.
detectChanges
();
});
}
swapSegmentColors
()
{
const
tempPaletteItem
=
chartPalette
.
segmentColorIds
[
0
];
chartPalette
.
segmentColorIds
[
0
]
=
chartPalette
.
segmentColorIds
[
1
];
chartPalette
.
segmentColorIds
[
1
]
=
tempPaletteItem
;
}
initPlot
()
{
this
.
data
=
[];
this
.
shapes
=
[];
this
.
markerOpacities
=
[];
this
.
segments
=
this
.
selectedMetric
.
visualisation
.
segments
;
this
.
segmentLength
=
this
.
segments
[
0
].
buckets
.
length
;
if
(
this
.
segments
.
length
===
2
)
{
this
.
isComparison
=
true
;
for
(
let
i
=
0
;
i
<
this
.
segmentLength
;
i
++
)
{
this
.
markerOpacities
[
i
]
=
0
;
// Reverse the segments so comparison line is layered behind current line
this
.
segments
.
reverse
()
;
// Current line should be blue
this
.
swapSegmentColors
();
}
this
.
pointsPerSegment
=
this
.
segments
[
0
].
buckets
.
length
;
for
(
let
i
=
0
;
i
<
this
.
pointsPerSegment
;
i
++
)
{
this
.
shapes
[
i
]
=
{
type
:
'
line
'
,
layer
:
'
below
'
,
...
...
@@ -147,213 +169,58 @@ export class AnalyticsChartComponent implements OnInit, OnDestroy {
x1
:
this
.
segments
[
0
].
buckets
[
i
].
date
,
y1
:
0
,
line
:
{
color
:
this
.
getColor
(
'
m-transparent
'
)
,
color
:
'
rgba(0, 0, 0, 0)
'
,
width
:
2
,
},
};
// if (this.selectedMetric.unit === 'usd'){
// this.segments.forEach(segment =>{ segment.buckets})
// }
}
this
.
data
=
this
.
getData
();
this
.
layout
=
this
.
getLayout
();
Plotly
.
newPlot
(
'
graphDiv
'
,
this
.
data
,
this
.
layout
,
this
.
config
);
this
.
setLineHeights
();
this
.
graphDiv
.
on
(
'
plotly_hover
'
,
$event
=>
{
this
.
onHover
(
$event
);
});
this
.
graphDiv
.
on
(
'
plotly_unhover
'
,
$event
=>
{
this
.
onUnhover
(
$event
);
});
this
.
getData
();
this
.
getLayout
();
this
.
init
=
true
;
this
.
detectChanges
();
}
// ----------------------------------------------
// EVENT: HOVER
// ----------------------------------------------
onHover
(
$event
)
{
// TODO: return if filters.component filter is expanded
this
.
hoverPoint
=
$event
.
points
[
0
].
pointIndex
;
this
.
showMarkers
();
this
.
showShapes
();
this
.
positionHoverInfo
(
$event
);
this
.
populateHoverInfo
();
this
.
showHoverInfo
();
this
.
detectChanges
();
}
// ----------------------------------------------
// EVENT: UNHOVER
// ----------------------------------------------
onUnhover
(
$event
)
{
this
.
hideMarkers
();
this
.
hideShapes
();
this
.
hideHoverInfo
();
}
showMarkers
()
{
this
.
markerOpacities
[
this
.
hoverPoint
]
=
1
;
Plotly
.
restyle
(
this
.
graphDiv
,
{
marker
:
{
opacity
:
this
.
markerOpacities
},
});
}
hideMarkers
()
{
this
.
markerOpacities
[
this
.
hoverPoint
]
=
0
;
Plotly
.
restyle
(
this
.
graphDiv
,
{
marker
:
{
opacity
:
this
.
markerOpacities
},
});
}
showShapes
()
{
this
.
layout
.
shapes
[
this
.
hoverPoint
].
line
.
color
=
this
.
getColor
(
'
m-grey-50
'
);
this
.
relayout
(
this
.
layout
);
}
hideShapes
()
{
// HIDE VERTICAL LINE
this
.
layout
.
shapes
[
this
.
hoverPoint
].
line
.
color
=
this
.
getColor
(
'
m-transparent
'
);
this
.
relayout
(
this
.
layout
);
}
populateHoverInfo
()
{
// TODO: format value strings here and remove ngSwitch from template?
this
.
hoverInfo
[
'
date
'
]
=
this
.
segments
[
0
].
buckets
[
this
.
hoverPoint
].
date
;
this
.
hoverInfo
[
'
value
'
]
=
this
.
selectedMetric
.
unit
!==
'
usd
'
?
this
.
segments
[
0
].
buckets
[
this
.
hoverPoint
].
value
:
this
.
segments
[
0
].
buckets
[
this
.
hoverPoint
].
value
/
100
;
if
(
this
.
isComparison
&&
this
.
segments
[
1
])
{
this
.
hoverInfo
[
'
comparisonValue
'
]
=
this
.
selectedMetric
.
unit
!==
'
usd
'
?
this
.
segments
[
1
].
buckets
[
this
.
hoverPoint
].
value
:
this
.
segments
[
1
].
buckets
[
this
.
hoverPoint
].
value
/
100
;
this
.
hoverInfo
[
'
comparisonDate
'
]
=
this
.
segments
[
1
].
buckets
[
this
.
hoverPoint
].
date
;
}
}
positionHoverInfo
(
$event
)
{
const
xAxis
=
$event
.
points
[
0
].
xaxis
,
yAxis
=
$event
.
points
[
0
].
yaxis
,
tooltipXDist
=
xAxis
.
d2p
(
$event
.
points
[
0
].
x
)
+
16
,
tooltipYDist
=
yAxis
.
d2p
(
$event
.
points
[
0
].
y
)
+
16
;
// if (this.hoverPoint < this.segmentLength / 2) {
this
.
hoverInfoDiv
.
style
.
top
=
tooltipYDist
+
yAxis
.
_offset
+
'
px
'
;
this
.
hoverInfoDiv
.
style
.
left
=
tooltipXDist
+
xAxis
.
_offset
+
'
px
'
;
// } else {
// // TODO move the second half of tooltips to the left of points
// // TODO also shift down/up if with x% of rangeMin/rangeMax???
// this.hoverInfoDiv.style.top = tooltipYDist + xAxis._offset + 'px';
// this.hoverInfoDiv.style.left = tooltipXDist + yAxis._offset + 'px';
// }
}
showHoverInfo
()
{
this
.
hoverInfoDiv
.
style
.
opacity
=
1
;
}
hideHoverInfo
()
{
this
.
hoverInfoDiv
.
style
.
opacity
=
0
;
}
// UTILITY \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
update
(
data
,
layout
)
{
this
.
data
=
data
;
this
.
layout
=
layout
;
Plotly
.
update
(
'
graphDiv
'
,
data
,
layout
);
}
restyle
(
data
)
{
this
.
data
=
data
;
Plotly
.
restyle
(
this
.
graphDiv
,
data
);
this
.
detectChanges
();
}
relayout
(
layout
)
{
this
.
layout
=
layout
;
Plotly
.
relayout
(
this
.
graphDiv
,
layout
);
this
.
detectChanges
();
}
unpack
(
rows
,
key
)
{
return
rows
.
map
(
row
=>
{
if
(
key
===
'
date
'
)
{
return
row
[
key
].
slice
(
0
,
10
);
}
else
if
(
this
.
selectedMetric
.
unit
===
'
usd
'
)
{
return
row
[
key
]
/
100
;
}
else
{
return
row
[
key
];
getData
()
{
this
.
markerFills
=
[];
this
.
segments
.
forEach
((
segment
,
index
)
=>
{
const
segmentMarkerFills
=
[];
for
(
let
i
=
0
;
i
<
this
.
pointsPerSegment
;
i
++
)
{
segmentMarkerFills
[
i
]
=
this
.
getColor
(
'
m-white
'
);
}
this
.
markerFills
.
push
(
segmentMarkerFills
);
});
}
getColor
(
colorId
)
{
const
palette
=
chartPalette
;
let
colorCode
=
'
#607d8b
'
;
if
(
palette
.
find
(
color
=>
color
.
id
===
colorId
))
{
colorCode
=
palette
.
find
(
color
=>
color
.
id
===
colorId
).
themeMap
[
+
this
.
isDark
];
}
return
colorCode
;
}
setLineHeights
()
{
this
.
shapes
.
forEach
(
shape
=>
{
shape
.
y0
=
this
.
graphDiv
.
layout
.
yaxis
.
range
[
0
];
shape
.
y1
=
this
.
graphDiv
.
layout
.
yaxis
.
range
[
1
];
});
this
.
relayout
(
this
.
getLayout
());
}
getData
()
{
const
globalSegmentSettings
:
any
=
{
const
globalSegmentSettings
=
{
type
:
'
scatter
'
,
mode
:
'
lines+markers
'
,
line
:
{
width
:
2
,
width
:
1
,
dash
:
'
solid
'
,
},
marker
:
{
size
:
10
,
opacity
:
0
,
size
:
7
,
},
showlegend
:
false
,
hoverinfo
:
'
text
'
,
x
:
this
.
unpack
(
this
.
segments
[
0
].
buckets
,
'
date
'
),
};
// COLORS FOR UP TO 6 SEGMENTS
const
segmentColorIds
=
[
'
m-blue
'
,
'
m-grey-160
'
,
'
m-amber-dark
'
,
'
m-green-dark
'
,
'
m-red-dark
'
,
'
m-blue-grey-500
'
,
];
this
.
segments
.
forEach
((
s
,
i
)
=>
{
const
segment
=
{
...
globalSegmentSettings
,
line
:
{
...
globalSegmentSettings
.
line
,
color
:
this
.
getColor
(
segmentColorIds
[
i
]),
color
:
this
.
getColor
(
chartPalette
.
segmentColorIds
[
i
]),
},
marker
:
{
...
globalSegmentSettings
.
marker
,
color
:
this
.
markerFills
[
i
],
line
:
{
color
:
this
.
getColor
(
chartPalette
.
segmentColorIds
[
i
]),
width
:
1
,
},
},
y
:
this
.
unpack
(
this
.
segments
[
i
].
buckets
,
'
value
'
),
};
...
...
@@ -361,17 +228,17 @@ export class AnalyticsChartComponent implements OnInit, OnDestroy {
this
.
data
[
i
]
=
segment
;
});
if
(
this
.
segments
.
length
===
2
)
{
this
.
isComparison
=
true
;
this
.
data
[
1
].
line
.
dash
=
'
dot
'
;
if
(
this
.
isComparison
)
{
this
.
data
[
0
].
line
.
dash
=
'
dot
'
;
}
return
this
.
data
;
}
getLayout
()
{
return
{
this
.
layout
=
{
width
:
0
,
height
:
0
,
autoexpand
:
'
true
'
,
autosize
:
'
true
'
,
hovermode
:
'
x
'
,
paper_bgcolor
:
this
.
getColor
(
'
m-white
'
),
plot_bgcolor
:
this
.
getColor
(
'
m-white
'
),
...
...
@@ -379,8 +246,8 @@ export class AnalyticsChartComponent implements OnInit, OnDestroy {
family
:
'
Roboto
'
,
},
xaxis
:
{
tickformat
:
this
.
t
ickFormat
,
tickmode
:
'
array
'
,
// || linear || auto
tickformat
:
this
.
xT
ickFormat
,
tickmode
:
'
array
'
,
tickson
:
'
labels
'
,
tickcolor
:
this
.
getColor
(
'
m-grey-130
'
),
tickangle
:
-
45
,
...
...
@@ -393,9 +260,14 @@ export class AnalyticsChartComponent implements OnInit, OnDestroy {
linewidth
:
1
,
zeroline
:
false
,
fixedrange
:
true
,
// automargin: true,
// rangemode: 'nonnegative',
},
yaxis
:
{
ticks
:
''
,
tickformat
:
this
.
yTickFormat
,
tickmode
:
'
array
'
,
tickson
:
'
labels
'
,
showgrid
:
true
,
gridcolor
:
this
.
getColor
(
'
m-grey-70
'
),
zeroline
:
false
,
...
...
@@ -405,27 +277,167 @@ export class AnalyticsChartComponent implements OnInit, OnDestroy {
color
:
this
.
getColor
(
'
m-grey-130
'
),
},
fixedrange
:
true
,
// automargin: true,
// rangemode: 'nonnegative',
},
margin
:
{
t
:
16
,
b
:
4
0
,
l
:
24
,
r
:
4
0
,
//
pad: 16,
b
:
8
0
,
l
:
0
,
r
:
8
0
,
pad
:
16
,
},
shapes
:
this
.
shapes
,
};
}
onHover
(
$event
)
{
this
.
hoverPoint
=
$event
.
points
[
0
].
pointIndex
;
this
.
addMarkerFill
();
this
.
showShape
(
$event
);
this
.
positionHoverInfo
(
$event
);
this
.
populateHoverInfo
();
this
.
hoverInfoDiv
.
style
.
opacity
=
1
;
this
.
detectChanges
();
}
onUnhover
(
$event
)
{
this
.
emptyMarkerFill
();
this
.
hideShape
();
this
.
hoverInfoDiv
.
style
.
opacity
=
0
;
this
.
detectChanges
();
}
onClick
(
$event
)
{
if
(
!
this
.
isTouchDevice
)
{
return
;
}
// TODO: use this for non-hover devices
}
addMarkerFill
()
{
this
.
data
.
forEach
((
segment
,
i
)
=>
{
this
.
markerFills
[
i
][
this
.
hoverPoint
]
=
this
.
getColor
(
chartPalette
.
segmentColorIds
[
i
]
);
});
}
emptyMarkerFill
()
{
this
.
data
.
forEach
((
segment
,
i
)
=>
{
this
.
markerFills
[
i
][
this
.
hoverPoint
]
=
this
.
getColor
(
'
m-white
'
);
segment
.
marker
.
color
=
this
.
markerFills
[
i
];
});
}
showShape
(
$event
)
{
const
hoverLine
=
this
.
shapes
[
this
.
hoverPoint
];
// Without this, entire graph resizes on every hover
if
(
this
.
newLineRange
)
{
this
.
newLineRange
=
false
;
this
.
lineRange
=
$event
.
yaxes
[
0
].
range
;
}
hoverLine
.
y0
=
this
.
lineRange
[
0
];
hoverLine
.
y1
=
this
.
lineRange
[
1
]
*
0.99
;
hoverLine
.
line
.
color
=
this
.
getColor
(
'
m-grey-70
'
);
this
.
layout
.
shapes
=
this
.
shapes
;
}
hideShape
()
{
this
.
shapes
[
this
.
hoverPoint
].
line
.
color
=
'
rgba(0, 0, 0, 0)
'
;
this
.
layout
.
shapes
=
this
.
shapes
;
}
populateHoverInfo
()
{
const
pt
=
this
.
isComparison
?
1
:
0
;
// TODO: format value strings here and remove ngSwitch from template?
this
.
hoverInfo
[
'
date
'
]
=
this
.
segments
[
pt
].
buckets
[
this
.
hoverPoint
].
date
;
this
.
hoverInfo
[
'
value
'
]
=
this
.
selectedMetric
.
unit
!==
'
usd
'
?
this
.
segments
[
pt
].
buckets
[
this
.
hoverPoint
].
value
:
this
.
segments
[
pt
].
buckets
[
this
.
hoverPoint
].
value
/
100
;
if
(
this
.
isComparison
&&
this
.
segments
[
1
])
{
this
.
hoverInfo
[
'
comparisonValue
'
]
=
this
.
selectedMetric
.
unit
!==
'
usd
'
?
this
.
segments
[
0
].
buckets
[
this
.
hoverPoint
].
value
:
this
.
segments
[
0
].
buckets
[
this
.
hoverPoint
].
value
/
100
;
this
.
hoverInfo
[
'
comparisonDate
'
]
=
this
.
segments
[
0
].
buckets
[
this
.
hoverPoint
].
date
;
}
}
positionHoverInfo
(
$event
)
{
const
pad
=
16
,
pt
=
this
.
isComparison
?
1
:
0
,
xAxis
=
$event
.
points
[
pt
].
xaxis
,
yAxis
=
$event
.
points
[
pt
].
yaxis
,
pointXDist
=
xAxis
.
d2p
(
$event
.
points
[
pt
].
x
)
+
xAxis
.
_offset
,
pointYDist
=
yAxis
.
d2p
(
$event
.
points
[
pt
].
y
)
+
yAxis
.
_offset
,
plotRect
=
document
.
querySelector
(
'
.js-plotly-plot
'
)
.
getBoundingClientRect
(),
hoverInfoRect
=
this
.
hoverInfoDiv
.
getBoundingClientRect
();
if
(
pointYDist
<
plotRect
.
height
/
2
)
{
// If point is in top half of plot, hoverinfo should go beneath it
this
.
hoverInfoDiv
.
style
.
top
=
pointYDist
+
pad
+
'
px
'
;
}
else
{
this
.
hoverInfoDiv
.
style
.
top
=
pointYDist
-
pad
-
hoverInfoRect
.
height
+
'
px
'
;
}
if
(
pointXDist
<
plotRect
.
width
/
2
)
{
// If point is in left half of plot, hoverinfo should go on the right
this
.
hoverInfoDiv
.
style
.
left
=
pointXDist
+
pad
+
'
px
'
;
}
else
{
this
.
hoverInfoDiv
.
style
.
left
=
pointXDist
-
pad
-
hoverInfoRect
.
width
+
'
px
'
;
}
}
@
HostListener
(
'
window:resize
'
)
applyDimensions
()
{
this
.
layout
=
{
...
this
.
layout
,
width
:
this
.
graphDiv
.
clientWidth
-
32
,
height
:
this
.
graphDiv
.
clientHeight
-
32
,
};
this
.
setLineHeights
();
this
.
detectChanges
();
if
(
this
.
init
)
{
// this.layout.width = this.hostElement.nativeElement.clientWidth;
// this.layout.height = this.hostElement.nativeElement.clientHeight;
this
.
layout
.
width
=
this
.
chartContainer
.
nativeElement
.
clientWidth
;
//-32; //- 56;
this
.
layout
.
height
=
this
.
chartContainer
.
nativeElement
.
clientHeight
;
this
.
newLineRange
=
true
;
this
.
detectChanges
();
}
}
// * UTILITY -----------------------------------
unpack
(
rows
,
key
)
{
return
rows
.
map
(
row
=>
{
if
(
key
===
'
date
'
)
{
return
row
[
key
].
slice
(
0
,
10
);
}
else
if
(
this
.
selectedMetric
.
unit
===
'
usd
'
)
{
return
row
[
key
]
/
100
;
}
else
{
return
row
[
key
];
}
});
}
getColor
(
colorId
)
{
const
palette
=
chartPalette
.
themeMaps
;
let
colorCode
=
'
#607d8b
'
;
if
(
palette
.
find
(
color
=>
color
.
id
===
colorId
))
{
colorCode
=
palette
.
find
(
color
=>
color
.
id
===
colorId
).
themeMap
[
+
this
.
isDark
];
}
return
colorCode
;
}
detectChanges
()
{
...
...
Prev
1
2
3
Next