1 | import {Transform} from 'vega-dataflow';
|
2 | import {
|
3 | error, inherits, isArray, isFunction, isString, peek, stringValue,
|
4 | toSet, zoomLinear, zoomLog, zoomPow, zoomSymlog
|
5 | } from 'vega-util';
|
6 |
|
7 | import {
|
8 | Band,
|
9 | BinOrdinal,
|
10 | Diverging,
|
11 | Linear,
|
12 | Log,
|
13 | Ordinal,
|
14 | Point,
|
15 | Pow,
|
16 | Quantile,
|
17 | Quantize,
|
18 | Sequential,
|
19 | Sqrt,
|
20 | Symlog,
|
21 | Threshold,
|
22 | Time,
|
23 | UTC,
|
24 | bandSpace,
|
25 | interpolate as getInterpolate,
|
26 | scale as getScale,
|
27 | scheme as getScheme,
|
28 | interpolateColors,
|
29 | interpolateRange,
|
30 | isContinuous,
|
31 | isInterpolating,
|
32 | isLogarithmic,
|
33 | quantizeInterpolator,
|
34 | scaleImplicit,
|
35 | tickCount
|
36 | } from 'vega-scale';
|
37 |
|
38 | import {range as sequence} from 'd3-array';
|
39 |
|
40 | import {
|
41 | interpolate,
|
42 | interpolateRound
|
43 | } from 'd3-interpolate';
|
44 |
|
45 | var DEFAULT_COUNT = 5;
|
46 |
|
47 | function includeZero(scale) {
|
48 | const type = scale.type;
|
49 | return !scale.bins && (
|
50 | type === Linear || type === Pow || type === Sqrt
|
51 | );
|
52 | }
|
53 |
|
54 | function includePad(type) {
|
55 | return isContinuous(type) && type !== Sequential;
|
56 | }
|
57 |
|
58 | var SKIP = toSet([
|
59 | 'set', 'modified', 'clear', 'type', 'scheme', 'schemeExtent', 'schemeCount',
|
60 | 'domain', 'domainMin', 'domainMid', 'domainMax',
|
61 | 'domainRaw', 'domainImplicit', 'nice', 'zero', 'bins',
|
62 | 'range', 'rangeStep', 'round', 'reverse', 'interpolate', 'interpolateGamma'
|
63 | ]);
|
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 | export default function Scale(params) {
|
71 | Transform.call(this, null, params);
|
72 | this.modified(true);
|
73 | }
|
74 |
|
75 | var prototype = inherits(Scale, Transform);
|
76 |
|
77 | prototype.transform = function(_, pulse) {
|
78 | var df = pulse.dataflow,
|
79 | scale = this.value,
|
80 | key = scaleKey(_);
|
81 |
|
82 | if (!scale || key !== scale.type) {
|
83 | this.value = scale = getScale(key)();
|
84 | }
|
85 |
|
86 | for (key in _) if (!SKIP[key]) {
|
87 |
|
88 | if (key === 'padding' && includePad(scale.type)) continue;
|
89 |
|
90 | isFunction(scale[key])
|
91 | ? scale[key](_[key])
|
92 | : df.warn('Unsupported scale property: ' + key);
|
93 | }
|
94 |
|
95 | configureRange(scale, _,
|
96 | configureBins(scale, _, configureDomain(scale, _, df))
|
97 | );
|
98 |
|
99 | return pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS);
|
100 | };
|
101 |
|
102 | function scaleKey(_) {
|
103 | var t = _.type, d = '', n;
|
104 |
|
105 |
|
106 | if (t === Sequential) return Sequential + '-' + Linear;
|
107 |
|
108 | if (isContinuousColor(_)) {
|
109 | n = _.rawDomain ? _.rawDomain.length
|
110 | : _.domain ? _.domain.length + +(_.domainMid != null)
|
111 | : 0;
|
112 | d = n === 2 ? Sequential + '-'
|
113 | : n === 3 ? Diverging + '-'
|
114 | : '';
|
115 | }
|
116 |
|
117 | return ((d + t) || Linear).toLowerCase();
|
118 | }
|
119 |
|
120 | function isContinuousColor(_) {
|
121 | const t = _.type;
|
122 | return isContinuous(t) && t !== Time && t !== UTC && (
|
123 | _.scheme || _.range && _.range.length && _.range.every(isString)
|
124 | );
|
125 | }
|
126 |
|
127 | function configureDomain(scale, _, df) {
|
128 |
|
129 | var raw = rawDomain(scale, _.domainRaw, df);
|
130 | if (raw > -1) return raw;
|
131 |
|
132 | var domain = _.domain,
|
133 | type = scale.type,
|
134 | zero = _.zero || (_.zero === undefined && includeZero(scale)),
|
135 | n, mid;
|
136 |
|
137 | if (!domain) return 0;
|
138 |
|
139 |
|
140 | if (includePad(type) && _.padding && domain[0] !== peek(domain)) {
|
141 | domain = padDomain(type, domain, _.range, _.padding, _.exponent, _.constant);
|
142 | }
|
143 |
|
144 |
|
145 | if (zero || _.domainMin != null || _.domainMax != null || _.domainMid != null) {
|
146 | n = ((domain = domain.slice()).length - 1) || 1;
|
147 | if (zero) {
|
148 | if (domain[0] > 0) domain[0] = 0;
|
149 | if (domain[n] < 0) domain[n] = 0;
|
150 | }
|
151 | if (_.domainMin != null) domain[0] = _.domainMin;
|
152 | if (_.domainMax != null) domain[n] = _.domainMax;
|
153 |
|
154 | if (_.domainMid != null) {
|
155 | mid = _.domainMid;
|
156 | if (mid < domain[0] || mid > domain[n]) {
|
157 | df.warn('Scale domainMid exceeds domain min or max.', mid);
|
158 | }
|
159 | domain.splice(n, 0, mid);
|
160 | }
|
161 | }
|
162 |
|
163 |
|
164 | scale.domain(domainCheck(type, domain, df));
|
165 |
|
166 |
|
167 |
|
168 | if (type === Ordinal) {
|
169 | scale.unknown(_.domainImplicit ? scaleImplicit : undefined);
|
170 | }
|
171 |
|
172 |
|
173 | if (_.nice && scale.nice) {
|
174 | scale.nice((_.nice !== true && tickCount(scale, _.nice)) || null);
|
175 | }
|
176 |
|
177 |
|
178 | return domain.length;
|
179 | }
|
180 |
|
181 | function rawDomain(scale, raw, df) {
|
182 | if (raw) {
|
183 | scale.domain(domainCheck(scale.type, raw, df));
|
184 | return raw.length;
|
185 | } else {
|
186 | return -1;
|
187 | }
|
188 | }
|
189 |
|
190 | function padDomain(type, domain, range, pad, exponent, constant) {
|
191 | var span = Math.abs(peek(range) - range[0]),
|
192 | frac = span / (span - 2 * pad),
|
193 | d = type === Log ? zoomLog(domain, null, frac)
|
194 | : type === Sqrt ? zoomPow(domain, null, frac, 0.5)
|
195 | : type === Pow ? zoomPow(domain, null, frac, exponent || 1)
|
196 | : type === Symlog ? zoomSymlog(domain, null, frac, constant || 1)
|
197 | : zoomLinear(domain, null, frac);
|
198 |
|
199 | domain = domain.slice();
|
200 | domain[0] = d[0];
|
201 | domain[domain.length-1] = d[1];
|
202 | return domain;
|
203 | }
|
204 |
|
205 | function domainCheck(type, domain, df) {
|
206 | if (isLogarithmic(type)) {
|
207 |
|
208 |
|
209 | var s = Math.abs(domain.reduce(function(s, v) {
|
210 | return s + (v < 0 ? -1 : v > 0 ? 1 : 0);
|
211 | }, 0));
|
212 |
|
213 | if (s !== domain.length) {
|
214 | df.warn('Log scale domain includes zero: ' + stringValue(domain));
|
215 | }
|
216 | }
|
217 | return domain;
|
218 | }
|
219 |
|
220 | function configureBins(scale, _, count) {
|
221 | let bins = _.bins;
|
222 |
|
223 | if (bins && !isArray(bins)) {
|
224 |
|
225 | let domain = scale.domain(),
|
226 | lo = domain[0],
|
227 | hi = peek(domain),
|
228 | start = bins.start == null ? lo : bins.start,
|
229 | stop = bins.stop == null ? hi : bins.stop,
|
230 | step = bins.step;
|
231 |
|
232 | if (!step) error('Scale bins parameter missing step property.');
|
233 | if (start < lo) start = step * Math.ceil(lo / step);
|
234 | if (stop > hi) stop = step * Math.floor(hi / step);
|
235 | bins = sequence(start, stop + step / 2, step);
|
236 | }
|
237 |
|
238 | if (bins) {
|
239 |
|
240 | scale.bins = bins;
|
241 | } else if (scale.bins) {
|
242 |
|
243 | delete scale.bins;
|
244 | }
|
245 |
|
246 |
|
247 | if (scale.type === BinOrdinal) {
|
248 | if (!bins) {
|
249 |
|
250 | scale.bins = scale.domain();
|
251 | } else if (!_.domain && !_.domainRaw) {
|
252 |
|
253 | scale.domain(bins);
|
254 | count = bins.length;
|
255 | }
|
256 | }
|
257 |
|
258 |
|
259 | return count;
|
260 | }
|
261 |
|
262 | function configureRange(scale, _, count) {
|
263 | var type = scale.type,
|
264 | round = _.round || false,
|
265 | range = _.range;
|
266 |
|
267 |
|
268 | if (_.rangeStep != null) {
|
269 | range = configureRangeStep(type, _, count);
|
270 | }
|
271 |
|
272 |
|
273 | else if (_.scheme) {
|
274 | range = configureScheme(type, _, count);
|
275 | if (isFunction(range)) {
|
276 | if (scale.interpolator) {
|
277 | return scale.interpolator(range);
|
278 | } else {
|
279 | error(`Scale type ${type} does not support interpolating color schemes.`);
|
280 | }
|
281 | }
|
282 | }
|
283 |
|
284 |
|
285 | if (range && isInterpolating(type)) {
|
286 | return scale.interpolator(
|
287 | interpolateColors(flip(range, _.reverse), _.interpolate, _.interpolateGamma)
|
288 | );
|
289 | }
|
290 |
|
291 |
|
292 | if (range && _.interpolate && scale.interpolate) {
|
293 | scale.interpolate(getInterpolate(_.interpolate, _.interpolateGamma));
|
294 | } else if (isFunction(scale.round)) {
|
295 | scale.round(round);
|
296 | } else if (isFunction(scale.rangeRound)) {
|
297 | scale.interpolate(round ? interpolateRound : interpolate);
|
298 | }
|
299 |
|
300 | if (range) scale.range(flip(range, _.reverse));
|
301 | }
|
302 |
|
303 | function configureRangeStep(type, _, count) {
|
304 | if (type !== Band && type !== Point) {
|
305 | error('Only band and point scales support rangeStep.');
|
306 | }
|
307 |
|
308 |
|
309 | var outer = (_.paddingOuter != null ? _.paddingOuter : _.padding) || 0,
|
310 | inner = type === Point ? 1
|
311 | : ((_.paddingInner != null ? _.paddingInner : _.padding) || 0);
|
312 | return [0, _.rangeStep * bandSpace(count, inner, outer)];
|
313 | }
|
314 |
|
315 | function configureScheme(type, _, count) {
|
316 | var extent = _.schemeExtent,
|
317 | name, scheme;
|
318 |
|
319 | if (isArray(_.scheme)) {
|
320 | scheme = interpolateColors(_.scheme, _.interpolate, _.interpolateGamma);
|
321 | } else {
|
322 | name = _.scheme.toLowerCase();
|
323 | scheme = getScheme(name);
|
324 | if (!scheme) error(`Unrecognized scheme name: ${_.scheme}`);
|
325 | }
|
326 |
|
327 |
|
328 | count = (type === Threshold) ? count + 1
|
329 | : (type === BinOrdinal) ? count - 1
|
330 | : (type === Quantile || type === Quantize) ? (+_.schemeCount || DEFAULT_COUNT)
|
331 | : count;
|
332 |
|
333 |
|
334 | return isInterpolating(type) ? adjustScheme(scheme, extent, _.reverse)
|
335 | : isFunction(scheme) ? quantizeInterpolator(adjustScheme(scheme, extent), count)
|
336 | : type === Ordinal ? scheme : scheme.slice(0, count);
|
337 | }
|
338 |
|
339 | function adjustScheme(scheme, extent, reverse) {
|
340 | return (isFunction(scheme) && (extent || reverse))
|
341 | ? interpolateRange(scheme, flip(extent || [0, 1], reverse))
|
342 | : scheme;
|
343 | }
|
344 |
|
345 | function flip(array, reverse) {
|
346 | return reverse ? array.slice().reverse() : array;
|
347 | }
|
348 |
|