Scrolling & Range Selectors (Angular)

Viewing large charts can be difficult in compact user interfaces. FlexChart allows the use of AxisScrollbar to give you an option of adding scrollbar to the axis of the chart.

This sample allows you to view the chart with the scrollbars on X and Y axis of the chart.

Learn about FlexChart | Scrolling and Range Selector Documentation | FlexChart API Reference

This example uses Angular.

import 'bootstrap.css'; import '@mescius/wijmo.styles/wijmo.css'; import './styles.css'; // import '@angular/compiler'; import { Component, Inject, enableProdMode, NgModule, AfterViewInit, ViewChild } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { BrowserModule } from '@angular/platform-browser'; import { WjChartModule } from '@mescius/wijmo.angular2.chart'; import { DataService } from './app.data'; import { AxisScrollbar } from './AxisScrollbar'; import * as wjChart from '@mescius/wijmo.chart'; // @Component({ selector: 'app-component', templateUrl: 'src/app.component.html' }) export class AppComponent implements AfterViewInit { data: any[]; @ViewChild('theChart', { static: true }) theChart: wjChart.FlexChart; // constructor(@Inject(DataService) private dataService: DataService) { this.data = dataService.getData(); } // ngAfterViewInit() { let axisXScrollbar = new AxisScrollbar(this.theChart.axisX, { minScale: 0.02 }); let axisYScrollbar = new AxisScrollbar(this.theChart.axisY, { buttonsVisible: false, minScale: 0.05 }); } } // @NgModule({ imports: [WjChartModule, BrowserModule], declarations: [AppComponent], providers: [DataService], bootstrap: [AppComponent] }) export class AppModule { } // enableProdMode(); // Bootstrap application with hash style navigation and global services. platformBrowserDynamic().bootstrapModule(AppModule);
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>MESCIUS Wijmo FlexChart Scrolling & Range Selectors</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- Polyfills --> <script src="node_modules/core-js/client/shim.min.js"></script> <script src="node_modules/zone.js/fesm2015/zone.min.js"></script> <!-- SystemJS --> <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.21.5/system.src.js" integrity="sha512-skZbMyvYdNoZfLmiGn5ii6KmklM82rYX2uWctBhzaXPxJgiv4XBwJnFGr5k8s+6tE1pcR1nuTKghozJHyzMcoA==" crossorigin="anonymous"></script> <script src="systemjs.config.js"></script> <script> // workaround to load 'rxjs/operators' from the rxjs bundle System.import('rxjs').then(function (m) { System.set(SystemJS.resolveSync('rxjs/operators'), System.newModule(m.operators)); System.import('./src/app.component'); }); </script> </head> <body> <app-component></app-component> </body> </html>
<div class="container-fluid"> <div class="form-group"> <wj-flex-chart #theChart tooltipContent="" [itemsSource]="data" chartType="Line" bindingX="date" plotMargin="NaN NaN NaN 80"> <wj-flex-chart-axis wjProperty="axisX" [axisLine]="false"></wj-flex-chart-axis> <wj-flex-chart-series name="Data" binding="yval"></wj-flex-chart-series> </wj-flex-chart> </div> </div>
import { Injectable } from '@angular/core'; // @Injectable() export class DataService { getData() { let dataCount = 3000; let data = []; for (var i = 0; i < dataCount; i++) { var mod = Math.floor(i / 10) % 10; data.push({ date: new Date(2005, 0, i), yval: mod == 0 ? null : Math.random() * 100 }); } return data; } }
.wj-flexchart { margin: 0px; padding: 0px; border-bottom: 0px; touch-action: none; -ms-touch-action: none; padding-bottom: 40px; } body { margin-bottom: 24pt; } /**** Axis Scrollbar Css *****/ .wj-flexchart .wj-axis-scrollbar-container { position: relative; } .wj-flexchart .wj-axis-scrollbar-container .wj-chart-rangeslider { border: 1px solid #EAEAEA; background-color: #EAEAEA; border-bottom-left-radius: 6px; border-bottom-right-radius: 6px; border-top-left-radius: 6px; border-top-right-radius: 6px; } .wj-flexchart .wj-axis-scrollbar-container .wj-chart-hrangeslider { height: 20px; } .wj-flexchart .wj-axis-scrollbar-container .wj-chart-vrangeslider { width: 20px; }
import * as core from '@mescius/wijmo'; import * as chart from '@mescius/wijmo.chart'; import * as interaction from '@mescius/wijmo.chart.interaction'; 'use strict'; /** * The @see:AxisScrollbar control displays a scrollbar that allows the user to * choose the range of Axis. */ export class AxisScrollbar { // fields private _isVisible = true; private _min: number = null; private _max: number = null; private _buttonsVisible: boolean = true; //private _minScale: number = 0.01; private _minScale: number = 0; // host private _chart: chart.FlexChartCore = null; private _axis: chart.Axis = null; private _rangeSlider: interaction._RangeSlider = null; // elements private _slbarContainer: HTMLElement = null; private _isXAxis: boolean = true; private _chartRefreshDelay: any = null; /** * Initializes a new instance of the @see:AxisScrollbar control. * * @param axis The axis that displays scrollbar. * @param options A JavaScript object containing initialization data for the control. */ constructor(axis: chart.Axis, options?: any) { if (!chart) { core.assert(false, 'The Axis cannot be null.'); } this._axis = axis; this._chart = axis._chart; this._isXAxis = this._axis.axisType === chart.AxisType.X; core.copy(this, options); this._createScrollbar(); } /** * Gets or sets the decrease button and increase button is visible or not. */ get buttonsVisible(): boolean { return this._buttonsVisible; } set buttonsVisible(value: boolean) { if (value !== this._buttonsVisible) { this._buttonsVisible = core.asBoolean(value); if (!this._rangeSlider) { return; } this._rangeSlider.buttonsVisible = this._buttonsVisible; } } /** * Gets or sets the visibility of the Axis scrollbar. */ get isVisible(): boolean { return this._isVisible; } set isVisible(value: boolean) { if (value != this._isVisible) { this._isVisible = core.asBoolean(value); if (!this._rangeSlider) { return; } this._rangeSlider.isVisible = value; } } set minPos(value: number) { if (value < 0 && value > 1 && value > this._rangeSlider._maxPos) { return; } this._rangeSlider._minPos = value; this._updateAxisRange(); } set maxPos(value: number) { if (value < 0 && value > 1 && value < this._rangeSlider._minPos) { return; } this._rangeSlider._maxPos = value; this._updateAxisRange(); } /** * Gets or sets the minimum range scale of the Axis scrollbar. * The minimum range scale should be greater than zero. */ get minScale(): number { return this._minScale; } set minScale(value: number) { if (value > 0 && value != this._minScale) { this._minScale = core.asNumber(value); if (!this._rangeSlider) { return; } this._rangeSlider.minScale = value; } } /** * Removes the range selector from the chart. */ remove() { if (this._slbarContainer) { this._chart.hostElement.removeChild(this._slbarContainer); this._switchEvent(false); this._slbarContainer = null; } } private _createScrollbar() { var chart = this._chart, chartHostEle = chart.hostElement; this._slbarContainer = core.createElement('<div class="wj-axis-scrollbar-container"></div>'); this._rangeSlider = new interaction._RangeSlider(this._slbarContainer, true, // has space click function true, // has dec and inc buttons { //options settings buttonsVisible: this._buttonsVisible, isHorizontal: this._isXAxis, isVisible: this._isVisible, minScale: this._minScale, seamless: true }); chartHostEle.appendChild(this._slbarContainer); this._switchEvent(true); } private _switchEvent(isOn: boolean) { var eventListener = isOn ? 'addEventListener' : 'removeEventListener', eventHandler = isOn ? 'addHandler' : 'removeHandler'; if (this._chart.hostElement) { this._chart.rendered[eventHandler](this._refresh, this); this._rangeSlider.rangeChanged[eventHandler](this._updateAxisRange, this); this._rangeSlider.rangeChanging[eventHandler](this._updatingAxisRange, this); } } private _refresh() { var chartHostEle = this._chart.hostElement, rangeSlider = this._rangeSlider, pa, pOffset, plotBox, axisRect = this._axis._axrect, axisEle, axisOffset, isBottom, isLeft, rsWidth, rOffset = core.getElementRect(this._slbarContainer); if (rangeSlider._isSliding) { return; } //init the scrollbar range if (this._min === null) { this._min = core.isDate(this._axis.actualMin) ? this._axis.actualMin.valueOf() : this._axis.actualMin; } if (this._max === null) { this._max = core.isDate(this._axis.actualMax) ? this._axis.actualMax.valueOf() : this._axis.actualMax; } pa = <SVGGElement>chartHostEle.querySelector('.' + chart.FlexChart._CSS_PLOT_AREA); pOffset = core.getElementRect(pa); plotBox = pa.getBBox(); //TODO: get the axis element when has multiple axes axisEle = chartHostEle.querySelector(this._isXAxis ? '.wj-axis-x' : '.wj-axis-y'); axisOffset = core.getElementRect(axisEle); if (axisOffset.height === 0) { return; } if (this._isXAxis) { isBottom = this._axis.position === chart.Position.Bottom; rangeSlider._refresh({ left: plotBox.x, top: isBottom ? axisOffset.top + axisOffset.height - rOffset.top : axisOffset.top - rOffset.top - axisOffset.height, width: plotBox.width }); } else { isLeft = this._axis.position === chart.Position.Left; rsWidth = rangeSlider._handleWidth; rangeSlider._refresh({ left: isLeft ? axisOffset.left - rOffset.left - rsWidth : axisOffset.left - rOffset.left + pOffset.width + this._axis._axrect.width, top: pOffset.top - rOffset.top, height: plotBox.height }); } } private _updateAxisRange() { var chart, axis, range, rangeSlider = this._rangeSlider; chart = this._chart; axis = this._axis; range = this._max - this._min; if (axis.reversed) { axis.min = this._max - rangeSlider._maxPos * range; axis.max = this._max - rangeSlider._minPos * range; } else { axis.min = this._min + rangeSlider._minPos * range; axis.max = this._min + rangeSlider._maxPos * range; } chart.invalidate(); } private _updatingAxisRange() { var self = this; this._chartRefreshDelay = window.setTimeout(function () { if (self._chartRefreshDelay) { clearTimeout(self._chartRefreshDelay); self._chartRefreshDelay = null; } self._updateAxisRange(); }, 200); } }
(function (global) { SystemJS.config({ transpiler: './plugin-typescript.js', typescriptOptions: { "target": "ES2022", "module": "system", "emitDecoratorMetadata": true, "experimentalDecorators": true, }, baseURL: 'node_modules/', meta: { 'typescript': { "exports": "ts" }, '*.css': { loader: 'systemjs-plugin-css' } }, paths: { // paths serve as alias 'npm:': '' }, packageConfigPaths: [ '/node_modules/*/package.json', "/node_modules/@angular/*/package.json", "/node_modules/@mescius/*/package.json" ], map: { 'core-js': 'https://cdn.jsdelivr.net/npm/core-js@2.6.12/client/shim.min.js', 'typescript': 'https://cdnjs.cloudflare.com/ajax/libs/typescript/5.2.2/typescript.min.js', "rxjs": "https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.8.1/rxjs.umd.min.js", 'systemjs-plugin-css': 'https://cdn.jsdelivr.net/npm/systemjs-plugin-css@0.1.37/css.js', '@mescius/wijmo': 'npm:@mescius/wijmo/index.js', '@mescius/wijmo.input': 'npm:@mescius/wijmo.input/index.js', '@mescius/wijmo.styles': 'npm:@mescius/wijmo.styles', '@mescius/wijmo.cultures': 'npm:@mescius/wijmo.cultures', '@mescius/wijmo.chart': 'npm:@mescius/wijmo.chart/index.js', '@mescius/wijmo.chart.analytics': 'npm:@mescius/wijmo.chart.analytics/index.js', '@mescius/wijmo.chart.animation': 'npm:@mescius/wijmo.chart.animation/index.js', '@mescius/wijmo.chart.annotation': 'npm:@mescius/wijmo.chart.annotation/index.js', '@mescius/wijmo.chart.finance': 'npm:@mescius/wijmo.chart.finance/index.js', '@mescius/wijmo.chart.finance.analytics': 'npm:@mescius/wijmo.chart.finance.analytics/index.js', '@mescius/wijmo.chart.hierarchical': 'npm:@mescius/wijmo.chart.hierarchical/index.js', '@mescius/wijmo.chart.interaction': 'npm:@mescius/wijmo.chart.interaction/index.js', '@mescius/wijmo.chart.radar': 'npm:@mescius/wijmo.chart.radar/index.js', '@mescius/wijmo.chart.render': 'npm:@mescius/wijmo.chart.render/index.js', '@mescius/wijmo.chart.webgl': 'npm:@mescius/wijmo.chart.webgl/index.js', '@mescius/wijmo.chart.map': 'npm:@mescius/wijmo.chart.map/index.js', '@mescius/wijmo.gauge': 'npm:@mescius/wijmo.gauge/index.js', '@mescius/wijmo.grid': 'npm:@mescius/wijmo.grid/index.js', '@mescius/wijmo.grid.detail': 'npm:@mescius/wijmo.grid.detail/index.js', '@mescius/wijmo.grid.filter': 'npm:@mescius/wijmo.grid.filter/index.js', '@mescius/wijmo.grid.search': 'npm:@mescius/wijmo.grid.search/index.js', '@mescius/wijmo.grid.grouppanel': 'npm:@mescius/wijmo.grid.grouppanel/index.js', '@mescius/wijmo.grid.multirow': 'npm:@mescius/wijmo.grid.multirow/index.js', '@mescius/wijmo.grid.transposed': 'npm:@mescius/wijmo.grid.transposed/index.js', '@mescius/wijmo.grid.transposedmultirow': 'npm:@mescius/wijmo.grid.transposedmultirow/index.js', '@mescius/wijmo.grid.pdf': 'npm:@mescius/wijmo.grid.pdf/index.js', '@mescius/wijmo.grid.sheet': 'npm:@mescius/wijmo.grid.sheet/index.js', '@mescius/wijmo.grid.xlsx': 'npm:@mescius/wijmo.grid.xlsx/index.js', '@mescius/wijmo.grid.selector': 'npm:@mescius/wijmo.grid.selector/index.js', '@mescius/wijmo.grid.cellmaker': 'npm:@mescius/wijmo.grid.cellmaker/index.js', '@mescius/wijmo.nav': 'npm:@mescius/wijmo.nav/index.js', '@mescius/wijmo.odata': 'npm:@mescius/wijmo.odata/index.js', '@mescius/wijmo.olap': 'npm:@mescius/wijmo.olap/index.js', '@mescius/wijmo.rest': 'npm:@mescius/wijmo.rest/index.js', '@mescius/wijmo.pdf': 'npm:@mescius/wijmo.pdf/index.js', '@mescius/wijmo.pdf.security': 'npm:@mescius/wijmo.pdf.security/index.js', '@mescius/wijmo.viewer': 'npm:@mescius/wijmo.viewer/index.js', '@mescius/wijmo.xlsx': 'npm:@mescius/wijmo.xlsx/index.js', '@mescius/wijmo.undo': 'npm:@mescius/wijmo.undo/index.js', '@mescius/wijmo.interop.grid': 'npm:@mescius/wijmo.interop.grid/index.js', '@mescius/wijmo.touch': 'npm:@mescius/wijmo.touch/index.js', '@mescius/wijmo.cloud': 'npm:@mescius/wijmo.cloud/index.js', '@mescius/wijmo.barcode': 'npm:@mescius/wijmo.barcode/index.js', '@mescius/wijmo.barcode.common': 'npm:@mescius/wijmo.barcode.common/index.js', '@mescius/wijmo.barcode.composite': 'npm:@mescius/wijmo.barcode.composite/index.js', '@mescius/wijmo.barcode.specialized': 'npm:@mescius/wijmo.barcode.specialized/index.js', "@mescius/wijmo.angular2.chart.analytics": "npm:@mescius/wijmo.angular2.chart.analytics/index.js", "@mescius/wijmo.angular2.chart.animation": "npm:@mescius/wijmo.angular2.chart.animation/index.js", "@mescius/wijmo.angular2.chart.annotation": "npm:@mescius/wijmo.angular2.chart.annotation/index.js", "@mescius/wijmo.angular2.chart.finance.analytics": "npm:@mescius/wijmo.angular2.chart.finance.analytics/index.js", "@mescius/wijmo.angular2.chart.finance": "npm:@mescius/wijmo.angular2.chart.finance/index.js", "@mescius/wijmo.angular2.chart.hierarchical": "npm:@mescius/wijmo.angular2.chart.hierarchical/index.js", "@mescius/wijmo.angular2.chart.interaction": "npm:@mescius/wijmo.angular2.chart.interaction/index.js", "@mescius/wijmo.angular2.chart.radar": "npm:@mescius/wijmo.angular2.chart.radar/index.js", '@mescius/wijmo.angular2.chart.map': 'npm:@mescius/wijmo.angular2.chart.map/index.js', "@mescius/wijmo.angular2.chart": "npm:@mescius/wijmo.angular2.chart/index.js", "@mescius/wijmo.angular2.core": "npm:@mescius/wijmo.angular2.core/index.js", "@mescius/wijmo.angular2.gauge": "npm:@mescius/wijmo.angular2.gauge/index.js", "@mescius/wijmo.angular2.grid.detail": "npm:@mescius/wijmo.angular2.grid.detail/index.js", "@mescius/wijmo.angular2.grid.filter": "npm:@mescius/wijmo.angular2.grid.filter/index.js", "@mescius/wijmo.angular2.grid.grouppanel": "npm:@mescius/wijmo.angular2.grid.grouppanel/index.js", "@mescius/wijmo.angular2.grid.search": "npm:@mescius/wijmo.angular2.grid.search/index.js", "@mescius/wijmo.angular2.grid.multirow": "npm:@mescius/wijmo.angular2.grid.multirow/index.js", "@mescius/wijmo.angular2.grid.sheet": "npm:@mescius/wijmo.angular2.grid.sheet/index.js", '@mescius/wijmo.angular2.grid.transposed': 'npm:@mescius/wijmo.angular2.grid.transposed/index.js', '@mescius/wijmo.angular2.grid.transposedmultirow': 'npm:@mescius/wijmo.angular2.grid.transposedmultirow/index.js', "@mescius/wijmo.angular2.grid": "npm:@mescius/wijmo.angular2.grid/index.js", "@mescius/wijmo.angular2.input": "npm:@mescius/wijmo.angular2.input/index.js", "@mescius/wijmo.angular2.olap": "npm:@mescius/wijmo.angular2.olap/index.js", "@mescius/wijmo.angular2.viewer": "npm:@mescius/wijmo.angular2.viewer/index.js", "@mescius/wijmo.angular2.nav": "npm:@mescius/wijmo.angular2.nav/index.js", "@mescius/wijmo.angular2.directivebase": "npm:@mescius/wijmo.angular2.directivebase/index.js", '@mescius/wijmo.angular2.barcode.common': 'npm:@mescius/wijmo.angular2.barcode.common/index.js', '@mescius/wijmo.angular2.barcode.composite': 'npm:@mescius/wijmo.angular2.barcode.composite/index.js', '@mescius/wijmo.angular2.barcode.specialized': 'npm:@mescius/wijmo.angular2.barcode.specialized/index.js', 'bootstrap.css': 'npm:bootstrap/dist/css/bootstrap.min.css', 'jszip': 'https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js', "@angular/common/http": "https://cdn.jsdelivr.net/npm/@angular/common@16.2.6/fesm2022/http.mjs", "@angular/core": "https://cdn.jsdelivr.net/npm/@angular/core@16.2.6/fesm2022/core.mjs", "@angular/platform-browser": "https://cdn.jsdelivr.net/npm/@angular/platform-browser@16.2.6/fesm2022/platform-browser.mjs", "@angular/common": "https://cdn.jsdelivr.net/npm/@angular/common@16.2.6/fesm2022/common.mjs", "@angular/compiler": "https://cdn.jsdelivr.net/npm/@angular/compiler@16.2.6/fesm2022/compiler.mjs", "@angular/forms": "https://cdn.jsdelivr.net/npm/@angular/forms@16.2.6/fesm2022/forms.mjs", "@angular/localize": "https://cdn.jsdelivr.net/npm/@angular/localize@16.2.6/fesm2022/localize.mjs", "@angular/platform-browser-dynamic": "https://cdn.jsdelivr.net/npm/@angular/platform-browser-dynamic@16.2.6/fesm2022/platform-browser-dynamic.mjs", }, // packages tells the System loader how to load when no filename and/or no extension packages: { "./src": { defaultExtension: 'ts' }, "node_modules": { defaultExtension: 'js' }, wijmo: { defaultExtension: 'js', } } }); })(this);