UNPKG

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