UNPKG

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