Merge pull request #738 from MiguelMedeiros/feature-echarts
Feature: New charts library.
This commit is contained in:
		
						commit
						47ae306a75
					
				| @ -281,12 +281,12 @@ describe('Mainnet', () => { | |||||||
|             }); |             }); | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         it('loads the tv screen - mobile', () => { |         it.only('loads the tv screen - mobile', () => { | ||||||
|             cy.viewport('iphone-6'); |             cy.viewport('iphone-6'); | ||||||
|             cy.visit('/tv'); |             cy.visit('/tv'); | ||||||
|             cy.waitForSkeletonGone(); |             cy.waitForSkeletonGone(); | ||||||
|             cy.get('.chart-holder'); |             cy.get('.chart-holder'); | ||||||
|             cy.get('.blockchain-wrapper').should('be.visible'); |             cy.get('.blockchain-wrapper').should('not.visible'); | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         it('loads the api screen', () => { |         it('loads the api screen', () => { | ||||||
|  | |||||||
							
								
								
									
										101
									
								
								frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										101
									
								
								frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -23,7 +23,7 @@ | |||||||
|         "@fortawesome/fontawesome-common-types": "^0.2.35", |         "@fortawesome/fontawesome-common-types": "^0.2.35", | ||||||
|         "@fortawesome/fontawesome-svg-core": "^1.2.35", |         "@fortawesome/fontawesome-svg-core": "^1.2.35", | ||||||
|         "@fortawesome/free-solid-svg-icons": "^5.15.3", |         "@fortawesome/free-solid-svg-icons": "^5.15.3", | ||||||
|         "@mempool/chartist": "^0.11.4", |         "@juggle/resize-observer": "^3.3.1", | ||||||
|         "@mempool/mempool.js": "^2.2.4", |         "@mempool/mempool.js": "^2.2.4", | ||||||
|         "@ng-bootstrap/ng-bootstrap": "^7.0.0", |         "@ng-bootstrap/ng-bootstrap": "^7.0.0", | ||||||
|         "@nguniversal/express-engine": "11.2.1", |         "@nguniversal/express-engine": "11.2.1", | ||||||
| @ -32,9 +32,11 @@ | |||||||
|         "browserify": "^17.0.0", |         "browserify": "^17.0.0", | ||||||
|         "clipboard": "^2.0.4", |         "clipboard": "^2.0.4", | ||||||
|         "domino": "^2.1.6", |         "domino": "^2.1.6", | ||||||
|  |         "echarts": "^5.1.2", | ||||||
|         "express": "^4.17.1", |         "express": "^4.17.1", | ||||||
|         "lightweight-charts": "^3.3.0", |         "lightweight-charts": "^3.3.0", | ||||||
|         "ngx-bootrap-multiselect": "^2.0.0", |         "ngx-bootrap-multiselect": "^2.0.0", | ||||||
|  |         "ngx-echarts": "^7.0.1", | ||||||
|         "ngx-infinite-scroll": "^10.0.1", |         "ngx-infinite-scroll": "^10.0.1", | ||||||
|         "qrcode": "^1.4.4", |         "qrcode": "^1.4.4", | ||||||
|         "rxjs": "^6.6.7", |         "rxjs": "^6.6.7", | ||||||
| @ -2252,13 +2254,10 @@ | |||||||
|         "schema-utils": "^2.7.0" |         "schema-utils": "^2.7.0" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "node_modules/@mempool/chartist": { |     "node_modules/@juggle/resize-observer": { | ||||||
|       "version": "0.11.4", |       "version": "3.3.1", | ||||||
|       "resolved": "https://registry.npmjs.org/@mempool/chartist/-/chartist-0.11.4.tgz", |       "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.3.1.tgz", | ||||||
|       "integrity": "sha512-wSemsw2NIWS7/SHxjDe9upSdUETxNRebY0ByaJzcONKUzJSUzMuSNmKEdD3kr/g02H++JvsXR2znLC6tYEAbPA==", |       "integrity": "sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw==" | ||||||
|       "engines": { |  | ||||||
|         "node": ">=4.6.0" |  | ||||||
|       } |  | ||||||
|     }, |     }, | ||||||
|     "node_modules/@mempool/mempool.js": { |     "node_modules/@mempool/mempool.js": { | ||||||
|       "version": "2.2.4", |       "version": "2.2.4", | ||||||
| @ -6931,6 +6930,20 @@ | |||||||
|         "safer-buffer": "^2.1.0" |         "safer-buffer": "^2.1.0" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "node_modules/echarts": { | ||||||
|  |       "version": "5.1.2", | ||||||
|  |       "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.1.2.tgz", | ||||||
|  |       "integrity": "sha512-okUhO4sw22vwZp+rTPNjd/bvTdpug4K4sHNHyrV8NdAncIX9/AarlolFqtJCAYKGFYhUBNjIWu1EznFrSWTFxg==", | ||||||
|  |       "dependencies": { | ||||||
|  |         "tslib": "2.0.3", | ||||||
|  |         "zrender": "5.1.1" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "node_modules/echarts/node_modules/tslib": { | ||||||
|  |       "version": "2.0.3", | ||||||
|  |       "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", | ||||||
|  |       "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" | ||||||
|  |     }, | ||||||
|     "node_modules/ee-first": { |     "node_modules/ee-first": { | ||||||
|       "version": "1.1.1", |       "version": "1.1.1", | ||||||
|       "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", |       "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", | ||||||
| @ -11916,6 +11929,18 @@ | |||||||
|         "tslib": "^2.0.0" |         "tslib": "^2.0.0" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "node_modules/ngx-echarts": { | ||||||
|  |       "version": "7.0.1", | ||||||
|  |       "resolved": "https://registry.npmjs.org/ngx-echarts/-/ngx-echarts-7.0.1.tgz", | ||||||
|  |       "integrity": "sha512-DDRwEssQRjjgPElEjF1emORoUEnF6OMZ20xnQBLueSHitM7XnIUErYVe9GMmm/jCtI+iPvJPEedyxMPl62nHLw==", | ||||||
|  |       "dependencies": { | ||||||
|  |         "tslib": "^2.0.0" | ||||||
|  |       }, | ||||||
|  |       "peerDependencies": { | ||||||
|  |         "@juggle/resize-observer": ">=3.3.1", | ||||||
|  |         "echarts": ">=5.0.0" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "node_modules/ngx-infinite-scroll": { |     "node_modules/ngx-infinite-scroll": { | ||||||
|       "version": "10.0.1", |       "version": "10.0.1", | ||||||
|       "resolved": "https://registry.npmjs.org/ngx-infinite-scroll/-/ngx-infinite-scroll-10.0.1.tgz", |       "resolved": "https://registry.npmjs.org/ngx-infinite-scroll/-/ngx-infinite-scroll-10.0.1.tgz", | ||||||
| @ -19926,6 +19951,19 @@ | |||||||
|       "dependencies": { |       "dependencies": { | ||||||
|         "tslib": "^2.0.0" |         "tslib": "^2.0.0" | ||||||
|       } |       } | ||||||
|  |     }, | ||||||
|  |     "node_modules/zrender": { | ||||||
|  |       "version": "5.1.1", | ||||||
|  |       "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.1.1.tgz", | ||||||
|  |       "integrity": "sha512-oeWlmUZPQdS9f5hK4pV21tHPqA3wgQ7CkKkw7l0CCBgWlJ/FP+lRgLFtUBW6yam4JX8y9CdHJo1o587VVrbcoQ==", | ||||||
|  |       "dependencies": { | ||||||
|  |         "tslib": "2.0.3" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "node_modules/zrender/node_modules/tslib": { | ||||||
|  |       "version": "2.0.3", | ||||||
|  |       "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", | ||||||
|  |       "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
| @ -21914,10 +21952,10 @@ | |||||||
|         "schema-utils": "^2.7.0" |         "schema-utils": "^2.7.0" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "@mempool/chartist": { |     "@juggle/resize-observer": { | ||||||
|       "version": "0.11.4", |       "version": "3.3.1", | ||||||
|       "resolved": "https://registry.npmjs.org/@mempool/chartist/-/chartist-0.11.4.tgz", |       "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.3.1.tgz", | ||||||
|       "integrity": "sha512-wSemsw2NIWS7/SHxjDe9upSdUETxNRebY0ByaJzcONKUzJSUzMuSNmKEdD3kr/g02H++JvsXR2znLC6tYEAbPA==" |       "integrity": "sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw==" | ||||||
|     }, |     }, | ||||||
|     "@mempool/mempool.js": { |     "@mempool/mempool.js": { | ||||||
|       "version": "2.2.4", |       "version": "2.2.4", | ||||||
| @ -25975,6 +26013,22 @@ | |||||||
|         "safer-buffer": "^2.1.0" |         "safer-buffer": "^2.1.0" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "echarts": { | ||||||
|  |       "version": "5.1.2", | ||||||
|  |       "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.1.2.tgz", | ||||||
|  |       "integrity": "sha512-okUhO4sw22vwZp+rTPNjd/bvTdpug4K4sHNHyrV8NdAncIX9/AarlolFqtJCAYKGFYhUBNjIWu1EznFrSWTFxg==", | ||||||
|  |       "requires": { | ||||||
|  |         "tslib": "2.0.3", | ||||||
|  |         "zrender": "5.1.1" | ||||||
|  |       }, | ||||||
|  |       "dependencies": { | ||||||
|  |         "tslib": { | ||||||
|  |           "version": "2.0.3", | ||||||
|  |           "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", | ||||||
|  |           "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "ee-first": { |     "ee-first": { | ||||||
|       "version": "1.1.1", |       "version": "1.1.1", | ||||||
|       "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", |       "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", | ||||||
| @ -30136,6 +30190,14 @@ | |||||||
|         "tslib": "^2.0.0" |         "tslib": "^2.0.0" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "ngx-echarts": { | ||||||
|  |       "version": "7.0.1", | ||||||
|  |       "resolved": "https://registry.npmjs.org/ngx-echarts/-/ngx-echarts-7.0.1.tgz", | ||||||
|  |       "integrity": "sha512-DDRwEssQRjjgPElEjF1emORoUEnF6OMZ20xnQBLueSHitM7XnIUErYVe9GMmm/jCtI+iPvJPEedyxMPl62nHLw==", | ||||||
|  |       "requires": { | ||||||
|  |         "tslib": "^2.0.0" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "ngx-infinite-scroll": { |     "ngx-infinite-scroll": { | ||||||
|       "version": "10.0.1", |       "version": "10.0.1", | ||||||
|       "resolved": "https://registry.npmjs.org/ngx-infinite-scroll/-/ngx-infinite-scroll-10.0.1.tgz", |       "resolved": "https://registry.npmjs.org/ngx-infinite-scroll/-/ngx-infinite-scroll-10.0.1.tgz", | ||||||
| @ -36780,6 +36842,21 @@ | |||||||
|       "requires": { |       "requires": { | ||||||
|         "tslib": "^2.0.0" |         "tslib": "^2.0.0" | ||||||
|       } |       } | ||||||
|  |     }, | ||||||
|  |     "zrender": { | ||||||
|  |       "version": "5.1.1", | ||||||
|  |       "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.1.1.tgz", | ||||||
|  |       "integrity": "sha512-oeWlmUZPQdS9f5hK4pV21tHPqA3wgQ7CkKkw7l0CCBgWlJ/FP+lRgLFtUBW6yam4JX8y9CdHJo1o587VVrbcoQ==", | ||||||
|  |       "requires": { | ||||||
|  |         "tslib": "2.0.3" | ||||||
|  |       }, | ||||||
|  |       "dependencies": { | ||||||
|  |         "tslib": { | ||||||
|  |           "version": "2.0.3", | ||||||
|  |           "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", | ||||||
|  |           "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" | ||||||
|  |         } | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -67,7 +67,7 @@ | |||||||
|     "@fortawesome/fontawesome-common-types": "^0.2.35", |     "@fortawesome/fontawesome-common-types": "^0.2.35", | ||||||
|     "@fortawesome/fontawesome-svg-core": "^1.2.35", |     "@fortawesome/fontawesome-svg-core": "^1.2.35", | ||||||
|     "@fortawesome/free-solid-svg-icons": "^5.15.3", |     "@fortawesome/free-solid-svg-icons": "^5.15.3", | ||||||
|     "@mempool/chartist": "^0.11.4", |     "@juggle/resize-observer": "^3.3.1", | ||||||
|     "@mempool/mempool.js": "^2.2.4", |     "@mempool/mempool.js": "^2.2.4", | ||||||
|     "@ng-bootstrap/ng-bootstrap": "^7.0.0", |     "@ng-bootstrap/ng-bootstrap": "^7.0.0", | ||||||
|     "@nguniversal/express-engine": "11.2.1", |     "@nguniversal/express-engine": "11.2.1", | ||||||
| @ -76,9 +76,11 @@ | |||||||
|     "browserify": "^17.0.0", |     "browserify": "^17.0.0", | ||||||
|     "clipboard": "^2.0.4", |     "clipboard": "^2.0.4", | ||||||
|     "domino": "^2.1.6", |     "domino": "^2.1.6", | ||||||
|  |     "echarts": "^5.1.2", | ||||||
|     "express": "^4.17.1", |     "express": "^4.17.1", | ||||||
|     "lightweight-charts": "^3.3.0", |     "lightweight-charts": "^3.3.0", | ||||||
|     "ngx-bootrap-multiselect": "^2.0.0", |     "ngx-bootrap-multiselect": "^2.0.0", | ||||||
|  |     "ngx-echarts": "^7.0.1", | ||||||
|     "ngx-infinite-scroll": "^10.0.1", |     "ngx-infinite-scroll": "^10.0.1", | ||||||
|     "qrcode": "^1.4.4", |     "qrcode": "^1.4.4", | ||||||
|     "rxjs": "^6.6.7", |     "rxjs": "^6.6.7", | ||||||
|  | |||||||
| @ -31,6 +31,46 @@ export const mempoolFeeColors = [ | |||||||
|   'b9254b', |   'b9254b', | ||||||
| ]; | ]; | ||||||
| 
 | 
 | ||||||
|  | export const chartColors = [ | ||||||
|  |   "#D81B60", | ||||||
|  |   "#8E24AA", | ||||||
|  |   "#5E35B1", | ||||||
|  |   "#3949AB", | ||||||
|  |   "#1E88E5", | ||||||
|  |   "#039BE5", | ||||||
|  |   "#00ACC1", | ||||||
|  |   "#00897B", | ||||||
|  |   "#43A047", | ||||||
|  |   "#7CB342", | ||||||
|  |   "#C0CA33", | ||||||
|  |   "#FDD835", | ||||||
|  |   "#FFB300", | ||||||
|  |   "#FB8C00", | ||||||
|  |   "#F4511E", | ||||||
|  |   "#6D4C41", | ||||||
|  |   "#757575", | ||||||
|  |   "#546E7A", | ||||||
|  |   "#b71c1c", | ||||||
|  |   "#880E4F", | ||||||
|  |   "#4A148C", | ||||||
|  |   "#311B92", | ||||||
|  |   "#1A237E", | ||||||
|  |   "#0D47A1", | ||||||
|  |   "#01579B", | ||||||
|  |   "#006064", | ||||||
|  |   "#004D40", | ||||||
|  |   "#1B5E20", | ||||||
|  |   "#33691E", | ||||||
|  |   "#827717", | ||||||
|  |   "#F57F17", | ||||||
|  |   "#FF6F00", | ||||||
|  |   "#E65100", | ||||||
|  |   "#BF360C", | ||||||
|  |   "#3E2723", | ||||||
|  |   "#212121", | ||||||
|  |   "#263238", | ||||||
|  | ]; | ||||||
|  | 
 | ||||||
| export const feeLevels = [1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200, | export const feeLevels = [1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200, | ||||||
|   250, 300, 350, 400, 500, 600, 700, 800, 900, 1000, 1200, 1400, 1600, 1800, 2000]; |   250, 300, 350, 400, 500, 600, 700, 800, 900, 1000, 1200, 1400, 1600, 1800, 2000]; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ import { NgModule } from '@angular/core'; | |||||||
| import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; | import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; | ||||||
| import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; | ||||||
| import { InfiniteScrollModule } from 'ngx-infinite-scroll'; | import { InfiniteScrollModule } from 'ngx-infinite-scroll'; | ||||||
|  | import { NgxEchartsModule } from 'ngx-echarts'; | ||||||
| 
 | 
 | ||||||
| import { AppRoutingModule } from './app-routing.module'; | import { AppRoutingModule } from './app-routing.module'; | ||||||
| import { AppComponent } from './components/app/app.component'; | import { AppComponent } from './components/app/app.component'; | ||||||
| @ -26,13 +27,13 @@ import { LiquidMasterPageComponent } from './components/liquid-master-page/liqui | |||||||
| import { AboutComponent } from './components/about/about.component'; | import { AboutComponent } from './components/about/about.component'; | ||||||
| import { TelevisionComponent } from './components/television/television.component'; | import { TelevisionComponent } from './components/television/television.component'; | ||||||
| import { StatisticsComponent } from './components/statistics/statistics.component'; | import { StatisticsComponent } from './components/statistics/statistics.component'; | ||||||
| import { ChartistComponent } from './components/statistics/chartist.component'; |  | ||||||
| import { BlockchainBlocksComponent } from './components/blockchain-blocks/blockchain-blocks.component'; | import { BlockchainBlocksComponent } from './components/blockchain-blocks/blockchain-blocks.component'; | ||||||
| import { BlockchainComponent } from './components/blockchain/blockchain.component'; | import { BlockchainComponent } from './components/blockchain/blockchain.component'; | ||||||
| import { FooterComponent } from './components/footer/footer.component'; | import { FooterComponent } from './components/footer/footer.component'; | ||||||
| import { AudioService } from './services/audio.service'; | import { AudioService } from './services/audio.service'; | ||||||
| import { MempoolBlockComponent } from './components/mempool-block/mempool-block.component'; | import { MempoolBlockComponent } from './components/mempool-block/mempool-block.component'; | ||||||
| import { FeeDistributionGraphComponent } from './components/fee-distribution-graph/fee-distribution-graph.component'; | import { FeeDistributionGraphComponent } from './components/fee-distribution-graph/fee-distribution-graph.component'; | ||||||
|  | import { IncomingTransactionsGraphComponent } from './components/incoming-transactions-graph/incoming-transactions-graph.component'; | ||||||
| import { TimeSpanComponent } from './components/time-span/time-span.component'; | import { TimeSpanComponent } from './components/time-span/time-span.component'; | ||||||
| import { SeoService } from './services/seo.service'; | import { SeoService } from './services/seo.service'; | ||||||
| import { MempoolGraphComponent } from './components/mempool-graph/mempool-graph.component'; | import { MempoolGraphComponent } from './components/mempool-graph/mempool-graph.component'; | ||||||
| @ -78,10 +79,10 @@ import { SponsorComponent } from './components/sponsor/sponsor.component'; | |||||||
|     TimeSpanComponent, |     TimeSpanComponent, | ||||||
|     AddressLabelsComponent, |     AddressLabelsComponent, | ||||||
|     MempoolBlocksComponent, |     MempoolBlocksComponent, | ||||||
|     ChartistComponent, |  | ||||||
|     FooterComponent, |     FooterComponent, | ||||||
|     MempoolBlockComponent, |     MempoolBlockComponent, | ||||||
|     FeeDistributionGraphComponent, |     FeeDistributionGraphComponent, | ||||||
|  |     IncomingTransactionsGraphComponent, | ||||||
|     MempoolGraphComponent, |     MempoolGraphComponent, | ||||||
|     AssetComponent, |     AssetComponent, | ||||||
|     AssetsComponent, |     AssetsComponent, | ||||||
| @ -106,6 +107,9 @@ import { SponsorComponent } from './components/sponsor/sponsor.component'; | |||||||
|     NgbTypeaheadModule, |     NgbTypeaheadModule, | ||||||
|     FontAwesomeModule, |     FontAwesomeModule, | ||||||
|     SharedModule, |     SharedModule, | ||||||
|  |     NgxEchartsModule.forRoot({ | ||||||
|  |       echarts: () => import('echarts') | ||||||
|  |     }) | ||||||
|   ], |   ], | ||||||
|   providers: [ |   providers: [ | ||||||
|     ElectrsApiService, |     ElectrsApiService, | ||||||
|  | |||||||
| @ -1,9 +1,5 @@ | |||||||
| <div style="height: 225px;" *ngIf="mempoolVsizeFeesData; else loadingFees"> | <div class="fee-distribution-chart" *ngIf="mempoolVsizeFeesOptions; else loadingFees"> | ||||||
|   <app-chartist |   <div echarts [initOpts]="mempoolVsizeFeesInitOptions" [options]="mempoolVsizeFeesOptions"></div> | ||||||
|     [data]="mempoolVsizeFeesData" |  | ||||||
|     [type]="'Line'" |  | ||||||
|     [options]="mempoolVsizeFeesOptions"> |  | ||||||
|   </app-chartist> |  | ||||||
| </div> | </div> | ||||||
| 
 | 
 | ||||||
| <ng-template #loadingFees> | <ng-template #loadingFees> | ||||||
|  | |||||||
| @ -1,70 +1,83 @@ | |||||||
| import { Component, Input, OnChanges, ChangeDetectionStrategy } from '@angular/core'; | import { OnChanges } from '@angular/core'; | ||||||
| import * as Chartist from '@mempool/chartist'; | import { Component, Input, OnInit, ChangeDetectionStrategy } from '@angular/core'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-fee-distribution-graph', |   selector: 'app-fee-distribution-graph', | ||||||
|   templateUrl: './fee-distribution-graph.component.html', |   templateUrl: './fee-distribution-graph.component.html', | ||||||
|   styleUrls: ['./fee-distribution-graph.component.scss'], |  | ||||||
|   changeDetection: ChangeDetectionStrategy.OnPush, |   changeDetection: ChangeDetectionStrategy.OnPush, | ||||||
| }) | }) | ||||||
| export class FeeDistributionGraphComponent implements OnChanges { | export class FeeDistributionGraphComponent implements OnInit, OnChanges { | ||||||
|   @Input() feeRange; |   @Input() data: any; | ||||||
|  |   @Input() height: number | string = 210; | ||||||
|  |   @Input() top: number | string = 20; | ||||||
|  |   @Input() right: number | string = 22; | ||||||
|  |   @Input() left: number | string = 30; | ||||||
| 
 | 
 | ||||||
|   mempoolVsizeFeesData: any; |  | ||||||
|   mempoolVsizeFeesOptions: any; |   mempoolVsizeFeesOptions: any; | ||||||
|  |   mempoolVsizeFeesInitOptions = { | ||||||
|  |     renderer: 'svg' | ||||||
|  |   }; | ||||||
| 
 | 
 | ||||||
|   feeLevels = [1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200, |   constructor() { } | ||||||
|   250, 300, 350, 400, 500]; |  | ||||||
| 
 | 
 | ||||||
|   constructor( |   ngOnInit() { | ||||||
|   ) { } |     this.mountChart(); | ||||||
| 
 |  | ||||||
|   ngOnChanges() { |  | ||||||
|     this.mempoolVsizeFeesOptions = { |  | ||||||
|       showArea: true, |  | ||||||
|       showLine: true, |  | ||||||
|       fullWidth: true, |  | ||||||
|       showPoint: true, |  | ||||||
|       low: 0, |  | ||||||
|       axisY: { |  | ||||||
|         showLabel: false, |  | ||||||
|         offset: 0 |  | ||||||
|       }, |  | ||||||
|       axisX: { |  | ||||||
|         showGrid: true, |  | ||||||
|         showLabel: false, |  | ||||||
|         offset: 0 |  | ||||||
|       }, |  | ||||||
|       plugins: [ |  | ||||||
|         Chartist.plugins.ctPointLabels({ |  | ||||||
|           textAnchor: 'middle', |  | ||||||
|           labelInterpolationFnc: (value) => Math.round(value) |  | ||||||
|         }) |  | ||||||
|       ] |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     const fees = this.feeRange; |  | ||||||
|     const series = []; |  | ||||||
| 
 |  | ||||||
|     for (let i = 0; i < this.feeLevels.length; i++) { |  | ||||||
|       let total = 0; |  | ||||||
|       // for (let j = 0; j < fees.length; j++) {
 |  | ||||||
|       for (const fee of fees) { |  | ||||||
|         if (i === this.feeLevels.length - 1) { |  | ||||||
|           if (fee >= this.feeLevels[i]) { |  | ||||||
|             total += 1; |  | ||||||
|           } |  | ||||||
|         } else  if (fee >= this.feeLevels[i] && fee < this.feeLevels[i + 1]) { |  | ||||||
|           total += 1; |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|       series.push(total); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     this.mempoolVsizeFeesData = { |  | ||||||
|       series: [fees], |  | ||||||
|       labels: fees.map((d, i) => i) |  | ||||||
|     }; |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   ngOnChanges() { | ||||||
|  |     this.mountChart(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   mountChart() { | ||||||
|  |     this.mempoolVsizeFeesOptions = { | ||||||
|  |       grid: { | ||||||
|  |         height: '210', | ||||||
|  |         right: '20', | ||||||
|  |         top: '22', | ||||||
|  |         left: '30', | ||||||
|  |       }, | ||||||
|  |       xAxis: { | ||||||
|  |         type: 'category', | ||||||
|  |         boundaryGap: false, | ||||||
|  |       }, | ||||||
|  |       yAxis: { | ||||||
|  |         type: 'value', | ||||||
|  |         splitLine: { | ||||||
|  |           lineStyle: { | ||||||
|  |             type: 'dotted', | ||||||
|  |             color: '#ffffff66', | ||||||
|  |             opacity: 0.25, | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       series: [{ | ||||||
|  |         data: this.data, | ||||||
|  |         type: 'line', | ||||||
|  |         label: { | ||||||
|  |           show: true, | ||||||
|  |           position: 'top', | ||||||
|  |           color: '#ffffff', | ||||||
|  |           textShadowBlur: 0, | ||||||
|  |           formatter: (label: any) => { | ||||||
|  |             return Math.floor(label.data); | ||||||
|  |           }, | ||||||
|  |         }, | ||||||
|  |         smooth: true, | ||||||
|  |         lineStyle: { | ||||||
|  |           color: '#D81B60', | ||||||
|  |           width: 4, | ||||||
|  |         }, | ||||||
|  |         itemStyle: { | ||||||
|  |           color: '#b71c1c', | ||||||
|  |           borderWidth: 10, | ||||||
|  |           borderMiterLimit: 10, | ||||||
|  |           opacity: 1, | ||||||
|  |         }, | ||||||
|  |         areaStyle: { | ||||||
|  |           color: '#D81B60', | ||||||
|  |           opacity: 1, | ||||||
|  |         } | ||||||
|  |       }] | ||||||
|  |     }; | ||||||
|  |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -0,0 +1 @@ | |||||||
|  | <div class="echarts" echarts [initOpts]="mempoolStatsChartInitOption" [options]="mempoolStatsChartOption"></div> | ||||||
| @ -0,0 +1,180 @@ | |||||||
|  | import { Component, OnInit, Input, Inject, LOCALE_ID, ChangeDetectionStrategy } from '@angular/core'; | ||||||
|  | import { formatDate } from '@angular/common'; | ||||||
|  | import { EChartsOption } from 'echarts'; | ||||||
|  | import { OnChanges } from '@angular/core'; | ||||||
|  | import { StorageService } from 'src/app/services/storage.service'; | ||||||
|  | 
 | ||||||
|  | @Component({ | ||||||
|  |   selector: 'app-incoming-transactions-graph', | ||||||
|  |   templateUrl: './incoming-transactions-graph.component.html', | ||||||
|  |   changeDetection: ChangeDetectionStrategy.OnPush, | ||||||
|  | }) | ||||||
|  | export class IncomingTransactionsGraphComponent implements OnInit, OnChanges { | ||||||
|  |   @Input() data: any; | ||||||
|  |   @Input() theme: string; | ||||||
|  |   @Input() height: number | string = '200'; | ||||||
|  |   @Input() right: number | string = '10'; | ||||||
|  |   @Input() top: number | string = '20'; | ||||||
|  |   @Input() left: number | string = '0'; | ||||||
|  |   @Input() template: ('widget' | 'advanced') = 'widget'; | ||||||
|  | 
 | ||||||
|  |   mempoolStatsChartOption: EChartsOption = {}; | ||||||
|  |   mempoolStatsChartInitOption = { | ||||||
|  |     renderer: 'svg' | ||||||
|  |   }; | ||||||
|  |   windowPreference: string; | ||||||
|  | 
 | ||||||
|  |   constructor( | ||||||
|  |     @Inject(LOCALE_ID) private locale: string, | ||||||
|  |     private storageService: StorageService, | ||||||
|  |   ) { } | ||||||
|  | 
 | ||||||
|  |   ngOnChanges(): void { | ||||||
|  |     this.windowPreference = this.storageService.getValue('graphWindowPreference'); | ||||||
|  |     this.mountChart(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   ngOnInit(): void { | ||||||
|  |     this.mountChart(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   mountChart(): void { | ||||||
|  |     this.mempoolStatsChartOption = { | ||||||
|  |       grid: { | ||||||
|  |         height: this.height, | ||||||
|  |         right: this.right, | ||||||
|  |         top: this.top, | ||||||
|  |         left: this.left, | ||||||
|  |       }, | ||||||
|  |       animation: false, | ||||||
|  |       dataZoom: [{ | ||||||
|  |         type: 'inside', | ||||||
|  |         realtime: true, | ||||||
|  |         zoomOnMouseWheel: (this.template === 'advanced') ? true : false, | ||||||
|  |         maxSpan: 100, | ||||||
|  |         minSpan: 10, | ||||||
|  |       }, { | ||||||
|  |         show: (this.template === 'advanced') ? true : false, | ||||||
|  |         type: 'slider', | ||||||
|  |         brushSelect: false, | ||||||
|  |         realtime: true, | ||||||
|  |         selectedDataBackground: { | ||||||
|  |           lineStyle: { | ||||||
|  |             color: '#fff', | ||||||
|  |             opacity: 0.45, | ||||||
|  |           }, | ||||||
|  |           areaStyle: { | ||||||
|  |             opacity: 0, | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       }], | ||||||
|  |       tooltip: { | ||||||
|  |         trigger: 'axis', | ||||||
|  |         position: (pos, params, el, elRect, size) => { | ||||||
|  |           const obj = { top: -20 }; | ||||||
|  |           obj[['left', 'right'][+(pos[0] < size.viewSize[0] / 2)]] = 80; | ||||||
|  |           return obj; | ||||||
|  |         }, | ||||||
|  |         extraCssText: `width: ${(['2h', '24h'].includes(this.windowPreference) || this.template === 'widget') ? '125px' : '135px'};
 | ||||||
|  |                       background: transparent; | ||||||
|  |                       border: none; | ||||||
|  |                       box-shadow: none;`,
 | ||||||
|  |         axisPointer: { | ||||||
|  |           type: 'line', | ||||||
|  |         }, | ||||||
|  |         formatter: (params: any) => { | ||||||
|  |           const colorSpan = (color: string) => `<span class="indicator" style="background-color: ` + color + `"></span>`; | ||||||
|  |           let itemFormatted = '<div class="title">' + params[0].axisValue + '</div>'; | ||||||
|  |           params.map((item: any, index: number) => { | ||||||
|  |             if (index < 26) { | ||||||
|  |               itemFormatted += `<div class="item">
 | ||||||
|  |                 <div class="indicator-container">${colorSpan(item.color)}</div> | ||||||
|  |                 <div class="grow"></div> | ||||||
|  |                 <div class="value">${item.value} <span class="symbol">vB/s</span></div> | ||||||
|  |               </div>`;
 | ||||||
|  |             } | ||||||
|  |           }); | ||||||
|  |           return `<div class="tx-wrapper-tooltip-chart ${(this.template === 'advanced') ? 'tx-wrapper-tooltip-chart-advanced' : ''}">${itemFormatted}</div>`; | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       xAxis: { | ||||||
|  |         type: 'category', | ||||||
|  |         data: this.data.labels.map((value: any) => `${formatDate(value, 'M/d', this.locale)}\n${formatDate(value, 'H:mm', this.locale)}`), | ||||||
|  |       }, | ||||||
|  |       yAxis: { | ||||||
|  |         type: 'value', | ||||||
|  |         splitLine: { | ||||||
|  |           lineStyle: { | ||||||
|  |             type: 'dotted', | ||||||
|  |             color: '#ffffff66', | ||||||
|  |             opacity: 0.25, | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       series: [ | ||||||
|  |         { | ||||||
|  |           data: this.data.series[0], | ||||||
|  |           type: 'line', | ||||||
|  |           smooth: (this.template === 'advanced') ? false : true, | ||||||
|  |           showSymbol: false, | ||||||
|  |           lineStyle: { | ||||||
|  |             width: 3, | ||||||
|  |           }, | ||||||
|  |           markLine: { | ||||||
|  |             silent: true, | ||||||
|  |             symbol: 'none', | ||||||
|  |             lineStyle: { | ||||||
|  |               color: '#fff', | ||||||
|  |               opacity: 0.75, | ||||||
|  |               width: 2, | ||||||
|  |             }, | ||||||
|  |             data: [{ | ||||||
|  |               yAxis: 1667, | ||||||
|  |               label: { | ||||||
|  |                 show: false, | ||||||
|  |                 color: '#ffffff', | ||||||
|  |               } | ||||||
|  |             }], | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |       ], | ||||||
|  |       visualMap: { | ||||||
|  |         show: false, | ||||||
|  |         top: 50, | ||||||
|  |         right: 10, | ||||||
|  |         pieces: [{ | ||||||
|  |           gt: 0, | ||||||
|  |           lte: 1667, | ||||||
|  |           color: '#7CB342' | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           gt: 1667, | ||||||
|  |           lte: 2000, | ||||||
|  |           color: '#FDD835' | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           gt: 2000, | ||||||
|  |           lte: 2500, | ||||||
|  |           color: '#FFB300' | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           gt: 2500, | ||||||
|  |           lte: 3000, | ||||||
|  |           color: '#FB8C00' | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           gt: 3000, | ||||||
|  |           lte: 3500, | ||||||
|  |           color: '#F4511E' | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           gt: 3500, | ||||||
|  |           color: '#D81B60' | ||||||
|  |         }], | ||||||
|  |         outOfRange: { | ||||||
|  |           color: '#999' | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -9,7 +9,7 @@ | |||||||
| 
 | 
 | ||||||
|   <div class="box"> |   <div class="box"> | ||||||
|     <div class="row"> |     <div class="row"> | ||||||
|       <div class="col-sm"> |       <div class="col-md"> | ||||||
|         <table class="table table-borderless table-striped"> |         <table class="table table-borderless table-striped"> | ||||||
|           <tbody> |           <tbody> | ||||||
|             <tr> |             <tr> | ||||||
| @ -40,8 +40,8 @@ | |||||||
|           </tbody> |           </tbody> | ||||||
|         </table> |         </table> | ||||||
|       </div> |       </div> | ||||||
|       <div class="col-sm"> |       <div class="col-md chart-container"> | ||||||
|         <app-fee-distribution-graph [feeRange]="mempoolBlock.feeRange"></app-fee-distribution-graph> |         <app-fee-distribution-graph [data]="mempoolBlock.feeRange" ></app-fee-distribution-graph> | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|   </div> |   </div> | ||||||
|  | |||||||
| @ -13,11 +13,8 @@ | |||||||
| 
 | 
 | ||||||
| .fiat { | .fiat { | ||||||
|   font-size: 13px; |   font-size: 13px; | ||||||
|   display: block; |   display: inline-block; | ||||||
|   @media (min-width: 992px) { |   margin-left: 10px; | ||||||
|     display: inline-block; |  | ||||||
|     margin-left: 10px; |  | ||||||
|   } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .table { | .table { | ||||||
| @ -38,4 +35,11 @@ h1 { | |||||||
|     float: left; |     float: left; | ||||||
|     margin-right: 10px; |     margin-right: 10px; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | .chart-container{ | ||||||
|  |   margin: 20px auto; | ||||||
|  |   @media (min-width: 768px) { | ||||||
|  |     margin: auto; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | |||||||
| @ -1,6 +1 @@ | |||||||
| <app-chartist | <div echarts class="echarts" (chartInit)="onChartReady($event)" [initOpts]="mempoolVsizeFeesInitOptions" [options]="mempoolVsizeFeesOptions"></div> | ||||||
|   *ngIf="mempoolVsizeFeesData" |  | ||||||
|   [data]="mempoolVsizeFeesData" |  | ||||||
|   [type]="'Line'" |  | ||||||
|   [options]="mempoolVsizeFeesOptions"> |  | ||||||
| </app-chartist> |  | ||||||
|  | |||||||
| @ -1,10 +1,11 @@ | |||||||
| import { Component, OnInit, Input, Inject, LOCALE_ID, ChangeDetectionStrategy, OnChanges } from '@angular/core'; | import { Component, OnInit, Input, Inject, LOCALE_ID, ChangeDetectionStrategy, OnChanges } from '@angular/core'; | ||||||
| import { formatDate } from '@angular/common'; | import { formatDate } from '@angular/common'; | ||||||
| import { VbytesPipe } from 'src/app/shared/pipes/bytes-pipe/vbytes.pipe'; | import { VbytesPipe } from 'src/app/shared/pipes/bytes-pipe/vbytes.pipe'; | ||||||
| import * as Chartist from '@mempool/chartist'; |  | ||||||
| import { OptimizedMempoolStats } from 'src/app/interfaces/node-api.interface'; | import { OptimizedMempoolStats } from 'src/app/interfaces/node-api.interface'; | ||||||
| import { StateService } from 'src/app/services/state.service'; | import { StateService } from 'src/app/services/state.service'; | ||||||
| import { StorageService } from 'src/app/services/storage.service'; | import { StorageService } from 'src/app/services/storage.service'; | ||||||
|  | import { EChartsOption } from 'echarts'; | ||||||
|  | import { feeLevels, chartColors } from 'src/app/app.constants'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-mempool-graph', |   selector: 'app-mempool-graph', | ||||||
| @ -12,134 +13,299 @@ import { StorageService } from 'src/app/services/storage.service'; | |||||||
|   changeDetection: ChangeDetectionStrategy.OnPush, |   changeDetection: ChangeDetectionStrategy.OnPush, | ||||||
| }) | }) | ||||||
| export class MempoolGraphComponent implements OnInit, OnChanges { | export class MempoolGraphComponent implements OnInit, OnChanges { | ||||||
|   @Input() data; |   @Input() data: any[]; | ||||||
|   @Input() dateSpan = '2h'; |   @Input() limitFee = 350; | ||||||
|   @Input() showLegend = true; |   @Input() height: number | string = 200; | ||||||
|   @Input() offsetX = 40; |   @Input() top: number | string = 20; | ||||||
|   @Input() small = false; |   @Input() right: number | string = 10; | ||||||
|  |   @Input() left: number | string = 75; | ||||||
|  |   @Input() template: ('widget' | 'advanced') = 'widget'; | ||||||
|  |   @Input() showZoom = true; | ||||||
| 
 | 
 | ||||||
|   mempoolVsizeFeesOptions: any; |  | ||||||
|   mempoolVsizeFeesData: any; |   mempoolVsizeFeesData: any; | ||||||
| 
 |   mempoolVsizeFeesOptions: EChartsOption; | ||||||
|   isMobile = window.innerWidth <= 767.98; |   mempoolVsizeFeesInitOptions = { | ||||||
|   inverted: boolean; |     renderer: 'svg', | ||||||
|  |   }; | ||||||
|  |   windowPreference: string; | ||||||
|  |   hoverIndexSerie = 0; | ||||||
|  |   feeLimitIndex: number; | ||||||
|  |   feeLevelsOrdered = []; | ||||||
| 
 | 
 | ||||||
|   constructor( |   constructor( | ||||||
|     private vbytesPipe: VbytesPipe, |     private vbytesPipe: VbytesPipe, | ||||||
|     private stateService: StateService, |     private stateService: StateService, | ||||||
|     @Inject(LOCALE_ID) private locale: string, |  | ||||||
|     private storageService: StorageService, |     private storageService: StorageService, | ||||||
|  |     @Inject(LOCALE_ID) private locale: string, | ||||||
|   ) { } |   ) { } | ||||||
| 
 | 
 | ||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
|     let labelHops = !this.showLegend ? 48 : 24; |     this.feeLevelsOrdered = feeLevels.map((sat, i, arr) => { | ||||||
|     if (this.small) { |       if (arr[i] === this.limitFee) { this.feeLimitIndex = i; } | ||||||
|       labelHops = labelHops / 2; |       if (arr[i] < this.limitFee) { | ||||||
|     } |         if (i === 0) { return '0 - 1'; } | ||||||
| 
 |         return `${arr[i - 1]} - ${arr[i]}`; | ||||||
|     if (this.isMobile) { |       } else { | ||||||
|       labelHops = 96; |         return `${this.limitFee}+`; | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     const labelInterpolationFnc = (value: any, index: any) => { |  | ||||||
|       switch (this.dateSpan) { |  | ||||||
|         case '2h': |  | ||||||
|         case '24h': |  | ||||||
|           value = formatDate(value, 'HH:mm', this.locale); |  | ||||||
|           break; |  | ||||||
|         case '1w': |  | ||||||
|           value = formatDate(value, 'dd/MM HH:mm', this.locale); |  | ||||||
|           break; |  | ||||||
|         case '1m': |  | ||||||
|         case '3m': |  | ||||||
|         case '6m': |  | ||||||
|         case '1y': |  | ||||||
|           value = formatDate(value, 'dd/MM', this.locale); |  | ||||||
|       } |       } | ||||||
|       return index % labelHops === 0 ? value : null; |     }); | ||||||
|     }; |     this.mountFeeChart(); | ||||||
| 
 |  | ||||||
|     this.mempoolVsizeFeesOptions = { |  | ||||||
|       showArea: true, |  | ||||||
|       showLine: false, |  | ||||||
|       fullWidth: true, |  | ||||||
|       showPoint: false, |  | ||||||
|       stackedLine: !this.inverted, |  | ||||||
|       low: 0, |  | ||||||
|       axisX: { |  | ||||||
|         labelInterpolationFnc: labelInterpolationFnc, |  | ||||||
|         offset: this.offsetX, |  | ||||||
|       }, |  | ||||||
|       axisY: { |  | ||||||
|         labelInterpolationFnc: (value: number): any => this.vbytesPipe.transform(value, 2, 'vB', 'MvB', true), |  | ||||||
|         offset: this.showLegend ? 160 : 60, |  | ||||||
|       }, |  | ||||||
|       plugins: this.inverted ? [Chartist.plugins.ctTargetLine({ value: this.stateService.blockVSize })] : [] |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     if (this.showLegend) { |  | ||||||
|       const legendNames: string[] = [1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200, |  | ||||||
|             250, 300, 350, 400].map((sat, i, arr) => { |  | ||||||
|               if (sat === 400) { |  | ||||||
|                 return '350+'; |  | ||||||
|               } |  | ||||||
|               if (i === 0) { |  | ||||||
|                 return '0 - 1'; |  | ||||||
|               } |  | ||||||
|               return arr[i - 1] + ' - ' + sat; |  | ||||||
|             }); |  | ||||||
|       // Only Liquid has lower than 1 sat/vb transactions
 |  | ||||||
|       if (this.stateService.network !== 'liquid') { |  | ||||||
|         legendNames.shift(); |  | ||||||
|       } |  | ||||||
|       this.mempoolVsizeFeesOptions.plugins.push( |  | ||||||
|         Chartist.plugins.legend({ legendNames: legendNames }) |  | ||||||
|       ); |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   ngOnChanges() { |   ngOnChanges() { | ||||||
|     this.inverted = this.storageService.getValue('inverted-graph') === 'true'; |     this.windowPreference = this.storageService.getValue('graphWindowPreference'); | ||||||
|     this.mempoolVsizeFeesData = this.handleNewMempoolData(this.data.concat([])); |     this.mempoolVsizeFeesData = this.handleNewMempoolData(this.data.concat([])); | ||||||
|  |     this.mountFeeChart(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   onChartReady(myChart: any) { | ||||||
|  |     myChart.on('mouseover', 'series', (serie: any) => { | ||||||
|  |       this.hoverIndexSerie = serie.seriesIndex; | ||||||
|  |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   handleNewMempoolData(mempoolStats: OptimizedMempoolStats[]) { |   handleNewMempoolData(mempoolStats: OptimizedMempoolStats[]) { | ||||||
|     mempoolStats.reverse(); |     mempoolStats.reverse(); | ||||||
|     const labels = mempoolStats.map(stats => stats.added); |     const labels = mempoolStats.map(stats => stats.added); | ||||||
| 
 |     const finalArrayVByte = this.generateArray(mempoolStats); | ||||||
|     const finalArrayVbyte = this.generateArray(mempoolStats); |  | ||||||
| 
 | 
 | ||||||
|     // Only Liquid has lower than 1 sat/vb transactions
 |     // Only Liquid has lower than 1 sat/vb transactions
 | ||||||
|     if (this.stateService.network !== 'liquid') { |     if (this.stateService.network !== 'liquid') { | ||||||
|       finalArrayVbyte.shift(); |       finalArrayVByte.shift(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return { |     return { | ||||||
|       labels: labels, |       labels: labels, | ||||||
|       series: finalArrayVbyte |       series: finalArrayVByte | ||||||
|     }; |     }; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   generateArray(mempoolStats: OptimizedMempoolStats[]) { |   generateArray(mempoolStats: OptimizedMempoolStats[]) { | ||||||
|     const finalArray: number[][] = []; |     const finalArray: number[][] = []; | ||||||
|     let feesArray: number[] = []; |     let feesArray: number[] = []; | ||||||
| 
 |  | ||||||
|     for (let index = 37; index > -1; index--) { |     for (let index = 37; index > -1; index--) { | ||||||
|       feesArray = []; |       feesArray = []; | ||||||
|       mempoolStats.forEach((stats) => { |       mempoolStats.forEach((stats) => { | ||||||
|         const theFee = stats.vsizes[index].toString(); |         feesArray.push(stats.vsizes[index] ? stats.vsizes[index] : 0); | ||||||
|         if (theFee) { |  | ||||||
|           feesArray.push(parseInt(theFee, 10)); |  | ||||||
|         } else { |  | ||||||
|           feesArray.push(0); |  | ||||||
|         } |  | ||||||
|       }); |       }); | ||||||
|       if (this.inverted && finalArray.length) { |  | ||||||
|         feesArray = feesArray.map((value, i) => value + finalArray[finalArray.length - 1][i]); |  | ||||||
|       } |  | ||||||
|       finalArray.push(feesArray); |       finalArray.push(feesArray); | ||||||
|     } |     } | ||||||
|     finalArray.reverse(); |     finalArray.reverse(); | ||||||
|     return finalArray; |     return finalArray; | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   mountFeeChart() { | ||||||
|  |     const { labels, series } = this.mempoolVsizeFeesData; | ||||||
|  | 
 | ||||||
|  |     const seriesGraph = series.map((value: Array<number>, index: number) => { | ||||||
|  |       if (index <= this.feeLimitIndex){ | ||||||
|  |         return { | ||||||
|  |           name: this.feeLevelsOrdered[index], | ||||||
|  |           type: 'line', | ||||||
|  |           stack: 'total', | ||||||
|  |           smooth: false, | ||||||
|  |           markPoint: { | ||||||
|  |             symbol: 'rect', | ||||||
|  |           }, | ||||||
|  |           lineStyle: { | ||||||
|  |             width: 0, | ||||||
|  |             opacity: 0, | ||||||
|  |           }, | ||||||
|  |           symbolSize: (this.template === 'advanced') ? 10 : 10, | ||||||
|  |           showSymbol: false, | ||||||
|  |           areaStyle: { | ||||||
|  |             opacity: 1, | ||||||
|  |             color: chartColors[index], | ||||||
|  |           }, | ||||||
|  |           emphasis: { | ||||||
|  |             focus: 'series', | ||||||
|  |             areaStyle: { | ||||||
|  |               opacity: 1, | ||||||
|  |             }, | ||||||
|  |             itemStyle: { | ||||||
|  |               opacity: 0.2, | ||||||
|  |             }, | ||||||
|  |           }, | ||||||
|  |           itemStyle: { | ||||||
|  |             opacity: 0, | ||||||
|  |           }, | ||||||
|  |           data: this.vbytesPipe.transform(value, 2, 'vB', 'MvB', true) | ||||||
|  |         }; | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     this.mempoolVsizeFeesOptions = { | ||||||
|  |       emphasis: { | ||||||
|  |         areaStyle: { | ||||||
|  |           opacity: 1, | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       color: chartColors, | ||||||
|  |       tooltip: { | ||||||
|  |         show: true, | ||||||
|  |         trigger: 'axis', | ||||||
|  |         alwaysShowContent: false, | ||||||
|  |         position: (pos, params, el, elRect, size) => { | ||||||
|  |           const positions = { top: (this.template === 'advanced') ? 30 : -30 }; | ||||||
|  |           positions[['left', 'right'][+(pos[0] < size.viewSize[0] / 2)]] = 60; | ||||||
|  |           return positions; | ||||||
|  |         }, | ||||||
|  |         extraCssText: `width: ${(this.template === 'advanced') ? '275px' : '200px'};
 | ||||||
|  |                       background: transparent; | ||||||
|  |                       border: none; | ||||||
|  |                       box-shadow: none;`,
 | ||||||
|  |         axisPointer: { | ||||||
|  |           type: 'line', | ||||||
|  |         }, | ||||||
|  |         formatter: (params: any) => { | ||||||
|  |           const legendName = (index: number) => this.feeLevelsOrdered[index]; | ||||||
|  |           const colorSpan = (index: number) => `<span class="indicator" style="background-color: ${chartColors[index]}"></span>
 | ||||||
|  |             <span> | ||||||
|  |               ${legendName(index)} | ||||||
|  |             </span>`;
 | ||||||
|  |           const totals = (values: any) => { | ||||||
|  |             let totalValueTemp = 0; | ||||||
|  |             const totalValueArrayTemp = []; | ||||||
|  |             const valuesInverted = values.slice(0).reverse(); | ||||||
|  |             for (const item of valuesInverted) { | ||||||
|  |               totalValueTemp += item.value; | ||||||
|  |               totalValueArrayTemp.push(totalValueTemp); | ||||||
|  |             } | ||||||
|  |             return { | ||||||
|  |               totalValue: totalValueTemp, | ||||||
|  |               totalValueArray: totalValueArrayTemp.reverse() | ||||||
|  |             }; | ||||||
|  |           }; | ||||||
|  |           const { totalValue, totalValueArray } = totals(params); | ||||||
|  |           const title = `<div class="title">
 | ||||||
|  |             ${params[0].axisValue} | ||||||
|  |             <span class="total-value"> | ||||||
|  |               ${this.vbytesPipe.transform(totalValue, 2, 'vB', 'MvB', false)} | ||||||
|  |             </span> | ||||||
|  |           </div>`;
 | ||||||
|  |           const itemFormatted = []; | ||||||
|  |           let totalParcial = 0; | ||||||
|  |           let progressPercentageText = ''; | ||||||
|  |           params.map((item: any, index: number) => { | ||||||
|  |             totalParcial += item.value; | ||||||
|  |             let progressPercentage = 0; | ||||||
|  |             let progressPercentageSum = 0; | ||||||
|  |             if (index <= this.feeLimitIndex) { | ||||||
|  |               progressPercentage = (item.value / totalValue) * 100; | ||||||
|  |               progressPercentageSum = (totalValueArray[index] / totalValue) * 100; | ||||||
|  |               let activeItemClass = ''; | ||||||
|  |               if (this.hoverIndexSerie === index) { | ||||||
|  |                 progressPercentageText = `<div class="total-parcial-active">
 | ||||||
|  |                   <span class="progress-percentage"> | ||||||
|  |                     ${progressPercentage.toFixed(2)} | ||||||
|  |                     <span class="symbol">%</span> | ||||||
|  |                   </span> | ||||||
|  |                   <span class="total-parcial-vbytes"> | ||||||
|  |                     ${this.vbytesPipe.transform(totalParcial, 2, 'vB', 'MvB', false)} | ||||||
|  |                   </span> | ||||||
|  |                   <div class="total-percentage-bar"> | ||||||
|  |                     <span> | ||||||
|  |                       <span style="width: ${progressPercentage}%; background: ${chartColors[index]}"></span> | ||||||
|  |                     </span> | ||||||
|  |                   </div> | ||||||
|  |                 </div>`;
 | ||||||
|  |                 activeItemClass = 'active'; | ||||||
|  |               } | ||||||
|  |               itemFormatted.push(`<tr class="item ${activeItemClass}">
 | ||||||
|  |               <td class="indicator-container"> | ||||||
|  |                 ${colorSpan(index)} | ||||||
|  |               </td> | ||||||
|  |               <td class="value"> | ||||||
|  |                 ${this.vbytesPipe.transform(item.value, 2, 'vB', 'MvB', false)} | ||||||
|  |               </td> | ||||||
|  |               <td class="total-progress-sum"> | ||||||
|  |                 <span> | ||||||
|  |                   ${this.vbytesPipe.transform(totalValueArray[index], 2, 'vB', 'MvB', false)} | ||||||
|  |                 </span> | ||||||
|  |               </td> | ||||||
|  |               <td class="total-progress-sum-bar"> | ||||||
|  |                 <div> | ||||||
|  |                   <span style="width: ${progressPercentageSum.toFixed(2)}%; background-color: ${chartColors[index]}"></span> | ||||||
|  |                 </div> | ||||||
|  |               </td> | ||||||
|  |             </tr>`);
 | ||||||
|  |             } | ||||||
|  |           }); | ||||||
|  |           const classActive = (this.template === 'advanced') ? 'fees-wrapper-tooltip-chart-advanced' : ''; | ||||||
|  |           return `<div class="fees-wrapper-tooltip-chart ${classActive}">
 | ||||||
|  |             ${title} | ||||||
|  |             <table> | ||||||
|  |               <thead> | ||||||
|  |                 <tr> | ||||||
|  |                   <th>Range</th> | ||||||
|  |                   <th>Size</th> | ||||||
|  |                   <th>Sum</th> | ||||||
|  |                   <th></th> | ||||||
|  |                 </tr> | ||||||
|  |               </thead> | ||||||
|  |               <tbody> | ||||||
|  |                 ${itemFormatted.reverse().join('')} | ||||||
|  |               </tbody> | ||||||
|  |             </table> | ||||||
|  |             <span class="total-value"> | ||||||
|  |               ${progressPercentageText} | ||||||
|  |             </span> | ||||||
|  |           </div>`;
 | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       dataZoom: [{ | ||||||
|  |         type: 'inside', | ||||||
|  |         realtime: true, | ||||||
|  |         zoomOnMouseWheel: (this.template === 'advanced') ? true : false, | ||||||
|  |         maxSpan: 100, | ||||||
|  |         minSpan: 10, | ||||||
|  |       }, { | ||||||
|  |         show: (this.template === 'advanced' && this.showZoom) ? true : false, | ||||||
|  |         type: 'slider', | ||||||
|  |         brushSelect: false, | ||||||
|  |         realtime: true, | ||||||
|  |         bottom: 0, | ||||||
|  |         selectedDataBackground: { | ||||||
|  |           lineStyle: { | ||||||
|  |             color: '#fff', | ||||||
|  |             opacity: 0.45, | ||||||
|  |           }, | ||||||
|  |           areaStyle: { | ||||||
|  |             opacity: 0, | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       }], | ||||||
|  |       animation: false, | ||||||
|  |       grid: { | ||||||
|  |         height: this.height, | ||||||
|  |         right: this.right, | ||||||
|  |         top: this.top, | ||||||
|  |         left: this.left, | ||||||
|  |       }, | ||||||
|  |       xAxis: [ | ||||||
|  |         { | ||||||
|  |           type: 'category', | ||||||
|  |           boundaryGap: false, | ||||||
|  |           axisLine: { onZero: false }, | ||||||
|  |           data: labels.map((value: any) => `${formatDate(value, 'M/d', this.locale)}\n${formatDate(value, 'H:mm', this.locale)}`), | ||||||
|  |         } | ||||||
|  |       ], | ||||||
|  |       yAxis: { | ||||||
|  |         type: 'value', | ||||||
|  |         axisLine: { onZero: false }, | ||||||
|  |         axisLabel: { | ||||||
|  |           formatter: (value: number) => (`${this.vbytesPipe.transform(value, 2, 'vB', 'MvB', true)}`), | ||||||
|  |         }, | ||||||
|  |         splitLine: { | ||||||
|  |           lineStyle: { | ||||||
|  |             type: 'dotted', | ||||||
|  |             color: '#ffffff66', | ||||||
|  |             opacity: 0.25, | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       series: seriesGraph | ||||||
|  |     }; | ||||||
|  |   } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | |||||||
| @ -1,11 +0,0 @@ | |||||||
| .ct-legend { |  | ||||||
|     top: 130px; |  | ||||||
|     display: flex; |  | ||||||
|     flex-direction: column-reverse; |  | ||||||
|     @media (min-width: 653px) { |  | ||||||
|         top: 90px; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   .ct-legend.inverted { |  | ||||||
|     flex-direction: column !important; |  | ||||||
|   } |  | ||||||
| @ -1,740 +0,0 @@ | |||||||
| import { |  | ||||||
|   Component, |  | ||||||
|   ElementRef, |  | ||||||
|   Inject, |  | ||||||
|   Input, |  | ||||||
|   OnChanges, |  | ||||||
|   OnDestroy, |  | ||||||
|   OnInit, |  | ||||||
|   PLATFORM_ID, |  | ||||||
|   SimpleChanges, |  | ||||||
|   ViewEncapsulation |  | ||||||
| } from '@angular/core'; |  | ||||||
| 
 |  | ||||||
| import { isPlatformBrowser } from '@angular/common'; |  | ||||||
| 
 |  | ||||||
| import * as Chartist from '@mempool/chartist'; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Possible chart types |  | ||||||
|  * @type {String} |  | ||||||
|  */ |  | ||||||
| export type ChartType = 'Pie' | 'Bar' | 'Line'; |  | ||||||
| 
 |  | ||||||
| export type ChartInterfaces = |  | ||||||
|   | Chartist.IChartistPieChart |  | ||||||
|   | Chartist.IChartistBarChart |  | ||||||
|   | Chartist.IChartistLineChart; |  | ||||||
| export type ChartOptions = |  | ||||||
|   | Chartist.IBarChartOptions |  | ||||||
|   | Chartist.ILineChartOptions |  | ||||||
|   | Chartist.IPieChartOptions; |  | ||||||
| export type ResponsiveOptionTuple = Chartist.IResponsiveOptionTuple< |  | ||||||
|   ChartOptions |  | ||||||
| >; |  | ||||||
| export type ResponsiveOptions = ResponsiveOptionTuple[]; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Represent a chart event. |  | ||||||
|  * For possible values, check the Chartist docs. |  | ||||||
|  */ |  | ||||||
| export interface ChartEvent { |  | ||||||
|   [eventName: string]: (data: any) => void; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| @Component({ |  | ||||||
|   selector: 'app-chartist', |  | ||||||
|   template: '<ng-content></ng-content>', |  | ||||||
|   styleUrls: ['./chartist.component.scss'], |  | ||||||
|   encapsulation: ViewEncapsulation.None, |  | ||||||
| }) |  | ||||||
| export class ChartistComponent implements OnInit, OnChanges, OnDestroy { |  | ||||||
|   @Input() |  | ||||||
|   // @ts-ignore
 |  | ||||||
|   public data: Promise<Chartist.IChartistData> | Chartist.IChartistData; |  | ||||||
| 
 |  | ||||||
|   // @ts-ignore
 |  | ||||||
|   @Input() public type: Promise<ChartType> | ChartType; |  | ||||||
| 
 |  | ||||||
|   @Input() |  | ||||||
|   // @ts-ignore
 |  | ||||||
|   public options: Promise<Chartist.IChartOptions> | Chartist.IChartOptions; |  | ||||||
| 
 |  | ||||||
|   @Input() |  | ||||||
|   // @ts-ignore
 |  | ||||||
|   public responsiveOptions: Promise<ResponsiveOptions> | ResponsiveOptions; |  | ||||||
| 
 |  | ||||||
|   // @ts-ignore
 |  | ||||||
|   @Input() public events: ChartEvent; |  | ||||||
| 
 |  | ||||||
|   isBrowser: boolean = isPlatformBrowser(this.platformId); |  | ||||||
| 
 |  | ||||||
|   // @ts-ignore
 |  | ||||||
|   public chart: ChartInterfaces; |  | ||||||
| 
 |  | ||||||
|   private element: HTMLElement;   |  | ||||||
| 
 |  | ||||||
|   constructor( |  | ||||||
|     element: ElementRef, |  | ||||||
|     @Inject(PLATFORM_ID) private platformId: any, |  | ||||||
|   ) { |  | ||||||
|     this.element = element.nativeElement; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   public ngOnInit(): Promise<ChartInterfaces> { |  | ||||||
|     if (!this.isBrowser) { |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if (!this.type || !this.data) { |  | ||||||
|       Promise.reject('Expected at least type and data.'); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return this.renderChart().then((chart) => { |  | ||||||
|       if (this.events !== undefined) { |  | ||||||
|         this.bindEvents(chart); |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       return chart; |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   public ngOnChanges(changes: SimpleChanges): void { |  | ||||||
|     if (!this.isBrowser) { |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     this.update(changes); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   public ngOnDestroy(): void { |  | ||||||
|     if (this.chart) { |  | ||||||
|       this.chart.detach(); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   public renderChart(): Promise<ChartInterfaces> { |  | ||||||
|     const promises: any[] = [ |  | ||||||
|       this.type, |  | ||||||
|       this.element, |  | ||||||
|       this.data, |  | ||||||
|       this.options, |  | ||||||
|       this.responsiveOptions |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     return Promise.all(promises).then((values) => { |  | ||||||
|       const [type, ...args]: any = values; |  | ||||||
| 
 |  | ||||||
|       if (!(type in Chartist)) { |  | ||||||
|         throw new Error(`${type} is not a valid chart type`); |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       this.chart = (Chartist as any)[type](...args); |  | ||||||
| 
 |  | ||||||
|       return this.chart; |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   public update(changes: SimpleChanges): void { |  | ||||||
|     if (!this.chart || 'type' in changes) { |  | ||||||
|       this.renderChart(); |  | ||||||
|     } else { |  | ||||||
|       if (changes.data) { |  | ||||||
|         this.data = changes.data.currentValue; |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       if (changes.options) { |  | ||||||
|         this.options = changes.options.currentValue; |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       (this.chart as any).update(this.data, this.options); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   public bindEvents(chart: any): void { |  | ||||||
|     for (const event of Object.keys(this.events)) { |  | ||||||
|       chart.on(event, this.events[event]); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Chartist.js plugin to display a "target" or "goal" line across the chart. |  | ||||||
|  * Only tested with bar charts. Works for horizontal and vertical bars. |  | ||||||
|  */ |  | ||||||
| (function(window, document, Chartist) { |  | ||||||
|   'use strict'; |  | ||||||
| 
 |  | ||||||
|   const defaultOptions = { |  | ||||||
|     // The class name so you can style the text
 |  | ||||||
|     className: 'ct-target-line', |  | ||||||
|     // The axis to draw the line. y == vertical bars, x == horizontal
 |  | ||||||
|     axis: 'y', |  | ||||||
|     // What value the target line should be drawn at
 |  | ||||||
|     value: null |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   Chartist.plugins = Chartist.plugins || {}; |  | ||||||
| 
 |  | ||||||
|   Chartist.plugins.ctTargetLine = function (options: any) { |  | ||||||
|     options = Chartist.extend({}, defaultOptions, options); |  | ||||||
|     return function ctTargetLine (chart: any) { |  | ||||||
| 
 |  | ||||||
|       chart.on('created', function(context: any) { |  | ||||||
|         const projectTarget = { |  | ||||||
|           y: function (chartRect: any, bounds: any, value: any) { |  | ||||||
|             const targetLineY = chartRect.y1 - (chartRect.height() / bounds.max * value); |  | ||||||
| 
 |  | ||||||
|             return { |  | ||||||
|               x1: chartRect.x1, |  | ||||||
|               x2: chartRect.x2, |  | ||||||
|               y1: targetLineY, |  | ||||||
|               y2: targetLineY |  | ||||||
|             }; |  | ||||||
|           }, |  | ||||||
|           x: function (chartRect: any, bounds: any, value: any) { |  | ||||||
|             const targetLineX = chartRect.x1 + (chartRect.width() / bounds.max * value); |  | ||||||
| 
 |  | ||||||
|             return { |  | ||||||
|               x1: targetLineX, |  | ||||||
|               x2: targetLineX, |  | ||||||
|               y1: chartRect.y1, |  | ||||||
|               y2: chartRect.y2 |  | ||||||
|             }; |  | ||||||
|           } |  | ||||||
|         }; |  | ||||||
|         // @ts-ignore
 |  | ||||||
|         const targetLine = projectTarget[options.axis](context.chartRect, context.bounds, options.value); |  | ||||||
| 
 |  | ||||||
|         context.svg.elem('line', targetLine, options.className); |  | ||||||
|       }); |  | ||||||
|     }; |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
| }(null, null, Chartist)); |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Chartist.js plugin to display a data label on top of the points in a line chart. |  | ||||||
|  * |  | ||||||
|  */ |  | ||||||
| /* global Chartist */ |  | ||||||
| (function(window, document, Chartist) { |  | ||||||
|   'use strict'; |  | ||||||
| 
 |  | ||||||
|   const defaultOptions = { |  | ||||||
|     labelClass: 'ct-label', |  | ||||||
|     labelOffset: { |  | ||||||
|       x: 0, |  | ||||||
|       y: -10 |  | ||||||
|     }, |  | ||||||
|     textAnchor: 'middle', |  | ||||||
|     align: 'center', |  | ||||||
|     labelInterpolationFnc: Chartist.noop |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   const labelPositionCalculation = { |  | ||||||
|     point: function(data: any) { |  | ||||||
|       return { |  | ||||||
|         x: data.x, |  | ||||||
|         y: data.y |  | ||||||
|       }; |  | ||||||
|     }, |  | ||||||
|     bar: { |  | ||||||
|       left: function(data: any) { |  | ||||||
|         return { |  | ||||||
|           x: data.x1, |  | ||||||
|           y: data.y1 |  | ||||||
|         }; |  | ||||||
|       }, |  | ||||||
|       center: function(data: any) { |  | ||||||
|         return { |  | ||||||
|           x: data.x1 + (data.x2 - data.x1) / 2, |  | ||||||
|           y: data.y1 |  | ||||||
|         }; |  | ||||||
|       }, |  | ||||||
|       right: function(data: any) { |  | ||||||
|         return { |  | ||||||
|           x: data.x2, |  | ||||||
|           y: data.y1 |  | ||||||
|         }; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   Chartist.plugins = Chartist.plugins || {}; |  | ||||||
|   Chartist.plugins.ctPointLabels = function(options: any) { |  | ||||||
| 
 |  | ||||||
|     options = Chartist.extend({}, defaultOptions, options); |  | ||||||
| 
 |  | ||||||
|     function addLabel(position: any, data: any) { |  | ||||||
|       // if x and y exist concat them otherwise output only the existing value
 |  | ||||||
|       const value = data.value.x !== undefined && data.value.y ? |  | ||||||
|         (data.value.x + ', ' + data.value.y) : |  | ||||||
|         data.value.y || data.value.x; |  | ||||||
| 
 |  | ||||||
|       data.group.elem('text', { |  | ||||||
|         x: position.x + options.labelOffset.x, |  | ||||||
|         y: position.y + options.labelOffset.y, |  | ||||||
|         style: 'text-anchor: ' + options.textAnchor |  | ||||||
|       }, options.labelClass).text(options.labelInterpolationFnc(value)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return function ctPointLabels(chart: any) { |  | ||||||
|       if (chart instanceof Chartist.Line || chart instanceof Chartist.Bar) { |  | ||||||
|         chart.on('draw', function(data: any) { |  | ||||||
|           // @ts-ignore
 |  | ||||||
|           const positonCalculator = labelPositionCalculation[data.type] |  | ||||||
|         // @ts-ignore
 |  | ||||||
|           && labelPositionCalculation[data.type][options.align] || labelPositionCalculation[data.type]; |  | ||||||
|           if (positonCalculator) { |  | ||||||
|             addLabel(positonCalculator(data), data); |  | ||||||
|           } |  | ||||||
|         }); |  | ||||||
|       } |  | ||||||
|     }; |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
| }(null, null, Chartist)); |  | ||||||
| 
 |  | ||||||
| const defaultOptions = { |  | ||||||
|     className: '', |  | ||||||
|     classNames: false, |  | ||||||
|     removeAll: false, |  | ||||||
|     legendNames: false, |  | ||||||
|     clickable: true, |  | ||||||
|     onClick: null, |  | ||||||
|     position: 'top' |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| Chartist.plugins.legend = function (options: any) { |  | ||||||
|     let cachedDOMPosition; |  | ||||||
|     let cacheInactiveLegends: { [key:number]: boolean } = {}; |  | ||||||
|     // Catch invalid options
 |  | ||||||
|     if (options && options.position) { |  | ||||||
|         if (!(options.position === 'top' || options.position === 'bottom' || options.position instanceof HTMLElement)) { |  | ||||||
|             throw Error('The position you entered is not a valid position'); |  | ||||||
|         } |  | ||||||
|         if (options.position instanceof HTMLElement) { |  | ||||||
|             // Detatch DOM element from options object, because Chartist.extend
 |  | ||||||
|             // currently chokes on circular references present in HTMLElements
 |  | ||||||
|             cachedDOMPosition = options.position; |  | ||||||
|             delete options.position; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     options = Chartist.extend({}, defaultOptions, options); |  | ||||||
| 
 |  | ||||||
|     if (cachedDOMPosition) { |  | ||||||
|         // Reattatch the DOM Element position if it was removed before
 |  | ||||||
|         options.position = cachedDOMPosition; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return function legend(chart: any) { |  | ||||||
| 
 |  | ||||||
|       var isSelfUpdate = false; |  | ||||||
| 
 |  | ||||||
|       chart.on('created', function (data: any) { |  | ||||||
| 
 |  | ||||||
|         const useLabels = chart instanceof Chartist.Pie && chart.data.labels && chart.data.labels.length; |  | ||||||
|         const legendNames = getLegendNames(useLabels); |  | ||||||
|         var dirtyChartData = (chart.data.series.length < legendNames.length); |  | ||||||
| 
 |  | ||||||
|         if (isSelfUpdate || dirtyChartData) |  | ||||||
|             return; |  | ||||||
| 
 |  | ||||||
|         function removeLegendElement() { |  | ||||||
|           const legendElement = chart.container.querySelector('.ct-legend'); |  | ||||||
|             if (legendElement) { |  | ||||||
|                 legendElement.parentNode.removeChild(legendElement); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // Set a unique className for each series so that when a series is removed,
 |  | ||||||
|         // the other series still have the same color.
 |  | ||||||
|         function setSeriesClassNames() { |  | ||||||
|             chart.data.series = chart.data.series.map(function (series: any, seriesIndex: any) { |  | ||||||
|                 if (typeof series !== 'object') { |  | ||||||
|                     series = { |  | ||||||
|                         value: series |  | ||||||
|                     }; |  | ||||||
|                 } |  | ||||||
|                 series.className = series.className || chart.options.classNames.series + '-' + Chartist.alphaNumerate(seriesIndex); |  | ||||||
|                 return series; |  | ||||||
|             }); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         function createLegendElement() { |  | ||||||
|             const legendElement = document.createElement('ul'); |  | ||||||
|             legendElement.className = 'ct-legend'; |  | ||||||
|             const inverted = localStorage.getItem('inverted-graph') === 'true'; |  | ||||||
|             if (inverted){ |  | ||||||
|                 legendElement.classList.add('inverted'); |  | ||||||
|             } |  | ||||||
|             if (chart instanceof Chartist.Pie) { |  | ||||||
|                 legendElement.classList.add('ct-legend-inside'); |  | ||||||
|             } |  | ||||||
|             if (typeof options.className === 'string' && options.className.length > 0) { |  | ||||||
|                 legendElement.classList.add(options.className); |  | ||||||
|             } |  | ||||||
|             if (chart.options.width) { |  | ||||||
|                 legendElement.style.cssText = 'width: ' + chart.options.width + 'px;margin: 0 auto;'; |  | ||||||
|             } |  | ||||||
|             return legendElement; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // Get the right array to use for generating the legend.
 |  | ||||||
|         function getLegendNames(useLabels: any) { |  | ||||||
|             return options.legendNames || (useLabels ? chart.data.labels : chart.data.series); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // Initialize the array that associates series with legends.
 |  | ||||||
|         // -1 indicates that there is no legend associated with it.
 |  | ||||||
|         function initSeriesMetadata(useLabels: any) { |  | ||||||
|             const seriesMetadata = new Array(chart.data.series.length); |  | ||||||
|             for (let i = 0; i < chart.data.series.length; i++) { |  | ||||||
|                 seriesMetadata[i] = { |  | ||||||
|                     data: chart.data.series[i], |  | ||||||
|                     label: useLabels ? chart.data.labels[i] : null, |  | ||||||
|                     legend: -1 |  | ||||||
|                 }; |  | ||||||
|             } |  | ||||||
|             return seriesMetadata; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         function createNameElement(i: any, legendText: any, classNamesViable: any) { |  | ||||||
|             const li = document.createElement('li'); |  | ||||||
|             li.classList.add('ct-series-' + i); |  | ||||||
|             // Append specific class to a legend element, if viable classes are given
 |  | ||||||
|             if (classNamesViable) { |  | ||||||
|                 li.classList.add(options.classNames[i]); |  | ||||||
|             } |  | ||||||
|             li.setAttribute('data-legend', i); |  | ||||||
|             li.textContent = legendText; |  | ||||||
|             return li; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // Append the legend element to the DOM
 |  | ||||||
|         function appendLegendToDOM(legendElement: any) { |  | ||||||
|             if (!(options.position instanceof HTMLElement)) { |  | ||||||
|                 switch (options.position) { |  | ||||||
|                     case 'top': |  | ||||||
|                         chart.container.insertBefore(legendElement, chart.container.childNodes[0]); |  | ||||||
|                         break; |  | ||||||
| 
 |  | ||||||
|                     case 'bottom': |  | ||||||
|                         chart.container.insertBefore(legendElement, null); |  | ||||||
|                         break; |  | ||||||
|                 } |  | ||||||
|             } else { |  | ||||||
|                 // Appends the legend element as the last child of a given HTMLElement
 |  | ||||||
|                 options.position.insertBefore(legendElement, null); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         function updateChart(newSeries: any, newLabels:any, useLabels: any) { |  | ||||||
|             chart.data.series = newSeries; |  | ||||||
|             if (useLabels) { |  | ||||||
|                 chart.data.labels = newLabels; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             isSelfUpdate = true; |  | ||||||
|             chart.update(); |  | ||||||
|             isSelfUpdate = false; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         function addClickHandler(legendElement: any, legends: any, seriesMetadata: any, useLabels: any) { |  | ||||||
|             legendElement.addEventListener('click', function(e: any) { |  | ||||||
|                 const li = e.target; |  | ||||||
|                 if (li.parentNode !== legendElement || !li.hasAttribute('data-legend')) |  | ||||||
|                     return; |  | ||||||
|                 e.preventDefault(); |  | ||||||
| 
 |  | ||||||
|                 const legendIndex = parseInt(li.getAttribute('data-legend')); |  | ||||||
|                 const legend = legends[legendIndex]; |  | ||||||
| 
 |  | ||||||
|                 const activateLegend = (_legendIndex: number): void => { |  | ||||||
|                     legends[_legendIndex].active = true; |  | ||||||
|                     legendElement.childNodes[_legendIndex].classList.remove('inactive'); |  | ||||||
| 
 |  | ||||||
|                     cacheInactiveLegends[_legendIndex] = false; |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 const deactivateLegend = (_legendIndex: number): void => { |  | ||||||
|                     legends[_legendIndex].active = false; |  | ||||||
|                     legendElement.childNodes[_legendIndex].classList.add('inactive'); |  | ||||||
|                     cacheInactiveLegends[_legendIndex] = true; |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 for (let i = legends.length - 1; i >= 0; i--) { |  | ||||||
|                     if (i >= legendIndex) { |  | ||||||
|                         if (!legend.active) activateLegend(i); |  | ||||||
|                     } else { |  | ||||||
|                         if (legend.active) deactivateLegend(i); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 // Make sure all values are undefined (falsy) when clicking the first legend
 |  | ||||||
|                 // After clicking the first legend all indices should be falsy
 |  | ||||||
|                 if (legendIndex === 0) cacheInactiveLegends = {}; |  | ||||||
| 
 |  | ||||||
|                 const newSeries = []; |  | ||||||
|                 const newLabels = []; |  | ||||||
| 
 |  | ||||||
|                 for (let i = 0; i < seriesMetadata.length; i++) { |  | ||||||
|                     if (seriesMetadata[i].legend !== -1 && legends[seriesMetadata[i].legend].active) { |  | ||||||
|                         newSeries.push(seriesMetadata[i].data); |  | ||||||
|                         newLabels.push(seriesMetadata[i].label); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 updateChart(newSeries, newLabels, useLabels); |  | ||||||
| 
 |  | ||||||
|                 if (options.onClick) { |  | ||||||
|                     options.onClick(chart, e); |  | ||||||
|                 } |  | ||||||
|             }); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         removeLegendElement(); |  | ||||||
| 
 |  | ||||||
|         const legendElement = createLegendElement(); |  | ||||||
|         const seriesMetadata = initSeriesMetadata(useLabels); |  | ||||||
|         const legends: any = []; |  | ||||||
| 
 |  | ||||||
|         // Check if given class names are viable to append to legends
 |  | ||||||
|         const classNamesViable = Array.isArray(options.classNames) && options.classNames.length === legendNames.length; |  | ||||||
| 
 |  | ||||||
|         var activeSeries = []; |  | ||||||
|         var activeLabels = []; |  | ||||||
| 
 |  | ||||||
|         // Loop through all legends to set each name in a list item.
 |  | ||||||
|         legendNames.forEach(function (legend: any, i: any) { |  | ||||||
|             const legendText = legend.name || legend; |  | ||||||
|             const legendSeries = legend.series || [i]; |  | ||||||
| 
 |  | ||||||
|             const li = createNameElement(i, legendText, classNamesViable); |  | ||||||
|             // If the value is undefined or false, isActive is true
 |  | ||||||
|             const isActive: boolean = !cacheInactiveLegends[i]; |  | ||||||
|             if (isActive) { |  | ||||||
|               activeSeries.push(seriesMetadata[i].data); |  | ||||||
|               activeLabels.push(seriesMetadata[i].label); |  | ||||||
|             } else { |  | ||||||
|               li.classList.add('inactive'); |  | ||||||
|             } |  | ||||||
|             legendElement.appendChild(li); |  | ||||||
| 
 |  | ||||||
|             legendSeries.forEach(function(seriesIndex: any) { |  | ||||||
|                 seriesMetadata[seriesIndex].legend = i; |  | ||||||
|             }); |  | ||||||
| 
 |  | ||||||
|             legends.push({ |  | ||||||
|                 text: legendText, |  | ||||||
|                 series: legendSeries, |  | ||||||
|                 active: isActive |  | ||||||
|             }); |  | ||||||
| 
 |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         appendLegendToDOM(legendElement); |  | ||||||
| 
 |  | ||||||
|         if (options.clickable) { |  | ||||||
|             setSeriesClassNames(); |  | ||||||
|             addClickHandler(legendElement, legends, seriesMetadata, useLabels); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         updateChart(activeSeries, activeLabels, useLabels); |  | ||||||
| 
 |  | ||||||
|       }); |  | ||||||
|     }; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| Chartist.plugins.tooltip = function (options: any) { |  | ||||||
|   options = Chartist.extend({}, defaultOptions, options); |  | ||||||
| 
 |  | ||||||
|   return function tooltip(chart: any) { |  | ||||||
|     let tooltipSelector = options.pointClass; |  | ||||||
|     if (chart instanceof Chartist.Bar) { |  | ||||||
|       tooltipSelector = 'ct-bar'; |  | ||||||
|     } else if (chart instanceof Chartist.Pie) { |  | ||||||
|       // Added support for donut graph
 |  | ||||||
|       if (chart.options.donut) { |  | ||||||
|         tooltipSelector = 'ct-slice-donut'; |  | ||||||
|       } else { |  | ||||||
|         tooltipSelector = 'ct-slice-pie'; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     const $chart = chart.container; |  | ||||||
|     let $toolTip = $chart.querySelector('.chartist-tooltip'); |  | ||||||
|     if (!$toolTip) { |  | ||||||
|       $toolTip = document.createElement('div'); |  | ||||||
|       $toolTip.className = (!options.class) ? 'chartist-tooltip' : 'chartist-tooltip ' + options.class; |  | ||||||
|       if (!options.appendToBody) { |  | ||||||
|         $chart.appendChild($toolTip); |  | ||||||
|       } else { |  | ||||||
|         document.body.appendChild($toolTip); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|     let height = $toolTip.offsetHeight; |  | ||||||
|     let width = $toolTip.offsetWidth; |  | ||||||
| 
 |  | ||||||
|     hide($toolTip); |  | ||||||
| 
 |  | ||||||
|     function on(event: any, selector: any, callback: any) { |  | ||||||
|       $chart.addEventListener(event, function (e: any) { |  | ||||||
|         if (!selector || hasClass(e.target, selector)) { |  | ||||||
|           callback(e); |  | ||||||
|         } |  | ||||||
|       }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     on('mouseover', tooltipSelector, function (event: any) { |  | ||||||
|       const $point = event.target; |  | ||||||
|       let tooltipText = ''; |  | ||||||
| 
 |  | ||||||
|       const isPieChart = (chart instanceof Chartist.Pie) ? $point : $point.parentNode; |  | ||||||
|       const seriesName = (isPieChart) ? $point.parentNode.getAttribute('ct:meta') || $point.parentNode.getAttribute('ct:series-name') : ''; |  | ||||||
|       let meta = $point.getAttribute('ct:meta') || seriesName || ''; |  | ||||||
|       const hasMeta = !!meta; |  | ||||||
|       let value = $point.getAttribute('ct:value'); |  | ||||||
| 
 |  | ||||||
|       if (options.transformTooltipTextFnc && typeof options.transformTooltipTextFnc === 'function') { |  | ||||||
|         value = options.transformTooltipTextFnc(value, $point.parentNode.getAttribute('class')); |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       if (options.tooltipFnc && typeof options.tooltipFnc === 'function') { |  | ||||||
|         tooltipText = options.tooltipFnc(meta, value); |  | ||||||
|       } else { |  | ||||||
|         if (options.metaIsHTML) { |  | ||||||
|           const txt = document.createElement('textarea'); |  | ||||||
|           txt.innerHTML = meta; |  | ||||||
|           meta = txt.value; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         meta = '<span class="chartist-tooltip-meta">' + meta + '</span>'; |  | ||||||
| 
 |  | ||||||
|         if (hasMeta) { |  | ||||||
|           tooltipText += meta + '<br>'; |  | ||||||
|         } else { |  | ||||||
|           // For Pie Charts also take the labels into account
 |  | ||||||
|           // Could add support for more charts here as well!
 |  | ||||||
|           if (chart instanceof Chartist.Pie) { |  | ||||||
|             const label = next($point, 'ct-label'); |  | ||||||
|             if (label) { |  | ||||||
|               tooltipText += text(label) + '<br>'; |  | ||||||
|             } |  | ||||||
|           } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if (value) { |  | ||||||
|           if (options.currency) { |  | ||||||
|             if (options.currencyFormatCallback != undefined) { |  | ||||||
|               value = options.currencyFormatCallback(value, options); |  | ||||||
|             } else { |  | ||||||
|               value = options.currency + value.replace(/(\d)(?=(\d{3})+(?:\.\d+)?$)/g, '$1,'); |  | ||||||
|             } |  | ||||||
|           } |  | ||||||
|           value = '<span class="chartist-tooltip-value">' + value + '</span>'; |  | ||||||
|           tooltipText += value; |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       if (tooltipText) { |  | ||||||
|         $toolTip.innerHTML = tooltipText; |  | ||||||
|         setPosition(event); |  | ||||||
|         show($toolTip); |  | ||||||
| 
 |  | ||||||
|         // Remember height and width to avoid wrong position in IE
 |  | ||||||
|         height = $toolTip.offsetHeight; |  | ||||||
|         width = $toolTip.offsetWidth; |  | ||||||
|       } |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     on('mouseout', tooltipSelector, function () { |  | ||||||
|       hide($toolTip); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     on('mousemove', null, function (event: any) { |  | ||||||
|       if (false === options.anchorToPoint) { |  | ||||||
|         setPosition(event); |  | ||||||
|       } |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     function setPosition(event: any) { |  | ||||||
|       height = height || $toolTip.offsetHeight; |  | ||||||
|       width = width || $toolTip.offsetWidth; |  | ||||||
|       const offsetX = - width / 2 + options.tooltipOffset.x |  | ||||||
|       const offsetY = - height + options.tooltipOffset.y; |  | ||||||
|       let anchorX, anchorY; |  | ||||||
| 
 |  | ||||||
|       if (!options.appendToBody) { |  | ||||||
|         const box = $chart.getBoundingClientRect(); |  | ||||||
|         const left = event.pageX - box.left - window.pageXOffset ; |  | ||||||
|         const top = event.pageY - box.top - window.pageYOffset ; |  | ||||||
| 
 |  | ||||||
|         if (true === options.anchorToPoint && event.target.x2 && event.target.y2) { |  | ||||||
|           anchorX = parseInt(event.target.x2.baseVal.value); |  | ||||||
|           anchorY = parseInt(event.target.y2.baseVal.value); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         $toolTip.style.top = (anchorY || top) + offsetY + 'px'; |  | ||||||
|         $toolTip.style.left = (anchorX || left) + offsetX + 'px'; |  | ||||||
|       } else { |  | ||||||
|         $toolTip.style.top = event.pageY + offsetY + 'px'; |  | ||||||
|         $toolTip.style.left = event.pageX + offsetX + 'px'; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| Chartist.plugins.ctPointLabels = (options) => { |  | ||||||
|   return function ctPointLabels(chart) { |  | ||||||
|       const defaultOptions2 = { |  | ||||||
|           labelClass: 'ct-point-label', |  | ||||||
|           labelOffset: { |  | ||||||
|               x: 0, |  | ||||||
|               y: -7 |  | ||||||
|           }, |  | ||||||
|           textAnchor: 'middle' |  | ||||||
|       }; |  | ||||||
|       options = Chartist.extend({}, defaultOptions2, options); |  | ||||||
| 
 |  | ||||||
|       if (chart instanceof Chartist.Line) { |  | ||||||
|           chart.on('draw', (data) => { |  | ||||||
|               if (data.type === 'point') { |  | ||||||
|                   data.group.elem('text', { |  | ||||||
|                       x: data.x + options.labelOffset.x, |  | ||||||
|                       y: data.y + options.labelOffset.y, |  | ||||||
|                       style: 'text-anchor: ' + options.textAnchor |  | ||||||
|                   }, options.labelClass).text(options.labelInterpolationFnc(data.value.y));  // 07.11.17 added ".y"
 |  | ||||||
|               } |  | ||||||
|           }); |  | ||||||
|       } |  | ||||||
|   }; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| function show(element: any) { |  | ||||||
|   if (!hasClass(element, 'tooltip-show')) { |  | ||||||
|     element.className = element.className + ' tooltip-show'; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function hide(element: any) { |  | ||||||
|   const regex = new RegExp('tooltip-show' + '\\s*', 'gi'); |  | ||||||
|   element.className = element.className.replace(regex, '').trim(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function hasClass(element: any, className: any) { |  | ||||||
|   return (' ' + element.getAttribute('class') + ' ').indexOf(' ' + className + ' ') > -1; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function next(element: any, className: any) { |  | ||||||
|   do { |  | ||||||
|     element = element.nextSibling; |  | ||||||
|   } while (element && !hasClass(element, className)); |  | ||||||
|   return element; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function text(element: any) { |  | ||||||
|   return element.innerText || element.textContent; |  | ||||||
| } |  | ||||||
| @ -36,29 +36,36 @@ | |||||||
|                 <input ngbButton type="radio" [value]="'1y'" [routerLink]="['/graphs' | relativeUrl]" fragment="1y"> 1Y |                 <input ngbButton type="radio" [value]="'1y'" [routerLink]="['/graphs' | relativeUrl]" fragment="1y"> 1Y | ||||||
|               </label> |               </label> | ||||||
|             </div> |             </div> | ||||||
|             <button (click)="invertGraph()" class="btn btn-primary btn-sm ml-2 d-none d-md-inline"><fa-icon [icon]="['fas', 'exchange-alt']" [rotate]="90" [fixedWidth]="true" i18n-title="statistics.component-invert.title" title="Invert"></fa-icon></button> |  | ||||||
|           </form> |           </form> | ||||||
|         </div> |         </div> | ||||||
|         <div class="card-body"> |         <div class="card-body"> | ||||||
|           <div style="height: 600px;"> |           <div class="incoming-transactions-graph"> | ||||||
|             <app-mempool-graph dir="ltr" [data]="mempoolStats" [dateSpan]="radioGroupForm.controls.dateSpan.value"></app-mempool-graph> |             <app-mempool-graph | ||||||
|  |               dir="ltr" | ||||||
|  |               [template]="'advanced'" | ||||||
|  |               [limitFee]="500" | ||||||
|  |               [height]="500" | ||||||
|  |               [left]="65" | ||||||
|  |               [data]="mempoolStats" | ||||||
|  |             ></app-mempool-graph> | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
| 
 | 
 | ||||||
|     <div class="col-lg-12"> |     <div> | ||||||
|       <div class="card mb-3" *ngIf="mempoolTransactionsWeightPerSecondData"> |       <div class="card mb-3" *ngIf="mempoolTransactionsWeightPerSecondData"> | ||||||
|         <div class="card-header"> |         <div class="card-header"> | ||||||
|             <i class="fa fa-area-chart"></i> <span i18n="statistics.transaction-vbytes-per-second">Transaction vBytes per second (vB/s)</span> |             <i class="fa fa-area-chart"></i> <span i18n="statistics.transaction-vbytes-per-second">Transaction vBytes per second (vB/s)</span> | ||||||
|         </div> |         </div> | ||||||
|         <div class="card-body"> |         <div class="card-body"> | ||||||
|           <div style="height: 600px;"> |           <div class="incoming-transactions-graph"> | ||||||
|             <app-chartist |             <app-incoming-transactions-graph | ||||||
|  |               [height]="500" | ||||||
|  |               [left]="65" | ||||||
|  |               [template]="'advanced'" | ||||||
|               [data]="mempoolTransactionsWeightPerSecondData" |               [data]="mempoolTransactionsWeightPerSecondData" | ||||||
|               [type]="'Line'" |             ></app-incoming-transactions-graph> | ||||||
|               [options]="transactionsWeightPerSecondOptions"> |  | ||||||
|             </app-chartist> |  | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|  | |||||||
| @ -56,4 +56,8 @@ | |||||||
|   text-align: center; |   text-align: center; | ||||||
|   height: 80vh; |   height: 80vh; | ||||||
|   justify-content: center; |   justify-content: center; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | .incoming-transactions-graph { | ||||||
|  |   height: 600px; | ||||||
|  | } | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| import { Component, OnInit, LOCALE_ID, Inject } from '@angular/core'; | import { Component, OnInit, LOCALE_ID, Inject } from '@angular/core'; | ||||||
| import { ActivatedRoute } from '@angular/router'; | import { ActivatedRoute } from '@angular/router'; | ||||||
| import { formatDate } from '@angular/common'; |  | ||||||
| import { FormGroup, FormBuilder } from '@angular/forms'; | import { FormGroup, FormBuilder } from '@angular/forms'; | ||||||
| import { of, merge} from 'rxjs'; | import { of, merge} from 'rxjs'; | ||||||
| import { switchMap } from 'rxjs/operators'; | import { switchMap } from 'rxjs/operators'; | ||||||
| @ -9,7 +8,6 @@ import { OptimizedMempoolStats } from '../../interfaces/node-api.interface'; | |||||||
| import { WebsocketService } from '../../services/websocket.service'; | import { WebsocketService } from '../../services/websocket.service'; | ||||||
| import { ApiService } from '../../services/api.service'; | import { ApiService } from '../../services/api.service'; | ||||||
| 
 | 
 | ||||||
| import * as Chartist from '@mempool/chartist'; |  | ||||||
| import { StateService } from 'src/app/services/state.service'; | import { StateService } from 'src/app/services/state.service'; | ||||||
| import { SeoService } from 'src/app/services/seo.service'; | import { SeoService } from 'src/app/services/seo.service'; | ||||||
| import { StorageService } from 'src/app/services/storage.service'; | import { StorageService } from 'src/app/services/storage.service'; | ||||||
| @ -31,10 +29,7 @@ export class StatisticsComponent implements OnInit { | |||||||
|   mempoolUnconfirmedTransactionsData: any; |   mempoolUnconfirmedTransactionsData: any; | ||||||
|   mempoolTransactionsWeightPerSecondData: any; |   mempoolTransactionsWeightPerSecondData: any; | ||||||
| 
 | 
 | ||||||
|   transactionsWeightPerSecondOptions: any; |  | ||||||
| 
 |  | ||||||
|   radioGroupForm: FormGroup; |   radioGroupForm: FormGroup; | ||||||
|   inverted: boolean; |  | ||||||
|   graphWindowPreference: String; |   graphWindowPreference: String; | ||||||
| 
 | 
 | ||||||
|   constructor( |   constructor( | ||||||
| @ -51,7 +46,6 @@ export class StatisticsComponent implements OnInit { | |||||||
|   ngOnInit() { |   ngOnInit() { | ||||||
|     this.seoService.setTitle($localize`:@@5d4f792f048fcaa6df5948575d7cb325c9393383:Graphs`); |     this.seoService.setTitle($localize`:@@5d4f792f048fcaa6df5948575d7cb325c9393383:Graphs`); | ||||||
|     this.stateService.networkChanged$.subscribe((network) => this.network = network); |     this.stateService.networkChanged$.subscribe((network) => this.network = network); | ||||||
|     this.inverted = this.storageService.getValue('inverted-graph') === 'true'; |  | ||||||
|     this.graphWindowPreference = this.storageService.getValue('graphWindowPreference') ? this.storageService.getValue('graphWindowPreference').trim() : '2h'; |     this.graphWindowPreference = this.storageService.getValue('graphWindowPreference') ? this.storageService.getValue('graphWindowPreference').trim() : '2h'; | ||||||
|     const isMobile = window.innerWidth <= 767.98; |     const isMobile = window.innerWidth <= 767.98; | ||||||
|     let labelHops = isMobile ? 48 : 24; |     let labelHops = isMobile ? 48 : 24; | ||||||
| @ -64,43 +58,6 @@ export class StatisticsComponent implements OnInit { | |||||||
|       dateSpan: this.graphWindowPreference |       dateSpan: this.graphWindowPreference | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     const labelInterpolationFnc = (value: any, index: any) => { |  | ||||||
|       switch (this.graphWindowPreference) { |  | ||||||
|         case '2h': |  | ||||||
|         case '24h': |  | ||||||
|           value = formatDate(value, 'HH:mm', this.locale); |  | ||||||
|           break; |  | ||||||
|         case '1w': |  | ||||||
|           value = formatDate(value, 'dd/MM HH:mm', this.locale); |  | ||||||
|           break; |  | ||||||
|         case '1m': |  | ||||||
|         case '3m': |  | ||||||
|         case '6m': |  | ||||||
|         case '1y': |  | ||||||
|           value = formatDate(value, 'dd/MM', this.locale); |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       return index % labelHops === 0 ? value : null; |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     this.transactionsWeightPerSecondOptions = { |  | ||||||
|       showArea: false, |  | ||||||
|       showLine: true, |  | ||||||
|       showPoint: false, |  | ||||||
|       low: 0, |  | ||||||
|       axisY: { |  | ||||||
|         offset: 40 |  | ||||||
|       }, |  | ||||||
|       axisX: { |  | ||||||
|         labelInterpolationFnc: labelInterpolationFnc |  | ||||||
|       }, |  | ||||||
|       plugins: [ |  | ||||||
|         Chartist.plugins.ctTargetLine({ |  | ||||||
|           value: 1667 |  | ||||||
|         }), |  | ||||||
|       ] |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     this.route |     this.route | ||||||
|       .fragment |       .fragment | ||||||
|       .subscribe((fragment) => { |       .subscribe((fragment) => { | ||||||
| @ -164,11 +121,6 @@ export class StatisticsComponent implements OnInit { | |||||||
|     }; |     }; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   invertGraph() { |  | ||||||
|     this.storageService.setValue('inverted-graph', !this.inverted); |  | ||||||
|     document.location.reload(); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   saveGraphPreference() { |   saveGraphPreference() { | ||||||
|     this.storageService.setValue('graphWindowPreference', this.radioGroupForm.controls.dateSpan.value); |     this.storageService.setValue('graphWindowPreference', this.radioGroupForm.controls.dateSpan.value); | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -4,12 +4,17 @@ | |||||||
|     <div class="spinner-border text-light"></div> |     <div class="spinner-border text-light"></div> | ||||||
|   </div> |   </div> | ||||||
| 
 | 
 | ||||||
|   <div class="tv-container"> |   <div class="tv-container" *ngIf="mempoolStats.length"> | ||||||
| 
 |     <div class="chart-holder"> | ||||||
|     <div class="chart-holder" *ngIf="mempoolStats.length"> |       <app-mempool-graph | ||||||
|       <app-mempool-graph dir="ltr" [data]="mempoolStats"></app-mempool-graph> |         [template]="'advanced'" | ||||||
|  |         [limitFee]="500" | ||||||
|  |         [height]="600" | ||||||
|  |         [left]="60" | ||||||
|  |         [data]="mempoolStats" | ||||||
|  |         [showZoom]="false" | ||||||
|  |       ></app-mempool-graph> | ||||||
|     </div> |     </div> | ||||||
| 
 |  | ||||||
|     <div class="blockchain-wrapper"> |     <div class="blockchain-wrapper"> | ||||||
|       <div class="position-container"> |       <div class="position-container"> | ||||||
|         <app-mempool-blocks></app-mempool-blocks> |         <app-mempool-blocks></app-mempool-blocks> | ||||||
|  | |||||||
| @ -16,40 +16,24 @@ | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .chart-holder { | .chart-holder { | ||||||
|   height: calc(100vh - 270px); |   height: 650px; | ||||||
|   min-height: 525px; |   width: 100%; | ||||||
|   padding-left: 20px; |   margin: 30px auto 0; | ||||||
|   width: 98.5%; |  | ||||||
|   padding-top: 20px; |  | ||||||
|   @media(min-width: 992px){ |  | ||||||
|     padding-top: 10px; |  | ||||||
|   } |  | ||||||
|   @media(min-height: 800px){ |  | ||||||
|     padding-top: 60px !important; |  | ||||||
|   } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .blockchain-wrapper { | .blockchain-wrapper { | ||||||
| 
 |   display: block; | ||||||
|   display: flex; |  | ||||||
|   height: 100%; |   height: 100%; | ||||||
|   min-height: 240px; |   min-height: 240px; | ||||||
|   position: relative; |   position: relative; | ||||||
|   top: -20px; |   top: 30px; | ||||||
|   @media(min-height: 800px) { | 
 | ||||||
|     top: 10px; |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   .position-container { |   .position-container { | ||||||
|     position: absolute; |     position: absolute; | ||||||
|     left: 50%; |     left: 50%; | ||||||
|     bottom: 170px; |     bottom: 170px; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   .chart-holder { |  | ||||||
|     height: calc(100% - 220px); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   #divider { |   #divider { | ||||||
|     width: 3px; |     width: 3px; | ||||||
|     height: 175px; |     height: 175px; | ||||||
| @ -64,29 +48,9 @@ | |||||||
|       top: -28px; |       top: -28px; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 |  | ||||||
|   @media (min-width: 1920px) { |  | ||||||
|     .position-container { |  | ||||||
|       transform: scale(1.3); |  | ||||||
|       bottom: 210px; |  | ||||||
|     } |  | ||||||
|     .chart-holder { |  | ||||||
|       height: calc(100% - 280px); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
| } | } | ||||||
| :host ::ng-deep .ct-legend { |  | ||||||
|   top: 20px !important; |  | ||||||
|   display: flex; |  | ||||||
|   flex-direction: column-reverse; |  | ||||||
|   @media(min-height: 800px){ |  | ||||||
|     padding-top: 40px !important; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .tv-container { | .tv-container { | ||||||
|   display: flex; |   display: flex; | ||||||
|   margin-top: 0px; |   margin-top: 0px; | ||||||
|   flex-direction: column; |   flex-direction: column; | ||||||
| } | } | ||||||
|  | |||||||
| @ -47,8 +47,12 @@ | |||||||
|               <ng-container *ngTemplateOutlet="mempoolTable; context: { $implicit: mempoolInfoData }"></ng-container> |               <ng-container *ngTemplateOutlet="mempoolTable; context: { $implicit: mempoolInfoData }"></ng-container> | ||||||
|               <hr> |               <hr> | ||||||
|             </div> |             </div> | ||||||
|             <div class="mempool-graph" *ngIf="(mempoolStats$ | async) as mempoolStats"> |             <div class="mempool-graph" *ngIf="(mempoolStats$ | async) as mempoolStats; else loadingSpinner"> | ||||||
|               <app-mempool-graph [data]="mempoolStats.mempool" [showLegend]="false" [offsetX]="20" [small]="true"></app-mempool-graph> |               <app-mempool-graph | ||||||
|  |                 [template]="'widget'" | ||||||
|  |                 [limitFee]="150" | ||||||
|  |                 [data]="mempoolStats.mempool" | ||||||
|  |               ></app-mempool-graph> | ||||||
|             </div> |             </div> | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
| @ -59,12 +63,11 @@ | |||||||
|             <ng-container *ngTemplateOutlet="txPerSecond; context: { $implicit: mempoolInfoData }"></ng-container> |             <ng-container *ngTemplateOutlet="txPerSecond; context: { $implicit: mempoolInfoData }"></ng-container> | ||||||
|             <br> |             <br> | ||||||
|             <hr> |             <hr> | ||||||
|             <div class="mempool-graph" *ngIf="(mempoolStats$ | async) as mempoolStats"> |             <div class="mempool-graph" *ngIf="(mempoolStats$ | async) as mempoolStats; else loadingSpinner"> | ||||||
|               <app-chartist |               <app-incoming-transactions-graph | ||||||
|                   [data]="mempoolStats.weightPerSecond" |                 [left]="50" | ||||||
|                   [type]="'Line'" |                 [data]="mempoolStats.weightPerSecond" | ||||||
|                   [options]="transactionsWeightPerSecondOptions"> |                 ></app-incoming-transactions-graph> | ||||||
|                 </app-chartist> |  | ||||||
|             </div> |             </div> | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
| @ -197,13 +200,14 @@ | |||||||
|     </span> |     </span> | ||||||
|     <ng-template #inSync> |     <ng-template #inSync> | ||||||
|       <div class="progress inc-tx-progress-bar"> |       <div class="progress inc-tx-progress-bar"> | ||||||
|         <div class="progress-bar {{ mempoolInfoData.value.progressClass }}" role="progressbar" [ngStyle]="{'width': mempoolInfoData.value.progressWidth}"> </div> |         <div class="progress-bar" role="progressbar" [ngStyle]="{'width': mempoolInfoData.value.progressWidth, 'background-color': mempoolInfoData.value.progressColor}"> </div> | ||||||
|         <div class="progress-text">{{ mempoolInfoData.value.vBytesPerSecond | ceil | number }} <ng-container i18n="shared.vbytes-per-second|vB/s">vB/s</ng-container></div> |         <div class="progress-text">{{ mempoolInfoData.value.vBytesPerSecond | ceil | number }} <ng-container i18n="shared.vbytes-per-second|vB/s">vB/s</ng-container></div> | ||||||
|       </div> |       </div> | ||||||
|     </ng-template> |     </ng-template> | ||||||
|   </ng-template> |   </ng-template> | ||||||
| </ng-template> | </ng-template> | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| <ng-template #difficultyEpoch> | <ng-template #difficultyEpoch> | ||||||
|   <div class="main-title" i18n="dashboard.difficulty-adjustment">Difficulty Adjustment</div> |   <div class="main-title" i18n="dashboard.difficulty-adjustment">Difficulty Adjustment</div> | ||||||
|   <div class="card-wrapper"> |   <div class="card-wrapper"> | ||||||
| @ -228,11 +232,11 @@ | |||||||
|               <ng-template #arrowDownDifficulty > |               <ng-template #arrowDownDifficulty > | ||||||
|                 <fa-icon class="retarget-sign" [icon]="['fas', 'caret-down']" [fixedWidth]="true"></fa-icon> |                 <fa-icon class="retarget-sign" [icon]="['fas', 'caret-down']" [fixedWidth]="true"></fa-icon> | ||||||
|               </ng-template> |               </ng-template> | ||||||
|               {{ epochData.change | absolute | number: '1.2-2' }}  |               {{ epochData.change | absolute | number: '1.2-2' }} | ||||||
|               <span class="symbol">%</span> |               <span class="symbol">%</span> | ||||||
|             </div> |             </div> | ||||||
|             <div class="symbol"> |             <div class="symbol"> | ||||||
|               <span i18n="difficulty-box.previous">Previous</span>:  |               <span i18n="difficulty-box.previous">Previous</span>: | ||||||
|               <span [ngStyle]="{'color': epochData.colorPreviousAdjustments}"> |               <span [ngStyle]="{'color': epochData.colorPreviousAdjustments}"> | ||||||
|                 <span *ngIf="epochData.previousRetarget > 0; else arrowDownPreviousDifficulty" > |                 <span *ngIf="epochData.previousRetarget > 0; else arrowDownPreviousDifficulty" > | ||||||
|                   <fa-icon class="previous-retarget-sign" [icon]="['fas', 'caret-up']" [fixedWidth]="true"></fa-icon> |                   <fa-icon class="previous-retarget-sign" [icon]="['fas', 'caret-up']" [fixedWidth]="true"></fa-icon> | ||||||
| @ -257,6 +261,12 @@ | |||||||
| </ng-template> | </ng-template> | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | <ng-template #loadingSpinner> | ||||||
|  |   <div class="text-center loadingGraphs"> | ||||||
|  |     <div class="spinner-border text-light"></div> | ||||||
|  |   </div> | ||||||
|  | </ng-template> | ||||||
|  | 
 | ||||||
| <ng-template #loadingDifficulty> | <ng-template #loadingDifficulty> | ||||||
|   <div class="difficulty-skeleton loading-container"> |   <div class="difficulty-skeleton loading-container"> | ||||||
|     <div class="item"> |     <div class="item"> | ||||||
|  | |||||||
| @ -58,11 +58,11 @@ | |||||||
|   display: block; |   display: block; | ||||||
|   @media (min-width: 485px) { |   @media (min-width: 485px) { | ||||||
|     display: flex; |     display: flex; | ||||||
|     flex-direction: row;  |     flex-direction: row; | ||||||
|   } |   } | ||||||
|   h5 { |   h5 { | ||||||
|     margin-bottom: 10px; |     margin-bottom: 10px; | ||||||
|   }  |   } | ||||||
|   .item { |   .item { | ||||||
|     width: 50%; |     width: 50%; | ||||||
|     margin: 0px auto 20px; |     margin: 0px auto 20px; | ||||||
| @ -131,7 +131,7 @@ | |||||||
| .latest-transactions { | .latest-transactions { | ||||||
|   width: 100%; |   width: 100%; | ||||||
|   text-align: left; |   text-align: left; | ||||||
|   table-layout:fixed;  |   table-layout:fixed; | ||||||
|   tr, td, th { |   tr, td, th { | ||||||
|     border: 0px; |     border: 0px; | ||||||
|   } |   } | ||||||
| @ -220,6 +220,11 @@ | |||||||
| .mempool-graph { | .mempool-graph { | ||||||
|   height: 250px; |   height: 250px; | ||||||
| } | } | ||||||
|  | .loadingGraphs{ | ||||||
|  |   height: 250px; | ||||||
|  |   display: grid; | ||||||
|  |   place-items: center; | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| .inc-tx-progress-bar { | .inc-tx-progress-bar { | ||||||
|   max-width: 250px; |   max-width: 250px; | ||||||
| @ -247,7 +252,7 @@ | |||||||
|     color: #ffffff66; |     color: #ffffff66; | ||||||
|     font-size: 12px; |     font-size: 12px; | ||||||
|   } |   } | ||||||
|   .item {     |   .item { | ||||||
|     padding: 0 5px; |     padding: 0 5px; | ||||||
|     width: 100%; |     width: 100%; | ||||||
|     &:nth-child(1) { |     &:nth-child(1) { | ||||||
| @ -276,25 +281,25 @@ | |||||||
|   justify-content: space-between; |   justify-content: space-between; | ||||||
|   @media (min-width: 376px) { |   @media (min-width: 376px) { | ||||||
|     flex-direction: row; |     flex-direction: row; | ||||||
|   }   |   } | ||||||
|   .item { |   .item { | ||||||
|     max-width: 150px; |     max-width: 150px; | ||||||
|     margin: 0; |     margin: 0; | ||||||
|     width: -webkit-fill-available; |     width: -webkit-fill-available; | ||||||
|     @media (min-width: 376px) { |     @media (min-width: 376px) { | ||||||
|       margin: 0 auto 0px; |       margin: 0 auto 0px; | ||||||
|     }     |     } | ||||||
|     &:first-child{ |     &:first-child{ | ||||||
|       display: none; |       display: none; | ||||||
|       @media (min-width: 485px) { |       @media (min-width: 485px) { | ||||||
|         display: block; |         display: block; | ||||||
|       }     |       } | ||||||
|       @media (min-width: 768px) { |       @media (min-width: 768px) { | ||||||
|         display: none; |         display: none; | ||||||
|       }     |       } | ||||||
|       @media (min-width: 992px) { |       @media (min-width: 992px) { | ||||||
|         display: block; |         display: block; | ||||||
|       }     |       } | ||||||
|     } |     } | ||||||
|     &:last-child { |     &:last-child { | ||||||
|       margin-bottom: 0; |       margin-bottom: 0; | ||||||
| @ -355,4 +360,4 @@ | |||||||
| .previous-retarget-sign { | .previous-retarget-sign { | ||||||
|   margin-right: -2px; |   margin-right: -2px; | ||||||
|   font-size: 10px; |   font-size: 10px; | ||||||
| } | } | ||||||
|  | |||||||
| @ -6,11 +6,11 @@ import { OptimizedMempoolStats } from '../interfaces/node-api.interface'; | |||||||
| import { MempoolInfo, TransactionStripped } from '../interfaces/websocket.interface'; | import { MempoolInfo, TransactionStripped } from '../interfaces/websocket.interface'; | ||||||
| import { ApiService } from '../services/api.service'; | import { ApiService } from '../services/api.service'; | ||||||
| import { StateService } from '../services/state.service'; | import { StateService } from '../services/state.service'; | ||||||
| import * as Chartist from '@mempool/chartist'; |  | ||||||
| import { formatDate } from '@angular/common'; | import { formatDate } from '@angular/common'; | ||||||
| import { WebsocketService } from '../services/websocket.service'; | import { WebsocketService } from '../services/websocket.service'; | ||||||
| import { SeoService } from '../services/seo.service'; | import { SeoService } from '../services/seo.service'; | ||||||
| import { StorageService } from '../services/storage.service'; | import { StorageService } from '../services/storage.service'; | ||||||
|  | import { EChartsOption } from 'echarts'; | ||||||
| 
 | 
 | ||||||
| interface MempoolBlocksData { | interface MempoolBlocksData { | ||||||
|   blocks: number; |   blocks: number; | ||||||
| @ -34,7 +34,7 @@ interface MempoolInfoData { | |||||||
|   memPoolInfo: MempoolInfo; |   memPoolInfo: MempoolInfo; | ||||||
|   vBytesPerSecond: number; |   vBytesPerSecond: number; | ||||||
|   progressWidth: string; |   progressWidth: string; | ||||||
|   progressClass: string; |   progressColor: string; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| interface MempoolStatsData { | interface MempoolStatsData { | ||||||
| @ -74,15 +74,15 @@ export class DashboardComponent implements OnInit { | |||||||
|   ) { } |   ) { } | ||||||
| 
 | 
 | ||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
| 
 |  | ||||||
|     this.isLoadingWebSocket$ = this.stateService.isLoadingWebSocket$; |     this.isLoadingWebSocket$ = this.stateService.isLoadingWebSocket$; | ||||||
|     this.seoService.resetTitle(); |     this.seoService.resetTitle(); | ||||||
|     this.websocketService.want(['blocks', 'stats', 'mempool-blocks', 'live-2h-chart']); |     this.websocketService.want(['blocks', 'stats', 'mempool-blocks', 'live-2h-chart']); | ||||||
|     this.network$ = merge(of(''), this.stateService.networkChanged$); |     this.network$ = merge(of(''), this.stateService.networkChanged$); | ||||||
|     this.collapseLevel = this.storageService.getValue('dashboard-collapsed') || 'one'; |     this.collapseLevel = this.storageService.getValue('dashboard-collapsed') || 'one'; | ||||||
|     this.mempoolLoadingStatus$ = this.stateService.loadingIndicators$.pipe( |     this.mempoolLoadingStatus$ = this.stateService.loadingIndicators$ | ||||||
|       map((indicators) => indicators.mempool !== undefined ? indicators.mempool : 100) |       .pipe( | ||||||
|     ); |         map((indicators) => indicators.mempool !== undefined ? indicators.mempool : 100) | ||||||
|  |       ); | ||||||
| 
 | 
 | ||||||
|     this.mempoolInfoData$ = combineLatest([ |     this.mempoolInfoData$ = combineLatest([ | ||||||
|       this.stateService.mempoolInfo$, |       this.stateService.mempoolInfo$, | ||||||
| @ -92,11 +92,21 @@ export class DashboardComponent implements OnInit { | |||||||
|       map(([mempoolInfo, vbytesPerSecond]) => { |       map(([mempoolInfo, vbytesPerSecond]) => { | ||||||
|         const percent = Math.round((Math.min(vbytesPerSecond, this.vBytesPerSecondLimit) / this.vBytesPerSecondLimit) * 100); |         const percent = Math.round((Math.min(vbytesPerSecond, this.vBytesPerSecondLimit) / this.vBytesPerSecondLimit) * 100); | ||||||
| 
 | 
 | ||||||
|         let progressClass = 'bg-danger'; |         let progressColor = '#7CB342'; | ||||||
|         if (percent <= 75) { |         if (vbytesPerSecond > 1667) { | ||||||
|           progressClass = 'bg-success'; |           progressColor = '#FDD835'; | ||||||
|         } else if (percent <= 99) { |         } | ||||||
|           progressClass = 'bg-warning'; |         if (vbytesPerSecond > 2000) { | ||||||
|  |           progressColor = '#FFB300'; | ||||||
|  |         } | ||||||
|  |         if (vbytesPerSecond > 2500) { | ||||||
|  |           progressColor = '#FB8C00'; | ||||||
|  |         } | ||||||
|  |         if (vbytesPerSecond > 3000) { | ||||||
|  |           progressColor = '#F4511E'; | ||||||
|  |         } | ||||||
|  |         if (vbytesPerSecond > 3500) { | ||||||
|  |           progressColor = '#D81B60'; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const mempoolSizePercentage = (mempoolInfo.usage / mempoolInfo.maxmempool * 100); |         const mempoolSizePercentage = (mempoolInfo.usage / mempoolInfo.maxmempool * 100); | ||||||
| @ -111,7 +121,7 @@ export class DashboardComponent implements OnInit { | |||||||
|           memPoolInfo: mempoolInfo, |           memPoolInfo: mempoolInfo, | ||||||
|           vBytesPerSecond: vbytesPerSecond, |           vBytesPerSecond: vbytesPerSecond, | ||||||
|           progressWidth: percent + '%', |           progressWidth: percent + '%', | ||||||
|           progressClass: progressClass, |           progressColor: progressColor, | ||||||
|           mempoolSizeProgress: mempoolSizeProgress, |           mempoolSizeProgress: mempoolSizeProgress, | ||||||
|         }; |         }; | ||||||
|       }) |       }) | ||||||
| @ -164,7 +174,7 @@ export class DashboardComponent implements OnInit { | |||||||
|           } |           } | ||||||
| 
 | 
 | ||||||
|           let colorPreviousAdjustments = '#dc3545'; |           let colorPreviousAdjustments = '#dc3545'; | ||||||
|           if (previousRetarget){ |           if (previousRetarget) { | ||||||
|             if (previousRetarget >= 0) { |             if (previousRetarget >= 0) { | ||||||
|               colorPreviousAdjustments = '#3bcc49'; |               colorPreviousAdjustments = '#3bcc49'; | ||||||
|             } |             } | ||||||
| @ -191,7 +201,6 @@ export class DashboardComponent implements OnInit { | |||||||
|         }) |         }) | ||||||
|       ); |       ); | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|     this.mempoolBlocksData$ = this.stateService.mempoolBlocks$ |     this.mempoolBlocksData$ = this.stateService.mempoolBlocks$ | ||||||
|       .pipe( |       .pipe( | ||||||
|         map((mempoolBlocks) => { |         map((mempoolBlocks) => { | ||||||
| @ -226,50 +235,32 @@ export class DashboardComponent implements OnInit { | |||||||
|         }, []), |         }, []), | ||||||
|       ); |       ); | ||||||
| 
 | 
 | ||||||
|     this.mempoolStats$ = this.stateService.connectionState$.pipe( |     this.mempoolStats$ = this.stateService.connectionState$ | ||||||
|       filter((state) => state === 2), |       .pipe( | ||||||
|       switchMap(() => this.apiService.list2HStatistics$()), |         filter((state) => state === 2), | ||||||
|       switchMap((mempoolStats) => { |         switchMap(() => this.apiService.list2HStatistics$()), | ||||||
|         return merge( |         switchMap((mempoolStats) => { | ||||||
|           this.stateService.live2Chart$ |           return merge( | ||||||
|             .pipe( |             this.stateService.live2Chart$ | ||||||
|               scan((acc, stats) => { |               .pipe( | ||||||
|                 acc.unshift(stats); |                 scan((acc, stats) => { | ||||||
|                 acc = acc.slice(0, 120); |                   acc.unshift(stats); | ||||||
|                 return acc; |                   acc = acc.slice(0, 120); | ||||||
|               }, mempoolStats) |                   return acc; | ||||||
|             ), |                 }, mempoolStats) | ||||||
|           of(mempoolStats) |               ), | ||||||
|         ); |             of(mempoolStats) | ||||||
|       }), |           ); | ||||||
|       map((mempoolStats) => { |         }), | ||||||
|         return { |         map((mempoolStats) => { | ||||||
|           mempool: mempoolStats, |           const data = this.handleNewMempoolData(mempoolStats.concat([])); | ||||||
|           weightPerSecond: this.handleNewMempoolData(mempoolStats.concat([])), |           return { | ||||||
|         }; |             mempool: mempoolStats, | ||||||
|       }), |             weightPerSecond: this.handleNewMempoolData(mempoolStats.concat([])), | ||||||
|       share(), |           }; | ||||||
|     ); |         }), | ||||||
| 
 |         share(), | ||||||
|     this.transactionsWeightPerSecondOptions = { |       ); | ||||||
|         showArea: false, |  | ||||||
|         showLine: true, |  | ||||||
|         fullWidth: true, |  | ||||||
|         showPoint: false, |  | ||||||
|         low: 0, |  | ||||||
|         axisY: { |  | ||||||
|           offset: 40 |  | ||||||
|         }, |  | ||||||
|         axisX: { |  | ||||||
|           labelInterpolationFnc: (value: any, index: any) => index % 24 === 0 ? formatDate(value, 'HH:mm', this.locale) : null, |  | ||||||
|           offset: 20 |  | ||||||
|         }, |  | ||||||
|         plugins: [ |  | ||||||
|           Chartist.plugins.ctTargetLine({ |  | ||||||
|             value: 1667 |  | ||||||
|           }), |  | ||||||
|         ] |  | ||||||
|       }; |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   handleNewMempoolData(mempoolStats: OptimizedMempoolStats[]) { |   handleNewMempoolData(mempoolStats: OptimizedMempoolStats[]) { | ||||||
|  | |||||||
| @ -6,7 +6,7 @@ export interface OptimizedMempoolStats { | |||||||
|   vbytes_per_second: number; |   vbytes_per_second: number; | ||||||
|   total_fee: number; |   total_fee: number; | ||||||
|   mempool_byte_weight: number; |   mempool_byte_weight: number; | ||||||
|   vsizes: number[] | string[]; |   vsizes: number[]; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| interface Ancestor { | interface Ancestor { | ||||||
|  | |||||||
| @ -235,7 +235,7 @@ body { | |||||||
|   color: #dc3545; |   color: #dc3545; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .yellow-color {  | .yellow-color { | ||||||
|   color: #ffd800; |   color: #ffd800; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -255,168 +255,304 @@ html:lang(ru) .card-title { | |||||||
|   font-size: 0.9rem; |   font-size: 0.9rem; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* Chartist */ | /* MEMPOOL CHARTS - start */ | ||||||
| $ct-series-names: (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z); |  | ||||||
| $ct-series-colors: ( |  | ||||||
|   #D81B60, |  | ||||||
|   #8E24AA, |  | ||||||
|   #5E35B1, |  | ||||||
|   #3949AB, |  | ||||||
|   #1E88E5, |  | ||||||
|   #039BE5, |  | ||||||
|   #00ACC1, |  | ||||||
|   #00897B, |  | ||||||
|   #43A047, |  | ||||||
|   #7CB342, |  | ||||||
|   #C0CA33, |  | ||||||
|   #FDD835, |  | ||||||
|   #FFB300, |  | ||||||
|   #FB8C00, |  | ||||||
|   #F4511E, |  | ||||||
|   #6D4C41, |  | ||||||
|   #757575, |  | ||||||
|   #546E7A, |  | ||||||
|   #b71c1c, |  | ||||||
|   #880E4F, |  | ||||||
|   #4A148C, |  | ||||||
|   #311B92, |  | ||||||
|   #1A237E, |  | ||||||
|   #0D47A1, |  | ||||||
|   #01579B, |  | ||||||
|   #006064, |  | ||||||
|   #004D40, |  | ||||||
|   #1B5E20, |  | ||||||
|   #33691E, |  | ||||||
|   #827717, |  | ||||||
|   #F57F17, |  | ||||||
|   #FF6F00, |  | ||||||
|   #E65100, |  | ||||||
|   #BF360C, |  | ||||||
|   #3E2723, |  | ||||||
|   #212121, |  | ||||||
|   #263238, |  | ||||||
|   #a748ca, |  | ||||||
|   #6188e2, |  | ||||||
|   #a748ca, |  | ||||||
|   #6188e2, |  | ||||||
| ); |  | ||||||
| 
 | 
 | ||||||
| @import "../node_modules/@mempool/chartist/dist/scss/chartist.scss"; | .mempool-wrapper-tooltip-chart { | ||||||
| 
 |   height: 250px; | ||||||
| .ct-bar-label { |  | ||||||
|   font-size: 20px; |  | ||||||
|   font-weight: bold; |  | ||||||
|   fill: #fff; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .ct-target-line { | .echarts { | ||||||
|   stroke: #f5f5f5; |   height: 100%; | ||||||
|   stroke-width: 3px; |   min-height: 180px; | ||||||
|   stroke-dasharray: 7px; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .ct-area { | .tx-wrapper-tooltip-chart, | ||||||
|   stroke: none; | .fees-wrapper-tooltip-chart { | ||||||
|   fill-opacity: 0.9; |   background: rgba(#11131f, 0.95); | ||||||
| } |   border-radius: 4px; | ||||||
| 
 |   box-shadow: 1px 1px 10px rgba(0,0,0,0.5); | ||||||
| .ct-label { |   color: #b1b1b1; | ||||||
|     fill: rgba(255, 255, 255, 0.4); |   display: flex; | ||||||
|     color: rgba(255, 255, 255, 0.4); |   flex-direction: column; | ||||||
| } |   justify-content: space-between; | ||||||
| 
 |   padding: 10px 15px; | ||||||
| .ct-point-label { |   text-align: left; | ||||||
|   fill: rgba(255, 255, 255, 1); |   width: 200px; | ||||||
|   color: rgba(255, 255, 255, 1); |   thead { | ||||||
|   font-size: 14px; |     th { | ||||||
| } |       font-size: 9px; | ||||||
| 
 |       color: #b1b1b1; | ||||||
| .ct-grid { |       text-align: right; | ||||||
|     stroke: rgba(255, 255, 255, 0.2); |       &:first-child { | ||||||
| } |         text-align: left; | ||||||
| 
 |         left: -1px; | ||||||
| /* LEGEND */ |         position: relative; | ||||||
| 
 |  | ||||||
| .ct-legend { |  | ||||||
|   position: absolute; |  | ||||||
|   z-index: 10; |  | ||||||
|   left: 0px; |  | ||||||
|   list-style: none; |  | ||||||
|   font-size: 13px; |  | ||||||
|   padding: 0px 0px 0px 30px; |  | ||||||
|   top: 90px; |  | ||||||
| 
 |  | ||||||
|   li { |  | ||||||
|       position: relative; |  | ||||||
|       padding-left: 23px; |  | ||||||
|       margin-bottom: 0px; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|   li:before { |  | ||||||
|       width: 12px; |  | ||||||
|       height: 12px; |  | ||||||
|       position: absolute; |  | ||||||
|       left: 0; |  | ||||||
|       bottom: 3px; |  | ||||||
|       content: ''; |  | ||||||
|       border: 3px solid transparent; |  | ||||||
|       border-radius: 2px; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   li.inactive:before { |  | ||||||
|       background: transparent; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   &.ct-legend-inside { |  | ||||||
|       position: absolute; |  | ||||||
|       top: 0; |  | ||||||
|       right: 0; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   @for $i from 0 to length($ct-series-colors) { |  | ||||||
|       .ct-series-#{$i}:before { |  | ||||||
|           background-color: nth($ct-series-colors, $i + 1); |  | ||||||
|           border-color: nth($ct-series-colors, $i + 1); |  | ||||||
|       } |       } | ||||||
|  |       &:nth-child(4){ | ||||||
|  |         display: none; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   .title { | ||||||
|  |     font-size: 12px; | ||||||
|  |     font-weight: 700; | ||||||
|  |     margin-bottom: 2px; | ||||||
|  |     color: #fff; | ||||||
|  |     .total-value { | ||||||
|  |       float: right; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   .active { | ||||||
|  |     color: yellow !important; | ||||||
|  |     .value, | ||||||
|  |     .total-partial { | ||||||
|  |       color: yellow !important; | ||||||
|  |       .symbol { | ||||||
|  |         color: yellow !important; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   .item { | ||||||
|  |     line-height: 0.8; | ||||||
|  |     .indicator-container { | ||||||
|  |       .indicator { | ||||||
|  |         display: inline-block; | ||||||
|  |         margin-right: 5px; | ||||||
|  |         border-radius: 2px; | ||||||
|  |         margin-top: 5px; | ||||||
|  |         width: 9px; | ||||||
|  |         height: 9px; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     .value { | ||||||
|  |       text-align: right; | ||||||
|  |       .symbol { | ||||||
|  |         color: #7e7e7e; | ||||||
|  |         font-size: 9px !important; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     .symbol { | ||||||
|  |       font-size: 9px; | ||||||
|  |     } | ||||||
|  |     .total-partial { | ||||||
|  |       font-size: 10px; | ||||||
|  |       width: 58px; | ||||||
|  |       text-align: right; | ||||||
|  |     } | ||||||
|  |     .total-percentage-bar { | ||||||
|  |       padding-left: 8px; | ||||||
|  |     } | ||||||
|  |     .total-progress-percentage { | ||||||
|  |       width: 45px; | ||||||
|  |       height: 5px; | ||||||
|  |       text-align: right; | ||||||
|  |       display: none; | ||||||
|  |     } | ||||||
|  |     .total-progress-sum { | ||||||
|  |       width: 58px; | ||||||
|  |       text-align: right; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   .total-label { | ||||||
|  |     width: 100%; | ||||||
|  |     text-align: left; | ||||||
|  |     color: #fff; | ||||||
|  |     margin-top: 5px; | ||||||
|  |     font-size: 14px; | ||||||
|  |     span { | ||||||
|  |       float: right; | ||||||
|  |     } | ||||||
|  |     .symbol { | ||||||
|  |       margin-left: 3px; | ||||||
|  |       font-size: 9px; | ||||||
|  |       position: relative; | ||||||
|  |       top: 2px; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   thead { | ||||||
|  |     th { | ||||||
|  |       font-size: 9px; | ||||||
|  |       color: #b1b1b1; | ||||||
|  |       text-align: right; | ||||||
|  |       &:first-child { | ||||||
|  |         text-align: left; | ||||||
|  |         left: -1px; | ||||||
|  |         position: relative; | ||||||
|  |       } | ||||||
|  |       &:nth-child(4){ | ||||||
|  |         display: none; | ||||||
|  |       } | ||||||
|  |       &:nth-child(5){ | ||||||
|  |         display: none; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   .total-percentage-bar { | ||||||
|  |     margin: auto; | ||||||
|  |     width: 35px; | ||||||
|  |     position: relative; | ||||||
|  |     span { | ||||||
|  |       display: block; | ||||||
|  |       background: #282d47; | ||||||
|  |       height: 5px; | ||||||
|  |       border-radius: 2px; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   .total-parcial-active { | ||||||
|  |     text-align: right; | ||||||
|  |     margin: 5px auto 5px; | ||||||
|  |     padding-left: 0px; | ||||||
|  |     span { | ||||||
|  |       font-size: 10px; | ||||||
|  |     } | ||||||
|  |     .symbol { | ||||||
|  |       font-size: 9px; | ||||||
|  |     } | ||||||
|  |     .total-percentage-bar { | ||||||
|  |       width: 100%; | ||||||
|  |       span { | ||||||
|  |         transition: 1000 all ease-in-out; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     .progress-percentage { | ||||||
|  |       float: left; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .tx-wrapper-tooltip-chart { | ||||||
|  |   width: 115px; | ||||||
|  |   .item { | ||||||
|  |     display: flex; | ||||||
|  |   } | ||||||
|  |   .value { | ||||||
|  |     margin-top: 5px; | ||||||
|  |   } | ||||||
|  |   .indicator-container { | ||||||
|  |     border-radius: 2px; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .fee-distribution-chart { | ||||||
|  |   height: 250px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .fees-wrapper-tooltip-chart { | ||||||
|  |   .item { | ||||||
|  |     font-size: 9px; | ||||||
|  |     line-height: 0.8; | ||||||
|  |     margin: 0px; | ||||||
|  |   } | ||||||
|  |   .indicator { | ||||||
|  |     margin-right: 5px !important; | ||||||
|  |     border-radius: 1px !important; | ||||||
|  |     margin-top: 0px !important; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .fees-wrapper-tooltip-chart-advanced, | ||||||
|  | .tx-wrapper-tooltip-chart-advanced { | ||||||
|  |   background: rgba(#1d1f31, 0.98); | ||||||
|  |   width: 250px; | ||||||
|  | 
 | ||||||
|  |   thead { | ||||||
|  |     th { | ||||||
|  |       &:nth-child(4){ | ||||||
|  |         display: table-cell; | ||||||
|  |       } | ||||||
|  |       &:nth-child(5){ | ||||||
|  |         display: table-cell; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   .title { | ||||||
|  |     font-size: 15px; | ||||||
|  |     margin-bottom: 5px; | ||||||
|  |   } | ||||||
|  |   .item { | ||||||
|  |     line-height: 1; | ||||||
|  |     .value { | ||||||
|  |       width: 60px; | ||||||
|  |       .symbol { | ||||||
|  |         font-size: 10px !important; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     .total-partial { | ||||||
|  |       font-size: 10px; | ||||||
|  |       width: 58px; | ||||||
|  |       text-align: right; | ||||||
|  |     } | ||||||
|  |     .total-progress-percentage { | ||||||
|  |       width: 65px; | ||||||
|  |       height: 4px; | ||||||
|  |       padding: 0px 5px; | ||||||
|  |       display: table-cell !important; | ||||||
|  |       border-radius: 4px; | ||||||
|  |     } | ||||||
|  |     .total-progress-sum { | ||||||
|  |       width: 65px; | ||||||
|  |       height: 4px; | ||||||
|  |       padding: 0px 5px; | ||||||
|  |       border-radius: 4px; | ||||||
|  |     } | ||||||
|  |     .total-progress-percentage-bar, | ||||||
|  |     .total-progress-sum-bar { | ||||||
|  |       width: 35px; | ||||||
|  |       height: 4px; | ||||||
|  |       div { | ||||||
|  |         width: 100%; | ||||||
|  |         border-radius: 4px; | ||||||
|  |         display: block; | ||||||
|  |         background: #29324c94; | ||||||
|  |       } | ||||||
|  |       span { | ||||||
|  |         height: 4px; | ||||||
|  |         border-radius: 4px; | ||||||
|  |         display: block; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   .total-label { | ||||||
|  |     margin-top: 5px; | ||||||
|  |     font-size: 14px; | ||||||
|  |     span { | ||||||
|  |       float: right; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   .total-parcial-active { | ||||||
|  |     text-align: right; | ||||||
|  |     margin: 5px auto 5px; | ||||||
|  |     span { | ||||||
|  |       font-size: 10px; | ||||||
|  |     } | ||||||
|  |     .total-percentage-bar { | ||||||
|  |       width: 100%; | ||||||
|  |       left: 0; | ||||||
|  |       span { | ||||||
|  |         transition: 1000 all ease-in-out; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .tx-wrapper-tooltip-chart-advanced { | ||||||
|  |   width: 115px; | ||||||
|  |   .indicator-container { | ||||||
|  |     .indicator { | ||||||
|  |       margin-right: 5px; | ||||||
|  |       border-radius: 0px; | ||||||
|  |       margin-top: 5px; | ||||||
|  |       width: 9px; | ||||||
|  |       height: 9px; | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| .chartist-tooltip { | /* MEMPOOL CHARTS - end */ | ||||||
|   position: absolute; |  | ||||||
|   display: inline-block; |  | ||||||
|   opacity: 0; |  | ||||||
|   min-width: 5em; |  | ||||||
|   padding: .5em; |  | ||||||
|   background: #F4C63D; |  | ||||||
|   color: #453D3F; |  | ||||||
|   font-family: Oxygen,Helvetica,Arial,sans-serif; |  | ||||||
|   font-weight: 700; |  | ||||||
|   text-align: center; |  | ||||||
|   pointer-events: none; |  | ||||||
|   z-index: 1; |  | ||||||
|   -webkit-transition: opacity .2s linear; |  | ||||||
|   -moz-transition: opacity .2s linear; |  | ||||||
|   -o-transition: opacity .2s linear; |  | ||||||
|   transition: opacity .2s linear; } |  | ||||||
|   .chartist-tooltip:before { |  | ||||||
|     content: ""; |  | ||||||
|     position: absolute; |  | ||||||
|     top: 100%; |  | ||||||
|     left: 50%; |  | ||||||
|     width: 0; |  | ||||||
|     height: 0; |  | ||||||
|     margin-left: -15px; |  | ||||||
|     border: 15px solid transparent; |  | ||||||
|     border-top-color: #F4C63D; } |  | ||||||
|   .chartist-tooltip.tooltip-show { |  | ||||||
|     opacity: 1; } |  | ||||||
| 
 | 
 | ||||||
| .ct-area, .ct-line { | .grow { | ||||||
|   pointer-events: none; } |   flex-grow: 1; | ||||||
| 
 |  | ||||||
| .ct-bar { |  | ||||||
|   stroke-width: 1px; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| hr { | hr { | ||||||
| @ -639,7 +775,7 @@ th { | |||||||
|   .card { |   .card { | ||||||
|     background-color: transparent; |     background-color: transparent; | ||||||
|     padding: 0; |     padding: 0; | ||||||
|    | 
 | ||||||
|     button { |     button { | ||||||
|       text-align: left; |       text-align: left; | ||||||
|       display: block; |       display: block; | ||||||
| @ -653,17 +789,17 @@ th { | |||||||
|         box-shadow: none; |         box-shadow: none; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|    | 
 | ||||||
|     .card-header { |     .card-header { | ||||||
|       padding: 0; |       padding: 0; | ||||||
|     } |     } | ||||||
|    | 
 | ||||||
|     .collapsed{ |     .collapsed{ | ||||||
|       background-color: #2d3348; |       background-color: #2d3348; | ||||||
|       color: #1bd8f4; |       color: #1bd8f4; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|    | 
 | ||||||
|   .subtitle { |   .subtitle { | ||||||
|     font-weight: bold; |     font-weight: bold; | ||||||
|     margin-bottom: 3px; |     margin-bottom: 3px; | ||||||
| @ -675,7 +811,6 @@ th { | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| .pagination-container { | .pagination-container { | ||||||
|    |  | ||||||
|   display: inline-block; |   display: inline-block; | ||||||
|   width: 100%; |   width: 100%; | ||||||
|   justify-content: space-between; |   justify-content: space-between; | ||||||
| @ -698,4 +833,4 @@ th { | |||||||
|   .tooltip.show { |   .tooltip.show { | ||||||
|     width: 220px; |     width: 220px; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user