1 | (function (global, factory) {
|
2 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('vega-util'), require('d3-dsv'), require('topojson-client'), require('vega-format')) :
|
3 | typeof define === 'function' && define.amd ? define(['exports', 'vega-util', 'd3-dsv', 'topojson-client', 'vega-format'], factory) :
|
4 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.vega = {}, global.vega, global.d3, global.topojson, global.vega));
|
5 | }(this, (function (exports, vegaUtil, d3Dsv, topojsonClient, vegaFormat) { 'use strict';
|
6 |
|
7 |
|
8 |
|
9 | const protocol_re = /^([A-Za-z]+:)?\/\//;
|
10 |
|
11 |
|
12 | const allowed_re = /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp|file|data):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i;
|
13 | const whitespace_re = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205f\u3000]/g;
|
14 |
|
15 |
|
16 |
|
17 | const fileProtocol = 'file://';
|
18 |
|
19 | |
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 | function loaderFactory(fetch, fs) {
|
31 | return options => ({
|
32 | options: options || {},
|
33 | sanitize: sanitize,
|
34 | load: load,
|
35 | fileAccess: !!fs,
|
36 | file: fileLoader(fs),
|
37 | http: httpLoader(fetch)
|
38 | });
|
39 | }
|
40 |
|
41 | |
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 | async function load(uri, options) {
|
52 | const opt = await this.sanitize(uri, options),
|
53 | url = opt.href;
|
54 |
|
55 | return opt.localFile
|
56 | ? this.file(url)
|
57 | : this.http(url, options);
|
58 | }
|
59 |
|
60 | |
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 | async function sanitize(uri, options) {
|
71 | options = vegaUtil.extend({}, this.options, options);
|
72 |
|
73 | const fileAccess = this.fileAccess,
|
74 | result = {href: null};
|
75 |
|
76 | let isFile, loadFile, base;
|
77 |
|
78 | const isAllowed = allowed_re.test(uri.replace(whitespace_re, ''));
|
79 |
|
80 | if (uri == null || typeof uri !== 'string' || !isAllowed) {
|
81 | vegaUtil.error('Sanitize failure, invalid URI: ' + vegaUtil.stringValue(uri));
|
82 | }
|
83 |
|
84 | const hasProtocol = protocol_re.test(uri);
|
85 |
|
86 |
|
87 | if ((base = options.baseURL) && !hasProtocol) {
|
88 |
|
89 | if (!uri.startsWith('/') && base[base.length-1] !== '/') {
|
90 | uri = '/' + uri;
|
91 | }
|
92 | uri = base + uri;
|
93 | }
|
94 |
|
95 |
|
96 | loadFile = (isFile = uri.startsWith(fileProtocol))
|
97 | || options.mode === 'file'
|
98 | || options.mode !== 'http' && !hasProtocol && fileAccess;
|
99 |
|
100 | if (isFile) {
|
101 |
|
102 | uri = uri.slice(fileProtocol.length);
|
103 | } else if (uri.startsWith('//')) {
|
104 | if (options.defaultProtocol === 'file') {
|
105 |
|
106 | uri = uri.slice(2);
|
107 | loadFile = true;
|
108 | } else {
|
109 |
|
110 | uri = (options.defaultProtocol || 'http') + ':' + uri;
|
111 | }
|
112 | }
|
113 |
|
114 |
|
115 | Object.defineProperty(result, 'localFile', {value: !!loadFile});
|
116 |
|
117 |
|
118 | result.href = uri;
|
119 |
|
120 |
|
121 | if (options.target) {
|
122 | result.target = options.target + '';
|
123 | }
|
124 |
|
125 |
|
126 | if (options.rel) {
|
127 | result.rel = options.rel + '';
|
128 | }
|
129 |
|
130 |
|
131 |
|
132 | if (options.context === 'image' && options.crossOrigin) {
|
133 | result.crossOrigin = options.crossOrigin + '';
|
134 | }
|
135 |
|
136 |
|
137 | return result;
|
138 | }
|
139 |
|
140 | |
141 |
|
142 |
|
143 |
|
144 |
|
145 |
|
146 |
|
147 |
|
148 | function fileLoader(fs) {
|
149 | return fs
|
150 | ? filename => new Promise((accept, reject) => {
|
151 | fs.readFile(filename, (error, data) => {
|
152 | if (error) reject(error);
|
153 | else accept(data);
|
154 | });
|
155 | })
|
156 | : fileReject;
|
157 | }
|
158 |
|
159 | |
160 |
|
161 |
|
162 | async function fileReject() {
|
163 | vegaUtil.error('No file system access.');
|
164 | }
|
165 |
|
166 | |
167 |
|
168 |
|
169 |
|
170 |
|
171 |
|
172 |
|
173 |
|
174 | function httpLoader(fetch) {
|
175 | return fetch
|
176 | ? async function(url, options) {
|
177 | const opt = vegaUtil.extend({}, this.options.http, options),
|
178 | type = options && options.response,
|
179 | response = await fetch(url, opt);
|
180 |
|
181 | return !response.ok
|
182 | ? vegaUtil.error(response.status + '' + response.statusText)
|
183 | : vegaUtil.isFunction(response[type]) ? response[type]()
|
184 | : response.text();
|
185 | }
|
186 | : httpReject;
|
187 | }
|
188 |
|
189 | |
190 |
|
191 |
|
192 | async function httpReject() {
|
193 | vegaUtil.error('No HTTP fetch method available.');
|
194 | }
|
195 |
|
196 | const isValid = _ => _ != null && _ === _;
|
197 |
|
198 | const isBoolean = _ => _ === 'true'
|
199 | || _ === 'false'
|
200 | || _ === true
|
201 | || _ === false;
|
202 |
|
203 | const isDate = _ => !Number.isNaN(Date.parse(_));
|
204 |
|
205 | const isNumber = _ => !Number.isNaN(+_) && !(_ instanceof Date);
|
206 |
|
207 | const isInteger = _ => isNumber(_) && Number.isInteger(+_);
|
208 |
|
209 | const typeParsers = {
|
210 | boolean: vegaUtil.toBoolean,
|
211 | integer: vegaUtil.toNumber,
|
212 | number: vegaUtil.toNumber,
|
213 | date: vegaUtil.toDate,
|
214 | string: vegaUtil.toString,
|
215 | unknown: vegaUtil.identity
|
216 | };
|
217 |
|
218 | const typeTests = [
|
219 | isBoolean,
|
220 | isInteger,
|
221 | isNumber,
|
222 | isDate
|
223 | ];
|
224 |
|
225 | const typeList = [
|
226 | 'boolean',
|
227 | 'integer',
|
228 | 'number',
|
229 | 'date'
|
230 | ];
|
231 |
|
232 | function inferType(values, field) {
|
233 | if (!values || !values.length) return 'unknown';
|
234 |
|
235 | const n = values.length,
|
236 | m = typeTests.length,
|
237 | a = typeTests.map((_, i) => i + 1);
|
238 |
|
239 | for (let i = 0, t = 0, j, value; i < n; ++i) {
|
240 | value = field ? values[i][field] : values[i];
|
241 | for (j = 0; j < m; ++j) {
|
242 | if (a[j] && isValid(value) && !typeTests[j](value)) {
|
243 | a[j] = 0;
|
244 | ++t;
|
245 | if (t === typeTests.length) return 'string';
|
246 | }
|
247 | }
|
248 | }
|
249 |
|
250 | return typeList[
|
251 | a.reduce((u, v) => u === 0 ? v : u, 0) - 1
|
252 | ];
|
253 | }
|
254 |
|
255 | function inferTypes(data, fields) {
|
256 | return fields.reduce((types, field) => {
|
257 | types[field] = inferType(data, field);
|
258 | return types;
|
259 | }, {});
|
260 | }
|
261 |
|
262 | function delimitedFormat(delimiter) {
|
263 | const parse = function(data, format) {
|
264 | const delim = {delimiter: delimiter};
|
265 | return dsv(data, format ? vegaUtil.extend(format, delim) : delim);
|
266 | };
|
267 |
|
268 | parse.responseType = 'text';
|
269 |
|
270 | return parse;
|
271 | }
|
272 |
|
273 | function dsv(data, format) {
|
274 | if (format.header) {
|
275 | data = format.header
|
276 | .map(vegaUtil.stringValue)
|
277 | .join(format.delimiter) + '\n' + data;
|
278 | }
|
279 | return d3Dsv.dsvFormat(format.delimiter).parse(data + '');
|
280 | }
|
281 |
|
282 | dsv.responseType = 'text';
|
283 |
|
284 | function isBuffer(_) {
|
285 | return (typeof Buffer === 'function' && vegaUtil.isFunction(Buffer.isBuffer))
|
286 | ? Buffer.isBuffer(_) : false;
|
287 | }
|
288 |
|
289 | function json(data, format) {
|
290 | const prop = (format && format.property) ? vegaUtil.field(format.property) : vegaUtil.identity;
|
291 | return vegaUtil.isObject(data) && !isBuffer(data)
|
292 | ? parseJSON(prop(data))
|
293 | : prop(JSON.parse(data));
|
294 | }
|
295 |
|
296 | json.responseType = 'json';
|
297 |
|
298 | function parseJSON(data, format) {
|
299 | return (format && format.copy)
|
300 | ? JSON.parse(JSON.stringify(data))
|
301 | : data;
|
302 | }
|
303 |
|
304 | const filters = {
|
305 | interior: (a, b) => a !== b,
|
306 | exterior: (a, b) => a === b
|
307 | };
|
308 |
|
309 | function topojson(data, format) {
|
310 | let method, object, property, filter;
|
311 | data = json(data, format);
|
312 |
|
313 | if (format && format.feature) {
|
314 | method = topojsonClient.feature;
|
315 | property = format.feature;
|
316 | } else if (format && format.mesh) {
|
317 | method = topojsonClient.mesh;
|
318 | property = format.mesh;
|
319 | filter = filters[format.filter];
|
320 | } else {
|
321 | vegaUtil.error('Missing TopoJSON feature or mesh parameter.');
|
322 | }
|
323 |
|
324 | object = (object = data.objects[property])
|
325 | ? method(data, object, filter)
|
326 | : vegaUtil.error('Invalid TopoJSON object: ' + property);
|
327 |
|
328 | return object && object.features || [object];
|
329 | }
|
330 |
|
331 | topojson.responseType = 'json';
|
332 |
|
333 | const format = {
|
334 | dsv: dsv,
|
335 | csv: delimitedFormat(','),
|
336 | tsv: delimitedFormat('\t'),
|
337 | json: json,
|
338 | topojson: topojson
|
339 | };
|
340 |
|
341 | function formats(name, reader) {
|
342 | if (arguments.length > 1) {
|
343 | format[name] = reader;
|
344 | return this;
|
345 | } else {
|
346 | return vegaUtil.hasOwnProperty(format, name) ? format[name] : null;
|
347 | }
|
348 | }
|
349 |
|
350 | function responseType(type) {
|
351 | const f = formats(type);
|
352 | return f && f.responseType || 'text';
|
353 | }
|
354 |
|
355 | function read(data, schema, timeParser, utcParser) {
|
356 | schema = schema || {};
|
357 |
|
358 | const reader = formats(schema.type || 'json');
|
359 | if (!reader) vegaUtil.error('Unknown data format type: ' + schema.type);
|
360 |
|
361 | data = reader(data, schema);
|
362 | if (schema.parse) parse(data, schema.parse, timeParser, utcParser);
|
363 |
|
364 | if (vegaUtil.hasOwnProperty(data, 'columns')) delete data.columns;
|
365 | return data;
|
366 | }
|
367 |
|
368 | function parse(data, types, timeParser, utcParser) {
|
369 | if (!data.length) return;
|
370 |
|
371 | const locale = vegaFormat.timeFormatDefaultLocale();
|
372 | timeParser = timeParser || locale.timeParse;
|
373 | utcParser = utcParser || locale.utcParse;
|
374 |
|
375 | let fields = data.columns || Object.keys(data[0]),
|
376 | datum, field, i, j, n, m;
|
377 |
|
378 | if (types === 'auto') types = inferTypes(data, fields);
|
379 |
|
380 | fields = Object.keys(types);
|
381 | const parsers = fields.map(field => {
|
382 | const type = types[field];
|
383 | let parts, pattern;
|
384 |
|
385 | if (type && (type.startsWith('date:') || type.startsWith('utc:'))) {
|
386 | parts = type.split(/:(.+)?/, 2);
|
387 | pattern = parts[1];
|
388 |
|
389 | if ((pattern[0] === '\'' && pattern[pattern.length-1] === '\'') ||
|
390 | (pattern[0] === '"' && pattern[pattern.length-1] === '"')) {
|
391 | pattern = pattern.slice(1, -1);
|
392 | }
|
393 |
|
394 | const parse = parts[0] === 'utc' ? utcParser : timeParser;
|
395 | return parse(pattern);
|
396 | }
|
397 |
|
398 | if (!typeParsers[type]) {
|
399 | throw Error('Illegal format pattern: ' + field + ':' + type);
|
400 | }
|
401 |
|
402 | return typeParsers[type];
|
403 | });
|
404 |
|
405 | for (i=0, n=data.length, m=fields.length; i<n; ++i) {
|
406 | datum = data[i];
|
407 | for (j=0; j<m; ++j) {
|
408 | field = fields[j];
|
409 | datum[field] = parsers[j](datum[field]);
|
410 | }
|
411 | }
|
412 | }
|
413 |
|
414 | var loader = loaderFactory(
|
415 | typeof fetch !== 'undefined' && fetch,
|
416 | null
|
417 | );
|
418 |
|
419 | exports.format = format;
|
420 | exports.formats = formats;
|
421 | exports.inferType = inferType;
|
422 | exports.inferTypes = inferTypes;
|
423 | exports.loader = loader;
|
424 | exports.read = read;
|
425 | exports.responseType = responseType;
|
426 | exports.typeParsers = typeParsers;
|
427 |
|
428 | Object.defineProperty(exports, '__esModule', { value: true });
|
429 |
|
430 | })));
|