-
Notifications
You must be signed in to change notification settings - Fork 130
/
loader.ts
153 lines (146 loc) · 5.67 KB
/
loader.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
/**
* @license
* Copyright 2014-2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {trustedResourceUrl} from 'safevalues';
import {safeScriptEl} from 'safevalues/dom';
/**
* Promise that resolves when the gviz loader script is loaded, which
* provides access to the Google Charts loading API.
*/
const loaderPromise: Promise<void> = new Promise((resolve, reject) => {
// Resolve immediately if the loader script has been added already and
// `google.charts.load` is available. Adding the loader script twice throws
// an error.
if (typeof google !== 'undefined' && google.charts &&
typeof google.charts.load === 'function') {
resolve();
} else {
// Try to find existing loader script.
let loaderScript: HTMLScriptElement|null = document.querySelector(
'script[src="https://www.gstatic.com/charts/loader.js"]');
if (!loaderScript) {
// If the loader is not present, add it.
loaderScript = document.createElement('script');
safeScriptEl.setSrc(
loaderScript,
trustedResourceUrl`https://www.gstatic.com/charts/loader.js`);
document.head.appendChild(loaderScript);
}
loaderScript.addEventListener('load', resolve as () => void);
loaderScript.addEventListener('error', reject);
}
});
interface LoadSettings {
version?: string;
packages?: string[];
language?: string;
mapsApiKey?: string;
}
/**
* Loads Google Charts API with the selected settings or using defaults.
*
* The following settings are available:
* - version: which version of library to load, default: 'current',
* - packages: which chart packages to load, default: ['corechart'],
* - language: what language to load library in, default: `lang` attribute on
* `<html>` or 'en' if not specified,
* - mapsApiKey: key to use for maps API.
*/
export async function load(settings: LoadSettings = {}): Promise<void> {
await loaderPromise;
const {
version = 'current',
packages = ['corechart'],
language = document.documentElement.lang || 'en',
mapsApiKey,
} = settings;
return google.charts.load(version, {
'packages': packages,
'language': language,
'mapsApiKey': mapsApiKey,
});
}
/** Types that can be converted to `DataTable`. */
export type DataTableLike = unknown[][]|{cols: unknown[], rows?: unknown[][]}|
google.visualization.DataTable;
/**
* Creates a DataTable object for use with a chart.
*
* Multiple different argument types are supported. This is because the
* result of loading the JSON data URL is fed into this function for
* DataTable construction and its format is unknown.
*
* The data argument can be one of a few options:
*
* - null/undefined: An empty DataTable is created. Columns must be added
* - !DataTable: The object is simply returned
* - {{cols: !Array, rows: !Array}}: A DataTable in object format
* - {{cols: !Array}}: A DataTable in object format without rows
* - !Array<!Array>: A DataTable in 2D array format
*
* Un-supported types:
*
* - Empty !Array<!Array>: (e.g. `[]`) While technically a valid data
* format, this is rejected as charts will not render empty DataTables.
* DataTables must at least have columns specified. An empty array is most
* likely due to a bug or bad data. If one wants an empty DataTable, pass
* no arguments.
* - Anything else
*
* See <a
* href="https://developers.google.com/chart/interactive/docs/reference#datatable-class">the
* docs</a> for more details.
*
* @param data The data which we should use to construct new DataTable object
*/
export async function dataTable(data: DataTableLike|undefined):
Promise<google.visualization.DataTable> {
// Ensure that `google.visualization` namespace is added to the document.
await load();
if (data == null) {
return new google.visualization.DataTable();
} else if ((data as google.visualization.DataTable).getNumberOfRows!) {
// Data is already a DataTable
return data as google.visualization.DataTable;
} else if ((data as {
cols: unknown[]
}).cols) { // data.rows may also be specified
// Data is in the form of object DataTable structure
return new google.visualization.DataTable(data);
} else if ((data as unknown[][]).length > 0) {
// Data is in the form of a two dimensional array.
return google.visualization.arrayToDataTable(data as unknown[][]);
} else if ((data as unknown[][]).length === 0) {
// Chart data was empty.
// We throw instead of creating an empty DataTable because most
// (if not all) charts will render a sticky error in this situation.
throw new Error('Data was empty.');
}
throw new Error('Data format was not recognized.');
}
/**
* Creates new `ChartWrapper`.
* @param container Element in which the chart will be drawn
*/
export async function createChartWrapper(container: HTMLElement):
Promise<google.visualization.ChartWrapper> {
// Ensure that `google.visualization` namespace is added to the document.
await load();
// Typings suggest that `chartType` is required in `ChartSpecs`, but it works
// without it.
return new google.visualization.ChartWrapper(
{'container': container} as unknown as google.visualization.ChartSpecs);
}