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

@ -37,6 +37,7 @@
"node_modules/nebular-icons/scss/nebular-icons.scss",
"node_modules/angular-tree-component/dist/angular-tree-component.css",
"node_modules/pace-js/templates/pace-theme-flash.tmpl.css",
"node_modules/leaflet/dist/leaflet.css",
"src/app/@theme/styles/styles.scss"
],
"scripts": [

View file

@ -6,6 +6,15 @@ import { ElectricityService } from './electricity.service';
import { StateService } from './state.service';
import { SmartTableService } from './smart-table.service';
import { PlayerService } from './player.service';
import { UserActivityService } from './user-activity.service';
import { OrdersChartService } from './orders-chart.service';
import { ProfitChartService } from './profit-chart.service';
import { TrafficListService } from './traffic-list.service';
import { PeriodsService } from './periods.service';
import { EarningService } from './earning.service';
import { OrdersProfitChartService } from './orders-profit-chart.service';
import { TrafficBarService } from './traffic-bar.service';
import { ProfitBarAnimationChartService } from './profit-bar-animation-chart.service';
const SERVICES = [
UserService,
@ -13,6 +22,15 @@ const SERVICES = [
StateService,
SmartTableService,
PlayerService,
UserActivityService,
OrdersChartService,
ProfitChartService,
TrafficListService,
PeriodsService,
EarningService,
OrdersProfitChartService,
TrafficBarService,
ProfitBarAnimationChartService,
];
@NgModule({

View file

@ -0,0 +1,116 @@
import { Injectable } from '@angular/core';
import { of as observableOf, Observable } from 'rxjs';
export class LiveUpdateChart {
liveChart: { value: [string, number] }[];
delta: {
up: boolean;
value: number;
};
dailyIncome: number;
}
export class PieChart {
value: number;
name: string;
}
@Injectable()
export class EarningService {
private currentDate: Date = new Date();
private currentValue = Math.random() * 1000;
private ONE_DAY = 24 * 3600 * 1000;
private pieChartData = [
{
value: 50,
name: 'Bitcoin',
},
{
value: 25,
name: 'Tether',
},
{
value: 25,
name: 'Ethereum',
},
];
private liveUpdateChartData = {
bitcoin: {
liveChart: [],
delta: {
up: true,
value: 4,
},
dailyIncome: 45895,
},
tether: {
liveChart: [],
delta: {
up: false,
value: 9,
},
dailyIncome: 5862,
},
ethereum: {
liveChart: [],
delta: {
up: false,
value: 21,
},
dailyIncome: 584,
},
};
getDefaultLiveChartData(elementsNumber: number) {
this.currentDate = new Date();
this.currentValue = Math.random() * 1000;
return Array.from(Array(elementsNumber))
.map(item => this.generateRandomLiveChartData());
}
generateRandomLiveChartData() {
this.currentDate = new Date(+this.currentDate + this.ONE_DAY);
this.currentValue = this.currentValue + Math.random() * 20 - 11;
if (this.currentValue < 0) {
this.currentValue = Math.random() * 100;
}
return {
value: [
[
this.currentDate.getFullYear(),
this.currentDate.getMonth(),
this.currentDate.getDate(),
].join('/'),
Math.round(this.currentValue),
],
};
}
generateRandomEarningData(currency) {
const data = this.liveUpdateChartData[currency.toLowerCase()];
const newValue = this.generateRandomLiveChartData();
data.liveChart.shift();
data.liveChart.push(newValue);
return observableOf(data.liveChart);
}
getEarningLiveUpdateCardData(currency: string) {
const data = this.liveUpdateChartData[currency.toLowerCase()];
data.liveChart = this.getDefaultLiveChartData(150);
return observableOf(data);
}
getEarningPieChartData(): Observable<PieChart[]> {
return observableOf(this.pieChartData);
}
}

View file

@ -0,0 +1,158 @@
import { Injectable } from '@angular/core';
import { PeriodsService } from './periods.service';
export class OrdersChart {
chartLabel: string[];
linesData: number[][];
}
@Injectable()
export class OrdersChartService {
private year = [
'2012',
'2013',
'2014',
'2015',
'2016',
'2017',
'2018',
];
private data = { };
constructor(private period: PeriodsService) {
this.data = {
week: this.getDataForWeekPeriod(),
month: this.getDataForMonthPeriod(),
year: this.getDataForYearPeriod(),
};
}
private getDataForWeekPeriod(): OrdersChart {
return {
chartLabel: this.getDataLabels(42, this.period.getWeeks()),
linesData: [
[
184, 267, 326, 366, 389, 399,
392, 371, 340, 304, 265, 227,
191, 158, 130, 108, 95, 91, 97,
109, 125, 144, 166, 189, 212,
236, 259, 280, 300, 316, 329,
338, 342, 339, 329, 312, 288,
258, 221, 178, 128, 71,
],
[
158, 178, 193, 205, 212, 213,
204, 190, 180, 173, 168, 164,
162, 160, 159, 158, 159, 166,
179, 195, 215, 236, 257, 276,
292, 301, 304, 303, 300, 293,
284, 273, 262, 251, 241, 234,
232, 232, 232, 232, 232, 232,
],
[
58, 137, 202, 251, 288, 312,
323, 324, 311, 288, 257, 222,
187, 154, 124, 100, 81, 68, 61,
58, 61, 69, 80, 96, 115, 137,
161, 186, 210, 233, 254, 271,
284, 293, 297, 297, 297, 297,
297, 297, 297, 297, 297,
],
],
};
}
private getDataForMonthPeriod(): OrdersChart {
return {
chartLabel: this.getDataLabels(47, this.period.getMonths()),
linesData: [
[
5, 63, 113, 156, 194, 225,
250, 270, 283, 289, 290,
286, 277, 264, 244, 220,
194, 171, 157, 151, 150,
152, 155, 160, 166, 170,
167, 153, 135, 115, 97,
82, 71, 64, 63, 62, 61,
62, 65, 73, 84, 102,
127, 159, 203, 259, 333,
],
[
6, 83, 148, 200, 240,
265, 273, 259, 211,
122, 55, 30, 28, 36,
50, 68, 88, 109, 129,
146, 158, 163, 165,
173, 187, 208, 236,
271, 310, 346, 375,
393, 400, 398, 387,
368, 341, 309, 275,
243, 220, 206, 202,
207, 222, 247, 286, 348,
],
[
398, 348, 315, 292, 274,
261, 251, 243, 237, 231,
222, 209, 192, 172, 152,
132, 116, 102, 90, 80, 71,
64, 58, 53, 49, 48, 54, 66,
84, 104, 125, 142, 156, 166,
172, 174, 172, 167, 159, 149,
136, 121, 105, 86, 67, 45, 22,
],
],
};
}
private getDataForYearPeriod(): OrdersChart {
return {
chartLabel: this.getDataLabels(42, this.year),
linesData: [
[
190, 269, 327, 366, 389, 398,
396, 387, 375, 359, 343, 327,
312, 298, 286, 276, 270, 268,
265, 258, 247, 234, 220, 204,
188, 172, 157, 142, 128, 116,
106, 99, 95, 94, 92, 89, 84,
77, 69, 60, 49, 36, 22,
],
[
265, 307, 337, 359, 375, 386,
393, 397, 399, 397, 390, 379,
365, 347, 326, 305, 282, 261,
241, 223, 208, 197, 190, 187,
185, 181, 172, 160, 145, 126,
105, 82, 60, 40, 26, 19, 22,
43, 82, 141, 220, 321,
],
[
9, 165, 236, 258, 244, 206,
186, 189, 209, 239, 273, 307,
339, 365, 385, 396, 398, 385,
351, 300, 255, 221, 197, 181,
170, 164, 162, 161, 159, 154,
146, 135, 122, 108, 96, 87,
83, 82, 82, 82, 82, 82, 82,
],
],
};
}
getDataLabels(nPoints: number, labelsArray: string[]): string[] {
const labelsArrayLength = labelsArray.length;
const step = Math.round(nPoints / labelsArrayLength);
return Array.from(Array(nPoints)).map((item, index) => {
const dataIndex = Math.round(index / step);
return index % step === 0 ? labelsArray[dataIndex] : '';
});
}
getOrdersChartData(period: string): OrdersChart {
return this.data[period];
}
}

View file

@ -0,0 +1,48 @@
import { of as observableOf, Observable } from 'rxjs';
import { Injectable } from '@angular/core';
import { OrdersChart, OrdersChartService } from './orders-chart.service';
import { ProfitChart, ProfitChartService } from './profit-chart.service';
export class OrderProfitChartSummary {
title: string;
value: number;
}
@Injectable()
export class OrdersProfitChartService {
private summary = [
{
title: 'Marketplace',
value: 3654,
},
{
title: 'Last Month',
value: 946,
},
{
title: 'Last Week',
value: 654,
},
{
title: 'Today',
value: 230,
},
];
constructor(private ordersChartService: OrdersChartService,
private profitChartService: ProfitChartService) {
}
getOrderProfitChartSummary(): Observable<OrderProfitChartSummary[]> {
return observableOf(this.summary);
}
getOrdersChartData(period: string): Observable<OrdersChart> {
return observableOf(this.ordersChartService.getOrdersChartData(period));
}
getProfitChartData(period: string): Observable<ProfitChart> {
return observableOf(this.profitChartService.getProfitChartData(period));
}
}

View file

@ -0,0 +1,33 @@
import { Injectable } from '@angular/core';
@Injectable()
export class PeriodsService {
getYears() {
return [
'2010', '2011', '2012',
'2013', '2014', '2015',
'2016', '2017', '2018',
];
}
getMonths() {
return [
'Jan', 'Feb', 'Mar',
'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep',
'Oct', 'Nov', 'Dec',
];
}
getWeeks() {
return [
'Mon',
'Tue',
'Wed',
'Thu',
'Fri',
'Sat',
'Sun',
];
}
}

View file

@ -0,0 +1,41 @@
import { Injectable } from '@angular/core';
import { of as observableOf, Observable } from 'rxjs';
@Injectable()
export class ProfitBarAnimationChartService {
private data: any;
constructor() {
this.data = {
firstLine: this.getDataForFirstLine(),
secondLine: this.getDataForSecondLine(),
};
}
getDataForFirstLine(): number[] {
return this.createEmptyArray(100)
.map((_, index) => {
const oneFifth = index / 5;
return (Math.sin(oneFifth) * (oneFifth - 10) + index / 6) * 5;
});
}
getDataForSecondLine(): number[] {
return this.createEmptyArray(100)
.map((_, index) => {
const oneFifth = index / 5;
return (Math.cos(oneFifth) * (oneFifth - 10) + index / 6) * 5;
});
}
createEmptyArray(nPoints: number) {
return Array.from(Array(nPoints));
}
getChartData(): Observable<{ firstLine: number[]; secondLine: number[] }> {
return observableOf(this.data);
}
}

View file

@ -0,0 +1,80 @@
import { Injectable } from '@angular/core';
import { PeriodsService } from './periods.service';
export class ProfitChart {
chartLabel: string[];
data: number[][];
}
@Injectable()
export class ProfitChartService {
private year = [
'2012',
'2013',
'2014',
'2015',
'2016',
'2017',
'2018',
];
private data = { };
constructor(private period: PeriodsService) {
this.data = {
week: this.getDataForWeekPeriod(),
month: this.getDataForMonthPeriod(),
year: this.getDataForYearPeriod(),
};
}
private getDataForWeekPeriod(): ProfitChart {
const nPoint = this.period.getWeeks().length;
return {
chartLabel: this.period.getWeeks(),
data: [
this.getRandomData(nPoint),
this.getRandomData(nPoint),
this.getRandomData(nPoint),
],
};
}
private getDataForMonthPeriod(): ProfitChart {
const nPoint = this.period.getMonths().length;
return {
chartLabel: this.period.getMonths(),
data: [
this.getRandomData(nPoint),
this.getRandomData(nPoint),
this.getRandomData(nPoint),
],
};
}
private getDataForYearPeriod(): ProfitChart {
const nPoint = this.year.length;
return {
chartLabel: this.year,
data: [
this.getRandomData(nPoint),
this.getRandomData(nPoint),
this.getRandomData(nPoint),
],
};
}
private getRandomData(nPoints: number): number[] {
return Array.from(Array(nPoints)).map(() => {
return Math.round(Math.random() * 500);
});
}
getProfitChartData(period: string): ProfitChart {
return this.data[period];
}
}

View file

@ -0,0 +1,51 @@
import { Injectable } from '@angular/core';
import { of as observableOf, Observable } from 'rxjs';
import { PeriodsService } from './periods.service';
export class TrafficBar {
data: number[];
labels: string[];
formatter: string;
}
@Injectable()
export class TrafficBarService {
private data = { };
constructor(private period: PeriodsService) {
this.data = {
week: this.getDataForWeekPeriod(),
month: this.getDataForMonthPeriod(),
year: this.getDataForYearPeriod(),
};
}
getDataForWeekPeriod(): TrafficBar {
return {
data: [10, 15, 19, 7, 20, 13, 15],
labels: this.period.getWeeks(),
formatter: '{c0} MB',
};
}
getDataForMonthPeriod(): TrafficBar {
return {
data: [0.5, 0.3, 0.8, 0.2, 0.3, 0.7, 0.8, 1, 0.7, 0.8, 0.6, 0.7],
labels: this.period.getMonths(),
formatter: '{c0} GB',
};
}
getDataForYearPeriod(): TrafficBar {
return {
data: [10, 15, 19, 7, 20, 13, 15, 19, 11],
labels: this.period.getYears(),
formatter: '{c0} GB',
};
}
getTrafficBarData(period: string): Observable<TrafficBar> {
return observableOf(this.data[period]);
}
}

View file

@ -0,0 +1,100 @@
import { Injectable } from '@angular/core';
import { of as observableOf, Observable } from 'rxjs';
import { PeriodsService } from './periods.service';
export class TrafficList {
date: string;
value: number;
delta: {
up: boolean;
value: number;
};
comparison: {
prevDate: string;
prevValue: number;
nextDate: string;
nextValue: number;
};
}
@Injectable()
export class TrafficListService {
private getRandom = (roundTo: number) => Math.round(Math.random() * roundTo);
private data = {};
constructor(private period: PeriodsService) {
this.data = {
week: this.getDataWeek(),
month: this.getDataMonth(),
year: this.getDataYear(),
};
}
private getDataWeek(): TrafficList[] {
const getFirstDateInPeriod = () => {
const weeks = this.period.getWeeks();
return weeks[weeks.length - 1];
};
return this.reduceData(this.period.getWeeks(), getFirstDateInPeriod);
}
private getDataMonth(): TrafficList[] {
const getFirstDateInPeriod = () => {
const months = this.period.getMonths();
const date = new Date();
const prevYear = date.getFullYear() - 1;
return `${months[months.length - 1]}, ${prevYear}`;
};
return this.reduceData(this.period.getMonths(), getFirstDateInPeriod);
}
private getDataYear(): TrafficList[] {
const getFirstDateInPeriod = () => {
const years = this.period.getYears();
return `${parseInt(years[0], 10) - 1}`;
};
return this.reduceData(this.period.getYears(), getFirstDateInPeriod);
}
private reduceData(timePeriods: string[], getFirstDateInPeriod: () => string): TrafficList[] {
return timePeriods.reduce((result, timePeriod, index) => {
const hasResult = result[index - 1];
const prevDate = hasResult ?
result[index - 1].comparison.nextDate :
getFirstDateInPeriod();
const prevValue = hasResult ?
result[index - 1].comparison.nextValue :
this.getRandom(100);
const nextValue = this.getRandom(100);
const deltaValue = prevValue - nextValue;
const item = {
date: timePeriod,
value: this.getRandom(1000),
delta: {
up: deltaValue <= 0,
value: Math.abs(deltaValue),
},
comparison: {
prevDate,
prevValue,
nextDate: timePeriod,
nextValue,
},
};
return [...result, item];
}, []);
}
getTrafficListData(period: string): Observable<TrafficList> {
return observableOf(this.data[period]);
}
}

View file

@ -0,0 +1,67 @@
import { Injectable } from '@angular/core';
import { of as observableOf, Observable } from 'rxjs';
import { PeriodsService } from './periods.service';
export class UserActive {
date: string;
pagesVisitCount: number;
deltaUp: boolean;
newVisits: number;
}
@Injectable()
export class UserActivityService {
private getRandom = (roundTo: number) => Math.round(Math.random() * roundTo);
data = {};
constructor(private periods: PeriodsService) {
this.data = {
week: this.getDataWeek(),
month: this.getDataMonth(),
year: this.getDataYear(),
};
}
private getDataWeek(): UserActive[] {
return this.periods.getWeeks().map((week) => {
return {
date: week,
pagesVisitCount: this.getRandom(1000),
deltaUp: this.getRandom(1) % 2 === 0,
newVisits: this.getRandom(100),
};
});
}
private getDataMonth(): UserActive[] {
const date = new Date();
const days = date.getDate();
const month = this.periods.getMonths()[date.getMonth()];
return Array.from(Array(days)).map((_, index) => {
return {
date: `${index + 1} ${month}`,
pagesVisitCount: this.getRandom(1000),
deltaUp: this.getRandom(1) % 2 === 0,
newVisits: this.getRandom(100),
};
});
}
private getDataYear(): UserActive[] {
return this.periods.getYears().map((year) => {
return {
date: year,
pagesVisitCount: this.getRandom(1000),
deltaUp: this.getRandom(1) % 2 === 0,
newVisits: this.getRandom(100),
};
});
}
getUserActivityData(period: string): Observable<UserActive[]> {
return observableOf(this.data[period]);
}
}

View file

@ -105,6 +105,28 @@
}
}
/deep/ nb-menu {
& > .menu-items {
& > .menu-item:first-child {
.menu-title {
&::after {
content: 'new';
color: nb-theme(color-white);
margin-left: 1rem;
background: nb-theme(color-danger);
padding: 0 0.5rem;
border-radius: nb-theme(radius);
font-size: nb-theme(font-size-sm);
}
}
}
}
.nb-e-commerce {
font-size: 2rem;
}
}
&.compacted {
/deep/ nb-sidebar-header {

View file

@ -2,3 +2,4 @@ export * from './capitalize.pipe';
export * from './plural.pipe';
export * from './round.pipe';
export * from './timing.pipe';
export * from './number-with-commas.pipe';

View file

@ -0,0 +1,9 @@
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({ name: 'ngxNumberWithCommas' })
export class NumberWithCommasPipe implements PipeTransform {
transform(input: number): string {
return new Intl.NumberFormat().format(input);
}
}

View file

@ -18,14 +18,14 @@ export const CORPORATE_THEME = {
},
traffic: {
colorBlack: '#000000',
colorBlack: '#ffffff',
tooltipBg: '#eef2f5',
tooltipBorderColor: '#eef2f5',
tooltipExtraCss: 'border-radius: 10px; padding: 4px 16px;',
tooltipTextColor: '#2a2a2a',
tooltipFontWeight: '400',
lineBg: '#c0c8d1',
lineBg: '#cae6f3',
lineShadowBlur: '0',
itemColor: '#bcc3cc',
itemBorderColor: '#bcc3cc',
@ -68,6 +68,62 @@ export const CORPORATE_THEME = {
areaBorderColor: '#ebeef2',
},
profitBarAnimationEchart: {
textColor: '#b2bac2',
firstAnimationBarColor: '#719efc',
secondAnimationBarColor: '#5dcfe3',
splitLineStyleOpacity: '0.06',
splitLineStyleWidth: '1',
splitLineStyleColor: '#000000',
tooltipTextColor: '#2a2a2a',
tooltipFontWeight: '400',
tooltipFontSize: '16',
tooltipBg: '#eef2f5',
tooltipBorderColor: '#eef2f5',
tooltipBorderWidth: '3',
tooltipExtraCss: 'border-radius: 10px; padding: 4px 16px;',
},
trafficBarEchart: {
gradientFrom: '#ff8ea0',
gradientTo: '#ffa36b',
shadow: 'rgba(0, 0, 0, 0)',
shadowBlur: '0',
axisTextColor: '#b2bac2',
axisFontSize: '12',
tooltipBg: '#edf0f4',
tooltipBorderColor: '#ebeef2',
tooltipExtraCss: 'border-radius: 10px; padding: 8px 24px;',
tooltipTextColor: '#2a2a2a',
tooltipFontWeight: 'bolder',
},
countryOrders: {
countryBorderColor: 'rgba(255, 255, 255, 1)',
countryFillColor: 'rgba(236, 242, 245, 1)',
countryBorderWidth: '1',
hoveredCountryBorderColor: 'rgba(113, 158, 252, 1)',
hoveredCountryFillColor: 'rgba(199, 216, 247, 1)',
hoveredCountryBorderWidth: '3',
chartAxisLineColor: 'rgba(0, 0, 0, 0)',
chartAxisTextColor: '#b2bac2',
chartAxisFontSize: '16',
chartGradientTo: 'rgba(113, 158, 252, 1)',
chartGradientFrom: 'rgba(113, 158, 252, 1)',
chartAxisSplitLine: '#ebeef2',
chartShadowLineColor: '#2f296b',
chartLineBottomShadowColor: 'rgba(113, 158, 252, 1)',
chartInnerLineColor: '#eceff4',
},
echarts: {
bg: '#ffffff',
textColor: '#484848',
@ -82,5 +138,167 @@ export const CORPORATE_THEME = {
axisLineColor: '#cccccc',
textColor: '#484848',
},
orders: {
tooltipBg: '#ffffff',
tooltipLineColor: 'rgba(0, 0, 0, 0)',
tooltipLineWidth: '0',
tooltipBorderColor: '#ebeef2',
tooltipExtraCss: 'border-radius: 10px; padding: 8px 24px;',
tooltipTextColor: '#2a2a2a',
tooltipFontWeight: 'bolder',
tooltipFontSize: '20',
axisLineColor: 'rgba(161, 161 ,229, 0.3)',
axisFontSize: '16',
axisTextColor: '#b2bac2',
yAxisSplitLine: 'rgba(161, 161 ,229, 0.2)',
itemBorderColor: '#73a1ff',
lineStyle: 'solid',
lineWidth: '4',
// first line
firstAreaGradFrom: 'rgba(227, 236, 254, 0.7)',
firstAreaGradTo: 'rgba(227, 236, 254, 0.7)',
firstShadowLineDarkBg: 'rgba(0, 0, 0, 0)',
// second line
secondLineGradFrom: 'rgba(93, 207, 227, 1)',
secondLineGradTo: 'rgba(93, 207, 227, 1)',
secondAreaGradFrom: 'rgba(0, 0, 0, 0)',
secondAreaGradTo: 'rgba(0, 0, 0, 0)',
secondShadowLineDarkBg: 'rgba(0, 0, 0, 0)',
// third line
thirdLineGradFrom: 'rgba(113, 158, 252, 1)',
thirdLineGradTo: 'rgba(113, 158, 252, 1)',
thirdAreaGradFrom: 'rgba(0, 0, 0, 0)',
thirdAreaGradTo: 'rgba(0, 0, 0, 0)',
thirdShadowLineDarkBg: 'rgba(0, 0, 0, 0)',
},
profit: {
bg: '#ffffff',
textColor: '#ffffff',
axisLineColor: 'rgba(161, 161 ,229, 0.3)',
splitLineColor: 'rgba(161, 161 ,229, 0.2)',
areaOpacity: '1',
axisFontSize: '16',
axisTextColor: '#b2bac2',
// first bar
firstLineGradFrom: '#719efc',
firstLineGradTo: '#719efc',
firstLineShadow: 'rgba(14, 16, 48, 0.4)',
// second bar
secondLineGradFrom: '#5dcfe3',
secondLineGradTo: '#5dcfe3',
secondLineShadow: 'rgba(14, 16, 48, 0.4)',
// third bar
thirdLineGradFrom: '#e3ecfe',
thirdLineGradTo: '#e3ecfe',
thirdLineShadow: 'rgba(14, 16, 48, 0.4)',
},
orderProfitLegend: {
firstItem: '#719efc',
secondItem: '#5dcfe3',
thirdItem: '#e3ecfe',
},
visitors: {
tooltipBg: '#ffffff',
tooltipLineColor: 'rgba(0, 0, 0, 0)',
tooltipLineWidth: '0',
tooltipBorderColor: '#ebeef2',
tooltipExtraCss: 'border-radius: 10px; padding: 8px 24px;',
tooltipTextColor: '#2a2a2a',
tooltipFontWeight: 'bolder',
tooltipFontSize: '20',
axisLineColor: 'rgba(161, 161 ,229, 0.3)',
axisFontSize: '16',
axisTextColor: '#b2bac2',
yAxisSplitLine: 'rgba(161, 161 ,229, 0.2)',
itemBorderColor: '#73a1ff',
lineStyle: 'dotted',
lineWidth: '6',
lineGradFrom: '#73a1ff',
lineGradTo: '#73a1ff',
lineShadow: 'rgba(0, 0, 0, 0)',
areaGradFrom: 'rgba(146, 181, 252, 1)',
areaGradTo: 'rgba(146, 181, 252, 1)',
shadowLineDarkBg: '#a695ff',
innerLineStyle: 'solid',
innerLineWidth: '1',
innerAreaGradFrom: 'rgba(227, 236, 254, 1)',
innerAreaGradTo: 'rgba(227, 236, 254, 1)',
},
visitorsLegend: {
firstIcon: '#e3ecfe',
secondIcon: '#719efc',
},
visitorsPie: {
firstPieGradientLeft: '#94e2ed',
firstPieGradientRight: '#94e2ed',
firstPieShadowColor: 'rgba(0, 0, 0, 0)',
firstPieRadius: ['65%', '90%'],
secondPieGradientLeft: '#719efc',
secondPieGradientRight: '#719efc',
secondPieShadowColor: '#b2cafb',
secondPieRadius: ['63%', '92%'],
shadowOffsetX: '-4',
shadowOffsetY: '-4',
},
visitorsPieLegend: {
firstSection: '#719efc',
secondSection: '#99e5ee',
},
earningPie: {
radius: ['65%', '100%'],
center: ['50%', '50%'],
fontSize: '22',
firstPieGradientLeft: '#719efc',
firstPieGradientRight: '#719efc',
firstPieShadowColor: 'rgba(0, 0, 0, 0)',
secondPieGradientLeft: '#ff9f6f',
secondPieGradientRight: '#ff9f6f',
secondPieShadowColor: 'rgba(0, 0, 0, 0)',
thirdPieGradientLeft: '#ff5e83',
thirdPieGradientRight: '#ff5e83',
thirdPieShadowColor: 'rgba(0, 0, 0, 0)',
},
earningLine: {
gradFrom: '#e3ecfe',
gradTo: '#e3ecfe',
tooltipTextColor: '#2a2a2a',
tooltipFontWeight: '400',
tooltipFontSize: '16',
tooltipBg: '#eef2f5',
tooltipBorderColor: '#eef2f5',
tooltipBorderWidth: '3',
tooltipExtraCss: 'border-radius: 10px; padding: 4px 16px;',
},
},
};

View file

@ -69,6 +69,62 @@ export const COSMIC_THEME = {
areaBorderColor: '#654ddb',
},
profitBarAnimationEchart: {
textColor: '#ffffff',
firstAnimationBarColor: '#0088ff',
secondAnimationBarColor: '#7659ff',
splitLineStyleOpacity: '0.06',
splitLineStyleWidth: '1',
splitLineStyleColor: '#000000',
tooltipTextColor: '#ffffff',
tooltipFontWeight: 'normal',
tooltipFontSize: '16',
tooltipBg: 'rgba(0, 255, 170, 0.35)',
tooltipBorderColor: '#00d977',
tooltipBorderWidth: '3',
tooltipExtraCss: 'box-shadow: 0px 2px 46px 0 rgba(0, 255, 170, 0.35); border-radius: 10px; padding: 4px 16px;',
},
trafficBarEchart: {
gradientFrom: '#fc0',
gradientTo: '#ffa100',
shadow: '#ffb600',
shadowBlur: '5',
axisTextColor: '#a1a1e5',
axisFontSize: '12',
tooltipBg: 'rgba(0, 255, 170, 0.35)',
tooltipBorderColor: '#00d977',
tooltipExtraCss: 'box-shadow: 0px 2px 46px 0 rgba(0, 255, 170, 0.35); border-radius: 10px; padding: 4px 16px;',
tooltipTextColor: '#ffffff',
tooltipFontWeight: 'normal',
},
countryOrders: {
countryBorderColor: '#525dbd',
countryFillColor: '#4f41a6',
countryBorderWidth: '2',
hoveredCountryBorderColor: '#00f9a6',
hoveredCountryFillColor: '#377aa7',
hoveredCountryBorderWidth: '3',
chartAxisLineColor: 'rgba(161, 161 ,229, 0.3)',
chartAxisTextColor: '#a1a1e5',
chartAxisFontSize: '16',
chartGradientTo: '#00c7c7',
chartGradientFrom: '#00d977',
chartAxisSplitLine: 'rgba(161, 161 ,229, 0.2)',
chartShadowBarColor: '#2f296b',
chartLineBottomShadowColor: '#00977e',
chartInnerLineColor: '#2f296b',
},
echarts: {
bg: '#3d3780',
textColor: '#ffffff',
@ -83,5 +139,166 @@ export const COSMIC_THEME = {
axisLineColor: '#a1a1e5',
textColor: '#ffffff',
},
orders: {
tooltipBg: 'rgba(0, 255, 170, 0.35)',
tooltipLineColor: 'rgba(255, 255, 255, 0.1)',
tooltipLineWidth: '1',
tooltipBorderColor: '#00d977',
tooltipExtraCss: 'box-shadow: 0px 2px 46px 0 rgba(0, 255, 170, 0.35); border-radius: 10px; padding: 8px 24px;',
tooltipTextColor: '#ffffff',
tooltipFontWeight: 'normal',
tooltipFontSize: '20',
axisLineColor: 'rgba(161, 161 ,229, 0.3)',
axisFontSize: '16',
axisTextColor: '#a1a1e5',
yAxisSplitLine: 'rgba(161, 161 ,229, 0.2)',
itemBorderColor: '#ffffff',
lineStyle: 'solid',
lineWidth: '4',
// first line
firstAreaGradFrom: 'rgba(78, 64, 164, 1)',
firstAreaGradTo: 'rgba(78, 64, 164, 1)',
firstShadowLineDarkBg: '#018dff',
// second line
secondLineGradFrom: '#00bece',
secondLineGradTo: '#00da78',
secondAreaGradFrom: 'rgba(38, 139, 145, 0.8)',
secondAreaGradTo: 'rgba(38, 139, 145, 0.5)',
secondShadowLineDarkBg: '#2c5a85',
// third line
thirdLineGradFrom: '#8069ff',
thirdLineGradTo: '#8357ff',
thirdAreaGradFrom: 'rgba(118, 73, 208, 0.7)',
thirdAreaGradTo: 'rgba(188, 92, 255, 0.4)',
thirdShadowLineDarkBg: '#a695ff',
},
profit: {
bg: '#3d3780',
textColor: '#ffffff',
axisLineColor: '#a1a1e5',
splitLineColor: '#342e73',
areaOpacity: '1',
axisFontSize: '16',
axisTextColor: '#a1a1e5',
// first bar
firstLineGradFrom: '#00bece',
firstLineGradTo: '#00da78',
firstLineShadow: 'rgba(14, 16, 48, 0.4)',
// second bar
secondLineGradFrom: '#8069ff',
secondLineGradTo: '#8357ff',
secondLineShadow: 'rgba(14, 16, 48, 0.4)',
// third bar
thirdLineGradFrom: '#4e40a4',
thirdLineGradTo: '#4e40a4',
thirdLineShadow: 'rgba(14, 16, 48, 0.4)',
},
orderProfitLegend: {
firstItem: 'linear-gradient(90deg, #00c7c7 0%, #00d977 100%)',
secondItem: 'linear-gradient(90deg, #a454ff 0%, #7659ff 100%)',
thirdItem: '#4e40a4',
},
visitors: {
tooltipBg: 'rgba(0, 255, 170, 0.35)',
tooltipLineColor: 'rgba(255, 255, 255, 0.1)',
tooltipLineWidth: '1',
tooltipBorderColor: '#00d977',
tooltipExtraCss: 'box-shadow: 0px 2px 46px 0 rgba(0, 255, 170, 0.35); border-radius: 10px; padding: 8px 24px;',
tooltipTextColor: '#ffffff',
tooltipFontWeight: 'normal',
axisLineColor: 'rgba(161, 161 ,229, 0.3)',
axisFontSize: '16',
axisTextColor: '#a1a1e5',
yAxisSplitLine: 'rgba(161, 161 ,229, 0.2)',
itemBorderColor: '#ffffff',
lineStyle: 'dotted',
lineWidth: '6',
lineGradFrom: '#ffffff',
lineGradTo: '#ffffff',
lineShadow: 'rgba(14, 16, 48, 0.4)',
areaGradFrom: 'rgba(188, 92, 255, 1)',
areaGradTo: 'rgba(188, 92, 255, 0.5)',
shadowLineDarkBg: '#a695ff',
innerLineStyle: 'solid',
innerLineWidth: '1',
innerAreaGradFrom: 'rgba(59, 165, 243, 1)',
innerAreaGradTo: 'rgba(4, 133, 243 , 1)',
},
visitorsLegend: {
firstIcon: 'linear-gradient(90deg, #0088ff 0%, #3dafff 100%)',
secondIcon: 'linear-gradient(90deg, #a454ff 0%, #7659ff 100%)',
},
visitorsPie: {
firstPieGradientLeft: '#7bff24',
firstPieGradientRight: '#2ec7fe',
firstPieShadowColor: '#19977E',
firstPieRadius: ['70%', '90%'],
secondPieGradientLeft: '#ff894a',
secondPieGradientRight: '#ffcc10',
secondPieShadowColor: '#cf7c1c',
secondPieRadius: ['60%', '95%'],
shadowOffsetX: '0',
shadowOffsetY: '3',
},
visitorsPieLegend: {
firstSection: 'linear-gradient(90deg, #ffcb17 0%, #ff874c 100%)',
secondSection: 'linear-gradient(90deg, #00c7c7 0%, #00d977 100%)',
},
earningPie: {
radius: ['65%', '100%'],
center: ['50%', '50%'],
fontSize: '22',
firstPieGradientLeft: '#00d77f',
firstPieGradientRight: '#00d77f',
firstPieShadowColor: 'rgba(0, 0, 0, 0)',
secondPieGradientLeft: '#7756f7',
secondPieGradientRight: '#7756f7',
secondPieShadowColor: 'rgba(0, 0, 0, 0)',
thirdPieGradientLeft: '#ffca00',
thirdPieGradientRight: '#ffca00',
thirdPieShadowColor: 'rgba(0, 0, 0, 0)',
},
earningLine: {
gradFrom: 'rgba(118, 89, 255, 0.4)',
gradTo: 'rgba(164, 84, 255, 0.5)',
tooltipTextColor: '#ffffff',
tooltipFontWeight: 'normal',
tooltipFontSize: '16',
tooltipBg: 'rgba(0, 255, 170, 0.35)',
tooltipBorderColor: '#00d977',
tooltipBorderWidth: '3',
tooltipExtraCss: 'box-shadow: 0px 2px 46px 0 rgba(0, 255, 170, 0.35); border-radius: 10px; padding: 4px 16px;',
},
},
};

View file

@ -70,6 +70,62 @@ export const DEFAULT_THEME = {
areaBorderColor: '#ebeef2',
},
profitBarAnimationEchart: {
textColor: '#484848',
firstAnimationBarColor: '#3edd81',
secondAnimationBarColor: '#8d7fff',
splitLineStyleOpacity: '0.06',
splitLineStyleWidth: '1',
splitLineStyleColor: '#000000',
tooltipTextColor: '#2a2a2a',
tooltipFontWeight: 'bolder',
tooltipFontSize: '16',
tooltipBg: '#ffffff',
tooltipBorderColor: '#c0c8d1',
tooltipBorderWidth: '3',
tooltipExtraCss: 'border-radius: 10px; padding: 4px 16px;',
},
trafficBarEchart: {
gradientFrom: '#fc0',
gradientTo: '#ffa100',
shadow: '#ffb600',
shadowBlur: '0',
axisTextColor: '#b2bac2',
axisFontSize: '12',
tooltipBg: '#ffffff',
tooltipBorderColor: '#c0c8d1',
tooltipExtraCss: 'border-radius: 10px; padding: 4px 16px;',
tooltipTextColor: '#2a2a2a',
tooltipFontWeight: 'bolder',
},
countryOrders: {
countryBorderColor: 'rgba(255, 255, 255, 1)',
countryFillColor: 'rgba(236, 242, 245, 1)',
countryBorderWidth: '1',
hoveredCountryBorderColor: '#40dc7e',
hoveredCountryFillColor: '#c7f4d9',
hoveredCountryBorderWidth: '3',
chartAxisLineColor: 'rgba(0, 0, 0, 0)',
chartAxisTextColor: '#b2bac2',
chartAxisFontSize: '16',
chartGradientTo: '#3edd81',
chartGradientFrom: '#3bddaf',
chartAxisSplitLine: '#ebeef2',
chartShadowLineColor: '#2f296b',
chartLineBottomShadowColor: '#eceff4',
chartInnerLineColor: '#eceff4',
},
echarts: {
bg: '#ffffff',
textColor: '#484848',
@ -84,5 +140,168 @@ export const DEFAULT_THEME = {
axisLineColor: '#cccccc',
textColor: '#484848',
},
orders: {
tooltipBg: '#ffffff',
tooltipLineColor: 'rgba(0, 0, 0, 0)',
tooltipLineWidth: '0',
tooltipBorderColor: '#ebeef2',
tooltipExtraCss: 'border-radius: 10px; padding: 8px 24px;',
tooltipTextColor: '#2a2a2a',
tooltipFontWeight: 'bolder',
tooltipFontSize: '20',
axisLineColor: 'rgba(161, 161 ,229, 0.3)',
axisFontSize: '16',
axisTextColor: '#b2bac2',
yAxisSplitLine: 'rgba(161, 161 ,229, 0.2)',
itemBorderColor: '#42db7d',
lineStyle: 'solid',
lineWidth: '4',
// first line
firstAreaGradFrom: 'rgba(236, 242, 245, 0.8)',
firstAreaGradTo: 'rgba(236, 242, 245, 0.8)',
firstShadowLineDarkBg: 'rgba(0, 0, 0, 0)',
// second line
secondLineGradFrom: 'rgba(164, 123, 255, 1)',
secondLineGradTo: 'rgba(164, 123, 255, 1)',
secondAreaGradFrom: 'rgba(188, 92, 255, 0.2)',
secondAreaGradTo: 'rgba(188, 92, 255, 0)',
secondShadowLineDarkBg: 'rgba(0, 0, 0, 0)',
// third line
thirdLineGradFrom: 'rgba(55, 220, 136, 1)',
thirdLineGradTo: 'rgba(55, 220, 136, 1)',
thirdAreaGradFrom: 'rgba(31 ,106, 124, 0.2)',
thirdAreaGradTo: 'rgba(4, 126, 230, 0)',
thirdShadowLineDarkBg: 'rgba(0, 0, 0, 0)',
},
// TODO: need design for default theme
profit: {
bg: '#ffffff',
textColor: '#ffffff',
axisLineColor: 'rgba(161, 161 ,229, 0.3)',
splitLineColor: 'rgba(161, 161 ,229, 0.2)',
areaOpacity: '1',
axisFontSize: '16',
axisTextColor: '#b2bac2',
// first bar
firstLineGradFrom: '#00bece',
firstLineGradTo: '#00da78',
firstLineShadow: 'rgba(14, 16, 48, 0.4)',
// second bar
secondLineGradFrom: '#8069ff',
secondLineGradTo: '#8357ff',
secondLineShadow: 'rgba(14, 16, 48, 0.4)',
// third bar
thirdLineGradFrom: 'rgba(236, 242, 245, 0.8)',
thirdLineGradTo: 'rgba(236, 242, 245, 0.8)',
thirdLineShadow: 'rgba(14, 16, 48, 0.4)',
},
orderProfitLegend: {
firstItem: 'linear-gradient(90deg, #3edd81 0%, #3bddad 100%)',
secondItem: 'linear-gradient(90deg, #8d7fff 0%, #b17fff 100%)',
thirdItem: 'rgba(236, 242, 245, 0.8)',
},
visitors: {
tooltipBg: '#ffffff',
tooltipLineColor: 'rgba(0, 0, 0, 0)',
tooltipLineWidth: '0',
tooltipBorderColor: '#ebeef2',
tooltipExtraCss: 'border-radius: 10px; padding: 8px 24px;',
tooltipTextColor: '#2a2a2a',
tooltipFontWeight: 'bolder',
tooltipFontSize: '20',
axisLineColor: 'rgba(161, 161 ,229, 0.3)',
axisFontSize: '16',
axisTextColor: '#b2bac2',
yAxisSplitLine: 'rgba(161, 161 ,229, 0.2)',
itemBorderColor: '#42db7d',
lineStyle: 'dotted',
lineWidth: '6',
lineGradFrom: '#ffffff',
lineGradTo: '#ffffff',
lineShadow: 'rgba(14, 16, 48, 0)',
areaGradFrom: 'rgba(188, 92, 255, 1)',
areaGradTo: 'rgba(188, 92, 255, 0.5)',
shadowLineDarkBg: '#a695ff',
innerLineStyle: 'solid',
innerLineWidth: '1',
innerAreaGradFrom: 'rgba(60, 221, 156, 1)',
innerAreaGradTo: 'rgba(60, 221, 156, 1)',
},
visitorsLegend: {
firstIcon: 'linear-gradient(90deg, #3edd81 0%, #3bddad 100%)',
secondIcon: 'linear-gradient(90deg, #8d7fff 0%, #b17fff 100%)',
},
visitorsPie: {
firstPieGradientLeft: '#8defbb',
firstPieGradientRight: '#8defbb',
firstPieShadowColor: 'rgba(0, 0, 0, 0)',
firstPieRadius: ['70%', '90%'],
secondPieGradientLeft: '#ff894a',
secondPieGradientRight: '#ffcc10',
secondPieShadowColor: 'rgba(0, 0, 0, 0)',
secondPieRadius: ['60%', '97%'],
shadowOffsetX: '0',
shadowOffsetY: '0',
},
visitorsPieLegend: {
firstSection: 'linear-gradient(90deg, #ffcb17 0%, #ff874c 100%)',
secondSection: '#8defbb',
},
earningPie: {
radius: ['65%', '100%'],
center: ['50%', '50%'],
fontSize: '22',
firstPieGradientLeft: '#00d77f',
firstPieGradientRight: '#00d77f',
firstPieShadowColor: 'rgba(0, 0, 0, 0)',
secondPieGradientLeft: '#7756f7',
secondPieGradientRight: '#7756f7',
secondPieShadowColor: 'rgba(0, 0, 0, 0)',
thirdPieGradientLeft: '#ffca00',
thirdPieGradientRight: '#ffca00',
thirdPieShadowColor: 'rgba(0, 0, 0, 0)',
},
earningLine: {
gradFrom: 'rgba(188, 92, 255, 0.5)',
gradTo: 'rgba(188, 92, 255, 0.5)',
tooltipTextColor: '#2a2a2a',
tooltipFontWeight: 'bolder',
tooltipFontSize: '16',
tooltipBg: '#ffffff',
tooltipBorderColor: '#c0c8d1',
tooltipBorderWidth: '3',
tooltipExtraCss: 'border-radius: 10px; padding: 4px 16px;',
},
},
};

View file

@ -18,6 +18,22 @@ $nb-themes: nb-register-theme((
switcher-background: #ebeff5,
switcher-background-percentage: 50%,
drops-icon-line-gadient: -webkit-linear-gradient(#01dbb5, #0bbb79),
list-item-border-width: 1px,
slide-out-container-width: 30%,
slide-out-background: linear-gradient(270deg, #f7fafb 0%, #ecf2f5 100%),
slide-out-shadow-color: 0 4px 14px 0 #a2d2c8,
slide-out-shadow-color-rtl: 0 4px 14px 0 #a2d2c8,
chart-panel-summary-box-shadow: none,
chart-panel-summary-background-color: #ecf2f5,
chart-panel-summary-border-color: #ebeff1,
chart-panel-summary-border-width: 1px,
ecommerce-card-border-width: 1px,
progress-bar-background: linear-gradient(90deg, #3edd81 0%, #3bddaf 100%),
), default, default);
$nb-themes: nb-register-theme((
@ -32,6 +48,22 @@ $nb-themes: nb-register-theme((
switcher-background: #4e41a5,
switcher-background-percentage: 14%,
drops-icon-line-gadient: -webkit-linear-gradient(#a258fe, #7958fa),
list-item-border-width: 1px,
slide-out-container-width: 30%,
slide-out-background: radial-gradient(circle, #302c6e 0%, #423f8c 100%),
slide-out-shadow-color: 2px 0 3px rgba(19, 19, 94, 0.9),
slide-out-shadow-color-rtl: -2px 0 3px rgba(19, 19, 94, 0.9),
chart-panel-summary-box-shadow: none,
chart-panel-summary-background-color: rgba(0, 0, 0, 0.1),
chart-panel-summary-border-color: #332e73,
chart-panel-summary-border-width: 1px,
ecommerce-card-border-width: 1px,
progress-bar-background: linear-gradient(90deg, #00c7c7 0%, #00d977 100%),
), cosmic, cosmic);
$nb-themes: nb-register-theme((
@ -46,4 +78,20 @@ $nb-themes: nb-register-theme((
switcher-background: #2b2d34,
switcher-background-percentage: 14%,
drops-icon-line-gadient: -webkit-linear-gradient(#e9e8eb, #a7a2be),
list-item-border-width: 1px,
slide-out-container-width: 30%,
slide-out-background: linear-gradient(270deg, #f7fafb 0%, #ecf2f5 100%),
slide-out-shadow-color: 0 4px 14px 0 #a2d2c8,
slide-out-shadow-color-rtl: 0 4px 14px 0 #a2d2c8,
chart-panel-summary-box-shadow: none,
chart-panel-summary-background-color: #f7fafb,
chart-panel-summary-border-color: #ebeff1,
chart-panel-summary-border-width: 1px,
ecommerce-card-border-width: 1px,
progress-bar-background: linear-gradient(90deg, #ff9f6f 0%, #ff8b97 100%),
), corporate, corporate);

View file

@ -17,6 +17,7 @@ import {
NbCheckboxModule,
NbPopoverModule,
NbContextMenuModule,
NbProgressBarModule,
} from '@nebular/theme';
import { NbSecurityModule } from '@nebular/security';
@ -32,7 +33,13 @@ import {
TinyMCEComponent,
ThemeSwitcherListComponent,
} from './components';
import { CapitalizePipe, PluralPipe, RoundPipe, TimingPipe } from './pipes';
import {
CapitalizePipe,
PluralPipe,
RoundPipe,
TimingPipe,
NumberWithCommasPipe,
} from './pipes';
import {
OneColumnLayoutComponent,
SampleLayoutComponent,
@ -59,7 +66,8 @@ const NB_MODULES = [
NbPopoverModule,
NbContextMenuModule,
NgbModule,
NbSecurityModule, // *nbIsGranted directive
NbSecurityModule, // *nbIsGranted directive,
NbProgressBarModule,
];
const COMPONENTS = [
@ -87,6 +95,7 @@ const PIPES = [
PluralPipe,
RoundPipe,
TimingPipe,
NumberWithCommasPipe,
];
const NB_THEME_PROVIDERS = [

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
}

Some files were not shown because too many files have changed in this diff Show more