UNPKG

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