...
 
Commits (2)
......@@ -90,7 +90,10 @@ const routes: Routes = [
{
path: 'dashboard',
component: AnalyticsDashboardComponent,
// children: [{ path: '', redirectTo: 'traffic', pathMatch: 'full' }],
children: [
{ path: '', redirectTo: 'traffic', pathMatch: 'full' },
{ path: ':category', component: AnalyticsDashboardComponent },
],
},
],
},
......
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'],
},
];
export default chartPalette;
......@@ -6,6 +6,7 @@
<div>
<!-- <div id="graphDiv"></div> -->
<plotly-plot
id="graphDiv"
[divId]="graphDiv"
[data]="data"
[layout]="layout"
......@@ -14,9 +15,9 @@
[style]="{ position: 'relative' }"
(hover)="onHover($event)"
(unhover)="onUnhover($event)"
(afterPlot)="updateGraph($event)"
>
</plotly-plot>
<!-- (afterPlot)="afterPlot()" -->
<div id="hoverInfo" class="hoverInfo">
<div id="hoverInfo__x" class="hoverInfo__row">
......
......@@ -18,7 +18,7 @@
(click)="updateFilter(option)"
*ngIf="option.label !== selectedOption.label"
[ngClass]="{
unavailable: !option.available
unavailable: option.available === false
}"
>
<span>{{ option.label }}</span>
......
m-analytics__filter {
position: relative;
margin: 16px 16px 0 0;
}
.filterWrapper {
cursor: pointer;
border-radius: 3px;
margin: 16px 16px 0 0;
@include m-theme() {
background-color: white;
border: 1px solid themed($m-grey-100);
......@@ -9,12 +13,13 @@
}
&.expanded {
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
@include m-theme() {
border-color: themed($m-blue);
}
.unselectedOptionsContainer {
visibility: visible;
height: auto;
display: block;
}
.option {
&:hover:not(.unavailable) {
......@@ -27,6 +32,9 @@
}
.option {
padding: 4px 8px;
@include m-theme() {
background-color: themed($m-white);
}
&.unavailable {
text-decoration: line-through;
......@@ -37,6 +45,7 @@
}
.filterHeader {
position: relative;
display: flex;
justify-content: space-between;
.filterLabel {
......@@ -54,9 +63,16 @@
}
.unselectedOptionsContainer {
visibility: hidden;
height: 0px;
position: absolute;
display: none;
width: 100%;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
box-sizing: border-box;
left: 0px;
@include m-theme() {
border: 1px solid themed($m-blue);
border-top: 1px solid themed($m-grey-100);
background-color: themed($m-white);
}
}
......@@ -44,45 +44,64 @@ export class AnalyticsFilterComponent implements OnInit, OnDestroy {
this.subscription = this.vm$.subscribe(viewModel => (this.vm = viewModel));
this.options = this.filter.options;
this.selectedOption =
this.options.find(option => option.selected === true) || this.options[0];
if (this.filter.id === 'timespan') {
this.selectedOption =
this.options.find(option => option.id === this.vm.timespan) ||
this.options[0];
// TODO: make selected option at top of array?
} else {
this.selectedOption =
this.options.find(option => option.selected === true) ||
this.options[0];
}
}
updateFilter(option: Option) {
this.expanded = false;
this.selectedOption = option;
const selectedFilterStr = `${this.filter.id}::${option.id}`;
if (
this.vm.filter.includes(selectedFilterStr) ||
!this.selectedOption.available
) {
if (this.filter.id === 'timespan') {
this.analyticsService.updateTimespan(option.id);
return;
}
console.log(this.vm.filter);
if (!this.selectedOption.available) {
return;
}
const selectedFilterStr = `${this.filter.id}::${option.id}`;
this.analyticsService.updateFilter(selectedFilterStr);
const filterArr = this.vm.filter;
const activeFilterIds = filterArr.map(filterStr => {
return filterStr.split('::')[0];
});
console.log(activeFilterIds);
const filterIndex = activeFilterIds.findIndex(
filterId => filterId === this.filter.id
);
// if (
// this.vm.filter.includes(selectedFilterStr) ||
// !this.selectedOption.available
// ) {
// return;
// }
console.log(filterIndex);
// console.log(this.vm.filter);
if (activeFilterIds.includes(selectedFilterStr)) {
filterArr.splice(filterIndex, 1, selectedFilterStr);
} else {
filterArr.push(selectedFilterStr);
}
console.log(filterArr);
// const filterArr = this.vm.filter;
// const activeFilterIds = filterArr.map(filterStr => {
// return filterStr.split('::')[0];
// });
// console.log(activeFilterIds);
// const filterIndex = activeFilterIds.findIndex(
// filterId => filterId === this.filter.id
// );
// console.log(filterIndex);
// if (activeFilterIds.includes(selectedFilterStr)) {
// filterArr.splice(filterIndex, 1, selectedFilterStr);
// } else {
// filterArr.push(selectedFilterStr);
// }
// console.log(filterArr);
//TODO make incoming string filterStr instead of option
// const filterStr = somethingToDoWith_optionLabel;
this.analyticsService.updateFilter(filterArr);
// //TODO make incoming string filterStr instead of option
// // const filterStr = somethingToDoWith_optionLabel;
// this.analyticsService.updateFilter(filterArr);
}
ngOnDestroy() {
......
......@@ -6,7 +6,7 @@
[ngClass]="{ active: metric.visualisation }"
>
<div class="metricLabel">{{ metric.label }}</div>
<!-- TODO the "| number" pipe should be from backend so it can dynamically handle diff decimals/currency formats -->
<!-- TODO the "number" pipe should be from backend so it can dynamically handle diff decimals/currency formats -->
<div class="metricSummary">
{{ metric.summary.current_value | number }}
</div>
......
m-analytics__metrics {
position: relative;
z-index: 999;
}
.metricsContainer {
display: flex;
width: 100%;
// position: relative;
width: 95%;
padding: 0 16px;
@include m-theme() {
border-bottom: 1px solid themed($m-grey-50);
box-shadow: 0px 0px 10px -3px rgba(themed($m-black-always), 0.3);
}
.metric {
width: 20%;
padding: 16px;
padding: 20px;
font-size: 12px;
box-sizing: border-box;
@include m-theme() {
border-bottom: 5px solid themed($m-white);
// border-bottom: 5px solid themed($m-white);
// border-bottom: 5px solid transparent;
color: themed($m-grey-200);
}
&.active {
......
......@@ -44,32 +44,31 @@ export class AnalyticsMetricsComponent implements OnInit, OnDestroy {
constructor(private analyticsService: AnalyticsDashboardService) {}
ngOnInit() {
this.metrics$ = this.analyticsService.metrics$
.pipe(
map(_metrics => {
const metrics = _metrics.map(metric => ({...metric})); // Clone to avoid updating
this.metrics$ = this.analyticsService.metrics$.pipe(
map(_metrics => {
const metrics = _metrics.map(metric => ({ ...metric })); // Clone to avoid updating
for (let metric of metrics) {
const delta =
(metric.summary.current_value - metric.summary.comparison_value) /
metric.summary.comparison_value;
for (let metric of metrics) {
const delta =
(metric.summary.current_value - metric.summary.comparison_value) /
metric.summary.comparison_value;
metric['delta'] = delta;
metric['hasChanged'] = delta === 0 ? false : true;
metric['delta'] = delta;
metric['hasChanged'] = delta === 0 ? false : true;
if (
(delta > 0 && metric.summary.comparison_positive_inclination) ||
(delta < 0 && !metric.summary.comparison_positive_inclination)
) {
metric['positiveTrend'] = true;
metric['positiveTrendy'] = true;
} else {
metric['positiveTrend'] = false;
if (
(delta > 0 && metric.summary.comparison_positive_inclination) ||
(delta < 0 && !metric.summary.comparison_positive_inclination)
) {
metric['positiveTrend'] = true;
} else {
metric['positiveTrend'] = false;
}
}
}
return metrics;
}));
return metrics;
})
);
}
updateMetric(metric) {
......@@ -77,6 +76,6 @@ export class AnalyticsMetricsComponent implements OnInit, OnDestroy {
}
ngOnDestroy() {
// this.subscription.unsubscribe();
// this.subscription.unsubscribe();
}
}
......@@ -5,35 +5,40 @@
<div class="catContainer">
<i class="material-icons">menu</i>
<div class="cat" *ngFor="let cat of cats">
<span [ngClass]="{ selected: cat.id === (category$ | async) }">{{
<a [routerLink]="'./' + cat.id" routerLinkActive="selected">{{
cat?.label
}}</span>
}}</a>
</div>
</div>
</section>
<section class="main" *ngIf="(ready$ | async)">
<!-- <section class="main" *ngIf="ready$ | async"> -->
<section class="main">
<div class="mainHeader">
<h3 class="selectedCatLabel">
{{ selectedCat?.label }}
</h3>
<div class="globalFilters">
<!-- TODO enable only show to admins -->
<!-- <div *ngIf="session.isAdmin()" class="channelSearch"> -->
<div class="channelSearch">
(todo: Channel search)
<!-- <input
type="text"
[formControl]="channelSearch"
placeholder="Search for a channel"
/> -->
</div>
<!-- <div class="timespanFilter">
<div class="timespanFilter">
<m-analytics__filter [filter]="timespanFilter"></m-analytics__filter>
</div> -->
</div>
</div>
</div>
<m-analytics__layout--chart
class="m-analytics__layout"
></m-analytics__layout--chart>
<!-- <m-analytics__layout--table
<div class="layoutWrapper">
<m-analytics__layout--chart
class="m-analytics__layout"
></m-analytics__layout--chart>
<!-- <m-analytics__layout--table
class="m-analytics__layout"
*ngIf="selectedCat?.type === 'table'"
></m-analytics__layout--table>
......@@ -41,5 +46,6 @@
class="m-analytics__layout"
*ngIf="selectedCat?.type === 'summary'"
></m-analytics__layout--summary> -->
</div>
</section>
</div>
......@@ -22,11 +22,14 @@
.catContainer {
.cat {
padding: 6px 0;
@include m-theme() {
color: themed($m-grey-200);
a {
text-decoration: none;
font-weight: 400;
@include m-theme() {
color: themed($m-grey-200);
}
}
span.selected,
a.selected,
&:hover {
cursor: pointer;
@include m-theme() {
......@@ -58,7 +61,14 @@
color: themed($m-grey-100);
}
}
m-analytics__filter {
display: table;
}
.timespanFilter {
.filterWrapper {
margin-top: 0px;
}
.filterLabel {
display: none;
}
......@@ -66,6 +76,7 @@
}
.m-analytics__layout {
position: relative;
@include m-theme() {
box-shadow: 0px 0px 10px -3px rgba(themed($m-black-always), 0.3);
}
......
......@@ -28,7 +28,7 @@ import {
UserState,
} from './dashboard.service';
import fakeData from './fake';
// import fakeData from './fake';
import categories from './categories.default';
import isMobileOrTablet from '../../../helpers/is-mobile-or-tablet';
......@@ -40,7 +40,6 @@ import isMobileOrTablet from '../../../helpers/is-mobile-or-tablet';
export class AnalyticsDashboardComponent implements OnInit, OnDestroy {
isMobile: boolean;
// TODO: get this from backend
cats = categories;
subscription: Subscription;
......@@ -49,7 +48,7 @@ export class AnalyticsDashboardComponent implements OnInit, OnDestroy {
selectedTimespan; //string? or Timespan?
timespanFilter: Filter = {
id: 'timespan',
label: 'timespan',
label: 'Timespan',
options: [],
};
vm$: Observable<UserState> = this.analyticsService.vm$;
......@@ -74,11 +73,9 @@ export class AnalyticsDashboardComponent implements OnInit, OnDestroy {
this.isMobile = isMobileOrTablet();
this.title.setTitle('Analytics');
// TODO: Autoset activeCat from url segment[2]
// console.log(this.route);
// console.log(this.route.snapshot.url);
// TODO: make timespans[] into a filter
this.route.params.subscribe(params => {
this.updateCategory(params['category']);
});
// TODO: implement channel filter
// const {channelGuid} = this.analyticsService.getStateSnapshot();
......@@ -88,16 +85,13 @@ export class AnalyticsDashboardComponent implements OnInit, OnDestroy {
this.paramsSubscription = this.route.queryParams.subscribe(params => {
// TODO: do the same filter, metric, channel
// TODO: something similar for category
if (params['timespan'] && params['timespan'] !== this.vm.timespan) {
this.updateTimespan(params['timespan']);
}
this.selectedCat = this.cats.find(
// TODO get this from url segment[2]
cat => cat.id === this.vm.category
);
this.selectedCat = this.cats.find(cat => cat.id === this.vm.category);
});
this.timespanFilter.options = this.vm.timespans;
}
updateTimespan(timespanId) {
......@@ -109,7 +103,7 @@ export class AnalyticsDashboardComponent implements OnInit, OnDestroy {
updateCategory(categoryId) {
// TODO
// update url
// this.analyticsService.updateTimespan(categoryId);
// this.analyticsService.updateCategory(categoryId);
}
ngOnDestroy() {
......
.filterableChartWrapper {
position: relative;
padding: 16px;
width: 95%;
@include m-theme() {
border-top: 1px solid themed($m-grey-50);
box-shadow: 0px 0px 10px -3px rgba(themed($m-black-always), 0.3);
}
}