feat(dashboard): add new E-commerce dashboard (#1754)

This commit is contained in:
ESadouski 2018-08-08 16:45:31 +03:00 committed by Dmitry Nehaychik
parent 3482404b88
commit 56e4709a55
106 changed files with 6333 additions and 19 deletions

View file

@ -76,23 +76,17 @@ export class EchartsBarAnimationComponent implements AfterViewInit, OnDestroy {
name: 'bar',
type: 'bar',
data: data1,
animationDelay: function(idx) {
return idx * 10;
},
animationDelay: idx => idx * 10,
},
{
name: 'bar2',
type: 'bar',
data: data2,
animationDelay: function(idx) {
return idx * 10 + 100;
},
animationDelay: idx => idx * 10 + 100,
},
],
animationEasing: 'elasticOut',
animationDelayUpdate: function(idx) {
return idx * 5;
},
animationDelayUpdate: idx => idx * 5,
};
for (let i = 0; i < 100; i++) {

View file

@ -51,7 +51,6 @@
<li class="dropdown-item" *ngFor="let t of types" (click)="type = t">{{ t }}</li>
</ul>
</div>
</div>
<ngx-electricity-chart></ngx-electricity-chart>
</div>

View file

@ -7,7 +7,6 @@
align-items: center;
justify-content: space-between;
padding: 0.675rem 0.5rem 0.5rem 1.25rem;
border: none;
}
nb-card-body {

View file

@ -0,0 +1,20 @@
<div class="chart-header">
<ngx-legend-chart [legendItems]="chartLegend"></ngx-legend-chart>
<div class="dropdown"
[ngClass]="{ 'ghost-dropdown': currentTheme === 'corporate' }"
ngbDropdown>
<button type="button" ngbDropdownToggle class="btn"
[ngClass]="{
'btn-outline-success': currentTheme === 'default',
'btn-primary': currentTheme !== 'default',
'btn-sm': breakpoint.width <= breakpoints.is}">
{{ type }}
</button>
<ul class="dropdown-menu" ngbDropdownMenu>
<li class="dropdown-item" *ngFor="let period of types" (click)="changePeriod(period)">
{{ period }}
</li>
</ul>
</div>
</div>

View file

@ -0,0 +1,41 @@
@import '../../../../@theme/styles/themes';
@import '~bootstrap/scss/mixins/breakpoints';
@import '~@nebular/theme/styles/global/bootstrap/breakpoints';
@include nb-install-component() {
.chart-header {
display: flex;
justify-content: space-between;
margin-bottom: 2.125rem;
align-items: center;
}
.dropdown {
min-width: 8.125rem;
}
@include media-breakpoint-down(is) {
.chart-header {
flex-direction: column-reverse;
}
ngx-legend-chart {
align-self: flex-start;
/deep/ .legends {
padding-left: 0;
font-size: nb-theme(font-size-sm);
}
/deep/ .legend {
margin-left: 1rem;
}
}
.dropdown {
margin-top: 1.5rem;
margin-bottom: 1.5rem;
align-self: flex-end;
}
}
}

View file

@ -0,0 +1,69 @@
import { Component, EventEmitter, Input, OnDestroy, Output } from '@angular/core';
import { NbMediaBreakpoint, NbMediaBreakpointsService, NbThemeService } from '@nebular/theme';
import { takeWhile } from 'rxjs/operators';
@Component({
selector: 'ngx-chart-panel-header',
styleUrls: ['./chart-panel-header.component.scss'],
templateUrl: './chart-panel-header.component.html',
})
export class ChartPanelHeaderComponent implements OnDestroy {
private alive = true;
@Output() periodChange = new EventEmitter<string>();
@Input() type: string = 'week';
types: string[] = ['week', 'month', 'year'];
chartLegend: {iconColor: string; title: string}[];
breakpoint: NbMediaBreakpoint = { name: '', width: 0 };
breakpoints: any;
currentTheme: string;
constructor(private themeService: NbThemeService,
private breakpointService: NbMediaBreakpointsService) {
this.themeService.getJsTheme()
.pipe(takeWhile(() => this.alive))
.subscribe(theme => {
const orderProfitLegend = theme.variables.orderProfitLegend;
this.currentTheme = theme.name;
this.setLegendItems(orderProfitLegend);
});
this.breakpoints = this.breakpointService.getBreakpointsMap();
this.themeService.onMediaQueryChange()
.pipe(takeWhile(() => this.alive))
.subscribe(([oldValue, newValue]) => {
this.breakpoint = newValue;
});
}
setLegendItems(orderProfitLegend) {
this.chartLegend = [
{
iconColor: orderProfitLegend.firstItem,
title: 'Payment',
},
{
iconColor: orderProfitLegend.secondItem,
title: 'Canceled',
},
{
iconColor: orderProfitLegend.thirdItem,
title: 'All orders',
},
];
}
changePeriod(period: string): void {
this.type = period;
this.periodChange.emit(period);
}
ngOnDestroy() {
this.alive = false;
}
}

View file

@ -0,0 +1,51 @@
@import '../../../../@theme/styles/themes';
@import '~bootstrap/scss/mixins/breakpoints';
@import '~@nebular/theme/styles/global/bootstrap/breakpoints';
@include nb-install-component() {
.summary-container {
display: flex;
flex: 1;
background-color: nb-theme(chart-panel-summary-background-color);
box-shadow: nb-theme(chart-panel-summary-box-shadow);
justify-content: space-between;
padding: 1.5rem 4rem 1rem;
margin-bottom: 1rem;
border:
nb-theme(chart-panel-summary-border-width)
solid
nb-theme(chart-panel-summary-border-color);
border-left: none;
border-right: none;
}
.value {
font-size: 2rem;
color: nb-theme(color-fg-heading);
}
@include media-breakpoint-down(sm) {
.value, .title {
font-weight: nb-theme(font-weight-bold);
}
.title {
font-size: nb-theme(font-size-sm);
}
.value {
font-size: nb-theme(font-size-xlg);
}
}
@include media-breakpoint-down(is) {
.summary-container {
padding-left: nb-theme(padding);
padding-right: nb-theme(padding);
}
.value {
margin-top: 0.5rem;
}
}
}

View file

@ -0,0 +1,18 @@
import { Component, Input } from '@angular/core';
@Component({
selector: 'ngx-chart-panel-summary',
styleUrls: ['./chart-panel-summary.component.scss'],
template: `
<div class="summary-container">
<div class="summory" *ngFor="let item of summary">
<div class="title">{{ item.title }}</div>
<div class="value">{{ item.value }}</div>
</div>
</div>
`,
})
export class ChartPanelSummaryComponent {
@Input() summary: {title: string; value: number}[];
}

View file

@ -0,0 +1,22 @@
<nb-card size="large">
<nb-tabset fullWidth (changeTab)="changeTab($event)">
<nb-tab tabTitle="Orders">
<div class="chart-container">
<ngx-chart-panel-summary [summary]="chartPanelSummary"></ngx-chart-panel-summary>
<ngx-chart-panel-header [type]="period"
(periodChange)="setPeriodAndGetChartData($event)">
</ngx-chart-panel-header>
<ngx-orders-chart #ordersChart [ordersChartData]="ordersChartData"></ngx-orders-chart>
</div>
</nb-tab>
<nb-tab tabTitle="Profit" [lazyLoad]="true">
<div class="chart-container">
<ngx-chart-panel-summary [summary]="chartPanelSummary"></ngx-chart-panel-summary>
<ngx-chart-panel-header [type]="period"
(periodChange)="setPeriodAndGetChartData($event)">
</ngx-chart-panel-header>
<ngx-profit-chart #profitChart [profitChartData]="profitChartData"></ngx-profit-chart>
</div>
</nb-tab>
</nb-tabset>
</nb-card>

View file

@ -0,0 +1,35 @@
@import '../../../@theme/styles/themes';
$legend-all-orders-color: #00977e;
$legend-payment-color: #6935ca;
$legend-canceled-color: #3f4fda;
@include nb-install-component() {
/deep/ nb-tabset {
display: flex;
flex-direction: column;
flex: 1;
ul {
border-bottom: none;
}
}
nb-tab {
flex: 1;
padding-bottom: 1.25rem;
}
.chart-container {
flex: 1;
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
}
ngx-chart-panel-header, ngx-profit-chart, ngx-orders-chart {
padding: 0 1.25rem;
}
}

View file

@ -0,0 +1,74 @@
import { Component, OnDestroy, ViewChild } from '@angular/core';
import { takeWhile } from 'rxjs/operators';
import { OrdersChartComponent } from './charts/orders-chart.component';
import { ProfitChartComponent } from './charts/profit-chart.component';
import { OrdersChart } from '../../../@core/data/orders-chart.service';
import { ProfitChart } from '../../../@core/data/profit-chart.service';
import { OrdersProfitChartService, OrderProfitChartSummary } from '../../../@core/data/orders-profit-chart.service';
@Component({
selector: 'ngx-ecommerce-charts',
styleUrls: ['./charts-panel.component.scss'],
templateUrl: './charts-panel.component.html',
})
export class ECommerceChartsPanelComponent implements OnDestroy {
private alive = true;
chartPanelSummary: OrderProfitChartSummary[];
period: string = 'week';
ordersChartData: OrdersChart;
profitChartData: ProfitChart;
@ViewChild('ordersChart') ordersChart: OrdersChartComponent;
@ViewChild('profitChart') profitChart: ProfitChartComponent;
constructor(private ordersProfitChartService: OrdersProfitChartService) {
this.ordersProfitChartService.getOrderProfitChartSummary()
.pipe(takeWhile(() => this.alive))
.subscribe((summary) => {
this.chartPanelSummary = summary;
});
this.getOrdersChartData(this.period);
this.getProfitChartData(this.period);
}
setPeriodAndGetChartData(value: string): void {
if (this.period !== value) {
this.period = value;
}
this.getOrdersChartData(value);
this.getProfitChartData(value);
}
changeTab(selectedTab) {
if (selectedTab.tabTitle === 'Profit') {
this.profitChart.resizeChart();
} else {
this.ordersChart.resizeChart();
}
}
getOrdersChartData(period: string) {
this.ordersProfitChartService.getOrdersChartData(period)
.pipe(takeWhile(() => this.alive))
.subscribe(ordersChartData => {
this.ordersChartData = ordersChartData;
});
}
getProfitChartData(period: string) {
this.ordersProfitChartService.getProfitChartData(period)
.pipe(takeWhile(() => this.alive))
.subscribe(profitChartData => {
this.profitChartData = profitChartData;
});
}
ngOnDestroy() {
this.alive = false;
}
}

View file

@ -0,0 +1,13 @@
@import '../../../../@theme/styles/themes';
@include nb-install-component() {
display: block;
height: 100%;
width: 100%;
.echart {
display: block;
height: 100%;
width: 100%;
}
}

View file

@ -0,0 +1,296 @@
import { AfterViewInit, Component, Input, OnChanges, OnDestroy } from '@angular/core';
import { NbThemeService } from '@nebular/theme';
import { delay, takeWhile } from 'rxjs/operators';
import { OrdersChart } from '../../../../@core/data/orders-chart.service';
@Component({
selector: 'ngx-orders-chart',
styleUrls: ['./charts-common.component.scss'],
template: `
<div echarts [options]="option" class="echart" (chartInit)="onChartInit($event)"></div>
`,
})
export class OrdersChartComponent implements AfterViewInit, OnDestroy, OnChanges {
@Input()
ordersChartData: OrdersChart;
private alive = true;
echartsIntance: any;
option: any;
ngOnChanges(): void {
if (this.option) {
this.updateOrdersChartOptions(this.ordersChartData);
}
}
constructor(private theme: NbThemeService) {
}
ngAfterViewInit(): void {
this.theme.getJsTheme()
.pipe(
takeWhile(() => this.alive),
delay(1),
)
.subscribe(config => {
const eTheme: any = config.variables.orders;
this.setOptions(eTheme);
this.updateOrdersChartOptions(this.ordersChartData);
});
}
setOptions(eTheme) {
this.option = {
grid: {
left: 40,
top: 20,
right: 0,
bottom: 40,
},
tooltip: {
trigger: 'item',
axisPointer: {
type: 'line',
lineStyle: {
color: eTheme.tooltipLineColor,
width: eTheme.tooltipLineWidth,
},
},
textStyle: {
color: eTheme.tooltipTextColor,
fontSize: eTheme.tooltipFontSize,
fontWeight: eTheme.tooltipFontWeight,
},
position: 'top',
backgroundColor: eTheme.tooltipBg,
borderColor: eTheme.tooltipBorderColor,
borderWidth: 3,
formatter: (params) => {
return Math.round(parseInt(params.value, 10));
},
extraCssText: eTheme.tooltipExtraCss,
},
xAxis: {
type: 'category',
boundaryGap: false,
offset: 5,
data: [],
axisTick: {
show: false,
},
axisLabel: {
color: eTheme.axisTextColor,
fontSize: eTheme.axisFontSize,
},
axisLine: {
lineStyle: {
color: eTheme.axisLineColor,
width: '2',
},
},
},
yAxis: {
type: 'value',
boundaryGap: false,
axisLine: {
lineStyle: {
color: eTheme.axisLineColor,
width: '1',
},
},
axisLabel: {
color: eTheme.axisTextColor,
fontSize: eTheme.axisFontSize,
},
axisTick: {
show: false,
},
splitLine: {
lineStyle: {
color: eTheme.yAxisSplitLine,
width: '1',
},
},
},
series: [
this.getFirstLine(eTheme),
this.getSecondLine(eTheme),
this.getThirdLine(eTheme),
],
};
}
getFirstLine(eTheme) {
return {
type: 'line',
smooth: true,
symbolSize: 20,
itemStyle: {
normal: {
opacity: 0,
},
emphasis: {
opacity: 0,
},
},
lineStyle: {
normal: {
width: 0,
},
},
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: eTheme.firstAreaGradFrom,
}, {
offset: 1,
color: eTheme.firstAreaGradTo,
}]),
opacity: 1,
},
},
data: [],
};
}
getSecondLine(eTheme) {
return {
type: 'line',
smooth: true,
symbolSize: 20,
itemStyle: {
normal: {
opacity: 0,
},
emphasis: {
color: '#ffffff',
borderColor: eTheme.itemBorderColor,
borderWidth: 2,
opacity: 1,
},
},
lineStyle: {
normal: {
width: eTheme.lineWidth,
type: eTheme.lineStyle,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: eTheme.secondLineGradFrom,
}, {
offset: 1,
color: eTheme.secondLineGradTo,
}]),
},
},
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: eTheme.secondAreaGradFrom,
}, {
offset: 1,
color: eTheme.secondAreaGradTo,
}]),
},
},
data: [],
};
}
getThirdLine(eTheme) {
return {
type: 'line',
smooth: true,
symbolSize: 20,
itemStyle: {
normal: {
opacity: 0,
},
emphasis: {
color: '#ffffff',
borderColor: eTheme.itemBorderColor,
borderWidth: 2,
opacity: 1,
},
},
lineStyle: {
normal: {
width: eTheme.lineWidth,
type: eTheme.lineStyle,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: eTheme.thirdLineGradFrom,
}, {
offset: 1,
color: eTheme.thirdLineGradTo,
}]),
},
},
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: eTheme.thirdAreaGradFrom,
}, {
offset: 1,
color: eTheme.thirdAreaGradTo,
}]),
},
},
data: [],
};
}
updateOrdersChartOptions(ordersChartData: OrdersChart) {
const options = this.option;
const series = this.getNewSeries(options.series, ordersChartData.linesData);
const xAxis = this.getNewXAxis(options.xAxis, ordersChartData.chartLabel);
this.option = {
...options,
xAxis,
series,
};
}
getNewSeries(series, linesData: number[][]) {
return series.map((line, index) => {
return {
...line,
data: linesData[index],
};
});
}
getNewXAxis(xAxis, chartLabel: string[]) {
return {
...xAxis,
data: chartLabel,
};
}
onChartInit(echarts) {
this.echartsIntance = echarts;
}
resizeChart() {
if (this.echartsIntance) {
// Fix recalculation chart size
// TODO: investigate more deeply
setTimeout(() => {
this.echartsIntance.resize();
}, 0);
}
}
ngOnDestroy() {
this.alive = false;
}
}

View file

@ -0,0 +1,193 @@
import { AfterViewInit, Component, Input, OnChanges, OnDestroy } from '@angular/core';
import { NbThemeService } from '@nebular/theme';
import { takeWhile } from 'rxjs/operators';
import { ProfitChart } from '../../../../@core/data/profit-chart.service';
@Component({
selector: 'ngx-profit-chart',
styleUrls: ['./charts-common.component.scss'],
template: `
<div echarts [options]="options" class="echart" (chartInit)="onChartInit($event)"></div>
`,
})
export class ProfitChartComponent implements AfterViewInit, OnDestroy, OnChanges {
@Input()
profitChartData: ProfitChart;
private alive = true;
echartsIntance: any;
options: any = {};
constructor(private theme: NbThemeService) {
}
ngOnChanges(): void {
if (this.echartsIntance) {
this.updateProfitChartOptions(this.profitChartData);
}
}
ngAfterViewInit() {
this.theme.getJsTheme()
.pipe(takeWhile(() => this.alive))
.subscribe(config => {
const eTheme: any = config.variables.profit;
this.setOptions(eTheme);
});
}
setOptions(eTheme) {
this.options = {
backgroundColor: eTheme.bg,
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
shadowStyle: {
color: 'rgba(0, 0, 0, 0.3)',
},
},
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
xAxis: [
{
type: 'category',
data: this.profitChartData.chartLabel,
axisTick: {
alignWithLabel: true,
},
axisLine: {
lineStyle: {
color: eTheme.axisLineColor,
},
},
axisLabel: {
color: eTheme.axisTextColor,
fontSize: eTheme.axisFontSize,
},
},
],
yAxis: [
{
type: 'value',
axisLine: {
lineStyle: {
color: eTheme.axisLineColor,
},
},
splitLine: {
lineStyle: {
color: eTheme.splitLineColor,
},
},
axisLabel: {
color: eTheme.axisTextColor,
fontSize: eTheme.axisFontSize,
},
},
],
series: [
{
name: 'Canceled',
type: 'bar',
barGap: 0,
barWidth: '20%',
itemStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: eTheme.firstLineGradFrom,
}, {
offset: 1,
color: eTheme.firstLineGradTo,
}]),
},
},
data: this.profitChartData.data[0],
},
{
name: 'Payment',
type: 'bar',
barWidth: '20%',
itemStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: eTheme.secondLineGradFrom,
}, {
offset: 1,
color: eTheme.secondLineGradTo,
}]),
},
},
data: this.profitChartData.data[1],
},
{
name: 'All orders',
type: 'bar',
barWidth: '20%',
itemStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: eTheme.thirdLineGradFrom,
}, {
offset: 1,
color: eTheme.thirdLineGradTo,
}]),
},
},
data: this.profitChartData.data[2],
},
],
};
}
updateProfitChartOptions(profitChartData: ProfitChart) {
const options = this.options;
const series = this.getNewSeries(options.series, profitChartData.data);
this.echartsIntance.setOption({
series: series,
xAxis: {
data: this.profitChartData.chartLabel,
},
});
}
getNewSeries(series, data: number[][]) {
return series.map((line, index) => {
return {
...line,
data: data[index],
};
});
}
onChartInit(echarts) {
this.echartsIntance = echarts;
}
resizeChart() {
if (this.echartsIntance) {
// Fix recalculation chart size
// TODO: investigate more deeply
setTimeout(() => {
this.echartsIntance.resize();
}, 0);
}
}
ngOnDestroy(): void {
this.alive = false;
}
}

View file

@ -0,0 +1,40 @@
@import '../../../../@theme/styles/themes';
@import '~bootstrap/scss/mixins/breakpoints';
@import '~@nebular/theme/styles/global/bootstrap/breakpoints';
@include nb-install-component() {
display: flex;
flex-direction: column;
height: 100%;
flex: 1;
padding: nb-theme(card-padding);
border-left:
nb-theme(card-header-border-width)
nb-theme(card-header-border-type)
nb-theme(card-header-border-color);
.header {
margin-left: 1rem;
}
.title {
font-family: nb-theme(card-header-font-family);
color: nb-theme(color-fg);
}
.echart {
height: 85%;
}
@include media-breakpoint-down(sm) {
height: 50%;
border-top:
nb-theme(card-border-width)
nb-theme(card-header-border-type)
nb-theme(card-header-border-color);
.echart {
height: 75%;
}
}
}

View file

@ -0,0 +1,164 @@
import { AfterViewInit, Component, Input, OnChanges, OnDestroy, SimpleChanges } from '@angular/core';
import { NbThemeService } from '@nebular/theme';
import { takeWhile } from 'rxjs/operators';
@Component({
selector: 'ngx-country-orders-chart',
styleUrls: ['./country-orders-chart.component.scss'],
template: `
<div class="header">
<span class="title">Selected Country</span>
<h2>{{countryName}}</h2>
</div>
<div echarts [options]="option" class="echart" (chartInit)="onChartInit($event)"></div>
`,
})
export class CountryOrdersChartComponent implements AfterViewInit, OnDestroy, OnChanges {
@Input() countryName: string;
@Input() data: number[];
@Input() maxValue: number;
@Input() labels: string[];
private alive = true;
option: any = {};
echartsInstance;
constructor(private theme: NbThemeService) {
}
ngOnChanges(changes: SimpleChanges): void {
if (changes.data && !changes.data.isFirstChange()) {
this.echartsInstance.setOption({
series: [
{
data: this.data.map(v => this.maxValue),
},
{
data: this.data,
},
{
data: this.data,
},
],
});
}
}
ngAfterViewInit() {
this.theme.getJsTheme()
.pipe(takeWhile(() => this.alive))
.subscribe(config => {
const countriesTheme: any = config.variables.countryOrders;
this.option = Object.assign({}, {
grid: {
left: '3%',
right: '3%',
bottom: '3%',
top: '3%',
containLabel: true,
},
xAxis: {
axisLabel: {
color: countriesTheme.chartAxisTextColor,
fontSize: countriesTheme.chartAxisFontSize,
},
axisLine: {
lineStyle: {
color: countriesTheme.chartAxisLineColor,
width: '2',
},
},
axisTick: {
show: false,
},
splitLine: {
lineStyle: {
color: countriesTheme.chartAxisSplitLine,
width: '1',
},
},
},
yAxis: {
data: this.labels,
axisLabel: {
color: countriesTheme.chartAxisTextColor,
fontSize: countriesTheme.chartAxisFontSize,
},
axisLine: {
lineStyle: {
color: countriesTheme.chartAxisLineColor,
width: '2',
},
},
axisTick: {
show: false,
},
},
series: [
{ // For shadow
type: 'bar',
data: this.data.map(v => this.maxValue),
cursor: 'default',
itemStyle: {
normal: {
color: countriesTheme.chartInnerLineColor,
},
opacity: 1,
},
barWidth: '40%',
barGap: '-100%',
barCategoryGap: '30%',
animation: false,
z: 1,
},
{ // For bottom line
type: 'bar',
data: this.data,
cursor: 'default',
itemStyle: {
normal: {
color: countriesTheme.chartLineBottomShadowColor,
},
opacity: 1,
},
barWidth: '40%',
barGap: '-100%',
barCategoryGap: '30%',
z: 2,
},
{
type: 'bar',
barWidth: '35%',
data: this.data,
cursor: 'default',
itemStyle: {
normal: {
color: new echarts.graphic.LinearGradient(1, 0, 0, 0, [{
offset: 0,
color: countriesTheme.chartGradientFrom,
}, {
offset: 1,
color: countriesTheme.chartGradientTo,
}]),
},
},
z: 3,
},
],
});
});
}
onChartInit(ec) {
this.echartsInstance = ec;
}
ngOnDestroy() {
this.alive = false;
}
}

View file

@ -0,0 +1,18 @@
@import '../../../@theme/styles/themes';
@import '~bootstrap/scss/mixins/breakpoints';
@import '~@nebular/theme/styles/global/bootstrap/breakpoints';
@include nb-install-component() {
nb-card-body {
display: flex;
height: 100%;
padding: 0;
}
@include media-breakpoint-down(sm) {
nb-card-body {
flex-direction: column;
}
}
}

View file

@ -0,0 +1,60 @@
import { Component, OnDestroy } from '@angular/core';
import { NbMediaBreakpoint, NbMediaBreakpointsService, NbThemeService } from '@nebular/theme';
import { takeWhile } from 'rxjs/operators';
@Component({
selector: 'ngx-country-orders',
styleUrls: ['./country-orders.component.scss'],
template: `
<nb-card [size]="breakpoint.width >= breakpoints.md ? 'medium' : 'xxlarge'">
<nb-card-header>Country Orders Statistics</nb-card-header>
<nb-card-body>
<ngx-country-orders-map (select)="selectCountryById($event)"
countryId="USA">
</ngx-country-orders-map>
<ngx-country-orders-chart [countryName]="countryName"
[data]="countryData"
[labels]="countriesCategories"
maxValue="20">
</ngx-country-orders-chart>
</nb-card-body>
</nb-card>
`,
})
export class CountryOrdersComponent implements OnDestroy {
private alive = true;
private getRandomData(nPoints: number): number[] {
return Array.from(Array(nPoints)).map(() => {
return Math.round(Math.random() * 20);
});
}
countryName = '';
countryData = [];
countriesCategories = ['Sofas', 'Furniture', 'Lighting', 'Tables', 'Textiles'];
breakpoint: NbMediaBreakpoint = { name: '', width: 0 };
breakpoints: any;
constructor(private themeService: NbThemeService,
private breakpointService: NbMediaBreakpointsService) {
this.breakpoints = this.breakpointService.getBreakpointsMap();
this.themeService.onMediaQueryChange()
.pipe(takeWhile(() => this.alive))
.subscribe(([oldValue, newValue]) => {
this.breakpoint = newValue;
});
}
selectCountryById(countryName: string) {
const nPoint = this.countriesCategories.length;
this.countryName = countryName;
this.countryData = this.getRandomData(nPoint);
}
ngOnDestroy() {
this.alive = false;
}
}

View file

@ -0,0 +1,70 @@
@import '../../../../@theme/styles/themes';
@import '~bootstrap/scss/mixins/breakpoints';
@import '~@nebular/theme/styles/global/bootstrap/breakpoints';
@include nb-install-component() {
display: block;
height: 100%;
width: 60%;
.leaflet-container {
height: 100%;
background-color: nb-theme(layout-bg);
@include nb-for-theme(default) {
background-color: nb-theme(color-white);
}
@include nb-for-theme(corporate) {
background-color: nb-theme(color-white);
}
}
/deep/ .leaflet-bar {
box-shadow: none;
}
/deep/ .leaflet-control-zoom {
border: none;
a {
background-color: nb-theme(color-success);
color: nb-theme(color-white);
border-bottom: none;
height: 2.5rem;
@include nb-for-theme(cosmic) {
background-color: nb-theme(color-primary);
}
@include nb-for-theme(corporate) {
background-color: nb-theme(color-primary);
}
}
.leaflet-control-zoom-in {
border-top-left-radius: nb-theme(btn-border-radius);
border-top-right-radius: nb-theme(btn-border-radius);
}
.leaflet-control-zoom-out {
margin-top: 1px;
border-bottom-left-radius: nb-theme(btn-border-radius);
border-bottom-right-radius: nb-theme(btn-border-radius);
}
}
/deep/ .leaflet-control-attribution {
background: transparent;
a {
color: nb-theme(color-fg);
}
}
@include media-breakpoint-down(sm) {
width: 100%;
height: 50%;
}
}

View file

@ -0,0 +1,144 @@
import { Component, EventEmitter, Input, OnDestroy, Output } from '@angular/core';
import * as L from 'leaflet';
import { CountryOrdersMapService } from './country-orders-map.service';
import { NbThemeService } from '@nebular/theme';
import { combineLatest } from 'rxjs';
import { takeWhile } from 'rxjs/operators';
@Component({
selector: 'ngx-country-orders-map',
styleUrls: ['./country-orders-map.component.scss'],
template: `
<div leaflet [leafletOptions]="options" [leafletLayers]="layers" (leafletMapReady)="mapReady($event)"></div>
`,
})
export class CountryOrdersMapComponent implements OnDestroy {
@Input() countryId: string;
@Output() select: EventEmitter<any> = new EventEmitter();
layers = [];
currentTheme: any;
alive = true;
selectedCountry;
options = {
zoom: 2,
minZoom: 2,
maxZoom: 6,
zoomControl: false,
center: L.latLng({lat: 38.991709, lng: -76.886109}),
maxBounds: new L.LatLngBounds(
new L.LatLng(-89.98155760646617, -180),
new L.LatLng(89.99346179538875, 180),
),
maxBoundsViscosity: 1.0,
};
constructor(private ecMapService: CountryOrdersMapService,
private theme: NbThemeService) {
combineLatest([
this.ecMapService.getCords(),
this.theme.getJsTheme(),
])
.pipe(takeWhile(() => this.alive))
.subscribe(([cords, config]: [any, any]) => {
this.currentTheme = config.variables.countryOrders;
this.layers = [this.createGeoJsonLayer(cords)];
this.selectFeature(this.findFeatureLayerByCountryId(this.countryId));
});
}
mapReady(map: L.Map) {
map.addControl(L.control.zoom({position: 'bottomright'}));
// fix the map fully displaying, existing leaflet bag
setTimeout(() => {
map.invalidateSize();
}, 0);
}
private createGeoJsonLayer(cords) {
return L.geoJSON(
cords as any,
{
style: () => ({
weight: this.currentTheme.countryBorderWidth,
fillColor: this.currentTheme.countryFillColor,
fillOpacity: 1,
color: this.currentTheme.countryBorderColor,
opacity: 1,
}),
onEachFeature: (f, l) => {
this.onEachFeature(f, l);
},
});
}
private onEachFeature(feature, layer) {
layer.on({
mouseover: (e) => this.highlightFeature(e.target),
mouseout: (e) => this.moveout(e.target),
click: (e) => this.selectFeature(e.target),
});
}
private highlightFeature(featureLayer) {
if (featureLayer) {
featureLayer.setStyle({
weight: this.currentTheme.hoveredCountryBorderWidth,
fillColor: this.currentTheme.hoveredCountryFillColor,
color: this.currentTheme.hoveredCountryBorderColor,
});
if (!L.Browser.ie && !L.Browser.opera12 && !L.Browser.edge) {
featureLayer.bringToFront();
}
}
}
private moveout(featureLayer) {
if (featureLayer !== this.selectedCountry) {
this.resetHighlight(featureLayer);
// When countries have common border we should highlight selected country once again
this.highlightFeature(this.selectedCountry);
}
}
private resetHighlight(featureLayer) {
if (featureLayer) {
const geoJsonLayer = this.layers[0];
geoJsonLayer.resetStyle(featureLayer);
}
}
private selectFeature(featureLayer) {
if (featureLayer !== this.selectedCountry) {
this.resetHighlight(this.selectedCountry);
this.highlightFeature(featureLayer);
this.selectedCountry = featureLayer;
this.select.emit(featureLayer.feature.properties.name);
}
}
private findFeatureLayerByCountryId(id) {
const layers = this.layers[0].getLayers();
const featureLayer = layers.find(item => {
return item.feature.id === id;
});
return featureLayer ? featureLayer : null;
}
ngOnDestroy(): void {
this.alive = false;
}
}

View file

@ -0,0 +1,15 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
@Injectable()
export class CountryOrdersMapService {
constructor(private http: HttpClient) {}
getCords(): Observable<any> {
return this.http.get('./assets/leaflet-countries/countries.geo.json');
}
}

View file

@ -0,0 +1,38 @@
<div class="row">
<div class="col-xxl-5">
<div class="row">
<div class="col-md-6">
<ngx-profit-card></ngx-profit-card>
</div>
<div class="col-md-6">
<ngx-earning-card></ngx-earning-card>
</div>
</div>
<ngx-traffic-reveal-card></ngx-traffic-reveal-card>
</div>
<div class="col-xxl-7">
<ngx-ecommerce-charts></ngx-ecommerce-charts>
</div>
</div>
<div class="row">
<div class="col-xxl-9">
<ngx-country-orders></ngx-country-orders>
</div>
<div class="col-xxl-3">
<ngx-progress-section></ngx-progress-section>
</div>
</div>
<div class="row">
<div class="col-xxl-9">
<ngx-ecommerce-visitors-analytics></ngx-ecommerce-visitors-analytics>
</div>
<div class="col-xxl-3">
<ngx-user-activity></ngx-user-activity>
</div>
</div>

View file

@ -0,0 +1,8 @@
import { Component } from '@angular/core';
@Component({
selector: 'ngx-ecommerce',
templateUrl: './e-commerce.component.html',
})
export class ECommerceComponent {
}

View file

@ -0,0 +1,95 @@
import { NgModule } from '@angular/core';
import { NgxEchartsModule } from 'ngx-echarts';
import { NgxChartsModule } from '@swimlane/ngx-charts';
import { ThemeModule } from '../../@theme/theme.module';
import { ECommerceComponent } from './e-commerce.component';
import { ProfitCardComponent } from './profit-card/profit-card.component';
import { ECommerceChartsPanelComponent } from './charts-panel/charts-panel.component';
import { OrdersChartComponent } from './charts-panel/charts/orders-chart.component';
import { ProfitChartComponent } from './charts-panel/charts/profit-chart.component';
import { ChartPanelHeaderComponent } from './charts-panel/chart-panel-header/chart-panel-header.component';
import { ChartPanelSummaryComponent } from './charts-panel/chart-panel-summary/chart-panel-summary.component';
import { ChartModule } from 'angular2-chartjs';
import { StatsCardBackComponent } from './profit-card/back-side/stats-card-back.component';
import { StatsAreaChartComponent } from './profit-card/back-side/stats-area-chart.component';
import { StatsBarAnimationChartComponent } from './profit-card/front-side/stats-bar-animation-chart.component';
import { StatsCardFrontComponent } from './profit-card/front-side/stats-card-front.component';
import { TrafficRevealCardComponent } from './traffic-reveal-card/traffic-reveal-card.component';
import { TrafficBarComponent } from './traffic-reveal-card/front-side/traffic-bar/traffic-bar.component';
import { TrafficFrontCardComponent } from './traffic-reveal-card/front-side/traffic-front-card.component';
import { TrafficCardsHeaderComponent } from './traffic-reveal-card/traffic-cards-header/traffic-cards-header.component';
import { TrafficBackCardComponent } from './traffic-reveal-card/back-side/traffic-back-card.component';
import { TrafficBarChartComponent } from './traffic-reveal-card/back-side/traffic-bar-chart.component';
import {
ECommerceVisitorsAnalyticsComponent,
} from './visitors-analytics/visitors-analytics.component';
import {
ECommerceVisitorsAnalyticsChartComponent,
} from './visitors-analytics/visitors-analytics-chart/visitors-analytics-chart.component';
import {
ECommerceVisitorsStatisticsComponent,
} from './visitors-analytics/visitors-statistics/visitors-statistics.component';
import { ECommerceLegendChartComponent } from './legend-chart/legend-chart.component';
import { ECommerceUserActivityComponent } from './user-activity/user-activity.component';
import { ECommerceProgressSectionComponent } from './progress-section/progress-section.component';
import { SlideOutComponent } from './slide-out/slide-out.component';
import { CountryOrdersComponent } from './country-orders/country-orders.component';
import { CountryOrdersMapComponent } from './country-orders/map/country-orders-map.component';
import { CountryOrdersMapService } from './country-orders/map/country-orders-map.service';
import { LeafletModule } from '@asymmetrik/ngx-leaflet';
import { CountryOrdersChartComponent } from './country-orders/chart/country-orders-chart.component';
import { EarningCardComponent } from './earning-card/earning-card.component';
import { EarningCardBackComponent } from './earning-card/back-side/earning-card-back.component';
import { EarningPieChartComponent } from './earning-card/back-side/earning-pie-chart.component';
import { EarningCardFrontComponent } from './earning-card/front-side/earning-card-front.component';
import { EarningLiveUpdateChartComponent } from './earning-card/front-side/earning-live-update-chart.component';
@NgModule({
imports: [
ThemeModule,
ChartModule,
NgxEchartsModule,
NgxChartsModule,
LeafletModule,
],
declarations: [
ECommerceComponent,
StatsCardFrontComponent,
StatsAreaChartComponent,
StatsBarAnimationChartComponent,
ProfitCardComponent,
ECommerceChartsPanelComponent,
ChartPanelHeaderComponent,
ChartPanelSummaryComponent,
OrdersChartComponent,
ProfitChartComponent,
StatsCardBackComponent,
TrafficRevealCardComponent,
TrafficBarChartComponent,
TrafficFrontCardComponent,
TrafficBackCardComponent,
TrafficBarComponent,
TrafficCardsHeaderComponent,
CountryOrdersComponent,
CountryOrdersMapComponent,
CountryOrdersChartComponent,
ECommerceVisitorsAnalyticsComponent,
ECommerceVisitorsAnalyticsChartComponent,
ECommerceVisitorsStatisticsComponent,
ECommerceLegendChartComponent,
ECommerceUserActivityComponent,
ECommerceProgressSectionComponent,
SlideOutComponent,
EarningCardComponent,
EarningCardFrontComponent,
EarningCardBackComponent,
EarningPieChartComponent,
EarningLiveUpdateChartComponent,
],
providers: [
CountryOrdersMapService,
],
})
export class ECommerceModule { }

View file

@ -0,0 +1,14 @@
<nb-card-header>
Earnings
</nb-card-header>
<nb-card-body>
<div class="chart-info">
<div class="title" [style.color]="color">{{ name }}</div>
<div class="time-period">Last week</div>
<div class="value">{{ value }}%</div>
</div>
<ngx-earning-pie-chart [values]="earningPieChartData"
(selectPie)="changeChartInfo($event)"
[defaultSelectedCurrency]="defaultSelectedCurrency">
</ngx-earning-pie-chart>
</nb-card-body>

View file

@ -0,0 +1,51 @@
@import '../../../../@theme/styles/themes';
@import '~bootstrap/scss/mixins/breakpoints';
@import '~@nebular/theme/styles/global/bootstrap/breakpoints';
@include nb-install-component() {
nb-card-header {
flex-direction: row;
}
ngx-earning-pie-chart, .chart-info {
flex: 1;
}
.chart-info {
padding-top: 0.7rem;
}
.title {
font-size: nb-theme(font-size-xlg);
}
.time-period {
margin-top: 1.5rem;
color: nb-theme(color-fg);
}
.value {
margin-top: 0.2rem;
font-size: 1.5rem;
font-weight: nb-theme(font-weight-bold);
color: nb-theme(color-fg-heading);
}
.echart {
position: absolute;
width: calc(50% - #{nb-theme(card-padding)});
height: calc(100% - 2 * #{nb-theme(card-padding)});
}
@include media-breakpoint-between(xl, xl) {
ngx-earning-pie-chart {
flex: 2;
}
}
@include media-breakpoint-between(sm, sm) {
ngx-earning-pie-chart {
flex: 2;
}
}
}

View file

@ -0,0 +1,36 @@
import { Component, OnDestroy } from '@angular/core';
import { EarningService, PieChart } from '../../../../@core/data/earning.service';
import { takeWhile } from 'rxjs/operators';
@Component({
selector: 'ngx-earning-card-back',
styleUrls: ['./earning-card-back.component.scss'],
templateUrl: './earning-card-back.component.html',
})
export class EarningCardBackComponent implements OnDestroy {
private alive = true;
earningPieChartData: PieChart[];
name: string;
color: string;
value: number;
defaultSelectedCurrency: string = 'Bitcoin';
constructor(private earningService: EarningService ) {
this.earningService.getEarningPieChartData()
.pipe(takeWhile(() => this.alive))
.subscribe((earningPieChartData) => {
this.earningPieChartData = earningPieChartData;
});
}
changeChartInfo(pieData: {value: number; name: string; color: any}) {
this.value = pieData.value;
this.name = pieData.name;
this.color = pieData.color;
}
ngOnDestroy() {
this.alive = false;
}
}

View file

@ -0,0 +1,209 @@
import { AfterViewInit, Component, EventEmitter, Input, OnDestroy, Output } from '@angular/core';
import { NbThemeService } from '@nebular/theme';
import { delay, takeWhile } from 'rxjs/operators';
@Component({
selector: 'ngx-earning-pie-chart',
styleUrls: ['./earning-card-back.component.scss'],
template: `
<div echarts
class="echart"
[options]="options"
(chartInit)="onChartInit($event)"
(chartClick)="onChartClick($event)">
</div>
`,
})
export class EarningPieChartComponent implements AfterViewInit, OnDestroy {
@Output() selectPie = new EventEmitter<{value: number; name: string; color: string}>();
@Input() values: {value: number; name: string; }[];
@Input() defaultSelectedCurrency: string;
private alive = true;
options: any = {};
echartsInstance;
constructor(private theme: NbThemeService) {
}
onChartInit(ec) {
this.echartsInstance = ec;
}
onChartClick(event) {
const pieData = {
value: event.value,
name: event.name,
color: event.color.colorStops[0].color,
};
this.emitSelectPie(pieData);
}
emitSelectPie(pieData: {value: number; name: string; color: any}) {
this.selectPie.emit(pieData);
}
ngAfterViewInit() {
this.theme.getJsTheme()
.pipe(
takeWhile(() => this.alive),
delay(1),
)
.subscribe(config => {
const variables = config.variables;
this.options = this.getOptions(variables);
const defaultSelectedData =
this.options.series[0].data.find((item) => item.name === this.defaultSelectedCurrency);
const color = defaultSelectedData.itemStyle.normal.color.colorStops[0].color;
const pieData = {
value: defaultSelectedData.value,
name: defaultSelectedData.name,
color,
};
this.emitSelectPie(pieData);
});
}
getOptions(variables) {
const earningPie: any = variables.earningPie;
return {
tooltip: {
trigger: 'item',
formatter: '',
},
series: [
{
name: ' ',
clockWise: true,
hoverAnimation: false,
type: 'pie',
center: earningPie.center,
radius: earningPie.radius,
data: [
{
value: this.values[0].value,
name: this.values[0].name,
label: {
normal: {
position: 'center',
formatter: '',
textStyle: {
fontSize: '22',
fontFamily: variables.fontSecondary,
fontWeight: '600',
color: variables.fgHeading,
},
},
},
tooltip: {
show: false,
},
itemStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: earningPie.firstPieGradientLeft,
},
{
offset: 1,
color: earningPie.firstPieGradientRight,
},
]),
shadowColor: earningPie.firstPieShadowColor,
shadowBlur: 0,
shadowOffsetX: 0,
shadowOffsetY: 3,
},
},
},
{
value: this.values[1].value,
name: this.values[1].name,
label: {
normal: {
position: 'center',
formatter: '',
textStyle: {
fontSize: '22',
fontFamily: variables.fontSecondary,
fontWeight: '600',
color: variables.fgHeading,
},
},
},
tooltip: {
show: false,
},
itemStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: earningPie.secondPieGradientLeft,
},
{
offset: 1,
color: earningPie.secondPieGradientRight,
},
]),
shadowColor: earningPie.secondPieShadowColor,
shadowBlur: 0,
shadowOffsetX: 0,
shadowOffsetY: 3,
},
},
},
{
value: this.values[2].value,
name: this.values[2].name,
label: {
normal: {
position: 'center',
formatter: '',
textStyle: {
fontSize: '22',
fontFamily: variables.fontSecondary,
fontWeight: '600',
color: variables.fgHeading,
},
},
},
tooltip: {
show: false,
},
itemStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: earningPie.thirdPieGradientLeft,
},
{
offset: 1,
color: earningPie.thirdPieGradientRight,
},
]),
shadowColor: earningPie.thirdPieShadowColor,
shadowBlur: 0,
shadowOffsetX: 0,
shadowOffsetY: 3,
},
},
},
],
},
],
};
}
ngOnDestroy() {
this.alive = false;
}
}

View file

@ -0,0 +1,14 @@
<nb-flip-card [showToggleButton]="false" [flipped]="flipped">
<nb-card-front>
<nb-card size="xsmall">
<ngx-earning-card-front></ngx-earning-card-front>
<i class="nb-arrow-right" (click)="toggleFlipView()"></i>
</nb-card>
</nb-card-front>
<nb-card-back>
<nb-card size="xsmall">
<ngx-earning-card-back></ngx-earning-card-back>
<i class="nb-arrow-right" (click)="toggleFlipView()"></i>
</nb-card>
</nb-card-back>
</nb-flip-card>

View file

@ -0,0 +1,49 @@
@import '../../../@theme/styles/themes';
@include nb-install-component() {
nb-card {
position: relative;
}
.nb-arrow-right {
position: absolute;
top: 0;
right: 0;
@include nb-rtl(right, auto);
@include nb-rtl(left, 0);
padding: 1.5rem;
cursor: pointer;
}
/deep/ .flipped {
.back-container {
.nb-arrow-right {
transform: scaleX(-1);
}
}
.front-container {
.nb-arrow-right {
display: none;
}
}
}
ngx-earning-card-back, ngx-earning-card-front {
display: flex;
flex-direction: column;
flex: 1;
}
/deep/ nb-card-header {
display: flex;
justify-content: space-between;
@include nb-rtl(flex-direction, row-reverse);
padding-left: 1rem;
}
/deep/ nb-card-body {
overflow: hidden;
display: flex;
}
}

View file

@ -0,0 +1,15 @@
import { Component } from '@angular/core';
@Component({
selector: 'ngx-earning-card',
styleUrls: ['./earning-card.component.scss'],
templateUrl: './earning-card.component.html',
})
export class EarningCardComponent {
flipped = false;
toggleFlipView() {
this.flipped = !this.flipped;
}
}

View file

@ -0,0 +1,27 @@
<nb-card-header>
<div class="dropdown ghost-dropdown" ngbDropdown>
<button type="button" ngbDropdownToggle class="btn"
[ngClass]="{
'btn-success': currentTheme === 'default',
'btn-primary': currentTheme !== 'default'}">
{{ selectedCurrency }}
</button>
<ul class="dropdown-menu" ngbDropdownMenu>
<li class="dropdown-item" *ngFor="let currency of currencies" (click)="changeCurrency(currency)">{{ currency }}</li>
</ul>
</div>
</nb-card-header>
<nb-card-body>
<div class="chart-info">
<div class="title">Daily Income</div>
<div class="value">{{ earningLiveUpdateCardData.dailyIncome | ngxNumberWithCommas }}</div>
<div class="delta"
[class.up]="earningLiveUpdateCardData.delta.up"
[class.down]="!earningLiveUpdateCardData.delta.up">
{{ earningLiveUpdateCardData.delta.value }}%
</div>
</div>
<ngx-earning-live-update-chart
[liveUpdateChartData]="liveUpdateChartData">
</ngx-earning-live-update-chart>
</nb-card-body>

View file

@ -0,0 +1,116 @@
@import '../../../../@theme/styles/themes';
@import '~@nebular/theme/styles/global/typography/typography';
@include nb-install-component() {
display: flex;
flex-direction: column;
flex: 1;
nb-card-header {
flex-direction: row;
padding-top: 0.45rem;
padding-bottom: 0.45rem;
}
.dropdown {
min-width: 8.125rem;
}
nb-card-body {
padding: 1rem 0 0;
flex-direction: column;
flex: 1;
}
.chart-info {
position: relative;
display: flex;
flex-direction: column;
margin: 0 nb-theme(card-padding);
}
.title {
color: nb-theme(color-fg-heading);
}
.value {
color: nb-theme(color-success);
font-size: 1.5rem;
@include nb-for-theme(corporate) {
color: nb-theme(color-primary);
}
}
.delta {
position: absolute;
display: inline-block;
color: nb-theme(color-fg-heading);
padding-left: 0.5rem;
font-size: 1rem;
top: 0;
right: 0;
@include nb-rtl(left, 1.25rem);
@include nb-rtl(right, inherit);
&::before {
position: absolute;
content: '';
right: 100%;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
}
&.down {
&::before {
bottom: 4px;
border-top: 7px solid text-danger();
}
}
&.up {
&::before {
top: 4px;
border-bottom: 7px solid text-success();
}
}
}
ngx-earning-live-update-chart {
flex: 1;
position: relative;
}
/deep/ canvas {
border-bottom-left-radius: nb-theme(card-border-radius);
border-bottom-right-radius: nb-theme(card-border-radius);
}
.echart {
position: absolute;
height: 100%;
width: 100%;
}
@include nb-for-theme(corporate) {
.delta {
&.down {
color: text-danger();
&::before {
bottom: 4px;
border-top: 7px solid text-danger();
}
}
&.up {
color: text-primary();
&::before {
top: 4px;
border-bottom: 7px solid text-primary();
}
}
}
}
}

View file

@ -0,0 +1,74 @@
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
import { NbThemeService } from '@nebular/theme';
import { interval } from 'rxjs';
import { switchMap, takeWhile } from 'rxjs/operators';
import { EarningService, LiveUpdateChart } from '../../../../@core/data/earning.service';
import { Subscription } from 'rxjs/Subscription';
@Component({
selector: 'ngx-earning-card-front',
styleUrls: ['./earning-card-front.component.scss'],
templateUrl: './earning-card-front.component.html',
})
export class EarningCardFrontComponent implements OnDestroy, OnInit {
private alive = true;
@Input() selectedCurrency: string = 'Bitcoin';
intervalSubscription: Subscription;
currencies: string[] = ['Bitcoin', 'Tether', 'Ethereum'];
currentTheme: string;
earningLiveUpdateCardData: LiveUpdateChart;
liveUpdateChartData: { value: [string, number] }[];
constructor(private themeService: NbThemeService,
private earningService: EarningService) {
this.themeService.getJsTheme()
.pipe(takeWhile(() => this.alive))
.subscribe(theme => {
this.currentTheme = theme.name;
});
}
ngOnInit() {
this.getEarningCardData(this.selectedCurrency);
}
changeCurrency(currency) {
if (this.selectedCurrency !== currency) {
this.selectedCurrency = currency;
this.getEarningCardData(this.selectedCurrency);
}
}
private getEarningCardData(currency) {
this.earningService.getEarningLiveUpdateCardData(currency)
.pipe(takeWhile(() => this.alive))
.subscribe((earningLiveUpdateCardData) => {
this.earningLiveUpdateCardData = earningLiveUpdateCardData;
this.liveUpdateChartData = earningLiveUpdateCardData.liveChart;
this.startReceivingLiveData(currency);
});
}
startReceivingLiveData(currency) {
if (this.intervalSubscription) {
this.intervalSubscription.unsubscribe();
}
this.intervalSubscription = interval(200)
.pipe(
takeWhile(() => this.alive),
switchMap(() => this.earningService.generateRandomEarningData(currency)),
)
.subscribe((liveUpdateChartData) => {
this.liveUpdateChartData = [...liveUpdateChartData];
});
}
ngOnDestroy() {
this.alive = false;
}
}

View file

@ -0,0 +1,151 @@
import { delay, takeWhile } from 'rxjs/operators';
import { AfterViewInit, Component, Input, OnChanges, OnDestroy } from '@angular/core';
import { NbThemeService } from '@nebular/theme';
@Component({
selector: 'ngx-earning-live-update-chart',
styleUrls: ['earning-card-front.component.scss'],
template: `
<div echarts
class="echart"
[options]="option"
(chartInit)="onChartInit($event)"></div>
`,
})
export class EarningLiveUpdateChartComponent implements AfterViewInit, OnDestroy, OnChanges {
private alive = true;
@Input() liveUpdateChartData: { value: [string, number] }[];
option: any;
echartsInstance;
constructor(private theme: NbThemeService) {
}
ngOnChanges(): void {
if (this.option) {
this.updateChartOptions(this.liveUpdateChartData);
}
}
onChartInit(ec) {
this.echartsInstance = ec;
}
ngAfterViewInit() {
this.theme.getJsTheme()
.pipe(
delay(1),
takeWhile(() => this.alive),
)
.subscribe(config => {
const earningLineTheme: any = config.variables.earningLine;
this.setChartOption(earningLineTheme);
});
}
setChartOption(earningLineTheme) {
this.option = {
grid: {
left: 0,
top: 0,
right: 0,
bottom: 0,
},
xAxis: {
type: 'time',
axisLine: {
show: false,
},
axisLabel: {
show: false,
},
axisTick: {
show: false,
},
splitLine: {
show: false,
},
},
yAxis: {
boundaryGap: [0, '5%'],
axisLine: {
show: false,
},
axisLabel: {
show: false,
},
axisTick: {
show: false,
},
splitLine: {
show: false,
},
},
tooltip: {
axisPointer: {
type: 'shadow',
},
textStyle: {
color: earningLineTheme.tooltipTextColor,
fontWeight: earningLineTheme.tooltipFontWeight,
fontSize: earningLineTheme.tooltipFontSize,
},
position: 'top',
backgroundColor: earningLineTheme.tooltipBg,
borderColor: earningLineTheme.tooltipBorderColor,
borderWidth: earningLineTheme.tooltipBorderWidth,
formatter: params => `$ ${Math.round(parseInt(params.value[1], 10))}`,
extraCssText: earningLineTheme.tooltipExtraCss,
},
series: [
{
type: 'line',
symbol: 'circle',
sampling: 'average',
itemStyle: {
normal: {
opacity: 0,
},
emphasis: {
opacity: 0,
},
},
lineStyle: {
normal: {
width: 0,
},
},
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: earningLineTheme.gradFrom,
}, {
offset: 1,
color: earningLineTheme.gradTo,
}]),
opacity: 1,
},
},
data: this.liveUpdateChartData,
},
],
animation: true,
};
}
updateChartOptions(chartData: { value: [string, number] }[]) {
this.echartsInstance.setOption({
series: [{
data: chartData,
}],
});
}
ngOnDestroy() {
this.alive = false;
}
}

View file

@ -0,0 +1,7 @@
export enum NgxLegendItemColor {
GREEN = 'green',
PURPLE = 'purple',
LIGHT_PURPLE = 'light-purple',
BLUE = 'blue',
YELLOW = 'yellow',
}

View file

@ -0,0 +1,7 @@
<div class="legends">
<div *ngFor="let legend of legendItems" class="legend">
<div class="legend-item-color"
[style.background]="legend.iconColor"></div>
<div class="legend-title">{{ legend.title }}</div>
</div>
</div>

View file

@ -0,0 +1,40 @@
@import '../../../@theme/styles/themes';
@import '~bootstrap/scss/mixins/breakpoints';
@import '~@nebular/theme/styles/global/bootstrap/breakpoints';
@include nb-install-component() {
.legends {
display: flex;
@include nb-rtl(flex-direction, row-reverse);
color: nb-theme(color-fg);
padding: 0 0 0 2.85rem;
}
.legend {
display: flex;
justify-content: space-between;
align-items: baseline;
margin-left: 4rem;
&:first-child {
margin-left: 0;
}
}
.legend-item-color {
min-width: 15px;
min-height: 15px;
border-radius: 0.2rem;
}
.legend-title {
padding: 0 0.75rem;
white-space: nowrap;
}
@include media-breakpoint-down(md) {
.legend {
margin-left: 1.5rem;
}
}
}

View file

@ -0,0 +1,19 @@
import { Component, Input } from '@angular/core';
import { NgxLegendItemColor } from './enum.legend-item-color';
@Component({
selector: 'ngx-legend-chart',
styleUrls: ['./legend-chart.component.scss'],
templateUrl: './legend-chart.component.html',
})
export class ECommerceLegendChartComponent {
/**
* Take an array of legend items
* Available iconColor: 'green', 'purple', 'light-purple', 'blue', 'yellow'
* @type {{iconColor: string; title: string}[]}
*/
@Input()
legendItems: { iconColor: NgxLegendItemColor; title: string }[] = [];
}

View file

@ -0,0 +1,152 @@
import { delay, takeWhile } from 'rxjs/operators';
import { AfterViewInit, Component, OnDestroy } from '@angular/core';
import { NbThemeService } from '@nebular/theme';
const points = [300, 520, 435, 530, 730, 620, 660, 860];
@Component({
selector: 'ngx-stats-ares-chart',
styleUrls: ['stats-card-back.component.scss'],
template: `
<div echarts [options]="option" class="echart"></div>
`,
})
export class StatsAreaChartComponent implements AfterViewInit, OnDestroy {
private alive = true;
option: any = {};
constructor(private theme: NbThemeService) {
}
ngAfterViewInit() {
this.theme.getJsTheme()
.pipe(
delay(1),
takeWhile(() => this.alive),
)
.subscribe(config => {
const trafficTheme: any = config.variables.traffic;
this.option = Object.assign({}, {
grid: {
left: 0,
top: 0,
right: 0,
bottom: 0,
},
xAxis: {
type: 'category',
boundaryGap: false,
data: points,
},
yAxis: {
boundaryGap: [0, '5%'],
axisLine: {
show: false,
},
axisLabel: {
show: false,
},
axisTick: {
show: false,
},
splitLine: {
show: true,
lineStyle: {
color: trafficTheme.colorBlack,
opacity: 0.06,
width: '1',
},
},
},
tooltip: {
axisPointer: {
type: 'shadow',
},
textStyle: {
color: trafficTheme.tooltipTextColor,
fontWeight: trafficTheme.tooltipFontWeight,
fontSize: 16,
},
position: 'top',
backgroundColor: trafficTheme.tooltipBg,
borderColor: trafficTheme.tooltipBorderColor,
borderWidth: 3,
formatter: '$ {c0}',
extraCssText: trafficTheme.tooltipExtraCss,
},
series: [
{
type: 'line',
symbol: 'circle',
symbolSize: 8,
sampling: 'average',
silent: true,
itemStyle: {
normal: {
color: trafficTheme.shadowLineDarkBg,
},
emphasis: {
color: 'rgba(0,0,0,0)',
borderColor: 'rgba(0,0,0,0)',
borderWidth: 0,
},
},
lineStyle: {
normal: {
width: 2,
color: trafficTheme.shadowLineDarkBg,
},
},
data: points.map(p => p - 15),
},
{
type: 'line',
symbol: 'circle',
symbolSize: 6,
sampling: 'average',
itemStyle: {
normal: {
color: trafficTheme.itemColor,
borderColor: trafficTheme.itemBorderColor,
borderWidth: 2,
},
emphasis: {
color: 'white',
borderColor: trafficTheme.itemEmphasisBorderColor,
borderWidth: 2,
},
},
lineStyle: {
normal: {
width: 2,
color: trafficTheme.lineBg,
shadowColor: trafficTheme.lineBg,
shadowBlur: trafficTheme.lineShadowBlur,
},
},
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: trafficTheme.gradFrom,
}, {
offset: 1,
color: trafficTheme.gradTo,
}]),
opacity: 1,
},
},
data: points,
},
],
});
});
}
ngOnDestroy() {
this.alive = false;
}
}

View file

@ -0,0 +1,27 @@
<nb-card-header>
<div class="header-container">
<div class="icon">
<i class="ion-social-usd"></i>
</div>
<span class="title">Profit</span>
</div>
</nb-card-header>
<nb-card-body class="p-0">
<div class="info">
<div class="period">
<span class="time-interval">Jun 1 - Jun 30</span>
<div class="value">
<span class="currency">$</span>
300
</div>
</div>
<div class="period latest">
<span class="time-interval">Jul 1 - Jul 31</span>
<div class="value">
<span class="currency">$</span>
860
</div>
</div>
</div>
<ngx-stats-ares-chart></ngx-stats-ares-chart>
</nb-card-body>

View file

@ -0,0 +1,84 @@
@import '../../../../@theme/styles/themes';
@include nb-install-component() {
display: flex;
flex-direction: column;
flex: 1;
/deep/ nb-card-header {
display: flex;
justify-content: space-between;
.header-container {
display: flex;
@include nb-rtl(flex-direction, row-reverse);
}
.title {
padding-left: 1rem;
}
}
/deep/ nb-card-body {
overflow: hidden;
display: flex;
flex-direction: column;
.info {
padding: 0.75rem;
padding-bottom: 0.5rem;
display: flex;
@include nb-rtl(flex-direction, row-reverse);
}
.period {
display: flex;
flex-direction: column;
flex: 1;
&.latest {
padding-left: 0.75rem;
}
}
.time-interval {
color: nb-theme(color-fg);
}
.value {
font-family: nb-theme(font-secondary), serif;
font-size: 1.25rem;
font-weight: nb-theme(font-weight-bold);
color: nb-theme(card-fg-heading);
@include nb-for-theme(default) {
color: nb-theme(color-success);
}
.currency {
color: nb-theme(color-success);
@include nb-for-theme(corporate) {
color: nb-theme(color-primary);
}
}
}
}
ngx-stats-ares-chart {
flex: 1;
position: relative;
}
/deep/ canvas {
border-bottom-left-radius: nb-theme(card-border-radius);
border-bottom-right-radius: nb-theme(card-border-radius);
}
.echart {
position: absolute;
height: 100%;
width: 100%;
}
}

View file

@ -0,0 +1,9 @@
import { Component } from '@angular/core';
@Component({
selector: 'ngx-stats-card-back',
styleUrls: ['./stats-card-back.component.scss'],
templateUrl: './stats-card-back.component.html',
})
export class StatsCardBackComponent {
}

View file

@ -0,0 +1,131 @@
import { AfterViewInit, Component, Input, OnDestroy } from '@angular/core';
import { NbThemeService } from '@nebular/theme';
import { takeWhile } from 'rxjs/operators';
@Component({
selector: 'ngx-stats-bar-animation-chart',
template: `
<div echarts [options]="options" class="echart"></div>
`,
})
export class StatsBarAnimationChartComponent implements AfterViewInit, OnDestroy {
private alive = true;
@Input() linesData: { firstLine: number[]; secondLine: number[] } = {
firstLine: [],
secondLine: [],
};
options: any = {};
constructor(private theme: NbThemeService) {
}
ngAfterViewInit() {
this.theme.getJsTheme()
.pipe(takeWhile(() => this.alive))
.subscribe(config => {
const profitBarAnimationEchart: any = config.variables.profitBarAnimationEchart;
this.setChartOption(profitBarAnimationEchart);
});
}
setChartOption(chartVariables) {
this.options = {
color: [
chartVariables.firstAnimationBarColor,
chartVariables.secondAnimationBarColor,
],
grid: {
left: 0,
top: 0,
right: 0,
bottom: 0,
},
legend: {
data: ['transactions', 'orders'],
borderWidth: 0,
borderRadius: 0,
itemWidth: 15,
itemHeight: 15,
textStyle: {
color: chartVariables.textColor,
},
},
tooltip: {
axisPointer: {
type: 'shadow',
},
textStyle: {
color: chartVariables.tooltipTextColor,
fontWeight: chartVariables.tooltipFontWeight,
fontSize: chartVariables.tooltipFontSize,
},
position: 'top',
backgroundColor: chartVariables.tooltipBg,
borderColor: chartVariables.tooltipBorderColor,
borderWidth: chartVariables.tooltipBorderWidth,
formatter: params => `$ ${Math.round(parseInt(params.value, 10))}`,
extraCssText: chartVariables.tooltipExtraCss,
},
xAxis: [
{
data: this.linesData.firstLine.map((_, index) => index),
silent: false,
axisLine: {
show: false,
},
axisLabel: {
show: false,
},
axisTick: {
show: false,
},
},
],
yAxis: [
{
axisLine: {
show: false,
},
axisLabel: {
show: false,
},
axisTick: {
show: false,
},
splitLine: {
show: true,
lineStyle: {
color: chartVariables.splitLineStyleColor,
opacity: chartVariables.splitLineStyleOpacity,
width: chartVariables.splitLineStyleWidth,
},
},
},
],
series: [
{
name: 'transactions',
type: 'bar',
data: this.linesData.firstLine,
animationDelay: idx => idx * 10,
},
{
name: 'orders',
type: 'bar',
data: this.linesData.secondLine,
animationDelay: idx => idx * 10 + 100,
},
],
animationEasing: 'elasticOut',
animationDelayUpdate: idx => idx * 5,
};
}
ngOnDestroy(): void {
this.alive = false;
}
}

View file

@ -0,0 +1,11 @@
<nb-card-header>
<div class="header-container">
<div class="icon">
<i class="ion-social-usd"></i>
</div>
<span class="title">Profit</span>
</div>
</nb-card-header>
<nb-card-body>
<ngx-stats-bar-animation-chart [linesData]="linesData"></ngx-stats-bar-animation-chart>
</nb-card-body>

View file

@ -0,0 +1,78 @@
@import '../../../../@theme/styles/themes';
@include nb-install-component() {
display: flex;
flex-direction: column;
flex: 1;
/deep/ nb-card-header {
display: flex;
justify-content: space-between;
.header-container {
display: flex;
@include nb-rtl(flex-direction, row-reverse);
}
.title {
padding-left: 1rem;
}
}
/deep/ nb-card-body {
display: flex;
position: relative;
flex: 1;
padding: 0;
overflow: hidden;
.period {
display: flex;
flex-direction: column;
flex: 1;
justify-content: flex-end;
}
.time-interval {
font-size: nb-theme(font-size-sm);
color: nb-theme(color-fg);
white-space: nowrap;
}
.value {
font-family: nb-theme(font-secondary), serif;
font-weight: nb-theme(font-weight-bold);
color: nb-theme(card-fg-heading);
.currency {
color: nb-theme(color-success);
@include nb-for-theme(corporate) {
color: nb-theme(color-primary);
}
}
}
.start-period {
.time-interval, .value {
text-align: right;
}
}
.end-period {
.time-interval, .value {
text-align: left;
}
}
ngx-stats-bar-animation-chart {
flex: 1;
position: relative;
.echart {
height: 100%;
width: 100%;
}
}
}
}

View file

@ -0,0 +1,23 @@
import { Component } from '@angular/core';
import { ProfitBarAnimationChartService } from '../../../../@core/data/profit-bar-animation-chart.service';
import { takeWhile } from 'rxjs/operators';
@Component({
selector: 'ngx-stats-card-front',
styleUrls: ['./stats-card-front.component.scss'],
templateUrl: './stats-card-front.component.html',
})
export class StatsCardFrontComponent {
private alive = true;
linesData: { firstLine: number[]; secondLine: number[] };
constructor(private profitBarAnimationChartService: ProfitBarAnimationChartService) {
this.profitBarAnimationChartService.getChartData()
.pipe(takeWhile(() => this.alive))
.subscribe((linesData) => {
this.linesData = linesData;
});
}
}

View file

@ -0,0 +1,14 @@
<nb-flip-card [showToggleButton]="false" [flipped]="flipped">
<nb-card-front>
<nb-card size="xsmall">
<ngx-stats-card-front></ngx-stats-card-front>
<i class="nb-arrow-right" (click)="toggleView()"></i>
</nb-card>
</nb-card-front>
<nb-card-back>
<nb-card size="xsmall">
<ngx-stats-card-back></ngx-stats-card-back>
<i class="nb-arrow-right" (click)="toggleView()"></i>
</nb-card>
</nb-card-back>
</nb-flip-card>

View file

@ -0,0 +1,27 @@
@import '../../../@theme/styles/themes';
@include nb-install-component() {
.nb-arrow-right {
position: absolute;
right: 0;
top: 0;
@include nb-rtl(right, auto);
@include nb-rtl(left, 0);
padding: 1.5rem;
cursor: pointer;
}
::ng-deep .flipped {
.back-container {
.nb-arrow-right {
transform: scaleX(-1);
}
}
.front-container {
.nb-arrow-right {
display: none;
}
}
}
}

View file

@ -0,0 +1,15 @@
import { Component } from '@angular/core';
@Component({
selector: 'ngx-profit-card',
styleUrls: ['./profit-card.component.scss'],
templateUrl: './profit-card.component.html',
})
export class ProfitCardComponent {
flipped = false;
toggleView() {
this.flipped = !this.flipped;
}
}

View file

@ -0,0 +1,12 @@
<nb-card size="medium">
<nb-card-body>
<div class="progress-info" *ngFor="let item of progressInfoData">
<div class="title">{{ item.title }}</div>
<div class="value">{{ item.value | ngxNumberWithCommas }}</div>
<nb-progress-bar [value]="item.activeProgress"></nb-progress-bar>
<div class="description">
<bdi>{{ item.description }}</bdi>
</div>
</div>
</nb-card-body>
</nb-card>

View file

@ -0,0 +1,53 @@
@import '../../../@theme/styles/themes';
$shadow-green: #00977e;
@include nb-install-component() {
.progress-info {
color: nb-theme(color-fg-heading);
margin-top: 2.5rem;
&:first-child {
margin-top: 0;
}
}
.title {
font-family: nb-theme(font-secondary);
font-size: nb-theme(font-size-lg);
font-weight: nb-theme(font-weight-bold);
}
.value {
font-size: 3rem;
font-weight: nb-theme(font-weight-light);
margin-top: 0.5rem;
}
.description {
color: nb-theme(color-fg);
margin-top: 0.5rem;
}
/deep/ nb-progress-bar {
margin-top: 0.2rem;
.progress-container {
height: 0.8rem;
border-radius: 0;
}
.progress-value {
height: 0.6rem;
background: nb-theme(progress-bar-background);
@include nb-for-theme(cosmic) {
box-shadow: 0 0.2rem $shadow-green;
}
@include nb-for-theme(corporate) {
height: 100%;
}
}
}
}

View file

@ -0,0 +1,29 @@
import {Component} from '@angular/core';
@Component({
selector: 'ngx-progress-section',
styleUrls: ['./progress-section.component.scss'],
templateUrl: './progress-section.component.html',
})
export class ECommerceProgressSectionComponent {
progressInfoData = [
{
title: 'Todays Profit',
value: 572900,
activeProgress: 70,
description: 'Better than last week (70%)',
},
{
title: 'New Orders',
value: 6378,
activeProgress: 30,
description: 'Better than last week (30%)',
},
{
title: 'New Comments',
value: 200,
activeProgress: 55,
description: 'Better than last week (55%)',
},
];
}

View file

@ -0,0 +1,11 @@
<i class="show-hide-toggle"
[class.nb-arrow-thin-left]="!showVisitorsStatistics"
[class.nb-arrow-thin-right]="showVisitorsStatistics"
(click)="toggleStatistics()"></i>
<div class="slide-out-container"
[class.expanded]="showVisitorsStatistics"
[class.collapsed]="!showVisitorsStatistics">
<div class="content-wrapper">
<ng-content></ng-content>
</div>
</div>

View file

@ -0,0 +1,113 @@
@import '../../../@theme/styles/themes';
@import '~bootstrap/scss/mixins/breakpoints';
@import '~@nebular/theme/styles/global/bootstrap/breakpoints';
@include nb-install-component() {
$slide-out-container-width: nb-theme(slide-out-container-width);
// toggle button
.show-hide-toggle {
position: absolute;
display: block;
font-size: 2rem;
font-weight: nb-theme(font-weight-bold);
top: 1.5rem;
right: 1.5rem;
cursor: pointer;
color: nb-theme(color-fg);
background-color: transparent;
z-index: 2;
@include nb-rtl(right, auto);
@include nb-rtl(left, 1.5rem);
}
// toggle button
.slide-out-container {
position: absolute;
padding: 1.5rem;
width: $slide-out-container-width;
}
.slide-out-container,
.slide-out-container::before {
display: block;
height: 100%;
top: 0;
overflow: hidden;
transition: all 0.5s ease-out;
}
.slide-out-container::before {
content: '';
right: 0;
@include nb-rtl(right, auto);
@include nb-rtl(left, 0);
width: 100%;
position: absolute;
background: nb-theme(slide-out-background);
box-shadow: nb-theme(slide-out-shadow-color);
@include nb-rtl(box-shadow, nb-theme(slide-out-shadow-color-rtl));
filter: blur(3px);
opacity: 0.9;
z-index: 1;
}
.slide-out-container.collapsed {
left: calc(100% - 6rem);
@include nb-rtl(left, auto);
@include nb-rtl(right, calc(100% - 6rem));
}
.slide-out-container.expanded {
left: calc(100% - #{$slide-out-container-width});
@include nb-rtl(left, auto);
@include nb-rtl(right, calc(100% - #{$slide-out-container-width}));
}
.content-wrapper {
z-index: 1;
position: relative;
width: 100%;
margin: 0 6rem;
transition: all 0.5s ease-out;
}
.expanded .content-wrapper {
margin: 0;
}
@include media-breakpoint-down(md) {
$slide-out-container-width: 50%;
.slide-out-container {
width: $slide-out-container-width;
}
.slide-out-container.expanded {
left: calc(100% - #{$slide-out-container-width});
@include nb-rtl(right, calc(100% - #{$slide-out-container-width}));
}
}
@include media-breakpoint-down(is) {
$slide-out-container-width: 100%;
.show-hide-toggle {
right: 0.5rem;
}
.slide-out-container {
width: $slide-out-container-width;
}
.slide-out-container.collapsed {
left: calc(100% - 3rem);
@include nb-rtl(right, calc(100% - 3rem));
}
.slide-out-container.expanded {
left: calc(100% - #{$slide-out-container-width});
@include nb-rtl(right, calc(100% - #{$slide-out-container-width}));
}
}
}

View file

@ -0,0 +1,15 @@
import { Component, Input } from '@angular/core';
@Component({
selector: 'ngx-slide-out',
styleUrls: ['./slide-out.component.scss'],
templateUrl: './slide-out.component.html',
})
export class SlideOutComponent {
@Input() showVisitorsStatistics: boolean = false;
toggleStatistics() {
this.showVisitorsStatistics = !this.showVisitorsStatistics;
}
}

View file

@ -0,0 +1,6 @@
<nb-card-body>
<ngx-traffic-bar-chart [data]="trafficBarData.data"
[labels]="trafficBarData.labels"
[formatter]="trafficBarData.formatter">
</ngx-traffic-bar-chart>
</nb-card-body>

View file

@ -0,0 +1,29 @@
@import '../../../../@theme/styles/themes';
@include nb-install-component() {
display: flex;
flex-direction: column;
flex: 1;
nb-card-body {
overflow: hidden;
position: relative;
display: flex;
flex-direction: column;
}
ngx-traffic-bar-chart {
flex: 1;
position: relative;
}
/deep/ canvas {
border-bottom-left-radius: nb-theme(card-border-radius);
border-bottom-right-radius: nb-theme(card-border-radius);
}
.echart {
height: 100%;
width: 100%;
}
}

View file

@ -0,0 +1,29 @@
import { Component, Input, OnDestroy } from '@angular/core';
import { NbThemeService } from '@nebular/theme';
import { takeWhile } from 'rxjs/operators';
@Component({
selector: 'ngx-traffic-back-card',
styleUrls: ['./traffic-back-card.component.scss'],
templateUrl: './traffic-back-card.component.html',
})
export class TrafficBackCardComponent implements OnDestroy {
private alive = true;
@Input() trafficBarData: any;
currentTheme: string;
constructor(private themeService: NbThemeService) {
this.themeService.getJsTheme()
.pipe(takeWhile(() => this.alive))
.subscribe(theme => {
this.currentTheme = theme.name;
});
}
ngOnDestroy() {
this.alive = false;
}
}

View file

@ -0,0 +1,134 @@
import { AfterViewInit, Component, Input, OnChanges, OnDestroy, SimpleChanges } from '@angular/core';
import { NbThemeService } from '@nebular/theme';
import { takeWhile } from 'rxjs/operators';
declare const echarts: any;
@Component({
selector: 'ngx-traffic-bar-chart',
styleUrls: ['traffic-back-card.component.scss'],
template: `
<div echarts [options]="option" class="echart" (chartInit)="onChartInit($event)"></div>
`,
})
export class TrafficBarChartComponent implements AfterViewInit, OnDestroy, OnChanges {
@Input() data: number[];
@Input() labels: string[];
@Input() formatter: string;
private alive = true;
option: any = {};
echartsInstance;
constructor(private theme: NbThemeService) {
}
onChartInit(ec) {
this.echartsInstance = ec;
}
ngOnChanges(changes: SimpleChanges): void {
if (!changes.data.isFirstChange() && !changes.labels.isFirstChange()) {
this.echartsInstance.setOption({
series: [{
data: this.data,
}],
xAxis: {
data: this.labels,
},
tooltip: {
formatter: this.formatter,
},
});
}
}
ngAfterViewInit() {
this.theme.getJsTheme()
.pipe(takeWhile(() => this.alive))
.subscribe(config => {
const trafficTheme: any = config.variables.trafficBarEchart;
this.option = Object.assign({}, {
grid: {
left: 0,
top: 0,
right: 0,
bottom: 0,
containLabel: true,
},
xAxis: {
type : 'category',
data : this.labels,
axisLabel: {
color: trafficTheme.axisTextColor,
fontSize: trafficTheme.axisFontSize,
},
axisLine: {
show: false,
},
axisTick: {
show: false,
},
},
yAxis: {
show: false,
axisLine: {
show: false,
},
axisLabel: {
show: false,
},
axisTick: {
show: false,
},
boundaryGap: [0, '5%'],
},
tooltip: {
axisPointer: {
type: 'shadow',
},
textStyle: {
color: trafficTheme.tooltipTextColor,
fontWeight: trafficTheme.tooltipFontWeight,
fontSize: 16,
},
position: 'top',
backgroundColor: trafficTheme.tooltipBg,
borderColor: trafficTheme.tooltipBorderColor,
borderWidth: 3,
formatter: this.formatter,
extraCssText: trafficTheme.tooltipExtraCss,
},
series: [
{
type: 'bar',
barWidth: '40%',
data: this.data,
itemStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: trafficTheme.gradientFrom,
}, {
offset: 1,
color: trafficTheme.gradientTo,
}]),
opacity: 1,
shadowColor: trafficTheme.gradientFrom,
shadowBlur: trafficTheme.shadowBlur,
},
},
},
],
});
});
}
ngOnDestroy() {
this.alive = false;
}
}

View file

@ -0,0 +1,16 @@
<div class="traffic-bar">
<div class="period-from">{{ barData.prevDate }}</div>
<div class="vertical-progress-bar">
<div class="progress-line"
[style.height.%]="barData.prevValue">
</div>
</div>
<div class="vertical-progress-bar">
<div class="progress-line"
[style.height.%]="barData.nextValue"
[class.success]="successDelta"
[class.failure]="!successDelta">
</div>
</div>
<div class="period-to">{{ barData.nextDate }}</div>
</div>

View file

@ -0,0 +1,72 @@
@import '../../../../../@theme/styles/themes';
@import '~@nebular/theme/styles/global/bootstrap/hero-buttons';
@import '~@nebular/theme/styles/global/typography/typography';
$traffic-bar-background-color: #d0d8e3;
@include nb-install-component() {
height: 100%;
.traffic-bar {
display: flex;
align-self: stretch;
position: relative;
height: 100%;
> * {
margin-left: 0.5rem;
}
}
.period-from, .period-to {
align-self: flex-end;
font-size: 1rem;
line-height: 0.8;
}
.period-from {
position: absolute;
right: 100%;
@include nb-rtl(right, inherit);
@include nb-rtl(left, 100%);
bottom: 0;
white-space: nowrap;
}
.vertical-progress-bar {
height: 100%;
width: 0.7rem;
transform: rotate(180deg);
.progress-line {
width: 100%;
background-color: $traffic-bar-background-color;
&.success {
background-color: text-success();
@include nb-for-theme(corporate) {
background-color: text-primary();
}
}
&.failure {
background-color: text-danger();
}
}
@include nb-for-theme(cosmic) {
.progress-line {
background: linear-gradient(180deg, #a054fe 0%, #7a58ff 100%);
&.success {
background: linear-gradient(0deg, #00caba 0%, #00d77f 100%);
}
&.failure {
background: linear-gradient(180deg, #ff38ab 0%, #ff386c 100%);
}
}
}
}
}

View file

@ -0,0 +1,12 @@
import { Component, Input } from '@angular/core';
@Component({
selector: 'ngx-traffic-bar',
styleUrls: ['./traffic-bar.component.scss'],
templateUrl: './traffic-bar.component.html',
})
export class TrafficBarComponent {
@Input() barData: { prevDate: string; prevValue: number; nextDate: string; nextValue: number };
@Input() successDelta: boolean;
}

View file

@ -0,0 +1,16 @@
<nb-card-body>
<ul class="traffic-list">
<li *ngFor="let item of frontCardData; trackBy: trackByDate">
<div class="date">{{ item.date }}</div>
<div class="value">{{ item.value }}</div>
<div class="delta"
[class.up]="item.delta.up"
[class.down]="!item.delta.up">
{{ item.delta.value }}%
</div>
<ngx-traffic-bar [barData]="item.comparison"
[successDelta]="item.delta.up">
</ngx-traffic-bar>
</li>
</ul>
</nb-card-body>

View file

@ -0,0 +1,158 @@
@import '../../../../@theme/styles/themes';
@import '~@nebular/theme/components/card/card.component.theme';
@import '~@nebular/theme/styles/global/typography/typography';
@import '~bootstrap/scss/mixins/breakpoints';
@import '~@nebular/theme/styles/global/bootstrap/breakpoints';
@include nb-install-component() {
overflow: auto;
nb-card-body {
padding: 0;
position: relative;
display: flex;
flex-direction: column;
}
.traffic-list {
padding: 0;
margin: 0;
}
li {
list-style: none;
display: flex;
justify-content: space-between;
align-items: center;
height: 4.5rem;
font-size: 1.25rem;
position: relative;
color: nb-theme(color-fg);
padding: nb-theme(card-padding);
border-bottom:
nb-theme(list-item-border-width)
nb-theme(card-header-border-type)
nb-theme(separator);
&:hover {
background-color: nb-theme(layout-bg);
&::before {
position: absolute;
content: '';
height: 100%;
width: 6px;
left: 0;
top: 0;
background-color: nb-theme(color-success);
border-radius: nb-theme(radius);
}
}
> * {
flex: 2;
}
ngx-traffic-bar {
flex: 1;
}
}
.date {
@include nb-for-theme(corporate) {
color: nb-theme(color-fg-heading);
}
}
.value {
color: nb-theme(color-fg-heading);
}
.delta {
display: flex;
padding-left: 0.5rem;
font-size: 1rem;
align-items: center;
&::before {
content: '';
right: 100%;
margin-right: 0.7rem;
@include nb-rtl(margin-left, 0.7rem);
@include nb-rtl(margin-right, 0);
border-left: 5px solid transparent;
border-right: 5px solid transparent;
}
&.down {
color: text-danger();
&::before {
bottom: 5px;
border-top: 6px solid text-danger();
}
}
&.up {
color: text-success();
&::before {
top: 5px;
border-bottom: 6px solid text-success();
}
}
}
@include nb-for-theme(cosmic) {
.traffic-list li {
&:hover {
&::before {
$color-top: nb-theme(btn-success-bg);
$color-bottom: btn-hero-success-left-color();
background-image: linear-gradient(to top, $color-top, $color-bottom);
box-shadow: 0 0 16px -2px btn-hero-success-middle-color();
}
}
}
}
@include nb-for-theme(corporate) {
.traffic-list li {
border-color: nb-theme(tabs-separator);
&:first-child {
border-top:
nb-theme(list-item-border-width)
nb-theme(card-header-border-type)
nb-theme(separator);
}
&:hover {
&::before {
background-color: nb-theme(color-primary);
}
}
}
.date {
color: nb-theme(color-fg-heading);
}
.delta {
&.up {
color: text-primary();
&::before {
border-bottom-color: text-primary();
}
}
}
}
@include media-breakpoint-down(is) {
ngx-traffic-bar {
display: none;
}
}
}

View file

@ -0,0 +1,35 @@
import { Component, Input, OnDestroy } from '@angular/core';
import { NbThemeService } from '@nebular/theme';
import { takeWhile } from 'rxjs/operators';
import { TrafficList } from '../../../../@core/data/traffic-list.service';
@Component({
selector: 'ngx-traffic-front-card',
styleUrls: ['./traffic-front-card.component.scss'],
templateUrl: './traffic-front-card.component.html',
})
export class TrafficFrontCardComponent implements OnDestroy {
private alive = true;
@Input() frontCardData: TrafficList;
currentTheme: string;
constructor(private themeService: NbThemeService) {
this.themeService.getJsTheme()
.pipe(takeWhile(() => this.alive))
.subscribe(theme => {
this.currentTheme = theme.name;
});
}
trackByDate(_, item) {
return item.date;
}
ngOnDestroy() {
this.alive = false;
}
}

View file

@ -0,0 +1,16 @@
<nb-card-header>
<span>Traffic</span>
<div class="dropdown ghost-dropdown" ngbDropdown>
<button type="button" class="btn btn-sm" ngbDropdownToggle
[ngClass]="{
'btn-success': currentTheme === 'default',
'btn-primary': currentTheme !== 'default'}">
{{ type }}
</button>
<ul ngbDropdownMenu class="dropdown-menu">
<li class="dropdown-item" *ngFor="let period of types" (click)="changePeriod(period)">
{{ period }}
</li>
</ul>
</div>
</nb-card-header>

View file

@ -0,0 +1,16 @@
@import '../../../../@theme/styles/themes';
@include nb-install-component() {
nb-card-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.675rem 4rem 0.5rem 1.25rem;
@include nb-rtl(padding-right, 1.25rem);
@include nb-rtl(padding-left, 4rem);
}
.dropdown {
min-width: 120px;
}
}

View file

@ -0,0 +1,36 @@
import { Component, EventEmitter, Input, OnDestroy, Output } from '@angular/core';
import { NbThemeService } from '@nebular/theme';
import { takeWhile } from 'rxjs/operators';
@Component({
selector: 'ngx-traffic-cards-header',
styleUrls: ['./traffic-cards-header.component.scss'],
templateUrl: './traffic-cards-header.component.html',
})
export class TrafficCardsHeaderComponent implements OnDestroy {
private alive = true;
@Output() periodChange = new EventEmitter<string>();
@Input() type: string = 'week';
types: string[] = ['week', 'month', 'year'];
currentTheme: string;
constructor(private themeService: NbThemeService) {
this.themeService.getJsTheme()
.pipe(takeWhile(() => this.alive))
.subscribe(theme => {
this.currentTheme = theme.name;
});
}
changePeriod(period: string): void {
this.type = period;
this.periodChange.emit(period);
}
ngOnDestroy() {
this.alive = false;
}
}

View file

@ -0,0 +1,16 @@
<nb-reveal-card [showToggleButton]="false" [revealed]="revealed">
<nb-card-front>
<nb-card size="small">
<ngx-traffic-cards-header (periodChange)="setPeriod($event)"></ngx-traffic-cards-header>
<ngx-traffic-front-card [frontCardData]="trafficListData"></ngx-traffic-front-card>
<i class="nb-arrow-up" (click)="toggleView()"></i>
</nb-card>
</nb-card-front>
<nb-card-back>
<nb-card size="small">
<ngx-traffic-cards-header (periodChange)="setPeriod($event)"></ngx-traffic-cards-header>
<ngx-traffic-back-card [trafficBarData]="trafficBarData"></ngx-traffic-back-card>
<i class="nb-arrow-down" (click)="toggleView()"></i>
</nb-card>>
</nb-card-back>
</nb-reveal-card>

View file

@ -0,0 +1,19 @@
@import '../../../@theme/styles/themes';
@include nb-install-component() {
.nb-arrow-up, .nb-arrow-down {
position: absolute;
top: 0;
right: 0;
@include nb-rtl(right, auto);
@include nb-rtl(left, 0);
padding: 1.5rem;
cursor: pointer;
}
nb-card-back {
/deep/ nb-card-header {
border: none;
}
}
}

View file

@ -0,0 +1,54 @@
import { Component, OnDestroy } from '@angular/core';
import { TrafficList, TrafficListService } from '../../../@core/data/traffic-list.service';
import { takeWhile } from 'rxjs/operators';
import { TrafficBar, TrafficBarService } from '../../../@core/data/traffic-bar.service';
@Component({
selector: 'ngx-traffic-reveal-card',
styleUrls: ['./traffic-reveal-card.component.scss'],
templateUrl: './traffic-reveal-card.component.html',
})
export class TrafficRevealCardComponent implements OnDestroy {
private alive = true;
trafficBarData: TrafficBar;
trafficListData: TrafficList;
revealed = false;
period: string = 'week';
constructor(private trafficListService: TrafficListService,
private trafficBarService: TrafficBarService) {
this.getTrafficFrontCardData(this.period);
this.getTrafficBackCardData(this.period);
}
toggleView() {
this.revealed = !this.revealed;
}
setPeriod(value: string): void {
this.getTrafficFrontCardData(value);
this.getTrafficBackCardData(value);
}
getTrafficBackCardData(period: string) {
this.trafficBarService.getTrafficBarData(period)
.pipe(takeWhile(() => this.alive ))
.subscribe(trafficBarData => {
this.trafficBarData = trafficBarData;
});
}
getTrafficFrontCardData(period: string) {
this.trafficListService.getTrafficListData(period)
.pipe(takeWhile(() => this.alive ))
.subscribe(trafficListData => {
this.trafficListData = trafficListData;
});
}
ngOnDestroy() {
this.alive = false;
}
}

View file

@ -0,0 +1,167 @@
@import '../../../@theme/styles/themes';
@import '~@nebular/theme/styles/global/typography/typography';
@include nb-install-component() {
nb-card-header {
display: flex;
align-items: center;
justify-content: space-between;
border: none;
}
nb-card-body {
padding: 0;
}
.dropdown {
min-width: 120px;
}
.user-activity-list {
padding: 0;
margin: 0;
}
.user-activity-list li {
list-style: none;
display: flex;
justify-content: space-between;
align-items: flex-end;
position: relative;
color: nb-theme(color-fg);
padding: nb-theme(card-padding);
border-bottom:
nb-theme(list-item-border-width)
nb-theme(card-header-border-type)
nb-theme(separator);
&:first-child {
border-top:
nb-theme(list-item-border-width)
nb-theme(card-header-border-type)
nb-theme(separator);
}
&:hover {
background-color: nb-theme(layout-bg);
&::before {
position: absolute;
content: '';
height: 100%;
width: 6px;
left: 0;
top: 0;
background-color: nb-theme(color-success);
border-radius: nb-theme(radius);
}
}
}
.visited-date,
.value,
.title {
font-size: 1.25rem;
}
.visited-date {
color: nb-theme(color-fg-heading);
@include nb-for-theme(cosmic) {
color: nb-theme(color-fg);
}
}
.title {
font-size: 1rem;
}
.value {
margin-top: 0.5rem;
color: nb-theme(color-success);
}
.delta {
display: flex;
align-items: center;
position: relative;
&::before {
content: '';
right: 100%;
margin-right: 0.7rem;
@include nb-rtl(margin-right, 0);
@include nb-rtl(margin-left, 0.7rem);
border-left: 6px solid transparent;
border-right: 6px solid transparent;
}
&.down {
color: text-danger();
&::before {
border-top: 6px solid text-danger();
}
}
&.up {
color: text-success();
&::before {
border-bottom: 6px solid text-success();
}
}
}
@include nb-for-theme(cosmic) {
.user-activity-list li {
&:hover {
&::before {
$color-top: nb-theme(btn-success-bg);
$color-bottom: btn-hero-success-left-color();
background-image: linear-gradient(to top, $color-top, $color-bottom);
box-shadow: 0 0 16px -2px btn-hero-success-middle-color();
}
}
}
.delta {
&.down, &.up {
color: nb-theme(color-fg-heading);
}
}
}
@include nb-for-theme(corporate) {
.user-activity-list li {
border-color: nb-theme(tabs-separator);
&:first-child {
border-color: nb-theme(tabs-separator);
}
&:hover {
&::before {
background-color: nb-theme(color-primary);
}
}
}
.visited-pages-count {
.value {
color: nb-theme(color-fg-heading);
}
}
.delta {
&.up {
color: text-primary();
&::before {
border-bottom-color: text-primary();
}
}
}
}
}

View file

@ -0,0 +1,84 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { NbThemeService } from '@nebular/theme';
import { takeWhile } from 'rxjs/operators';
import { UserActivityService, UserActive } from '../../../@core/data/user-activity.service';
@Component({
selector: 'ngx-user-activity',
styleUrls: ['./user-activity.component.scss'],
template: `
<nb-card size="medium">
<nb-card-header>
<span>User Activity</span>
<div class="dropdown ghost-dropdown" ngbDropdown>
<button type="button" class="btn btn-sm" ngbDropdownToggle
[ngClass]="{ 'btn-success': currentTheme == 'default', 'btn-primary': currentTheme != 'default'}">
{{ type }}
</button>
<ul ngbDropdownMenu class="dropdown-menu">
<li class="dropdown-item"
*ngFor="let t of types"
(click)="getUserActivity(t); type = t">
{{ t }}
</li>
</ul>
</div>
</nb-card-header>
<nb-card-body>
<ul class="user-activity-list">
<li *ngFor="let item of userActivity">
<div class="visited-date">
{{ item.date }}
</div>
<div class="visited-pages-count">
<div class="title">Pages Visit</div>
<div class="value">{{ item.pagesVisitCount }}</div>
</div>
<div class="visited-percentages">
<div class="title">New visits, %</div>
<div class="delta value"
[class.up]="item.deltaUp"
[class.down]="!item.deltaUp">
{{ item.newVisits }}%
</div>
</div>
</li>
</ul>
</nb-card-body>
</nb-card>
`,
})
export class ECommerceUserActivityComponent implements OnDestroy, OnInit {
private alive = true;
userActivity: UserActive[] = [];
type = 'month';
types = ['week', 'month', 'year'];
currentTheme: string;
constructor(private themeService: NbThemeService,
private userActivityService: UserActivityService) {
this.themeService.getJsTheme()
.pipe(takeWhile(() => this.alive))
.subscribe(theme => {
this.currentTheme = theme.name;
});
}
ngOnInit() {
this.getUserActivity(this.type);
}
getUserActivity(period: string) {
this.userActivityService.getUserActivityData(period)
.subscribe(userActivityData => {
this.userActivity = userActivityData;
});
}
ngOnDestroy() {
this.alive = false;
}
}

View file

@ -0,0 +1,13 @@
@import '../../../../@theme/styles/themes';
@include nb-install-component() {
display: block;
height: 290px;
width: 100%;
.echart {
display: block;
height: 100%;
width: 100%;
}
}

View file

@ -0,0 +1,241 @@
import { delay, takeWhile } from 'rxjs/operators';
import { AfterViewInit, Component, OnDestroy } from '@angular/core';
import { NbThemeService } from '@nebular/theme';
@Component({
selector: 'ngx-visitors-analytics-chart',
styleUrls: ['./visitors-analytics-chart.component.scss'],
template: `
<div echarts [options]="option" class="echart"></div>
`,
})
export class ECommerceVisitorsAnalyticsChartComponent implements AfterViewInit, OnDestroy {
private alive = true;
private innerLinePoints: number[] = [
94, 188, 225, 244, 253, 254, 249, 235, 208,
173, 141, 118, 105, 97, 94, 96, 104, 121, 147,
183, 224, 265, 302, 333, 358, 375, 388, 395,
400, 400, 397, 390, 377, 360, 338, 310, 278,
241, 204, 166, 130, 98, 71, 49, 32, 20, 13, 9,
];
private outerLinePoints: number[] = [
85, 71, 59, 50, 45, 42, 41, 44 , 58, 88,
136 , 199, 267, 326, 367, 391, 400, 397,
376, 319, 200, 104, 60, 41, 36, 37, 44,
55, 74, 100 , 131, 159, 180, 193, 199, 200,
195, 184, 164, 135, 103, 73, 50, 33, 22, 15, 11,
];
private months: string[] = [
'Jan', 'Feb', 'Mar',
'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep',
'Oct', 'Nov', 'Dec',
];
option: any;
data: Array<any>;
themeSubscription: any;
constructor(private theme: NbThemeService) {
const outerLinePointsLength = this.outerLinePoints.length;
const monthsLength = this.months.length;
this.data = this.outerLinePoints.map((p, index) => {
const monthIndex = Math.round(index / 4);
const label = (index % Math.round(outerLinePointsLength / monthsLength) === 0)
? this.months[monthIndex]
: '';
return {
label,
value: p,
};
});
}
ngAfterViewInit(): void {
this.theme.getJsTheme()
.pipe(
delay(1),
takeWhile(() => this.alive),
)
.subscribe(config => {
const eTheme: any = config.variables.visitors;
this.setOptions(eTheme);
});
}
setOptions(eTheme) {
this.option = {
grid: {
left: 40,
top: 20,
right: 0,
bottom: 60,
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'line',
lineStyle: {
color: eTheme.tooltipLineColor,
width: eTheme.tooltipLineWidth,
},
},
textStyle: {
color: eTheme.tooltipTextColor,
fontSize: 20,
fontWeight: eTheme.tooltipFontWeight,
},
position: 'top',
backgroundColor: eTheme.tooltipBg,
borderColor: eTheme.tooltipBorderColor,
borderWidth: 3,
formatter: (params) => {
return Math.round(parseInt(params[0].value, 10));
},
extraCssText: eTheme.tooltipExtraCss,
},
xAxis: {
type: 'category',
boundaryGap: false,
offset: 25,
data: this.data.map(i => i.label),
axisTick: {
show: false,
},
axisLabel: {
color: eTheme.axisTextColor,
fontSize: eTheme.axisFontSize,
},
axisLine: {
lineStyle: {
color: eTheme.axisLineColor,
width: '2',
},
},
},
yAxis: {
type: 'value',
boundaryGap: false,
axisLine: {
lineStyle: {
color: eTheme.axisLineColor,
width: '1',
},
},
axisLabel: {
color: eTheme.axisTextColor,
fontSize: eTheme.axisFontSize,
},
axisTick: {
show: false,
},
splitLine: {
lineStyle: {
color: eTheme.yAxisSplitLine,
width: '1',
},
},
},
series: [
this.getInnerLine(eTheme),
this.getOuterLine(eTheme),
],
};
}
getOuterLine(eTheme) {
return {
type: 'line',
smooth: true,
symbolSize: 20,
itemStyle: {
normal: {
opacity: 0,
},
emphasis: {
color: '#ffffff',
borderColor: eTheme.itemBorderColor,
borderWidth: 2,
opacity: 1,
},
},
lineStyle: {
normal: {
width: eTheme.lineWidth,
type: eTheme.lineStyle,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: eTheme.lineGradFrom,
}, {
offset: 1,
color: eTheme.lineGradTo,
}]),
shadowColor: eTheme.lineShadow,
shadowBlur: 6,
shadowOffsetY: 12,
},
},
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: eTheme.areaGradFrom,
}, {
offset: 1,
color: eTheme.areaGradTo,
}]),
},
},
data: this.data.map(i => i.value),
};
}
getInnerLine(eTheme) {
return {
type: 'line',
smooth: true,
symbolSize: 20,
tooltip: {
show: false,
extraCssText: '',
},
itemStyle: {
normal: {
opacity: 0,
},
emphasis: {
opacity: 0,
},
},
lineStyle: {
normal: {
width: eTheme.innerLineWidth,
type: eTheme.innerLineStyle,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1),
},
},
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: eTheme.innerAreaGradFrom,
}, {
offset: 1,
color: eTheme.innerAreaGradTo,
}]),
opacity: 1,
},
},
data: this.innerLinePoints,
};
}
ngOnDestroy() {
this.alive = false;
}
}

View file

@ -0,0 +1,18 @@
<nb-card size="medium">
<nb-card-header>
<div class="title">Visitors Analytics</div>
<div class="sub-title">Consumption</div>
</nb-card-header>
<nb-card-body>
<div class="chart-container">
<div class="chart-header">
<ngx-legend-chart [legendItems]="chartLegend"></ngx-legend-chart>
</div>
<ngx-visitors-analytics-chart></ngx-visitors-analytics-chart>
</div>
</nb-card-body>
<ngx-slide-out [showVisitorsStatistics]="true">
<ngx-visitors-statistics></ngx-visitors-statistics>
</ngx-slide-out>
</nb-card>

View file

@ -0,0 +1,55 @@
@import '../../../@theme/styles/themes';
@import '~bootstrap/scss/mixins/breakpoints';
@import '~@nebular/theme/styles/global/bootstrap/breakpoints';
@include nb-install-component() {
position: relative;
display: block;
overflow: hidden;
nb-card {
position: relative;
}
nb-card-header {
border-bottom: none;
.title {
font-size: 1.5rem;
font-weight: nb-theme(font-weight-bold);
}
.sub-title {
color: nb-theme(color-fg);
}
}
.container {
display: flex;
flex-direction: row;
}
.chart-container {
flex: 1;
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
position: relative;
}
.chart-header {
display: flex;
justify-content: space-between;
margin-bottom: 2.125rem;
}
@include media-breakpoint-down(is) {
ngx-legend-chart {
/deep/ .legends {
padding-left: 0;
font-size: nb-theme(font-size-sm);
}
}
}
}

View file

@ -0,0 +1,40 @@
import { Component, OnDestroy } from '@angular/core';
import { takeWhile } from 'rxjs/operators';
import { NbThemeService } from '@nebular/theme';
@Component({
selector: 'ngx-ecommerce-visitors-analytics',
styleUrls: ['./visitors-analytics.component.scss'],
templateUrl: './visitors-analytics.component.html',
})
export class ECommerceVisitorsAnalyticsComponent implements OnDestroy {
private alive = true;
chartLegend: {iconColor: string; title: string}[];
constructor(private themeService: NbThemeService) {
this.themeService.getJsTheme()
.pipe(takeWhile(() => this.alive))
.subscribe(theme => {
this.setLegendItems(theme.variables.visitorsLegend);
});
}
setLegendItems(visitorsLegend): void {
this.chartLegend = [
{
iconColor: visitorsLegend.firstIcon,
title: 'Unique Visitors',
},
{
iconColor: visitorsLegend.secondIcon,
title: 'Page Views',
},
];
}
ngOnDestroy() {
this.alive = false;
}
}

View file

@ -0,0 +1,16 @@
<div class="visitors-statistics">
<div class="statistics-header">
<div class="visitors-value">1,100</div>
<div class="visitors-title">New Visitors</div>
</div>
<div class="statistics-chart">
<div echarts [options]="option" class="echart"></div>
</div>
<div class="statistics-footer">
<div class="chart-values">
<span class="chart-value">25%</span>
<span class="chart-value">75%</span>
</div>
<ngx-legend-chart class="visitors-statistics-legend" [legendItems]="chartLegend"></ngx-legend-chart>
</div>
</div>

View file

@ -0,0 +1,60 @@
@import '../../../../@theme/styles/themes';
@include nb-install-component() {
.visitors-value {
font-size: 3rem;
font-weight: nb-theme(font-weight-light);
color: nb-theme(color-fg-heading);
line-height: 0.8;
}
.visitors-title {
margin-top: 1rem;
font-size: 1.25rem;
color: nb-theme(color-fg);
}
.visitors-statistics {
width: 100%;
}
.statistics-chart {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
width: 100%;
.echart {
display: block;
height: 190px;
width: 100%;
}
}
.chart-values {
display: flex;
}
.chart-value {
color: nb-theme(color-fg-heading);
font-size: 2rem;
font-weight: nb-theme(font-weight-bold);
margin-bottom: 1rem;
flex: 1;
}
// legend start
.visitors-statistics-legend {
/deep/ .legends {
padding: 0;
margin-left: 0;
}
/deep/ .legend {
flex: 1;
margin-left: 0;
justify-content: flex-start;
}
}
// legend end
}

View file

@ -0,0 +1,196 @@
import { AfterViewInit, Component, OnDestroy } from '@angular/core';
import { NbThemeService } from '@nebular/theme';
import { delay, takeWhile } from 'rxjs/operators';
@Component({
selector: 'ngx-visitors-statistics',
styleUrls: ['./visitors-statistics.component.scss'],
templateUrl: './visitors-statistics.component.html',
})
export class ECommerceVisitorsStatisticsComponent implements AfterViewInit, OnDestroy {
private alive = true;
private value = 75;
option: any = {};
chartLegend: {iconColor: string; title: string}[];
constructor(private theme: NbThemeService) {
}
ngAfterViewInit() {
this.theme.getJsTheme()
.pipe(
takeWhile(() => this.alive),
delay(1),
)
.subscribe(config => {
const variables: any = config.variables;
const visitorsPieLegend: any = config.variables.visitorsPieLegend;
this.setOptions(variables);
this.setLegendItems(visitorsPieLegend);
});
}
setLegendItems(visitorsPieLegend) {
this.chartLegend = [
{
iconColor: visitorsPieLegend.firstSection,
title: 'New Visitors',
},
{
iconColor: visitorsPieLegend.secondSection,
title: 'Return Visitors',
},
];
}
setOptions(variables) {
const visitorsPie: any = variables.visitorsPie;
this.option = {
tooltip: {
trigger: 'item',
formatter: '',
},
series: [
{
name: ' ',
clockWise: true,
hoverAnimation: false,
type: 'pie',
center: ['50%', '50%'],
radius: visitorsPie.firstPieRadius,
data: [
{
value: this.value,
name: ' ',
label: {
normal: {
position: 'center',
formatter: '',
textStyle: {
fontSize: '22',
fontFamily: variables.fontSecondary,
fontWeight: '600',
color: variables.fgHeading,
},
},
},
tooltip: {
show: false,
},
itemStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: visitorsPie.firstPieGradientLeft,
},
{
offset: 1,
color: visitorsPie.firstPieGradientRight,
},
]),
shadowColor: visitorsPie.firstPieShadowColor,
shadowBlur: 0,
shadowOffsetX: 0,
shadowOffsetY: 3,
},
},
hoverAnimation: false,
},
{
value: 100 - this.value,
name: ' ',
tooltip: {
show: false,
},
label: {
normal: {
position: 'inner',
},
},
itemStyle: {
normal: {
color: variables.layoutBg,
},
},
},
],
},
{
name: ' ',
clockWise: true,
hoverAnimation: false,
type: 'pie',
center: ['50%', '50%'],
radius: visitorsPie.secondPieRadius,
data: [
{
value: this.value,
name: ' ',
label: {
normal: {
position: 'center',
formatter: '',
textStyle: {
fontSize: '22',
fontFamily: variables.fontSecondary,
fontWeight: '600',
color: variables.fgHeading,
},
},
},
tooltip: {
show: false,
},
itemStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1),
},
},
hoverAnimation: false,
},
{
value: 100 - this.value,
name: ' ',
tooltip: {
show: false,
},
label: {
normal: {
position: 'inner',
},
},
itemStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: visitorsPie.secondPieGradientLeft,
},
{
offset: 1,
color: visitorsPie.secondPieGradientRight,
},
]),
shadowColor: visitorsPie.secondPieShadowColor,
shadowBlur: 0,
shadowOffsetX: visitorsPie.shadowOffsetX,
shadowOffsetY: visitorsPie.shadowOffsetY,
},
},
},
],
},
],
};
}
ngOnDestroy() {
this.alive = false;
}
}

View file

@ -2,11 +2,16 @@ import { NbMenuItem } from '@nebular/theme';
export const MENU_ITEMS: NbMenuItem[] = [
{
title: 'Dashboard',
icon: 'nb-home',
title: 'E-commerce',
icon: 'nb-e-commerce',
link: '/pages/dashboard',
home: true,
},
{
title: 'IoT Dashboard',
icon: 'nb-home',
link: '/pages/iot-dashboard',
},
{
title: 'FEATURES',
group: true,

View file

@ -3,6 +3,7 @@ import { NgModule } from '@angular/core';
import { PagesComponent } from './pages.component';
import { DashboardComponent } from './dashboard/dashboard.component';
import { ECommerceComponent } from './e-commerce/e-commerce.component';
import { NotFoundComponent } from './miscellaneous/not-found/not-found.component';
const routes: Routes = [{
@ -10,6 +11,9 @@ const routes: Routes = [{
component: PagesComponent,
children: [{
path: 'dashboard',
component: ECommerceComponent,
}, {
path: 'iot-dashboard',
component: DashboardComponent,
}, {
path: 'ui-features',

View file

@ -2,6 +2,7 @@ import { NgModule } from '@angular/core';
import { PagesComponent } from './pages.component';
import { DashboardModule } from './dashboard/dashboard.module';
import { ECommerceModule } from './e-commerce/e-commerce.module';
import { PagesRoutingModule } from './pages-routing.module';
import { ThemeModule } from '../@theme/theme.module';
import { MiscellaneousModule } from './miscellaneous/miscellaneous.module';
@ -15,6 +16,7 @@ const PAGES_COMPONENTS = [
PagesRoutingModule,
ThemeModule,
DashboardModule,
ECommerceModule,
MiscellaneousModule,
],
declarations: [

View file

@ -9,7 +9,8 @@ export class IconsComponent {
icons = {
nebular: ['nb-alert', 'nb-angle-double-left', 'nb-arrow-down', 'nb-arrow-dropdown', 'nb-arrow-dropleft',
nebular: ['nb-alert', 'nb-angle-double-left', 'nb-angle-double-right',
'nb-arrow-down', 'nb-arrow-dropdown', 'nb-arrow-dropleft',
'nb-arrow-dropright', 'nb-arrow-dropup', 'nb-arrow-left', 'nb-arrow-retweet', 'nb-arrow-right',
'nb-arrow-thin-down', 'nb-arrow-thin-left', 'nb-arrow-thin-right', 'nb-arrow-thin-up',
'nb-arrow-up', 'nb-audio', 'nb-bar-chart', 'nb-checkmark', 'nb-chevron-down',
@ -25,7 +26,8 @@ export class IconsComponent {
'nb-rainy', 'nb-roller-shades', 'nb-search', 'nb-shuffle', 'nb-skip-backward',
'nb-skip-backward-outline', 'nb-skip-forward', 'nb-skip-forward-outline', 'nb-snowy-circled',
'nb-square', 'nb-square-outline', 'nb-star', 'nb-sunny', 'nb-sunny-circled', 'nb-tables', 'nb-title',
'nb-trash', 'nb-volume-high', 'nb-volume-mute', 'nb-drop', 'nb-drops', 'nb-info'],
'nb-trash', 'nb-volume-high', 'nb-volume-mute', 'nb-drop', 'nb-drops', 'nb-info', 'nb-expand', 'nb-collapse',
'nb-e-commerce'],
ionicons: [
'ion-ionic', 'ion-arrow-right-b', 'ion-arrow-down-b', 'ion-arrow-left-b', 'ion-arrow-up-c', 'ion-arrow-right-c',