UNPKG

angular-google-charts

Version:

A wrapper for the Google Charts library written with Angular

893 lines (875 loc) 39.9 kB
import * as i1 from 'rxjs'; import { Subject, of, Observable, ReplaySubject, combineLatest, fromEvent } from 'rxjs'; import * as i0 from '@angular/core'; import { InjectionToken, inject, InjectFlags, LOCALE_ID, Injectable, Inject, Component, ChangeDetectionStrategy, EventEmitter, Input, Output, HostBinding, ContentChildren, Optional, NgModule } from '@angular/core'; import { mergeMap, map, switchMap, debounceTime } from 'rxjs/operators'; /// <reference path="./chart-editor.ts" /> class ChartEditorRef { constructor(editor) { this.editor = editor; this.doneSubject = new Subject(); this.addEventListeners(); } /** * Gets an observable that is notified when the dialog is saved. * Emits either the result if the dialog was saved or `null` if editing was cancelled. */ afterClosed() { return this.doneSubject.asObservable(); } /** * Stops editing the chart and closes the dialog. */ cancel() { this.editor.closeDialog(); } addEventListeners() { google.visualization.events.addOneTimeListener(this.editor, 'ok', () => { google.visualization.events.removeAllListeners(this.editor); const updatedChartWrapper = this.editor.getChartWrapper(); this.doneSubject.next(updatedChartWrapper); this.doneSubject.complete(); }); google.visualization.events.addOneTimeListener(this.editor, 'cancel', () => { google.visualization.events.removeAllListeners(this.editor); this.doneSubject.next(null); this.doneSubject.complete(); }); } } var ChartType; (function (ChartType) { ChartType["AnnotationChart"] = "AnnotationChart"; ChartType["AreaChart"] = "AreaChart"; ChartType["Bar"] = "Bar"; ChartType["BarChart"] = "BarChart"; ChartType["BubbleChart"] = "BubbleChart"; ChartType["Calendar"] = "Calendar"; ChartType["CandlestickChart"] = "CandlestickChart"; ChartType["ColumnChart"] = "ColumnChart"; ChartType["ComboChart"] = "ComboChart"; ChartType["PieChart"] = "PieChart"; ChartType["Gantt"] = "Gantt"; ChartType["Gauge"] = "Gauge"; ChartType["GeoChart"] = "GeoChart"; ChartType["Histogram"] = "Histogram"; ChartType["Line"] = "Line"; ChartType["LineChart"] = "LineChart"; ChartType["Map"] = "Map"; ChartType["OrgChart"] = "OrgChart"; ChartType["Sankey"] = "Sankey"; ChartType["Scatter"] = "Scatter"; ChartType["ScatterChart"] = "ScatterChart"; ChartType["SteppedAreaChart"] = "SteppedAreaChart"; ChartType["Table"] = "Table"; ChartType["Timeline"] = "Timeline"; ChartType["TreeMap"] = "TreeMap"; ChartType["WordTree"] = "WordTree"; })(ChartType || (ChartType = {})); const ChartTypesToPackages = { [ChartType.AnnotationChart]: 'annotationchart', [ChartType.AreaChart]: 'corechart', [ChartType.Bar]: 'bar', [ChartType.BarChart]: 'corechart', [ChartType.BubbleChart]: 'corechart', [ChartType.Calendar]: 'calendar', [ChartType.CandlestickChart]: 'corechart', [ChartType.ColumnChart]: 'corechart', [ChartType.ComboChart]: 'corechart', [ChartType.PieChart]: 'corechart', [ChartType.Gantt]: 'gantt', [ChartType.Gauge]: 'gauge', [ChartType.GeoChart]: 'geochart', [ChartType.Histogram]: 'corechart', [ChartType.Line]: 'line', [ChartType.LineChart]: 'corechart', [ChartType.Map]: 'map', [ChartType.OrgChart]: 'orgchart', [ChartType.Sankey]: 'sankey', [ChartType.Scatter]: 'scatter', [ChartType.ScatterChart]: 'corechart', [ChartType.SteppedAreaChart]: 'corechart', [ChartType.Table]: 'table', [ChartType.Timeline]: 'timeline', [ChartType.TreeMap]: 'treemap', [ChartType.WordTree]: 'wordtree' }; function getPackageForChart(type) { return ChartTypesToPackages[type]; } function getDefaultConfig() { return { version: 'current', safeMode: false }; } const GOOGLE_CHARTS_CONFIG = new InjectionToken('GOOGLE_CHARTS_CONFIG'); const GOOGLE_CHARTS_LAZY_CONFIG = new InjectionToken('GOOGLE_CHARTS_LAZY_CONFIG', { providedIn: 'root', factory: () => { const configFromModule = inject(GOOGLE_CHARTS_CONFIG, InjectFlags.Optional); return of({ ...getDefaultConfig(), ...(configFromModule || {}) }); } }); class ScriptLoaderService { constructor(zone, localeId, config$) { this.zone = zone; this.localeId = localeId; this.config$ = config$; this.scriptSource = 'https://www.gstatic.com/charts/loader.js'; this.scriptLoadSubject = new Subject(); } /** * Checks whether `google.charts` is available. * * If not, it can be loaded by calling `loadChartPackages`. * * @returns `true` if `google.charts` is available, `false` otherwise. */ isGoogleChartsAvailable() { if (typeof google === 'undefined' || typeof google.charts === 'undefined') { return false; } return true; } /** * Loads the Google Chart script and the provided chart packages. * Can be called multiple times to load more packages. * * When called without any arguments, this will just load the default package * containing the namespaces `google.charts` and `google.visualization` without any charts. * * @param packages The packages to load. * @returns A stream emitting as soon as the chart packages are loaded. */ loadChartPackages(...packages) { return this.loadGoogleCharts().pipe(mergeMap(() => this.config$), map(config => { return { ...getDefaultConfig(), ...(config || {}) }; }), switchMap((googleChartsConfig) => { return new Observable(observer => { const config = { packages, language: this.localeId, mapsApiKey: googleChartsConfig.mapsApiKey, safeMode: googleChartsConfig.safeMode }; google.charts.load(googleChartsConfig.version, config); google.charts.setOnLoadCallback(() => { this.zone.run(() => { observer.next(); observer.complete(); }); }); }); })); } /** * Loads the Google Charts script. After the script is loaded, `google.charts` is defined. * * @returns A stream emitting as soon as loading has completed. * If the google charts script is already loaded, the stream emits immediately. */ loadGoogleCharts() { if (this.isGoogleChartsAvailable()) { return of(undefined); } else if (!this.isLoadingGoogleCharts()) { const script = this.createGoogleChartsScript(); script.onload = () => { this.zone.run(() => { this.scriptLoadSubject.next(); this.scriptLoadSubject.complete(); }); }; script.onerror = () => { this.zone.run(() => { console.error('Failed to load the google charts script!'); this.scriptLoadSubject.error(new Error('Failed to load the google charts script!')); }); }; } return this.scriptLoadSubject.asObservable(); } isLoadingGoogleCharts() { return this.getGoogleChartsScript() != null; } getGoogleChartsScript() { const pageScripts = Array.from(document.getElementsByTagName('script')); return pageScripts.find(script => script.src === this.scriptSource); } createGoogleChartsScript() { const script = document.createElement('script'); script.type = 'text/javascript'; script.src = this.scriptSource; script.async = true; document.getElementsByTagName('head')[0].appendChild(script); return script; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ScriptLoaderService, deps: [{ token: i0.NgZone }, { token: LOCALE_ID }, { token: GOOGLE_CHARTS_LAZY_CONFIG }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ScriptLoaderService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ScriptLoaderService, decorators: [{ type: Injectable }], ctorParameters: function () { return [{ type: i0.NgZone }, { type: undefined, decorators: [{ type: Inject, args: [LOCALE_ID] }] }, { type: i1.Observable, decorators: [{ type: Inject, args: [GOOGLE_CHARTS_LAZY_CONFIG] }] }]; } }); /// <reference path="./chart-editor.ts" /> /// <reference path="./chart-editor.ts" /> class ChartEditorComponent { constructor(scriptLoaderService) { this.scriptLoaderService = scriptLoaderService; this.initializedSubject = new Subject(); } /** * Emits as soon as the chart editor is fully initialized. */ get initialized$() { return this.initializedSubject.asObservable(); } ngOnInit() { this.scriptLoaderService.loadChartPackages('charteditor').subscribe(() => { this.editor = new google.visualization.ChartEditor(); this.initializedSubject.next(this.editor); this.initializedSubject.complete(); }); } editChart(component, options) { if (!component.chartWrapper) { throw new Error('Chart wrapper is `undefined`. Please wait for the `initialized$` observable before trying to edit a chart.'); } if (!this.editor) { throw new Error('Chart editor is `undefined`. Please wait for the `initialized$` observable before trying to edit a chart.'); } const handle = new ChartEditorRef(this.editor); this.editor.openDialog(component.chartWrapper, options || {}); handle.afterClosed().subscribe(result => { if (result) { component.chartWrapper = result; } }); return handle; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ChartEditorComponent, deps: [{ token: ScriptLoaderService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: ChartEditorComponent, selector: "chart-editor", host: { classAttribute: "chart-editor" }, ngImport: i0, template: `<ng-content></ng-content>`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ChartEditorComponent, decorators: [{ type: Component, args: [{ selector: 'chart-editor', template: `<ng-content></ng-content>`, host: { class: 'chart-editor' }, changeDetection: ChangeDetectionStrategy.OnPush }] }], ctorParameters: function () { return [{ type: ScriptLoaderService }]; } }); class DataTableService { create(data, columns, formatters) { if (data == null) { return undefined; } let firstRowIsData = true; if (columns != null) { firstRowIsData = false; } const dataTable = google.visualization.arrayToDataTable(this.getDataAsTable(data, columns), firstRowIsData); if (formatters) { this.applyFormatters(dataTable, formatters); } return dataTable; } getDataAsTable(data, columns) { if (columns) { return [columns, ...data]; } else { return data; } } applyFormatters(dataTable, formatters) { for (const val of formatters) { val.formatter.format(dataTable, val.colIndex); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: DataTableService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: DataTableService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: DataTableService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); /** * Generates a random ID which can be used to uniquely identify an element. */ function generateRandomId() { // Math.random should be unique because of its seeding algorithm. // Convert it to base 36 (numbers + letters), and grab the first 9 characters // after the decimal. return '_' + Math.random().toString(36).substr(2, 9); } var FilterType; (function (FilterType) { FilterType["Category"] = "CategoryFilter"; FilterType["ChartRange"] = "ChartRangeFilter"; FilterType["DateRange"] = "DateRangeFilter"; FilterType["NumberRange"] = "NumberRangeFilter"; FilterType["String"] = "StringFilter"; })(FilterType || (FilterType = {})); class ControlWrapperComponent { constructor(loaderService) { this.loaderService = loaderService; /** * Emits when an error occurs when attempting to render the control. */ this.error = new EventEmitter(); /** * The control is ready to accept user interaction and for external method calls. * * Alternatively, you can listen for a ready event on the dashboard holding the control * and call control methods only after the event was fired. */ this.ready = new EventEmitter(); /** * Emits when the user interacts with the control, affecting its state. * For example, a `stateChange` event will be emitted whenever you move the thumbs of a range slider control. * * To retrieve an updated control state after the event fired, call `ControlWrapper.getState()`. */ this.stateChange = new EventEmitter(); /** * A generated id assigned to this components DOM element. */ this.id = generateRandomId(); this.wrapperReadySubject = new ReplaySubject(1); } /** * Emits after the `ControlWrapper` was created. */ get wrapperReady$() { return this.wrapperReadySubject.asObservable(); } get controlWrapper() { if (!this._controlWrapper) { throw new Error(`Cannot access the control wrapper before it being initialized.`); } return this._controlWrapper; } ngOnInit() { this.loaderService.loadChartPackages('controls').subscribe(() => { this.createControlWrapper(); }); } ngOnChanges(changes) { if (!this._controlWrapper) { return; } if (changes.type) { this._controlWrapper.setControlType(this.type); } if (changes.options) { this._controlWrapper.setOptions(this.options || {}); } if (changes.state) { this._controlWrapper.setState(this.state || {}); } } createControlWrapper() { this._controlWrapper = new google.visualization.ControlWrapper({ containerId: this.id, controlType: this.type, state: this.state, options: this.options }); this.addEventListeners(); this.wrapperReadySubject.next(this._controlWrapper); } addEventListeners() { google.visualization.events.removeAllListeners(this._controlWrapper); google.visualization.events.addListener(this._controlWrapper, 'ready', (event) => this.ready.emit(event)); google.visualization.events.addListener(this._controlWrapper, 'error', (event) => this.error.emit(event)); google.visualization.events.addListener(this._controlWrapper, 'statechange', (event) => this.stateChange.emit(event)); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ControlWrapperComponent, deps: [{ token: ScriptLoaderService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: ControlWrapperComponent, selector: "control-wrapper", inputs: { for: "for", type: "type", options: "options", state: "state" }, outputs: { error: "error", ready: "ready", stateChange: "stateChange" }, host: { properties: { "id": "this.id" }, classAttribute: "control-wrapper" }, exportAs: ["controlWrapper"], usesOnChanges: true, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ControlWrapperComponent, decorators: [{ type: Component, args: [{ selector: 'control-wrapper', template: '', host: { class: 'control-wrapper' }, exportAs: 'controlWrapper', changeDetection: ChangeDetectionStrategy.OnPush }] }], ctorParameters: function () { return [{ type: ScriptLoaderService }]; }, propDecorators: { for: [{ type: Input }], type: [{ type: Input }], options: [{ type: Input }], state: [{ type: Input }], error: [{ type: Output }], ready: [{ type: Output }], stateChange: [{ type: Output }], id: [{ type: HostBinding, args: ['id'] }] } }); class DashboardComponent { constructor(element, loaderService, dataTableService) { this.element = element; this.loaderService = loaderService; this.dataTableService = dataTableService; /** * The dashboard has completed drawing and is ready to accept changes. * * The ready event will also fire: * - after the completion of a dashboard refresh triggered by a user or programmatic interaction with one of the controls, * - after redrawing any chart on the dashboard. */ this.ready = new EventEmitter(); /** * Emits when an error occurs when attempting to render the dashboard. * One or more of the controls and charts that are part of the dashboard may have failed rendering. */ this.error = new EventEmitter(); this.initialized = false; } ngOnInit() { this.loaderService.loadChartPackages('controls').subscribe(() => { this.dataTable = this.dataTableService.create(this.data, this.columns, this.formatters); this.createDashboard(); this.initialized = true; }); } ngOnChanges(changes) { if (!this.initialized) { return; } if (changes.data || changes.columns || changes.formatters) { this.dataTable = this.dataTableService.create(this.data, this.columns, this.formatters); this.dashboard.draw(this.dataTable); } } createDashboard() { // TODO: This should happen in the control wrapper // However, I don't yet know how to do this because then `bind()` would get called multiple times // for the same control if something changes. This is not supported by google charts as far as I can tell // from their source code. const controlWrappersReady$ = this.controlWrappers.map(control => control.wrapperReady$); const chartsReady$ = this.controlWrappers .map(control => control.for) .map(charts => { if (Array.isArray(charts)) { // CombineLatest waits for all observables return combineLatest(charts.map(chart => chart.wrapperReady$)); } else { return charts.wrapperReady$; } }); // We have to wait for all chart wrappers and control wrappers to be initialized // before we can compose them together to create the dashboard combineLatest([...controlWrappersReady$, ...chartsReady$]).subscribe(() => { this.dashboard = new google.visualization.Dashboard(this.element.nativeElement); this.initializeBindings(); this.registerEvents(); this.dashboard.draw(this.dataTable); }); } registerEvents() { google.visualization.events.removeAllListeners(this.dashboard); const registerDashEvent = (object, eventName, callback) => { google.visualization.events.addListener(object, eventName, callback); }; registerDashEvent(this.dashboard, 'ready', () => this.ready.emit()); registerDashEvent(this.dashboard, 'error', (error) => this.error.emit(error)); } initializeBindings() { this.controlWrappers.forEach(control => { if (Array.isArray(control.for)) { const chartWrappers = control.for.map(chart => chart.chartWrapper); this.dashboard.bind(control.controlWrapper, chartWrappers); } else { this.dashboard.bind(control.controlWrapper, control.for.chartWrapper); } }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: DashboardComponent, deps: [{ token: i0.ElementRef }, { token: ScriptLoaderService }, { token: DataTableService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: DashboardComponent, selector: "dashboard", inputs: { data: "data", columns: "columns", formatters: "formatters" }, outputs: { ready: "ready", error: "error" }, host: { classAttribute: "dashboard" }, queries: [{ propertyName: "controlWrappers", predicate: ControlWrapperComponent }], exportAs: ["dashboard"], usesOnChanges: true, ngImport: i0, template: '<ng-content></ng-content>', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: DashboardComponent, decorators: [{ type: Component, args: [{ selector: 'dashboard', template: '<ng-content></ng-content>', changeDetection: ChangeDetectionStrategy.OnPush, exportAs: 'dashboard', host: { class: 'dashboard' } }] }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: ScriptLoaderService }, { type: DataTableService }]; }, propDecorators: { data: [{ type: Input }], columns: [{ type: Input }], formatters: [{ type: Input }], ready: [{ type: Output }], error: [{ type: Output }], controlWrappers: [{ type: ContentChildren, args: [ControlWrapperComponent] }] } }); class GoogleChartComponent { constructor(element, scriptLoaderService, dataTableService, dashboard) { this.element = element; this.scriptLoaderService = scriptLoaderService; this.dataTableService = dataTableService; this.dashboard = dashboard; /** * The chart-specific options. All options listen in the Google Charts documentation applying * to the chart type specified can be used here. */ this.options = {}; /** * If this is set to `true`, the chart will be redrawn if the browser window is resized. * Defaults to `false` and should only be used when specifying the width or height of the chart * in percent. * * Note that this can impact performance. */ this.dynamicResize = false; this.ready = new EventEmitter(); this.error = new EventEmitter(); this.select = new EventEmitter(); this.mouseover = new EventEmitter(); this.mouseleave = new EventEmitter(); this.wrapperReadySubject = new ReplaySubject(1); this.initialized = false; this.eventListeners = new Map(); } get chart() { return this.chartWrapper.getChart(); } get wrapperReady$() { return this.wrapperReadySubject.asObservable(); } get chartWrapper() { if (!this.wrapper) { throw new Error('Trying to access the chart wrapper before it was fully initialized'); } return this.wrapper; } set chartWrapper(wrapper) { this.wrapper = wrapper; this.drawChart(); } ngOnInit() { // We don't need to load any chart packages, the chart wrapper will handle this for us this.scriptLoaderService.loadChartPackages(getPackageForChart(this.type)).subscribe(() => { this.dataTable = this.dataTableService.create(this.data, this.columns, this.formatters); // Only ever create the wrapper once to allow animations to happen when something changes. this.wrapper = new google.visualization.ChartWrapper({ container: this.element.nativeElement, chartType: this.type, dataTable: this.dataTable, options: this.mergeOptions() }); this.registerChartEvents(); this.wrapperReadySubject.next(this.wrapper); this.initialized = true; this.drawChart(); }); } ngOnChanges(changes) { if (changes.dynamicResize) { this.updateResizeListener(); } if (this.initialized) { let shouldRedraw = false; if (changes.data || changes.columns || changes.formatters) { this.dataTable = this.dataTableService.create(this.data, this.columns, this.formatters); this.wrapper.setDataTable(this.dataTable); shouldRedraw = true; } if (changes.type) { this.wrapper.setChartType(this.type); shouldRedraw = true; } if (changes.options || changes.width || changes.height || changes.title) { this.wrapper.setOptions(this.mergeOptions()); shouldRedraw = true; } if (shouldRedraw) { this.drawChart(); } } } ngOnDestroy() { this.unsubscribeToResizeIfSubscribed(); } /** * For listening to events other than the most common ones (available via Output properties). * * Can be called after the chart emits that it's "ready". * * Returns a handle that can be used for `removeEventListener`. */ addEventListener(eventName, callback) { const handle = this.registerChartEvent(this.chart, eventName, callback); this.eventListeners.set(handle, { eventName, callback, handle }); return handle; } removeEventListener(handle) { const entry = this.eventListeners.get(handle); if (entry) { google.visualization.events.removeListener(entry.handle); this.eventListeners.delete(handle); } } updateResizeListener() { this.unsubscribeToResizeIfSubscribed(); if (this.dynamicResize) { this.resizeSubscription = fromEvent(window, 'resize', { passive: true }) .pipe(debounceTime(100)) .subscribe(() => { if (this.initialized) { this.drawChart(); } }); } } unsubscribeToResizeIfSubscribed() { if (this.resizeSubscription != null) { this.resizeSubscription.unsubscribe(); this.resizeSubscription = undefined; } } mergeOptions() { return { title: this.title, width: this.width, height: this.height, ...this.options }; } registerChartEvents() { google.visualization.events.removeAllListeners(this.wrapper); this.registerChartEvent(this.wrapper, 'ready', () => { // This could also be done by checking if we already subscribed to the events google.visualization.events.removeAllListeners(this.chart); this.registerChartEvent(this.chart, 'onmouseover', (event) => this.mouseover.emit(event)); this.registerChartEvent(this.chart, 'onmouseout', (event) => this.mouseleave.emit(event)); this.registerChartEvent(this.chart, 'select', () => { const selection = this.chart.getSelection(); this.select.emit({ selection }); }); this.eventListeners.forEach(x => (x.handle = this.registerChartEvent(this.chart, x.eventName, x.callback))); this.ready.emit({ chart: this.chart }); }); this.registerChartEvent(this.wrapper, 'error', (error) => this.error.emit(error)); } registerChartEvent(object, eventName, callback) { return google.visualization.events.addListener(object, eventName, callback); } drawChart() { if (this.dashboard != null) { // If this chart is part of a dashboard, the dashboard takes care of drawing return; } this.wrapper.draw(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: GoogleChartComponent, deps: [{ token: i0.ElementRef }, { token: ScriptLoaderService }, { token: DataTableService }, { token: DashboardComponent, optional: true }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: GoogleChartComponent, selector: "google-chart", inputs: { type: "type", data: "data", columns: "columns", title: "title", width: "width", height: "height", options: "options", formatters: "formatters", dynamicResize: "dynamicResize" }, outputs: { ready: "ready", error: "error", select: "select", mouseover: "mouseover", mouseleave: "mouseleave" }, host: { classAttribute: "google-chart" }, exportAs: ["googleChart"], usesOnChanges: true, ngImport: i0, template: '', isInline: true, styles: [":host{width:-moz-fit-content;width:fit-content;display:block}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: GoogleChartComponent, decorators: [{ type: Component, args: [{ selector: 'google-chart', template: '', host: { class: 'google-chart' }, exportAs: 'googleChart', changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{width:-moz-fit-content;width:fit-content;display:block}\n"] }] }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: ScriptLoaderService }, { type: DataTableService }, { type: DashboardComponent, decorators: [{ type: Optional }] }]; }, propDecorators: { type: [{ type: Input }], data: [{ type: Input }], columns: [{ type: Input }], title: [{ type: Input }], width: [{ type: Input }], height: [{ type: Input }], options: [{ type: Input }], formatters: [{ type: Input }], dynamicResize: [{ type: Input }], ready: [{ type: Output }], error: [{ type: Output }], select: [{ type: Output }], mouseover: [{ type: Output }], mouseleave: [{ type: Output }] } }); class ChartWrapperComponent { constructor(element, scriptLoaderService) { this.element = element; this.scriptLoaderService = scriptLoaderService; this.error = new EventEmitter(); this.ready = new EventEmitter(); this.select = new EventEmitter(); this.wrapperReadySubject = new ReplaySubject(1); this.initialized = false; } get chart() { return this.chartWrapper.getChart(); } get wrapperReady$() { return this.wrapperReadySubject.asObservable(); } get chartWrapper() { if (!this.wrapper) { throw new Error('Cannot access the chart wrapper before initialization.'); } return this.wrapper; } set chartWrapper(wrapper) { this.wrapper = wrapper; this.drawChart(); } ngOnInit() { // We don't need to load any chart packages, the chart wrapper will handle this else for us this.scriptLoaderService.loadChartPackages().subscribe(() => { if (!this.specs) { this.specs = {}; } const { containerId, container, ...specs } = this.specs; // Only ever create the wrapper once to allow animations to happen if something changes. this.wrapper = new google.visualization.ChartWrapper({ ...specs, container: this.element.nativeElement }); this.registerChartEvents(); this.wrapperReadySubject.next(this.wrapper); this.drawChart(); this.initialized = true; }); } ngOnChanges(changes) { if (!this.initialized) { return; } if (changes.specs) { this.updateChart(); this.drawChart(); } } updateChart() { if (!this.specs) { // When creating the wrapper with empty specs, the google charts library will show an error // If we don't do this, a javascript error will be thrown, which is not as visible to the user this.specs = {}; } // The typing here are not correct. These methods accept `undefined` as well. // That's why we have to cast to `any` this.wrapper.setChartType(this.specs.chartType); this.wrapper.setDataTable(this.specs.dataTable); this.wrapper.setDataSourceUrl(this.specs.dataSourceUrl); this.wrapper.setDataSourceUrl(this.specs.dataSourceUrl); this.wrapper.setQuery(this.specs.query); this.wrapper.setOptions(this.specs.options); this.wrapper.setRefreshInterval(this.specs.refreshInterval); this.wrapper.setView(this.specs.view); } drawChart() { if (this.wrapper) { this.wrapper.draw(); } } registerChartEvents() { google.visualization.events.removeAllListeners(this.wrapper); const registerChartEvent = (object, eventName, callback) => { google.visualization.events.addListener(object, eventName, callback); }; registerChartEvent(this.wrapper, 'ready', () => this.ready.emit({ chart: this.chart })); registerChartEvent(this.wrapper, 'error', (error) => this.error.emit(error)); registerChartEvent(this.wrapper, 'select', () => { const selection = this.chart.getSelection(); this.select.emit({ selection }); }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ChartWrapperComponent, deps: [{ token: i0.ElementRef }, { token: ScriptLoaderService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: ChartWrapperComponent, selector: "chart-wrapper", inputs: { specs: "specs" }, outputs: { error: "error", ready: "ready", select: "select" }, host: { classAttribute: "chart-wrapper" }, exportAs: ["chartWrapper"], usesOnChanges: true, ngImport: i0, template: '', isInline: true, styles: [":host{width:-moz-fit-content;width:fit-content;display:block}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ChartWrapperComponent, decorators: [{ type: Component, args: [{ selector: 'chart-wrapper', template: '', host: { class: 'chart-wrapper' }, exportAs: 'chartWrapper', changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{width:-moz-fit-content;width:fit-content;display:block}\n"] }] }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: ScriptLoaderService }]; }, propDecorators: { specs: [{ type: Input }], error: [{ type: Output }], ready: [{ type: Output }], select: [{ type: Output }] } }); class GoogleChartsModule { static forRoot(config = {}) { return { ngModule: GoogleChartsModule, providers: [{ provide: GOOGLE_CHARTS_CONFIG, useValue: config }] }; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: GoogleChartsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "16.2.12", ngImport: i0, type: GoogleChartsModule, declarations: [GoogleChartComponent, ChartWrapperComponent, DashboardComponent, ControlWrapperComponent, ChartEditorComponent], exports: [GoogleChartComponent, ChartWrapperComponent, DashboardComponent, ControlWrapperComponent, ChartEditorComponent] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: GoogleChartsModule, providers: [ScriptLoaderService] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: GoogleChartsModule, decorators: [{ type: NgModule, args: [{ declarations: [ GoogleChartComponent, ChartWrapperComponent, DashboardComponent, ControlWrapperComponent, ChartEditorComponent ], providers: [ScriptLoaderService], exports: [ GoogleChartComponent, ChartWrapperComponent, DashboardComponent, ControlWrapperComponent, ChartEditorComponent ] }] }] }); /* * Public API Surface of angular-google-charts */ /** * Generated bundle index. Do not edit. */ export { ChartEditorComponent, ChartEditorRef, ChartType, ChartWrapperComponent, ControlWrapperComponent, DashboardComponent, FilterType, GOOGLE_CHARTS_CONFIG, GOOGLE_CHARTS_LAZY_CONFIG, GoogleChartComponent, GoogleChartsModule, ScriptLoaderService, getDefaultConfig, getPackageForChart }; //# sourceMappingURL=angular-google-charts.mjs.map