UNPKG

42.1 kBJavaScriptView Raw
1(function (global, factory) {
2 typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('vega-scale'), require('vega-util'), require('d3-time-format'), require('d3-format'), require('vega-dataflow'), require('d3-array'), require('d3-interpolate')) :
3 typeof define === 'function' && define.amd ? define(['exports', 'vega-scale', 'vega-util', 'd3-time-format', 'd3-format', 'vega-dataflow', 'd3-array', 'd3-interpolate'], factory) :
4 (global = global || self, factory((global.vega = global.vega || {}, global.vega.transforms = {}), global.vega, global.vega, global.d3, global.d3, global.vega, global.d3, global.d3));
5}(this, function (exports, vegaScale, vegaUtil, d3TimeFormat, d3Format, vegaDataflow, d3Array, d3Interpolate) { 'use strict';
6
7 /**
8 * Determine the tick count or interval function.
9 * @param {Scale} scale - The scale for which to generate tick values.
10 * @param {*} count - The desired tick count or interval specifier.
11 * @param {number} minStep - The desired minimum step between tick values.
12 * @return {*} - The tick count or interval function.
13 */
14 function tickCount(scale, count, minStep) {
15 var step;
16
17 if (vegaUtil.isNumber(count)) {
18 if (scale.bins) {
19 count = Math.max(count, scale.bins.length);
20 }
21 if (minStep != null) {
22 count = Math.min(count, ~~(vegaUtil.span(scale.domain()) / minStep) || 1);
23 }
24 }
25
26 if (vegaUtil.isObject(count)) {
27 step = count.step;
28 count = count.interval;
29 }
30
31 if (vegaUtil.isString(count)) {
32 count = vegaScale.timeInterval(count, scale.type)
33 || vegaUtil.error('Only time and utc scales accept interval strings.');
34 if (step) count = count.every(step);
35 }
36
37 return count;
38 }
39
40 /**
41 * Filter a set of candidate tick values, ensuring that only tick values
42 * that lie within the scale range are included.
43 * @param {Scale} scale - The scale for which to generate tick values.
44 * @param {Array<*>} ticks - The candidate tick values.
45 * @param {*} count - The tick count or interval function.
46 * @return {Array<*>} - The filtered tick values.
47 */
48 function validTicks(scale, ticks, count) {
49 var range = scale.range(),
50 lo = Math.floor(range[0]),
51 hi = Math.ceil(vegaUtil.peek(range));
52
53 if (lo > hi) {
54 range = hi;
55 hi = lo;
56 lo = range;
57 }
58
59 ticks = ticks.filter(function(v) {
60 v = scale(v);
61 return lo <= v && v <= hi;
62 });
63
64 if (count > 0 && ticks.length > 1) {
65 var endpoints = [ticks[0], vegaUtil.peek(ticks)];
66 while (ticks.length > count && ticks.length >= 3) {
67 ticks = ticks.filter(function(_, i) { return !(i % 2); });
68 }
69 if (ticks.length < 3) {
70 ticks = endpoints;
71 }
72 }
73
74 return ticks;
75 }
76
77 /**
78 * Generate tick values for the given scale and approximate tick count or
79 * interval value. If the scale has a 'ticks' method, it will be used to
80 * generate the ticks, with the count argument passed as a parameter. If the
81 * scale lacks a 'ticks' method, the full scale domain will be returned.
82 * @param {Scale} scale - The scale for which to generate tick values.
83 * @param {*} [count] - The approximate number of desired ticks.
84 * @return {Array<*>} - The generated tick values.
85 */
86 function tickValues(scale, count) {
87 return scale.bins ? validTicks(scale, scale.bins)
88 : scale.ticks ? scale.ticks(count)
89 : scale.domain();
90 }
91
92 /**
93 * Generate a label format function for a scale. If the scale has a
94 * 'tickFormat' method, it will be used to generate the formatter, with the
95 * count and specifier arguments passed as parameters. If the scale lacks a
96 * 'tickFormat' method, the returned formatter performs simple string coercion.
97 * If the input scale is a logarithmic scale and the format specifier does not
98 * indicate a desired decimal precision, a special variable precision formatter
99 * that automatically trims trailing zeroes will be generated.
100 * @param {Scale} scale - The scale for which to generate the label formatter.
101 * @param {*} [count] - The approximate number of desired ticks.
102 * @param {string} [specifier] - The format specifier. Must be a legal d3
103 * specifier string (see https://github.com/d3/d3-format#formatSpecifier).
104 * @return {function(*):string} - The generated label formatter.
105 */
106 function tickFormat(scale, count, specifier, formatType) {
107 var format = scale.tickFormat ? scale.tickFormat(count, specifier)
108 : specifier && formatType === vegaScale.Time ? d3TimeFormat.timeFormat(specifier)
109 : specifier ? d3Format.format(specifier)
110 : String;
111
112 if (vegaScale.isLogarithmic(scale.type)) {
113 var logfmt = variablePrecision(specifier);
114 format = scale.bins ? logfmt : filter(format, logfmt);
115 }
116
117 return format;
118 }
119
120 function filter(sourceFormat, targetFormat) {
121 return function(_) {
122 return sourceFormat(_) ? targetFormat(_) : '';
123 };
124 }
125
126 function variablePrecision(specifier) {
127 var s = d3Format.formatSpecifier(specifier || ',');
128
129 if (s.precision == null) {
130 s.precision = 12;
131 switch (s.type) {
132 case '%': s.precision -= 2; break;
133 case 'e': s.precision -= 1; break;
134 }
135 return trimZeroes(
136 d3Format.format(s), // number format
137 d3Format.format('.1f')(1)[1] // decimal point character
138 );
139 } else {
140 return d3Format.format(s);
141 }
142 }
143
144 function trimZeroes(format, decimalChar) {
145 return function(x) {
146 var str = format(x),
147 dec = str.indexOf(decimalChar),
148 idx, end;
149
150 if (dec < 0) return str;
151
152 idx = rightmostDigit(str, dec);
153 end = idx < str.length ? str.slice(idx) : '';
154 while (--idx > dec) if (str[idx] !== '0') { ++idx; break; }
155
156 return str.slice(0, idx) + end;
157 };
158 }
159
160 function rightmostDigit(str, dec) {
161 var i = str.lastIndexOf('e'), c;
162 if (i > 0) return i;
163 for (i=str.length; --i > dec;) {
164 c = str.charCodeAt(i);
165 if (c >= 48 && c <= 57) return i + 1; // is digit
166 }
167 }
168
169 /**
170 * Generates axis ticks for visualizing a spatial scale.
171 * @constructor
172 * @param {object} params - The parameters for this operator.
173 * @param {Scale} params.scale - The scale to generate ticks for.
174 * @param {*} [params.count=10] - The approximate number of ticks, or
175 * desired tick interval, to use.
176 * @param {Array<*>} [params.values] - The exact tick values to use.
177 * These must be legal domain values for the provided scale.
178 * If provided, the count argument is ignored.
179 * @param {function(*):string} [params.formatSpecifier] - A format specifier
180 * to use in conjunction with scale.tickFormat. Legal values are
181 * any valid d3 4.0 format specifier.
182 * @param {function(*):string} [params.format] - The format function to use.
183 * If provided, the formatSpecifier argument is ignored.
184 */
185 function AxisTicks(params) {
186 vegaDataflow.Transform.call(this, null, params);
187 }
188
189 var prototype = vegaUtil.inherits(AxisTicks, vegaDataflow.Transform);
190
191 prototype.transform = function(_, pulse) {
192 if (this.value && !_.modified()) {
193 return pulse.StopPropagation;
194 }
195
196 var out = pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS),
197 ticks = this.value,
198 scale = _.scale,
199 tally = _.count == null ? (_.values ? _.values.length : 10) : _.count,
200 count = tickCount(scale, tally, _.minstep),
201 format = _.format || tickFormat(scale, count, _.formatSpecifier, _.formatType),
202 values = _.values ? validTicks(scale, _.values, count) : tickValues(scale, count);
203
204 if (ticks) out.rem = ticks;
205
206 ticks = values.map(function(value, i) {
207 return vegaDataflow.ingest({
208 index: i / (values.length - 1 || 1),
209 value: value,
210 label: format(value)
211 });
212 });
213
214 if (_.extra && ticks.length) {
215 // add an extra tick pegged to the initial domain value
216 // this is used to generate axes with 'binned' domains
217 ticks.push(vegaDataflow.ingest({
218 index: -1,
219 extra: {value: ticks[0].value},
220 label: ''
221 }));
222 }
223
224 out.source = ticks;
225 out.add = ticks;
226 this.value = ticks;
227
228 return out;
229 };
230
231 /**
232 * Joins a set of data elements against a set of visual items.
233 * @constructor
234 * @param {object} params - The parameters for this operator.
235 * @param {function(object): object} [params.item] - An item generator function.
236 * @param {function(object): *} [params.key] - The key field associating data and visual items.
237 */
238 function DataJoin(params) {
239 vegaDataflow.Transform.call(this, null, params);
240 }
241
242 var prototype$1 = vegaUtil.inherits(DataJoin, vegaDataflow.Transform);
243
244 function defaultItemCreate() {
245 return vegaDataflow.ingest({});
246 }
247
248 function isExit(t) {
249 return t.exit;
250 }
251
252 prototype$1.transform = function(_, pulse) {
253 var df = pulse.dataflow,
254 out = pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS),
255 item = _.item || defaultItemCreate,
256 key = _.key || vegaDataflow.tupleid,
257 map = this.value;
258
259 // prevent transient (e.g., hover) requests from
260 // cascading across marks derived from marks
261 if (vegaUtil.isArray(out.encode)) {
262 out.encode = null;
263 }
264
265 if (map && (_.modified('key') || pulse.modified(key))) {
266 vegaUtil.error('DataJoin does not support modified key function or fields.');
267 }
268
269 if (!map) {
270 pulse = pulse.addAll();
271 this.value = map = vegaUtil.fastmap().test(isExit);
272 map.lookup = function(t) { return map.get(key(t)); };
273 }
274
275 pulse.visit(pulse.ADD, function(t) {
276 var k = key(t),
277 x = map.get(k);
278
279 if (x) {
280 if (x.exit) {
281 map.empty--;
282 out.add.push(x);
283 } else {
284 out.mod.push(x);
285 }
286 } else {
287 map.set(k, (x = item(t)));
288 out.add.push(x);
289 }
290
291 x.datum = t;
292 x.exit = false;
293 });
294
295 pulse.visit(pulse.MOD, function(t) {
296 var k = key(t),
297 x = map.get(k);
298
299 if (x) {
300 x.datum = t;
301 out.mod.push(x);
302 }
303 });
304
305 pulse.visit(pulse.REM, function(t) {
306 var k = key(t),
307 x = map.get(k);
308
309 if (t === x.datum && !x.exit) {
310 out.rem.push(x);
311 x.exit = true;
312 ++map.empty;
313 }
314 });
315
316 if (pulse.changed(pulse.ADD_MOD)) out.modifies('datum');
317
318 if (_.clean && map.empty > df.cleanThreshold) df.runAfter(map.clean);
319
320 return out;
321 };
322
323 /**
324 * Invokes encoding functions for visual items.
325 * @constructor
326 * @param {object} params - The parameters to the encoding functions. This
327 * parameter object will be passed through to all invoked encoding functions.
328 * @param {object} [params.mod=false] - Flag indicating if tuples in the input
329 * mod set that are unmodified by encoders should be included in the output.
330 * @param {object} param.encoders - The encoding functions
331 * @param {function(object, object): boolean} [param.encoders.update] - Update encoding set
332 * @param {function(object, object): boolean} [param.encoders.enter] - Enter encoding set
333 * @param {function(object, object): boolean} [param.encoders.exit] - Exit encoding set
334 */
335 function Encode(params) {
336 vegaDataflow.Transform.call(this, null, params);
337 }
338
339 var prototype$2 = vegaUtil.inherits(Encode, vegaDataflow.Transform);
340
341 prototype$2.transform = function(_, pulse) {
342 var out = pulse.fork(pulse.ADD_REM),
343 fmod = _.mod || false,
344 encoders = _.encoders,
345 encode = pulse.encode;
346
347 // if an array, the encode directive includes additional sets
348 // that must be defined in order for the primary set to be invoked
349 // e.g., only run the update set if the hover set is defined
350 if (vegaUtil.isArray(encode)) {
351 if (out.changed() || encode.every(function(e) { return encoders[e]; })) {
352 encode = encode[0];
353 out.encode = null; // consume targeted encode directive
354 } else {
355 return pulse.StopPropagation;
356 }
357 }
358
359 // marshall encoder functions
360 var reenter = encode === 'enter',
361 update = encoders.update || vegaUtil.falsy,
362 enter = encoders.enter || vegaUtil.falsy,
363 exit = encoders.exit || vegaUtil.falsy,
364 set = (encode && !reenter ? encoders[encode] : update) || vegaUtil.falsy;
365
366 if (pulse.changed(pulse.ADD)) {
367 pulse.visit(pulse.ADD, function(t) { enter(t, _); update(t, _); });
368 out.modifies(enter.output);
369 out.modifies(update.output);
370 if (set !== vegaUtil.falsy && set !== update) {
371 pulse.visit(pulse.ADD, function(t) { set(t, _); });
372 out.modifies(set.output);
373 }
374 }
375
376 if (pulse.changed(pulse.REM) && exit !== vegaUtil.falsy) {
377 pulse.visit(pulse.REM, function(t) { exit(t, _); });
378 out.modifies(exit.output);
379 }
380
381 if (reenter || set !== vegaUtil.falsy) {
382 var flag = pulse.MOD | (_.modified() ? pulse.REFLOW : 0);
383 if (reenter) {
384 pulse.visit(flag, function(t) {
385 var mod = enter(t, _) || fmod;
386 if (set(t, _) || mod) out.mod.push(t);
387 });
388 if (out.mod.length) out.modifies(enter.output);
389 } else {
390 pulse.visit(flag, function(t) {
391 if (set(t, _) || fmod) out.mod.push(t);
392 });
393 }
394 if (out.mod.length) out.modifies(set.output);
395 }
396
397 return out.changed() ? out : pulse.StopPropagation;
398 };
399
400 var Symbols = 'symbol';
401 var Discrete = 'discrete';
402 var Gradient = 'gradient';
403
404 const symbols = {
405 [vegaScale.Quantile]: 'quantiles',
406 [vegaScale.Quantize]: 'thresholds',
407 [vegaScale.Threshold]: 'domain'
408 };
409
410 const formats = {
411 [vegaScale.Quantile]: 'quantiles',
412 [vegaScale.Quantize]: 'domain'
413 };
414
415 function labelValues(scale, count) {
416 return scale.bins ? binValues(scale.bins)
417 : symbols[scale.type] ? thresholdValues(scale[symbols[scale.type]]())
418 : tickValues(scale, count);
419 }
420
421 function thresholdFormat(scale, specifier) {
422 var _ = scale[formats[scale.type]](),
423 n = _.length,
424 d = n > 1 ? _[1] - _[0] : _[0], i;
425
426 for (i=1; i<n; ++i) {
427 d = Math.min(d, _[i] - _[i-1]);
428 }
429
430 // 3 ticks times 10 for increased resolution
431 return vegaScale.tickFormat(0, d, 3 * 10, specifier);
432 }
433
434 function thresholdValues(thresholds) {
435 const values = [-Infinity].concat(thresholds);
436 values.max = +Infinity;
437
438 return values;
439 }
440
441 function binValues(bins) {
442 const values = bins.slice(0, -1);
443 values.max = vegaUtil.peek(bins);
444
445 return values;
446 }
447
448 function isDiscreteRange(scale) {
449 return symbols[scale.type] || scale.bins;
450 }
451
452 function labelFormat(scale, count, type, specifier, formatType) {
453 const format = formats[scale.type] && formatType !== vegaScale.Time
454 ? thresholdFormat(scale, specifier)
455 : tickFormat(scale, count, specifier, formatType);
456
457 return type === Symbols && isDiscreteRange(scale) ? formatRange(format)
458 : type === Discrete ? formatDiscrete(format)
459 : formatPoint(format);
460 }
461
462 function formatRange(format) {
463 return function(value, index, array) {
464 var limit = array[index + 1] || array.max || +Infinity,
465 lo = formatValue(value, format),
466 hi = formatValue(limit, format);
467 return lo && hi ? lo + '\u2013' + hi : hi ? '< ' + hi : '\u2265 ' + lo;
468 };
469 }
470
471 function formatDiscrete(format) {
472 return function(value, index) {
473 return index ? format(value) : null;
474 }
475 }
476
477 function formatPoint(format) {
478 return function(value) {
479 return format(value);
480 };
481 }
482
483 function formatValue(value, format) {
484 return isFinite(value) ? format(value) : null;
485 }
486
487 function labelFraction(scale) {
488 var domain = scale.domain(),
489 count = domain.length - 1,
490 lo = +domain[0],
491 hi = +vegaUtil.peek(domain),
492 span = hi - lo;
493
494 if (scale.type === vegaScale.Threshold) {
495 var adjust = count ? span / count : 0.1;
496 lo -= adjust;
497 hi += adjust;
498 span = hi - lo;
499 }
500
501 return function(value) {
502 return (value - lo) / span;
503 };
504 }
505
506 /**
507 * Generates legend entries for visualizing a scale.
508 * @constructor
509 * @param {object} params - The parameters for this operator.
510 * @param {Scale} params.scale - The scale to generate items for.
511 * @param {*} [params.count=5] - The approximate number of items, or
512 * desired tick interval, to use.
513 * @param {*} [params.limit] - The maximum number of entries to
514 * include in a symbol legend.
515 * @param {Array<*>} [params.values] - The exact tick values to use.
516 * These must be legal domain values for the provided scale.
517 * If provided, the count argument is ignored.
518 * @param {string} [params.formatSpecifier] - A format specifier
519 * to use in conjunction with scale.tickFormat. Legal values are
520 * any valid D3 format specifier string.
521 * @param {function(*):string} [params.format] - The format function to use.
522 * If provided, the formatSpecifier argument is ignored.
523 */
524 function LegendEntries(params) {
525 vegaDataflow.Transform.call(this, [], params);
526 }
527
528 var prototype$3 = vegaUtil.inherits(LegendEntries, vegaDataflow.Transform);
529
530 prototype$3.transform = function(_, pulse) {
531 if (this.value != null && !_.modified()) {
532 return pulse.StopPropagation;
533 }
534
535 var out = pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS),
536 items = this.value,
537 type = _.type || Symbols,
538 scale = _.scale,
539 limit = +_.limit,
540 count = tickCount(scale, _.count == null ? 5 : _.count, _.minstep),
541 format = _.format || labelFormat(scale, count, type, _.formatSpecifier, _.formatType),
542 values = _.values || labelValues(scale, count),
543 domain, fraction, size, offset, ellipsis;
544
545 if (items) out.rem = items;
546
547 if (type === Symbols) {
548 if (limit && values.length > limit) {
549 pulse.dataflow.warn('Symbol legend count exceeds limit, filtering items.');
550 items = values.slice(0, limit - 1);
551 ellipsis = true;
552 } else {
553 items = values;
554 }
555
556 if (vegaUtil.isFunction(size = _.size)) {
557 // if first value maps to size zero, remove from list (vega#717)
558 if (!_.values && scale(items[0]) === 0) {
559 items = items.slice(1);
560 }
561 // compute size offset for legend entries
562 offset = items.reduce(function(max, value) {
563 return Math.max(max, size(value, _));
564 }, 0);
565 } else {
566 size = vegaUtil.constant(offset = size || 8);
567 }
568
569 items = items.map(function(value, index) {
570 return vegaDataflow.ingest({
571 index: index,
572 label: format(value, index, values),
573 value: value,
574 offset: offset,
575 size: size(value, _)
576 });
577 });
578
579 if (ellipsis) {
580 ellipsis = values[items.length];
581 items.push(vegaDataflow.ingest({
582 index: items.length,
583 label: `\u2026${values.length-items.length} entries`,
584 value: ellipsis,
585 offset: offset,
586 size: size(ellipsis, _)
587 }));
588 }
589 }
590
591 else if (type === Gradient) {
592 domain = scale.domain(),
593 fraction = vegaScale.scaleFraction(scale, domain[0], vegaUtil.peek(domain));
594
595 // if automatic label generation produces 2 or fewer values,
596 // use the domain end points instead (fixes vega/vega#1364)
597 if (values.length < 3 && !_.values && domain[0] !== vegaUtil.peek(domain)) {
598 values = [domain[0], vegaUtil.peek(domain)];
599 }
600
601 items = values.map(function(value, index) {
602 return vegaDataflow.ingest({
603 index: index,
604 label: format(value, index, values),
605 value: value,
606 perc: fraction(value)
607 });
608 });
609 }
610
611 else {
612 size = values.length - 1;
613 fraction = labelFraction(scale);
614
615 items = values.map(function(value, index) {
616 return vegaDataflow.ingest({
617 index: index,
618 label: format(value, index, values),
619 value: value,
620 perc: index ? fraction(value) : 0,
621 perc2: index === size ? 1 : fraction(values[index+1])
622 });
623 });
624 }
625
626 out.source = items;
627 out.add = items;
628 this.value = items;
629
630 return out;
631 };
632
633 var Paths = vegaUtil.fastmap({
634 'line': line,
635 'line-radial': lineR,
636 'arc': arc,
637 'arc-radial': arcR,
638 'curve': curve,
639 'curve-radial': curveR,
640 'orthogonal-horizontal': orthoX,
641 'orthogonal-vertical': orthoY,
642 'orthogonal-radial': orthoR,
643 'diagonal-horizontal': diagonalX,
644 'diagonal-vertical': diagonalY,
645 'diagonal-radial': diagonalR
646 });
647
648 function sourceX(t) { return t.source.x; }
649 function sourceY(t) { return t.source.y; }
650 function targetX(t) { return t.target.x; }
651 function targetY(t) { return t.target.y; }
652
653 /**
654 * Layout paths linking source and target elements.
655 * @constructor
656 * @param {object} params - The parameters for this operator.
657 */
658 function LinkPath(params) {
659 vegaDataflow.Transform.call(this, {}, params);
660 }
661
662 LinkPath.Definition = {
663 "type": "LinkPath",
664 "metadata": {"modifies": true},
665 "params": [
666 { "name": "sourceX", "type": "field", "default": "source.x" },
667 { "name": "sourceY", "type": "field", "default": "source.y" },
668 { "name": "targetX", "type": "field", "default": "target.x" },
669 { "name": "targetY", "type": "field", "default": "target.y" },
670 { "name": "orient", "type": "enum", "default": "vertical",
671 "values": ["horizontal", "vertical", "radial"] },
672 { "name": "shape", "type": "enum", "default": "line",
673 "values": ["line", "arc", "curve", "diagonal", "orthogonal"] },
674 { "name": "require", "type": "signal" },
675 { "name": "as", "type": "string", "default": "path" }
676 ]
677 };
678
679 var prototype$4 = vegaUtil.inherits(LinkPath, vegaDataflow.Transform);
680
681 prototype$4.transform = function(_, pulse) {
682 var sx = _.sourceX || sourceX,
683 sy = _.sourceY || sourceY,
684 tx = _.targetX || targetX,
685 ty = _.targetY || targetY,
686 as = _.as || 'path',
687 orient = _.orient || 'vertical',
688 shape = _.shape || 'line',
689 path = Paths.get(shape + '-' + orient) || Paths.get(shape);
690
691 if (!path) {
692 vegaUtil.error('LinkPath unsupported type: ' + _.shape
693 + (_.orient ? '-' + _.orient : ''));
694 }
695
696 pulse.visit(pulse.SOURCE, function(t) {
697 t[as] = path(sx(t), sy(t), tx(t), ty(t));
698 });
699
700 return pulse.reflow(_.modified()).modifies(as);
701 };
702
703 // -- Link Path Generation Methods -----
704
705 function line(sx, sy, tx, ty) {
706 return 'M' + sx + ',' + sy +
707 'L' + tx + ',' + ty;
708 }
709
710 function lineR(sa, sr, ta, tr) {
711 return line(
712 sr * Math.cos(sa), sr * Math.sin(sa),
713 tr * Math.cos(ta), tr * Math.sin(ta)
714 );
715 }
716
717 function arc(sx, sy, tx, ty) {
718 var dx = tx - sx,
719 dy = ty - sy,
720 rr = Math.sqrt(dx * dx + dy * dy) / 2,
721 ra = 180 * Math.atan2(dy, dx) / Math.PI;
722 return 'M' + sx + ',' + sy +
723 'A' + rr + ',' + rr +
724 ' ' + ra + ' 0 1' +
725 ' ' + tx + ',' + ty;
726 }
727
728 function arcR(sa, sr, ta, tr) {
729 return arc(
730 sr * Math.cos(sa), sr * Math.sin(sa),
731 tr * Math.cos(ta), tr * Math.sin(ta)
732 );
733 }
734
735 function curve(sx, sy, tx, ty) {
736 var dx = tx - sx,
737 dy = ty - sy,
738 ix = 0.2 * (dx + dy),
739 iy = 0.2 * (dy - dx);
740 return 'M' + sx + ',' + sy +
741 'C' + (sx+ix) + ',' + (sy+iy) +
742 ' ' + (tx+iy) + ',' + (ty-ix) +
743 ' ' + tx + ',' + ty;
744 }
745
746 function curveR(sa, sr, ta, tr) {
747 return curve(
748 sr * Math.cos(sa), sr * Math.sin(sa),
749 tr * Math.cos(ta), tr * Math.sin(ta)
750 );
751 }
752
753 function orthoX(sx, sy, tx, ty) {
754 return 'M' + sx + ',' + sy +
755 'V' + ty + 'H' + tx;
756 }
757
758 function orthoY(sx, sy, tx, ty) {
759 return 'M' + sx + ',' + sy +
760 'H' + tx + 'V' + ty;
761 }
762
763 function orthoR(sa, sr, ta, tr) {
764 var sc = Math.cos(sa),
765 ss = Math.sin(sa),
766 tc = Math.cos(ta),
767 ts = Math.sin(ta),
768 sf = Math.abs(ta - sa) > Math.PI ? ta <= sa : ta > sa;
769 return 'M' + (sr*sc) + ',' + (sr*ss) +
770 'A' + sr + ',' + sr + ' 0 0,' + (sf?1:0) +
771 ' ' + (sr*tc) + ',' + (sr*ts) +
772 'L' + (tr*tc) + ',' + (tr*ts);
773 }
774
775 function diagonalX(sx, sy, tx, ty) {
776 var m = (sx + tx) / 2;
777 return 'M' + sx + ',' + sy +
778 'C' + m + ',' + sy +
779 ' ' + m + ',' + ty +
780 ' ' + tx + ',' + ty;
781 }
782
783 function diagonalY(sx, sy, tx, ty) {
784 var m = (sy + ty) / 2;
785 return 'M' + sx + ',' + sy +
786 'C' + sx + ',' + m +
787 ' ' + tx + ',' + m +
788 ' ' + tx + ',' + ty;
789 }
790
791 function diagonalR(sa, sr, ta, tr) {
792 var sc = Math.cos(sa),
793 ss = Math.sin(sa),
794 tc = Math.cos(ta),
795 ts = Math.sin(ta),
796 mr = (sr + tr) / 2;
797 return 'M' + (sr*sc) + ',' + (sr*ss) +
798 'C' + (mr*sc) + ',' + (mr*ss) +
799 ' ' + (mr*tc) + ',' + (mr*ts) +
800 ' ' + (tr*tc) + ',' + (tr*ts);
801 }
802
803 /**
804 * Pie and donut chart layout.
805 * @constructor
806 * @param {object} params - The parameters for this operator.
807 * @param {function(object): *} params.field - The value field to size pie segments.
808 * @param {number} [params.startAngle=0] - The start angle (in radians) of the layout.
809 * @param {number} [params.endAngle=2π] - The end angle (in radians) of the layout.
810 * @param {boolean} [params.sort] - Boolean flag for sorting sectors by value.
811 */
812 function Pie(params) {
813 vegaDataflow.Transform.call(this, null, params);
814 }
815
816 Pie.Definition = {
817 "type": "Pie",
818 "metadata": {"modifies": true},
819 "params": [
820 { "name": "field", "type": "field" },
821 { "name": "startAngle", "type": "number", "default": 0 },
822 { "name": "endAngle", "type": "number", "default": 6.283185307179586 },
823 { "name": "sort", "type": "boolean", "default": false },
824 { "name": "as", "type": "string", "array": true, "length": 2, "default": ["startAngle", "endAngle"] }
825 ]
826 };
827
828 var prototype$5 = vegaUtil.inherits(Pie, vegaDataflow.Transform);
829
830 prototype$5.transform = function(_, pulse) {
831 var as = _.as || ['startAngle', 'endAngle'],
832 startAngle = as[0],
833 endAngle = as[1],
834 field = _.field || vegaUtil.one,
835 start = _.startAngle || 0,
836 stop = _.endAngle != null ? _.endAngle : 2 * Math.PI,
837 data = pulse.source,
838 values = data.map(field),
839 n = values.length,
840 a = start,
841 k = (stop - start) / d3Array.sum(values),
842 index = d3Array.range(n),
843 i, t, v;
844
845 if (_.sort) {
846 index.sort(function(a, b) {
847 return values[a] - values[b];
848 });
849 }
850
851 for (i=0; i<n; ++i) {
852 v = values[index[i]];
853 t = data[index[i]];
854 t[startAngle] = a;
855 t[endAngle] = (a += v * k);
856 }
857
858 this.value = values;
859 return pulse.reflow(_.modified()).modifies(as);
860 };
861
862 var DEFAULT_COUNT = 5;
863
864 function includeZero(scale) {
865 const type = scale.type;
866 return !scale.bins && (
867 type === vegaScale.Linear || type === vegaScale.Pow || type === vegaScale.Sqrt
868 );
869 }
870
871 function includePad(type) {
872 return vegaScale.isContinuous(type) && type !== vegaScale.Sequential;
873 }
874
875 var SKIP = vegaUtil.toSet([
876 'set', 'modified', 'clear', 'type', 'scheme', 'schemeExtent', 'schemeCount',
877 'domain', 'domainMin', 'domainMid', 'domainMax',
878 'domainRaw', 'domainImplicit', 'nice', 'zero', 'bins',
879 'range', 'rangeStep', 'round', 'reverse', 'interpolate', 'interpolateGamma'
880 ]);
881
882 /**
883 * Maintains a scale function mapping data values to visual channels.
884 * @constructor
885 * @param {object} params - The parameters for this operator.
886 */
887 function Scale(params) {
888 vegaDataflow.Transform.call(this, null, params);
889 this.modified(true); // always treat as modified
890 }
891
892 var prototype$6 = vegaUtil.inherits(Scale, vegaDataflow.Transform);
893
894 prototype$6.transform = function(_, pulse) {
895 var df = pulse.dataflow,
896 scale = this.value,
897 key = scaleKey(_);
898
899 if (!scale || key !== scale.type) {
900 this.value = scale = vegaScale.scale(key)();
901 }
902
903 for (key in _) if (!SKIP[key]) {
904 // padding is a scale property for band/point but not others
905 if (key === 'padding' && includePad(scale.type)) continue;
906 // invoke scale property setter, raise warning if not found
907 vegaUtil.isFunction(scale[key])
908 ? scale[key](_[key])
909 : df.warn('Unsupported scale property: ' + key);
910 }
911
912 configureRange(scale, _,
913 configureBins(scale, _, configureDomain(scale, _, df))
914 );
915
916 return pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS);
917 };
918
919 function scaleKey(_) {
920 var t = _.type, d = '', n;
921
922 // backwards compatibility pre Vega 5.
923 if (t === vegaScale.Sequential) return vegaScale.Sequential + '-' + vegaScale.Linear;
924
925 if (isContinuousColor(_)) {
926 n = _.rawDomain ? _.rawDomain.length
927 : _.domain ? _.domain.length + +(_.domainMid != null)
928 : 0;
929 d = n === 2 ? vegaScale.Sequential + '-'
930 : n === 3 ? vegaScale.Diverging + '-'
931 : '';
932 }
933
934 return ((d + t) || vegaScale.Linear).toLowerCase();
935 }
936
937 function isContinuousColor(_) {
938 const t = _.type;
939 return vegaScale.isContinuous(t) && t !== vegaScale.Time && t !== vegaScale.UTC && (
940 _.scheme || _.range && _.range.length && _.range.every(vegaUtil.isString)
941 );
942 }
943
944 function configureDomain(scale, _, df) {
945 // check raw domain, if provided use that and exit early
946 var raw = rawDomain(scale, _.domainRaw, df);
947 if (raw > -1) return raw;
948
949 var domain = _.domain,
950 type = scale.type,
951 zero = _.zero || (_.zero === undefined && includeZero(scale)),
952 n, mid;
953
954 if (!domain) return 0;
955
956 // adjust continuous domain for minimum pixel padding
957 if (includePad(type) && _.padding && domain[0] !== vegaUtil.peek(domain)) {
958 domain = padDomain(type, domain, _.range, _.padding, _.exponent, _.constant);
959 }
960
961 // adjust domain based on zero, min, max settings
962 if (zero || _.domainMin != null || _.domainMax != null || _.domainMid != null) {
963 n = ((domain = domain.slice()).length - 1) || 1;
964 if (zero) {
965 if (domain[0] > 0) domain[0] = 0;
966 if (domain[n] < 0) domain[n] = 0;
967 }
968 if (_.domainMin != null) domain[0] = _.domainMin;
969 if (_.domainMax != null) domain[n] = _.domainMax;
970
971 if (_.domainMid != null) {
972 mid = _.domainMid;
973 if (mid < domain[0] || mid > domain[n]) {
974 df.warn('Scale domainMid exceeds domain min or max.', mid);
975 }
976 domain.splice(n, 0, mid);
977 }
978 }
979
980 // set the scale domain
981 scale.domain(domainCheck(type, domain, df));
982
983 // if ordinal scale domain is defined, prevent implicit
984 // domain construction as side-effect of scale lookup
985 if (type === vegaScale.Ordinal) {
986 scale.unknown(_.domainImplicit ? vegaScale.scaleImplicit : undefined);
987 }
988
989 // perform 'nice' adjustment as requested
990 if (_.nice && scale.nice) {
991 scale.nice((_.nice !== true && tickCount(scale, _.nice)) || null);
992 }
993
994 // return the cardinality of the domain
995 return domain.length;
996 }
997
998 function rawDomain(scale, raw, df) {
999 if (raw) {
1000 scale.domain(domainCheck(scale.type, raw, df));
1001 return raw.length;
1002 } else {
1003 return -1;
1004 }
1005 }
1006
1007 function padDomain(type, domain, range, pad, exponent, constant) {
1008 var span = Math.abs(vegaUtil.peek(range) - range[0]),
1009 frac = span / (span - 2 * pad),
1010 d = type === vegaScale.Log ? vegaUtil.zoomLog(domain, null, frac)
1011 : type === vegaScale.Sqrt ? vegaUtil.zoomPow(domain, null, frac, 0.5)
1012 : type === vegaScale.Pow ? vegaUtil.zoomPow(domain, null, frac, exponent || 1)
1013 : type === vegaScale.Symlog ? vegaUtil.zoomSymlog(domain, null, frac, constant || 1)
1014 : vegaUtil.zoomLinear(domain, null, frac);
1015
1016 domain = domain.slice();
1017 domain[0] = d[0];
1018 domain[domain.length-1] = d[1];
1019 return domain;
1020 }
1021
1022 function domainCheck(type, domain, df) {
1023 if (vegaScale.isLogarithmic(type)) {
1024 // sum signs of domain values
1025 // if all pos or all neg, abs(sum) === domain.length
1026 var s = Math.abs(domain.reduce(function(s, v) {
1027 return s + (v < 0 ? -1 : v > 0 ? 1 : 0);
1028 }, 0));
1029
1030 if (s !== domain.length) {
1031 df.warn('Log scale domain includes zero: ' + vegaUtil.stringValue(domain));
1032 }
1033 }
1034 return domain;
1035 }
1036
1037 function configureBins(scale, _, count) {
1038 let bins = _.bins;
1039
1040 if (bins && !vegaUtil.isArray(bins)) {
1041 // generate bin boundary array
1042 let domain = scale.domain(),
1043 lo = domain[0],
1044 hi = vegaUtil.peek(domain),
1045 start = bins.start == null ? lo : bins.start,
1046 stop = bins.stop == null ? hi : bins.stop,
1047 step = bins.step;
1048
1049 if (!step) vegaUtil.error('Scale bins parameter missing step property.');
1050 if (start < lo) start = step * Math.ceil(lo / step);
1051 if (stop > hi) stop = step * Math.floor(hi / step);
1052 bins = d3Array.range(start, stop + step / 2, step);
1053 }
1054
1055 if (bins) {
1056 // assign bin boundaries to scale instance
1057 scale.bins = bins;
1058 } else if (scale.bins) {
1059 // no current bins, remove bins if previously set
1060 delete scale.bins;
1061 }
1062
1063 // special handling for bin-ordinal scales
1064 if (scale.type === vegaScale.BinOrdinal) {
1065 if (!bins) {
1066 // the domain specifies the bins
1067 scale.bins = scale.domain();
1068 } else if (!_.domain && !_.domainRaw) {
1069 // the bins specify the domain
1070 scale.domain(bins);
1071 count = bins.length;
1072 }
1073 }
1074
1075 // return domain cardinality
1076 return count;
1077 }
1078
1079 function configureRange(scale, _, count) {
1080 var type = scale.type,
1081 round = _.round || false,
1082 range = _.range;
1083
1084 // if range step specified, calculate full range extent
1085 if (_.rangeStep != null) {
1086 range = configureRangeStep(type, _, count);
1087 }
1088
1089 // else if a range scheme is defined, use that
1090 else if (_.scheme) {
1091 range = configureScheme(type, _, count);
1092 if (vegaUtil.isFunction(range)) {
1093 if (scale.interpolator) {
1094 return scale.interpolator(range);
1095 } else {
1096 vegaUtil.error(`Scale type ${type} does not support interpolating color schemes.`);
1097 }
1098 }
1099 }
1100
1101 // given a range array for an interpolating scale, convert to interpolator
1102 if (range && vegaScale.isInterpolating(type)) {
1103 return scale.interpolator(
1104 vegaScale.interpolateColors(flip(range, _.reverse), _.interpolate, _.interpolateGamma)
1105 );
1106 }
1107
1108 // configure rounding / interpolation
1109 if (range && _.interpolate && scale.interpolate) {
1110 scale.interpolate(vegaScale.interpolate(_.interpolate, _.interpolateGamma));
1111 } else if (vegaUtil.isFunction(scale.round)) {
1112 scale.round(round);
1113 } else if (vegaUtil.isFunction(scale.rangeRound)) {
1114 scale.interpolate(round ? d3Interpolate.interpolateRound : d3Interpolate.interpolate);
1115 }
1116
1117 if (range) scale.range(flip(range, _.reverse));
1118 }
1119
1120 function configureRangeStep(type, _, count) {
1121 if (type !== vegaScale.Band && type !== vegaScale.Point) {
1122 vegaUtil.error('Only band and point scales support rangeStep.');
1123 }
1124
1125 // calculate full range based on requested step size and padding
1126 var outer = (_.paddingOuter != null ? _.paddingOuter : _.padding) || 0,
1127 inner = type === vegaScale.Point ? 1
1128 : ((_.paddingInner != null ? _.paddingInner : _.padding) || 0);
1129 return [0, _.rangeStep * vegaScale.bandSpace(count, inner, outer)];
1130 }
1131
1132 function configureScheme(type, _, count) {
1133 var extent = _.schemeExtent,
1134 name, scheme;
1135
1136 if (vegaUtil.isArray(_.scheme)) {
1137 scheme = vegaScale.interpolateColors(_.scheme, _.interpolate, _.interpolateGamma);
1138 } else {
1139 name = _.scheme.toLowerCase();
1140 scheme = vegaScale.scheme(name);
1141 if (!scheme) vegaUtil.error(`Unrecognized scheme name: ${_.scheme}`);
1142 }
1143
1144 // determine size for potential discrete range
1145 count = (type === vegaScale.Threshold) ? count + 1
1146 : (type === vegaScale.BinOrdinal) ? count - 1
1147 : (type === vegaScale.Quantile || type === vegaScale.Quantize) ? (+_.schemeCount || DEFAULT_COUNT)
1148 : count;
1149
1150 // adjust and/or quantize scheme as appropriate
1151 return vegaScale.isInterpolating(type) ? adjustScheme(scheme, extent, _.reverse)
1152 : vegaUtil.isFunction(scheme) ? vegaScale.quantizeInterpolator(adjustScheme(scheme, extent), count)
1153 : type === vegaScale.Ordinal ? scheme : scheme.slice(0, count);
1154 }
1155
1156 function adjustScheme(scheme, extent, reverse) {
1157 return (vegaUtil.isFunction(scheme) && (extent || reverse))
1158 ? vegaScale.interpolateRange(scheme, flip(extent || [0, 1], reverse))
1159 : scheme;
1160 }
1161
1162 function flip(array, reverse) {
1163 return reverse ? array.slice().reverse() : array;
1164 }
1165
1166 /**
1167 * Sorts scenegraph items in the pulse source array.
1168 * @constructor
1169 * @param {object} params - The parameters for this operator.
1170 * @param {function(*,*): number} [params.sort] - A comparator
1171 * function for sorting tuples.
1172 */
1173 function SortItems(params) {
1174 vegaDataflow.Transform.call(this, null, params);
1175 }
1176
1177 var prototype$7 = vegaUtil.inherits(SortItems, vegaDataflow.Transform);
1178
1179 prototype$7.transform = function(_, pulse) {
1180 var mod = _.modified('sort')
1181 || pulse.changed(pulse.ADD)
1182 || pulse.modified(_.sort.fields)
1183 || pulse.modified('datum');
1184
1185 if (mod) pulse.source.sort(vegaDataflow.stableCompare(_.sort));
1186
1187 this.modified(mod);
1188 return pulse;
1189 };
1190
1191 var Zero = 'zero',
1192 Center = 'center',
1193 Normalize = 'normalize',
1194 DefOutput = ['y0', 'y1'];
1195
1196 /**
1197 * Stack layout for visualization elements.
1198 * @constructor
1199 * @param {object} params - The parameters for this operator.
1200 * @param {function(object): *} params.field - The value field to stack.
1201 * @param {Array<function(object): *>} [params.groupby] - An array of accessors to groupby.
1202 * @param {function(object,object): number} [params.sort] - A comparator for stack sorting.
1203 * @param {string} [offset='zero'] - Stack baseline offset. One of 'zero', 'center', 'normalize'.
1204 */
1205 function Stack(params) {
1206 vegaDataflow.Transform.call(this, null, params);
1207 }
1208
1209 Stack.Definition = {
1210 "type": "Stack",
1211 "metadata": {"modifies": true},
1212 "params": [
1213 { "name": "field", "type": "field" },
1214 { "name": "groupby", "type": "field", "array": true },
1215 { "name": "sort", "type": "compare" },
1216 { "name": "offset", "type": "enum", "default": Zero, "values": [Zero, Center, Normalize] },
1217 { "name": "as", "type": "string", "array": true, "length": 2, "default": DefOutput }
1218 ]
1219 };
1220
1221 var prototype$8 = vegaUtil.inherits(Stack, vegaDataflow.Transform);
1222
1223 prototype$8.transform = function(_, pulse) {
1224 var as = _.as || DefOutput,
1225 y0 = as[0],
1226 y1 = as[1],
1227 sort = vegaDataflow.stableCompare(_.sort),
1228 field = _.field || vegaUtil.one,
1229 stack = _.offset === Center ? stackCenter
1230 : _.offset === Normalize ? stackNormalize
1231 : stackZero,
1232 groups, i, n, max;
1233
1234 // partition, sum, and sort the stack groups
1235 groups = partition(pulse.source, _.groupby, sort, field);
1236
1237 // compute stack layouts per group
1238 for (i=0, n=groups.length, max=groups.max; i<n; ++i) {
1239 stack(groups[i], max, field, y0, y1);
1240 }
1241
1242 return pulse.reflow(_.modified()).modifies(as);
1243 };
1244
1245 function stackCenter(group, max, field, y0, y1) {
1246 var last = (max - group.sum) / 2,
1247 m = group.length,
1248 j = 0, t;
1249
1250 for (; j<m; ++j) {
1251 t = group[j];
1252 t[y0] = last;
1253 t[y1] = (last += Math.abs(field(t)));
1254 }
1255 }
1256
1257 function stackNormalize(group, max, field, y0, y1) {
1258 var scale = 1 / group.sum,
1259 last = 0,
1260 m = group.length,
1261 j = 0, v = 0, t;
1262
1263 for (; j<m; ++j) {
1264 t = group[j];
1265 t[y0] = last;
1266 t[y1] = last = scale * (v += Math.abs(field(t)));
1267 }
1268 }
1269
1270 function stackZero(group, max, field, y0, y1) {
1271 var lastPos = 0,
1272 lastNeg = 0,
1273 m = group.length,
1274 j = 0, v, t;
1275
1276 for (; j<m; ++j) {
1277 t = group[j];
1278 v = +field(t);
1279 if (v < 0) {
1280 t[y0] = lastNeg;
1281 t[y1] = (lastNeg += v);
1282 } else {
1283 t[y0] = lastPos;
1284 t[y1] = (lastPos += v);
1285 }
1286 }
1287 }
1288
1289 function partition(data, groupby, sort, field) {
1290 var groups = [],
1291 get = function(f) { return f(t); },
1292 map, i, n, m, t, k, g, s, max;
1293
1294 // partition data points into stack groups
1295 if (groupby == null) {
1296 groups.push(data.slice());
1297 } else {
1298 for (map={}, i=0, n=data.length; i<n; ++i) {
1299 t = data[i];
1300 k = groupby.map(get);
1301 g = map[k];
1302 if (!g) {
1303 map[k] = (g = []);
1304 groups.push(g);
1305 }
1306 g.push(t);
1307 }
1308 }
1309
1310 // compute sums of groups, sort groups as needed
1311 for (k=0, max=0, m=groups.length; k<m; ++k) {
1312 g = groups[k];
1313 for (i=0, s=0, n=g.length; i<n; ++i) {
1314 s += Math.abs(field(g[i]));
1315 }
1316 g.sum = s;
1317 if (s > max) max = s;
1318 if (sort) g.sort(sort);
1319 }
1320 groups.max = max;
1321
1322 return groups;
1323 }
1324
1325 exports.axisticks = AxisTicks;
1326 exports.datajoin = DataJoin;
1327 exports.encode = Encode;
1328 exports.legendentries = LegendEntries;
1329 exports.linkpath = LinkPath;
1330 exports.pie = Pie;
1331 exports.scale = Scale;
1332 exports.sortitems = SortItems;
1333 exports.stack = Stack;
1334 exports.validTicks = validTicks;
1335
1336 Object.defineProperty(exports, '__esModule', { value: true });
1337
1338}));