diff --git a/src/marks/box.js b/src/marks/box.js
index dc15536b81..bc467bec83 100644
--- a/src/marks/box.js
+++ b/src/marks/box.js
@@ -23,7 +23,7 @@ export function boxX(data, {
const group = y != null ? groupY : groupZ;
return marks(
ruleY(data, group({x1: loqr1, x2: hiqr2}, {x, y, stroke, strokeOpacity, ...options})),
- barX(data, group({x1: "p25", x2: "p75"}, {x, y, fill, fillOpacity, ...options})),
+ barX(data, group({x1: "p25", x2: "p75"}, {x, y, fill, fillOpacity, z: stroke, ...options})),
tickX(data, group({x: "p50"}, {x, y, stroke, strokeOpacity, strokeWidth, sort, ...options})),
dot(data, map({x: oqr}, {x, y, z: y, stroke, strokeOpacity, ...options}))
);
diff --git a/src/plot.js b/src/plot.js
index 6d68feaa90..6be4cfdd91 100644
--- a/src/plot.js
+++ b/src/plot.js
@@ -1,16 +1,15 @@
-import {bisectLeft, cross, difference, groups, InternMap, interpolate, interpolateNumber, select} from "d3";
+import {cross, difference, group, groups, InternMap, select} from "d3";
import {Axes, autoAxisTicks, autoScaleLabels} from "./axes.js";
import {Channel, Channels, channelDomain, valueObject} from "./channel.js";
import {Context, create} from "./context.js";
import {defined} from "./defined.js";
import {Dimensions} from "./dimensions.js";
import {Legends, exposeLegends} from "./legends.js";
-import {arrayify, isDomainSort, isScaleOptions, keyword, map, maybeNamed, range, second, take, where, yes} from "./options.js";
-import {Scales, ScaleFunctions, autoScaleRange, exposeScales, coerceNumbers} from "./scales.js";
+import {arrayify, isDomainSort, isScaleOptions, keyword, map, maybeNamed, range, second, valueof, where, yes} from "./options.js";
+import {Scales, ScaleFunctions, autoScaleRange, exposeScales} from "./scales.js";
import {position, registry as scaleRegistry} from "./scales/index.js";
-import {inferDomain} from "./scales/quantitative.js";
import {applyInlineStyles, maybeClassName, maybeClip, styles} from "./style.js";
-import {maybeTimeFilter} from "./time.js";
+import {animate, maybeTimeFilter, defaultKey, prepareTimeScale} from "./time.js";
import {basic, initializer} from "./transforms/basic.js";
import {maybeInterval} from "./transforms/interval.js";
import {consumeWarnings} from "./warnings.js";
@@ -73,16 +72,24 @@ export function plot(options = {}) {
}
// Initialize the marks’ state.
+ let hasTime = !!options.time;
for (const mark of marks) {
if (stateByMark.has(mark)) throw new Error("duplicate mark; each mark must be unique");
- const markFacets = facetsIndex === undefined ? undefined
+
+ let markFacets = facetsIndex === undefined ? undefined
: mark.facet === "auto" ? mark.data === facet.data ? facetsIndex : undefined
: mark.facet === "include" ? facetsIndex
: mark.facet === "exclude" ? facetsExclude || (facetsExclude = facetsIndex.map(f => Uint32Array.from(difference(facetIndex, f))))
: undefined;
- const {data, facets, channels} = mark.initialize(markFacets, facetChannels);
+
+ const {data, facets, channels, time, timeFacets} = mark.initialize(markFacets, facetChannels);
applyScaleTransforms(channels, options);
- stateByMark.set(mark, {data, facets, channels});
+ if (timeFacets.length) {
+ stateByMark.set(mark, {data, facets, channels, time, timeFacets, layouts: []});
+ hasTime = true;
+ } else {
+ stateByMark.set(mark, {data, facets, channels});
+ }
}
// Initalize the scales and axes.
@@ -126,16 +133,42 @@ export function plot(options = {}) {
autoScaleLabels(channelsByScale, scaleDescriptors, axes, dimensions, options);
- // Aggregate and sort time channels.
- const timeChannels = findTimeChannels(stateByMark);
- const timeDomain = inferDomain(timeChannels);
- const times = aggregateTimes(timeChannels);
-
// Compute value objects, applying scales as needed.
for (const state of stateByMark.values()) {
state.values = valueObject(state.channels, scales);
}
+ // Infer the time scale
+ const time = hasTime && prepareTimeScale(options, stateByMark);
+ if (time) {
+ scaleDescriptors.time = time;
+ scales.time = time.scale;
+ }
+
+ for (const [mark, state] of stateByMark) {
+ const {facets, values} = state;
+
+ // Reassemble time facets
+ if (mark.time) {
+ const m = stateByMark.get(mark);
+ const {domain, time, timeFacets} = m;
+ if (domain.length <= 1) continue;
+
+ const newTime = [];
+ const newFacets = [];
+ for (let k = 0; k < facets.length; ++k) {
+ const t = time[k], j = timeFacets[k];
+ for (const i of facets[k]) newTime[i] = t;
+ newFacets[j] = newFacets[j] ? newFacets[j].concat(facets[k]) : facets[k];
+ }
+
+ state.facets = newFacets;
+ state.interp = Object.fromEntries(Object.entries(values).map(([key, value]) => [key, Array.from(value)]));
+ state.interp.time = newTime;
+ state.opacity = new Array(newTime.length).fill(1);
+ }
+ }
+
const {width, height} = dimensions;
const svg = create("svg", context)
@@ -213,90 +246,39 @@ export function plot(options = {}) {
.attr("transform", facetTranslate(fx, fy))
.each(function(key) {
const j = indexByFacet.get(key);
- for (const [mark, {channels, values, facets}] of stateByMark) {
+ for (const [mark, {channels, values, facets, layouts}] of stateByMark) {
const facet = facets ? mark.filter(facets[j] ?? facets[0], channels, values) : null;
- const node = mark.render(facet, scales, values, subdimensions, context);
- if (node != null) this.appendChild(node);
+ const index = layouts ? [] : facet;
+ const node = mark.render(index, scales, values, subdimensions, context);
+ if (node != null) {
+ this.appendChild(node);
+ if (layouts) {
+ layouts.push({
+ mark,
+ node,
+ facet,
+ dimensions: subdimensions
+ });
+ }
+ }
}
});
} else {
- const timeMarks = [];
- for (const [mark, {channels, values, facets}] of stateByMark) {
+ for (const [mark, {channels, values, facets, layouts}] of stateByMark) {
const facet = facets ? mark.filter(facets[0], channels, values) : null;
- const index = channels.time ? [] : facet;
+ const index = layouts ? [] : facet;
const node = mark.render(index, scales, values, dimensions, context);
- if (channels.time) timeMarks.push({mark, node, interp: Object.fromEntries(Object.entries(values).map(([key, value]) => [key, Array.from(value)]))});
- if (node != null) svg.appendChild(node);
- }
- if (timeMarks.length) {
- // TODO There needs to be an option to avoid interpolation and just play
- // the distinct times, as given, in ascending order, as keyframes. And
- // there needs to be an option to control the delay, duration, iterations,
- // and other timing parameters of the animation.
- const interpolateTime = interpolateNumber(...timeDomain);
- const delay = 0; // TODO configurable; delay initial rendering
- const duration = 5000; // TODO configurable
- const startTime = performance.now() + delay;
- requestAnimationFrame(function tick() {
- const t = Math.max(0, Math.min(1, (performance.now() - startTime) / duration));
- const currentTime = interpolateTime(t);
- const i0 = bisectLeft(times, currentTime);
- const time0 = times[i0 - 1];
- const time1 = times[i0];
- const timet = (currentTime - time0) / (time1 - time0);
- for (const timeMark of timeMarks) {
- const {mark, node, interp} = timeMark;
- const {channels, values, facets} = stateByMark.get(mark);
- const facet = facets ? mark.filter(facets[0], channels, values) : null;
- const {time: T} = values;
- let timeNode;
- if (isFinite(timet)) {
- const I0 = facet.filter(i => T[i] === time0); // preceding keyframe
- const I1 = facet.filter(i => T[i] === time1); // following keyframe
- const n = I0.length; // TODO enter, exit, key
- const Ii = I0.map((_, i) => i + facet.length); // TODO optimize
-
- // TODO This is interpolating the already-scaled values, but we
- // probably want to interpolate in data space instead and then
- // re-apply the scales. I’m not sure what to do for ordinal data,
- // but interpolating in data space will ensure that the resulting
- // instantaneous visualization is meaningful and valid. TODO If the
- // data is sparse (not all series have values for all times), or if
- // the data is inconsistently ordered, then we will need a separate
- // key channel to align the start and end values for interpolation;
- // this code currently assumes that the data is complete and the
- // order is consistent. TODO The sort transform (which happens by
- // default with the dot mark) breaks consistent ordering! TODO If
- // the time filter is not “eq” (strict equals) here, then we’ll need
- // to combine the interpolated data with the filtered data.
- for (const k in values) {
- if (k === "time") {
- for (let i = 0; i < n; ++i) {
- interp[k][Ii[i]] = currentTime;
- }
- } else {
- for (let i = 0; i < n; ++i) {
- interp[k][Ii[i]] = interpolate(values[k][I0[i]], values[k][I1[i]])(timet);
- }
- }
- }
-
- // TODO We need to switch to using temporal facets so that the
- // facets are guaranteed to be in chronological order. (Within a
- // facet, there’s no guarantee that the index is sorted
- // chronologically.)
- const ifacet = [...facet.filter(i => T[i] <= time0), ...Ii, ...facet.filter(i => T[i] > time0)];
- const index = mark.timeFilter(ifacet, interp.time, currentTime);
- timeNode = mark.render(index, scales, interp, dimensions, context);
- } else {
- const index = mark.timeFilter(facet, T, currentTime);
- timeNode = mark.render(index, scales, values, dimensions, context);
- }
- node.replaceWith(timeNode);
- timeMark.node = timeNode;
+ if (node != null) {
+ svg.appendChild(node);
+ if (mark.time) {
+ layouts.push({
+ mark,
+ node,
+ facet,
+ dimensions
+ });
}
- if (t < 1) requestAnimationFrame(tick);
- });
+ }
}
}
@@ -332,22 +314,34 @@ export function plot(options = {}) {
.text(`${w.toLocaleString("en-US")} warning${w === 1 ? "" : "s"}. Please check the console.`);
}
+ if (hasTime) {
+ animate(
+ stateByMark,
+ time,
+ scales,
+ figure,
+ context
+ );
+ }
+
return figure;
}
export class Mark {
constructor(data, channels = {}, options = {}, defaults) {
- const {facet = "auto", sort, time, timeFilter, dx, dy, clip, channels: extraChannels} = options;
+ const {facet = "auto", sort, time, key, timeFilter, tween, dx, dy, clip, channels: extraChannels} = options;
this.data = data;
this.sort = isDomainSort(sort) ? sort : null;
this.initializer = initializer(options).initializer;
this.transform = this.initializer ? options.transform : basic(options).transform;
this.facet = facet == null || facet === false ? null : keyword(facet === true ? "include" : facet, "facet", ["auto", "include", "exclude"]);
this.timeFilter = maybeTimeFilter(timeFilter);
+ this.tween = tween;
channels = maybeNamed(channels);
if (extraChannels !== undefined) channels = {...maybeNamed(extraChannels), ...channels};
if (defaults !== undefined) channels = {...styles(this, options, defaults), ...channels};
- if (time != null) channels = {time: {value: time}, ...channels};
+ this.time = time;
+ this.key = key;
this.channels = Object.fromEntries(Object.entries(channels).filter(([name, {value, optional}]) => {
if (value != null) return true;
if (optional) return false;
@@ -360,10 +354,20 @@ export class Mark {
initialize(facets, facetChannels) {
let data = arrayify(this.data);
if (facets === undefined && data != null) facets = [range(data)];
- if (this.transform != null) ({facets, data} = this.transform(data, facets)), data = arrayify(data);
+ const T = valueof(data, this.time), time = [], timeFacets = [];
+ if (T != null) {
+ facets = facets.flatMap((facet, j) => Array.from(
+ group(facet, i => T[i]),
+ ([t, I]) => (timeFacets.push(j), time.push(t), I)
+ ));
+ this.channels.key = {value: this.key ?? defaultKey(T), filter: null};
+ }
+ if (this.transform != null) {
+ ({data, facets} = this.transform(data, facets)), data = arrayify(data);
+ }
const channels = Channels(this.channels, data);
if (this.sort != null) channelDomain(channels, facetChannels, data, this.sort);
- return {data, facets, channels};
+ return {data, facets, channels, time, timeFacets};
}
filter(index, channels, values) {
for (const name in channels) {
@@ -449,34 +453,6 @@ function addScaleChannels(channelsByScale, stateByMark, filter = yes) {
return channelsByScale;
}
-// TODO There should be a way to set at explicit domain of the time scale, and
-// probably also a way to control whether times are expressed (and coerced) to
-// numbers or dates. And maybe non-linear (log or sqrt) time, too, or should
-// that be controlled with easing?
-function findTimeChannels(stateByMark) {
- const channels = [];
- for (const {channels: {time}} of stateByMark.values()) {
- if (time) {
- coerceNumbers(time.value); // Note: mutates!
- channels.push(time);
- }
- }
- return channels;
-}
-
-function aggregateTimes(channels) {
- const times = [];
- for (const {value} of channels) {
- for (let t of value) {
- if (t == null || isNaN(t = +t)) continue;
- const i = bisectLeft(times, t);
- if (times[i] === t) continue;
- times.splice(i, 0, t);
- }
- }
- return times;
-}
-
// Derives a copy of the specified axis with the label disabled.
function nolabel(axis) {
return axis === undefined || axis.label === undefined
diff --git a/src/scales.js b/src/scales.js
index c388997bce..e478aafa5e 100644
--- a/src/scales.js
+++ b/src/scales.js
@@ -414,10 +414,23 @@ function exposeScale({
interval,
transform,
percent,
- pivot
+ pivot,
+
+ // time
+ alternate,
+ autoplay,
+ delay,
+ direction,
+ duration,
+ initial,
+ iterations,
+ loopDelay,
+ loop,
+ playbackRate
}) {
if (type === "identity") return {type: "identity", apply: d => d, invert: d => d};
const unknown = scale.unknown ? scale.unknown() : undefined;
+
return {
type,
domain: slice(domain), // defensive copy
@@ -449,6 +462,9 @@ function exposeScale({
...scale.padding && (scale.paddingInner ? {paddingInner: scale.paddingInner(), paddingOuter: scale.paddingOuter()} : {padding: scale.padding()}),
...scale.bandwidth && {bandwidth: scale.bandwidth(), step: scale.step()},
+ // time
+ ...duration !== undefined && {alternate, autoplay, delay, direction, duration, initial, iterations, loopDelay, loop, playbackRate},
+
// utilities
apply: t => scale(t),
...scale.invert && {invert: t => scale.invert(t)}
diff --git a/src/scales/index.js b/src/scales/index.js
index 8acd8ae051..b3b0330d95 100644
--- a/src/scales/index.js
+++ b/src/scales/index.js
@@ -22,6 +22,9 @@ export const opacity = Symbol("opacity");
// Symbol scales have a default range of d3.symbols.
export const symbol = Symbol("symbol");
+// A time scale maps the given times (ordinal or continuous) to the [0, 1] interval
+export const time = Symbol("time");
+
// TODO Rather than hard-coding the list of known scale names, collect the names
// and categories for each plot specification, so that custom marks can register
// custom scales.
@@ -34,5 +37,6 @@ export const registry = new Map([
["color", color],
["opacity", opacity],
["symbol", symbol],
- ["length", length]
+ ["length", length],
+ ["time", time]
]);
diff --git a/src/time.js b/src/time.js
index ae2ae87ce9..56e3d83914 100644
--- a/src/time.js
+++ b/src/time.js
@@ -1,37 +1,410 @@
+import {
+ bisectLeft,
+ difference,
+ easeQuadInOut,
+ extent,
+ InternMap,
+ interpolate,
+ interpolateHcl,
+ interpolateHsl,
+ interpolateNumber,
+ interpolateRgb,
+ interpolateRound,
+ intersection,
+ scaleLinear
+} from "d3";
+import {constant, isObject} from "./options.js";
+import {isOrdinalScale, Scales, coerceNumbers} from "./scales.js";
+
export function maybeTimeFilter(filter = "eq") {
if (typeof filter === "function") return timeFunction(filter);
switch (`${filter}`.toLowerCase()) {
- case "lt": return timeLt;
- case "lte": return timeLte;
- case "gt": return timeGt;
- case "gte": return timeGte;
- case "eq": return timeEq;
+ case "lt":
+ return timeLt;
+ case "lte":
+ return timeLte;
+ case "gt":
+ return timeGt;
+ case "gte":
+ return timeGte;
+ case "eq":
+ return timeEq;
}
throw new Error(`invalid time filter: ${filter}`);
}
function timeFunction(f) {
return (I, T, time) => {
- return I.filter(i => f(T[i], time));
+ return I.filter((i) => f(T[i], time));
};
}
function timeLt(I, T, time) {
- return I.filter(i => T[i] < time);
+ return I.filter((i) => T[i] < time);
}
function timeLte(I, T, time) {
- return I.filter(i => T[i] <= time);
+ return I.filter((i) => T[i] <= time);
}
function timeGt(I, T, time) {
- return I.filter(i => T[i] > time);
+ return I.filter((i) => T[i] > time);
}
function timeGte(I, T, time) {
- return I.filter(i => T[i] >= time);
+ return I.filter((i) => T[i] >= time);
}
function timeEq(I, T, time) {
- return I.filter(i => T[i] === time);
+ return I.filter((i) => T[i] === time);
+}
+
+export function defaultKey(times) {
+ const tkey = new InternMap();
+ return times.map(
+ (t) => (tkey.set(t, tkey.has(t) ? 1 + tkey.get(t) : 0), tkey.get(t))
+ );
+}
+
+function maybeTween(tween, k) {
+ const t = isObject(tween) ? tween[k] : tween;
+ if (t == null) return;
+ switch (t) {
+ case "interpolateRound":
+ case "round":
+ return interpolateRound;
+ case "number":
+ return interpolateNumber;
+ case "rgb":
+ return interpolateRgb;
+ case "hsl":
+ return interpolateHsl;
+ case "hcl":
+ return interpolateHcl;
+ }
+ if (typeof t !== "function") throw new Error(`invalid tween: ${t}`);
+ return t;
+}
+
+export function prepareTimeScale(options, stateByMark) {
+ const { time } = Scales(
+ new Map([
+ ["time", Array.from(stateByMark, ([, { time }]) => ({ value: time }))]
+ ]),
+ options
+ );
+ if (isOrdinalScale(time)) {
+ const index = new InternMap(time.domain.map((d, i) => [d, i]));
+ // ordinal times are mapped to their rank in the ordinal domain
+ for (const [, m] of stateByMark) {
+ const domain = [...intersection(time.domain, m.time)];
+ m.time = m.time.map((d) => index.get(d));
+ m.domain = domain.map((d) => index.get(d));
+ }
+ } else {
+ for (const [, m] of stateByMark) {
+ if (m.time) {
+ m.time = coerceNumbers(m.time);
+ m.domain = [];
+ for (const t of m.time) {
+ if (isNaN(t) || !isFinite(t)) continue;
+ const i = bisectLeft(m.domain, t);
+ if (m.domain[i] === t) continue;
+ m.domain.splice(i, 0, t);
+ }
+ }
+ }
+ }
+ const {
+ delay = 0,
+ duration = 5000,
+ direction = 1,
+ playbackRate = 1,
+ initial,
+ autoplay = true,
+ iterations = 0,
+ loop = !!iterations,
+ alternate = false,
+ loopDelay = 1000
+ } = options.time != null ? options.time : {};
+ const interpolateTime = scaleLinear()
+ .domain(
+ isOrdinalScale(time) ? [0, time.domain.length - 1] : extent(time.domain)
+ )
+ .range([0, 1]);
+ if (typeof delay !== "number" || delay < 0 || !isFinite(delay))
+ throw new Error(`Unsupported delay ${delay}.`);
+ if (typeof duration !== "number" || duration < 0 || !isFinite(duration))
+ throw new Error(`Unsupported duration ${duration}.`);
+ if (![-1, 1, null].includes(direction))
+ throw new Error(`Unsupported direction ${direction}.`);
+ if (initial != null && Number.isNaN(interpolateTime(initial)))
+ throw new Error(`Unsupported initial time ${initial}.`);
+ if (typeof autoplay !== "boolean")
+ throw new Error(`Unsupported autoplay option ${autoplay}.`);
+ if (typeof loop !== "boolean")
+ throw new Error(`Unsupported loop option ${loop}.`);
+ if (typeof playbackRate !== "number")
+ throw new Error(`Unsupported playback rate ${playbackRate}.`);
+ if (typeof alternate !== "boolean")
+ throw new Error(`Unsupported alternate option ${alternate}.`);
+ if (typeof loopDelay !== "number" || loopDelay < 0)
+ throw new Error(`Unsupported loop delay ${loopDelay}.`);
+ return {
+ ...time,
+ delay,
+ duration,
+ direction,
+ playbackRate,
+ initial,
+ autoplay,
+ iterations,
+ loop,
+ alternate,
+ loopDelay,
+ interpolateTime
+ };
+}
+
+export function animate(stateByMark, time, scales, figure, context) {
+ const {
+ alternate,
+ autoplay,
+ delay,
+ direction,
+ duration,
+ initial,
+ iterations,
+ loopDelay,
+ interpolateTime
+ } = time;
+ let { loop, playbackRate } = time,
+ lastTick,
+ t1,
+ currentTime,
+ ended = false,
+ paused = !autoplay;
+
+ const timeupdate = (t) => {
+ if (t1 === (t = Math.max(0, Math.min(1, t)))) return;
+ currentTime = interpolateTime.invert((t1 = t));
+ for (const [mark, { layouts }] of stateByMark) {
+ if (!layouts?.length) continue;
+ for (const layout of layouts) {
+ const { facet, dimensions } = layout;
+ const {
+ channels: { key }
+ } = stateByMark.get(mark);
+ const { domain } = stateByMark.get(mark);
+ const K = key ? key.value : null;
+ const i0 = bisectLeft(domain, currentTime);
+ const time0 = domain[i0 - 1];
+ const time1 = domain[i0] !== undefined ? domain[i0] : time0;
+ const timet =
+ time1 === time0
+ ? 0
+ : (t - interpolateTime(time0)) /
+ (interpolateTime(time1) - interpolateTime(time0));
+ const { interp, opacity } = stateByMark.get(mark);
+ const T = interp.time;
+ let timeNode;
+ const I0 = facet.filter((i) => T[i] === time0); // preceding keyframe
+ const I1 = facet.filter((i) => T[i] === time1); // following keyframe
+ let enter = [],
+ update = [],
+ target = [],
+ exit = [];
+ if (K) {
+ const K0 = new Set(I0.map((i) => K[i]));
+ const K1 = new Set(I1.map((i) => K[i]));
+ const Kenter = difference(K1, K0);
+ const Kupdate = intersection(K0, K1);
+ const Kexit = difference(K0, K1);
+ enter = I1.filter((i) => Kenter.has(K[i]));
+ update = I0.filter((i) => Kupdate.has(K[i]));
+ target = update.map((i) => I1.find((j) => K[i] === K[j])); // TODO: use an index
+ exit = I0.filter((i) => Kexit.has(K[i]));
+ } else {
+ enter = I1;
+ exit = I0;
+ }
+ const n = update.length;
+ const nt = n + enter.length + exit.length;
+ const Ii = Uint32Array.from({ length: nt }).map((_, i) => i + T.length);
+ if (exit.length || enter.length) interp.opacity = opacity;
+
+ // TODO This is interpolating the already-scaled values, but we
+ // probably want to interpolate in data space instead and then
+ // re-apply the scales. I’m not sure what to do for ordinal data,
+ // but interpolating in data space will ensure that the resulting
+ // instantaneous visualization is meaningful and valid. TODO If the
+ // data is sparse (not all series have values for all times), then
+ // we will need a separate key channel to align the start and end
+ // values for interpolation; this code currently assumes that the
+ // data is complete.
+ for (const k in interp) {
+ if (k === "time") {
+ for (let i = 0; i < nt; ++i) interp[k][Ii[i]] = currentTime;
+ } else if (k === "opacity") {
+ const _exit = easeQuadInOut(1 - timet);
+ const _enter = easeQuadInOut(timet);
+ for (let i = 0; i < exit.length; ++i) interp[k][Ii[i]] = _exit;
+ for (let i = 0; i < n; ++i) interp[k][Ii[exit.length + i]] = 1;
+ for (let i = 0; i < enter.length; ++i)
+ interp[k][Ii[exit.length + n + i]] = _enter;
+ } else {
+ const tween = maybeTween(mark.tween, k);
+ const interpolator = tween
+ ? tween
+ : ["time"].includes(k)
+ ? () => constant(currentTime)
+ : ["x", "x1", "x2", "y", "y1", "y2", "r"].includes(k)
+ ? interpolateNumber
+ : ["fill", "stroke"].includes(k)
+ ? interpolateHsl
+ : ["text"].includes(k)
+ ? (a, b) =>
+ typeof a === "number"
+ ? (a % 1) || (b % 1) || Math.abs(a - b) < 3
+ ? interpolateNumber(a, b)
+ : interpolateRound(a, b)
+ : constant(a)
+ : interpolate;
+ for (let i = 0; i < exit.length; ++i)
+ interp[k][Ii[i]] = interp[k][exit[i]];
+ for (let i = 0; i < n; ++i) {
+ const prev = interp[k][update[i]],
+ next = interp[k][target[i]];
+ interp[k][Ii[i + exit.length]] =
+ prev == next ? prev : interpolator(prev, next)(timet);
+ }
+ for (let i = 0; i < enter.length; ++i)
+ interp[k][Ii[i + n + exit.length]] = interp[k][enter[i]];
+ }
+ }
+ const ifacet = [
+ ...facet.filter((i) => T[i] < time1),
+ ...(currentTime < time1 ? Ii : []),
+ ...facet.filter((i) => T[i] >= time1)
+ ];
+ const index = mark.timeFilter(ifacet, T, currentTime);
+ timeNode = mark.render(index, scales, interp, dimensions, context);
+ layout.node.replaceWith((layout.node = timeNode));
+ }
+ }
+
+ // https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/timeupdate_event
+ if (window.CustomEvent)
+ figure.dispatchEvent(new window.CustomEvent("timeupdate"));
+ };
+
+ let ticker = direction * playbackRate < 0 ? 1 : 0;
+ const tick = function () {
+ if (paused) {
+ lastTick = undefined;
+ } else {
+ // advance (or rewind) the clock by dt
+ const dt =
+ lastTick === undefined
+ ? ((lastTick = performance.now()), 0)
+ : performance.now() - lastTick;
+ lastTick += dt;
+ ticker += (dt * direction * playbackRate) / duration;
+ }
+
+ // t is the projection of the clock to the looping interval
+ let t = ticker;
+
+ if (loop) {
+ const s = 1 + loopDelay / duration;
+ const t0 = Math.floor((0.5 + Math.abs(t - 0.5)) / s);
+ if (iterations && t0 >= iterations) {
+ t = 2; // ends
+ } else {
+ const f = t - s * t0 * Math.sign(t - 0.5);
+ t = Math.max(0, Math.min(1, alternate && t0 % 2 ? 1 - f : f));
+ }
+ }
+ ended = t < 0 || t > 1;
+ if (ended) paused = true;
+
+ timeupdate(t);
+ if (figure.parentElement) requestAnimationFrame(tick);
+ };
+
+ // When using setTime, the argument is in the original time domain
+ const setTime = function (t) {
+ if (isOrdinalScale(time)) {
+ const i = time.domain.indexOf(t);
+ if (i === -1) throw new Error(`unknown time ${t}`);
+ t = i;
+ }
+ ticker = interpolateTime(t);
+ currentTime = interpolateTime.invert(ticker);
+ ended = ticker < 0 || ticker > 1;
+ lastTick = t1 = undefined;
+ timeupdate(Math.max(0, Math.min(1, ticker)));
+ };
+
+ // https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/play
+ figure.play = () => {
+ if (ended) {
+ setTime(
+ initial == null
+ ? time.domain[
+ direction * playbackRate < 0 ? time.domain.length - 1 : 0
+ ]
+ : initial
+ );
+ }
+ paused = false;
+ return new Promise((r) => r());
+ };
+
+ // https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/pause
+ figure.pause = () => {
+ paused = true;
+ t1 = undefined;
+ };
+
+ // https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/duration
+ Object.defineProperty(figure, "duration", { get: () => duration / 1000 });
+
+ // https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/paused
+ Object.defineProperty(figure, "paused", { get: () => paused });
+
+ // https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/ended
+ Object.defineProperty(figure, "ended", { get: () => ended });
+
+ // https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/currentTime
+ Object.defineProperty(figure, "currentTime", {
+ get: () =>
+ isOrdinalScale(time)
+ ? time.domain[Math.floor(currentTime)]
+ : time.type === "utc" || time.type === "time"
+ ? new Date(currentTime)
+ : currentTime,
+ set: setTime
+ });
+
+ // https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/playbackRate
+ // https://github.com/whatwg/html/issues/3754
+ Object.defineProperty(figure, "playbackRate", {
+ get: () => playbackRate,
+ set: (l) => {
+ !isNaN((l = +l)) && (playbackRate = l);
+ }
+ });
+
+ // https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/loop
+ Object.defineProperty(figure, "loop", {
+ get: () => loop,
+ set: (l) => {
+ loop = !!l;
+ }
+ });
+
+ if (initial != null) setTime(initial);
+ timeupdate(ticker);
+ setTimeout(tick, delay);
}
diff --git a/src/transforms/bin.js b/src/transforms/bin.js
index 174082ab47..344bc6e78f 100644
--- a/src/transforms/bin.js
+++ b/src/transforms/bin.js
@@ -1,5 +1,5 @@
import {bin as binner, extent, thresholdFreedmanDiaconis, thresholdScott, thresholdSturges, utcTickInterval} from "d3";
-import {valueof, range, identity, maybeColumn, maybeTuple, maybeColorChannel, maybeValue, mid, labelof, isTemporal, isIterable} from "../options.js";
+import {valueof, range, identity, maybeColumn, maybeTuple, maybeColorChannel, maybeValue, maybeZ, mid, labelof, isTemporal, isIterable} from "../options.js";
import {coerceDate, coerceNumber} from "../scales.js";
import {basic} from "./basic.js";
import {hasOutput, maybeEvaluator, maybeGroup, maybeOutput, maybeOutputs, maybeReduce, maybeSort, maybeSubgroup, reduceCount, reduceFirst, reduceIdentity} from "./group.js";
@@ -58,6 +58,11 @@ function binn(
// Compute the outputs.
outputs = maybeOutputs(outputs, inputs);
+ if (!hasOutput(outputs, "key")) {
+ outputs.push(...maybeOutputs({
+ key: (data, bin) => JSON.stringify({...bin, first: `${data[0]}`}) // TODO: InternMap approach (numbers; the keys should be opaque)
+ }, {key: maybeZ(inputs)}));
+ }
reduceData = maybeReduce(reduceData, identity);
sort = sort == null ? undefined : maybeOutput("sort", sort, inputs);
filter = filter == null ? undefined : maybeEvaluator("filter", filter, inputs);
diff --git a/src/transforms/dodge.js b/src/transforms/dodge.js
index fc68567075..73866ed49d 100644
--- a/src/transforms/dodge.js
+++ b/src/transforms/dodge.js
@@ -51,7 +51,7 @@ function dodge(y, x, anchor, padding, options) {
options = {...options, channels: {r: {value: r, scale: "r"}, ...maybeNamed(channels)}};
if (sort === undefined && reverse === undefined) options.sort = {channel: "r", order: "descending"};
}
- return initializer(options, function(data, facets, {[x]: X, r: R}, scales, dimensions) {
+ return initializer(options, function(data, facets, {[x]: X, r: R, key: K}, scales, dimensions) {
if (!X) throw new Error(`missing channel: ${x}`);
X = coerceNumbers(valueof(X.value, scales[X.scale] || identity));
const r = R ? undefined : this.r !== undefined ? this.r : options.r !== undefined ? number(options.r) : 3;
@@ -111,7 +111,8 @@ function dodge(y, x, anchor, padding, options) {
return {data, facets, channels: {
[x]: {value: X},
[y]: {value: Y},
- ...R && {r: {value: R}}
+ ...R && {r: {value: R}},
+ ...K && {key: K}
}};
});
}
diff --git a/src/transforms/group.js b/src/transforms/group.js
index f0758b32e3..99a8a3ca0f 100644
--- a/src/transforms/group.js
+++ b/src/transforms/group.js
@@ -1,6 +1,6 @@
import {group as grouper, sort, sum, deviation, min, max, mean, median, mode, variance, InternSet, minIndex, maxIndex, rollup} from "d3";
import {ascendingDefined} from "../defined.js";
-import {valueof, maybeColorChannel, maybeInput, maybeTuple, maybeColumn, column, first, identity, take, labelof, range, second, percentile} from "../options.js";
+import {valueof, maybeColorChannel, maybeInput, maybeTuple, maybeColumn, maybeZ, column, first, identity, take, labelof, range, second, percentile} from "../options.js";
import {basic} from "./basic.js";
// Group on {z, fill, stroke}.
@@ -46,6 +46,10 @@ function groupn(
// Compute the outputs.
outputs = maybeOutputs(outputs, inputs);
+ if (!hasOutput(outputs, "key")) {
+ // TODO: we also need to key on x, y…
+ outputs.push(...maybeOutputs({key: (data) => data[0]}, {key: maybeZ(inputs)}));
+ }
reduceData = maybeReduce(reduceData, identity);
sort = sort == null ? undefined : maybeOutput("sort", sort, inputs);
filter = filter == null ? undefined : maybeEvaluator("filter", filter, inputs);
diff --git a/test/data/README.md b/test/data/README.md
index 4fd08ec4a8..35cab73920 100644
--- a/test/data/README.md
+++ b/test/data/README.md
@@ -27,6 +27,10 @@ https://www.bls.gov/
1983 ASA Data Exposition
http://lib.stat.cmu.edu/datasets/
+## category-brands.csv
+Interbrand
+https://interbrand.com/best-brands/
+
## covid-ihme-projected-deaths.csv
Institute for Health Metrics and Evaluation
https://covid19.healthdata.org/
@@ -67,6 +71,10 @@ https://data.giss.nasa.gov/gistemp/
Met Office Hadley Centre
https://www.metoffice.gov.uk/hadobs/hadcrut4/data/current/series_format.html
+## historical-life-expectancy.tsv
+Wikipedia
+https://en.wikipedia.org/wiki/Life_expectancy#Variation_over_time
+
## metros.csv
The New York Times
https://www.nytimes.com/2019/12/02/upshot/wealth-poverty-divide-american-cities.html
diff --git a/test/data/category-brands.csv b/test/data/category-brands.csv
new file mode 100644
index 0000000000..755b9e1991
--- /dev/null
+++ b/test/data/category-brands.csv
@@ -0,0 +1,1976 @@
+date,name,category,value
+2000-01-01,Coca-Cola,Beverages,72537
+2000-01-01,Microsoft,Technology,70196
+2000-01-01,IBM,Business Services,53183
+2000-01-01,Intel,Technology,39048
+2000-01-01,Nokia,Technology,38528
+2000-01-01,GE,Diversified,38127
+2000-01-01,Ford,Automotive,36368
+2000-01-01,Disney,Media,33553
+2000-01-01,McDonald's,Restaurants,27859
+2000-01-01,AT&T,Telecommunications,25548
+2000-01-01,Marlboro,Tobacco,22110
+2000-01-01,Mercedes-Benz,Automotive,21104
+2000-01-01,HP,Electronics,20572
+2000-01-01,Cisco,Business Services,20067
+2000-01-01,Toyota,Automotive,18823
+2000-01-01,Citi,Financial Services,18809
+2000-01-01,Gillette,FMCG,17358
+2000-01-01,Sony,Electronics,16409
+2000-01-01,American Express,Financial Services,16122
+2000-01-01,Honda,Automotive,15244
+2000-01-01,Compaq,Technology,14602
+2000-01-01,NESCAFÉ,Beverages,13680
+2000-01-01,BMW,Automotive,12969
+2000-01-01,Kodak,Electronics,11822
+2000-01-01,Heinz,FMCG,11742
+2000-01-01,Budweiser,Alcohol,10684
+2000-01-01,Xerox,Business Services,9699
+2000-01-01,Dell,Electronics,9476
+2000-01-01,Gap,Apparel,9316
+2000-01-01,Nike,Sporting Goods,8015
+2000-01-01,Volkswagen,Automotive,7834
+2000-01-01,Ericsson,Electronics,7805
+2000-01-01,Kellogg's,FMCG,7357
+2000-01-01,Louis Vuitton,Luxury,6887
+2000-01-01,Pepsi,Beverages,6636
+2000-01-01,Apple,Technology,6594
+2000-01-01,MTV,Media,6411
+2000-01-01,Yahoo!,Media,6299
+2000-01-01,SAP,Business Services,6135
+2000-01-01,IKEA,Retail,6031
+2000-01-01,Duracell,FMCG,5885
+2000-01-01,Philips,Electronics,5481
+2000-01-01,Samsung,Technology,5223
+2000-01-01,Gucci,Luxury,5149
+2000-01-01,Kleenex,FMCG,5144
+2000-01-01,Reuters,Media,4876
+2000-01-01,AOL,Media,4531
+2000-01-01,Amazon,Technology,4528
+2000-01-01,Motorola,Electronics,4445
+2000-01-01,Colgate,FMCG,4417
+2000-01-01,Wrigley,FMCG,4324
+2000-01-01,Chanel,Luxury,4141
+2000-01-01,adidas,Sporting Goods,3791
+2000-01-01,Panasonic,Electronics,3734
+2000-01-01,Rolex,Luxury,3561
+2000-01-01,Hertz,Automotive,3438
+2000-01-01,Bacardi,Alcohol,3187
+2000-01-01,BP,Energy,3066
+2000-01-01,Moët & Chandon,Alcohol,2799
+2000-01-01,Shell,Energy,2786
+2000-01-01,Burger King,Restaurants,2701
+2000-01-01,Smirnoff,Alcohol,2443
+2000-01-01,Barbie,Toys & Games,2315
+2000-01-01,Heineken,Alcohol,2218
+2000-01-01,Wall Street Journal,Media,2184
+2000-01-01,Ralph Lauren,Apparel,1834
+2000-01-01,Johnnie Walker,Alcohol,1541
+2000-01-01,Hilton,Hospitality,1483
+2000-01-01,Jack Daniel's,Alcohol,1480
+2000-01-01,Armani,Luxury,1456
+2000-01-01,Pampers,FMCG,1400
+2000-01-01,Starbucks,Restaurants,1329
+2000-01-01,Guinness,Alcohol,1224
+2000-01-01,Financial Times,Media,1148
+2000-01-01,Benetton,Apparel,1008
+2001-01-01,Coca-Cola,Beverages,68945
+2001-01-01,Microsoft,Technology,65068
+2001-01-01,IBM,Business Services,52752
+2001-01-01,GE,Diversified,42396
+2001-01-01,Nokia,Technology,35035
+2001-01-01,Intel,Technology,34665
+2001-01-01,Disney,Media,32591
+2001-01-01,Ford,Automotive,30092
+2001-01-01,McDonald's,Restaurants,25289
+2001-01-01,AT&T,Telecommunications,22828
+2001-01-01,Marlboro,Tobacco,22053
+2001-01-01,Mercedes-Benz,Automotive,21728
+2001-01-01,Citi,Financial Services,19005
+2001-01-01,Toyota,Automotive,18578
+2001-01-01,HP,Electronics,17983
+2001-01-01,Cisco,Business Services,17209
+2001-01-01,American Express,Financial Services,16919
+2001-01-01,Gillette,FMCG,15298
+2001-01-01,Merrill Lynch,Financial Services,15015
+2001-01-01,Sony,Electronics,15005
+2001-01-01,Honda,Automotive,14638
+2001-01-01,BMW,Automotive,13858
+2001-01-01,NESCAFÉ,Beverages,13250
+2001-01-01,Compaq,Technology,12354
+2001-01-01,Oracle,Business Services,12224
+2001-01-01,Budweiser,Alcohol,10838
+2001-01-01,Kodak,Electronics,10801
+2001-01-01,"Merck & Co., Inc.",Pharmaceuticals,9672
+2001-01-01,Nintendo,Electronics,9460
+2001-01-01,Pfizer,Pharmaceuticals,8951
+2001-01-01,Gap,Apparel,8746
+2001-01-01,Dell,Electronics,8269
+2001-01-01,Goldman Sachs,Financial Services,7862
+2001-01-01,Nike,Sporting Goods,7589
+2001-01-01,Volkswagen,Automotive,7338
+2001-01-01,Ericsson,Electronics,7069
+2001-01-01,Heinz,FMCG,7062
+2001-01-01,Louis Vuitton,Luxury,7053
+2001-01-01,Kellogg's,FMCG,7005
+2001-01-01,MTV,Media,6599
+2001-01-01,Canon,Electronics,6580
+2001-01-01,Samsung,Technology,6374
+2001-01-01,SAP,Business Services,6307
+2001-01-01,Pepsi,Beverages,6214
+2001-01-01,Xerox,Business Services,6019
+2001-01-01,IKEA,Retail,6005
+2001-01-01,Pizza Hut,Restaurants,5978
+2001-01-01,Harley-Davidson,Automotive,5532
+2001-01-01,Apple,Technology,5464
+2001-01-01,Gucci,Luxury,5363
+2001-01-01,KFC,Restaurants,5261
+2001-01-01,Reuters,Media,5236
+2001-01-01,Sun Microsystems,Business Services,5149
+2001-01-01,Kleenex,FMCG,5085
+2001-01-01,Philips,Electronics,4900
+2001-01-01,Colgate,FMCG,4572
+2001-01-01,Wrigley,FMCG,4530
+2001-01-01,AOL,Media,4495
+2001-01-01,Yahoo!,Media,4378
+2001-01-01,Avon,FMCG,4369
+2001-01-01,Chanel,Luxury,4265
+2001-01-01,Duracell,FMCG,4140
+2001-01-01,Boeing,Diversified,4060
+2001-01-01,Texas Instruments,Technology,4041
+2001-01-01,Kraft,FMCG,4032
+2001-01-01,Motorola,Electronics,3761
+2001-01-01,Levi's,Apparel,3747
+2001-01-01,Time,Media,3724
+2001-01-01,Rolex,Luxury,3701
+2001-01-01,adidas,Sporting Goods,3650
+2001-01-01,Hertz,Automotive,3617
+2001-01-01,Panasonic,Electronics,3490
+2001-01-01,Tiffany & Co.,Luxury,3483
+2001-01-01,BP,Energy,3247
+2001-01-01,Bacardi,Alcohol,3204
+2001-01-01,Amazon,Technology,3130
+2001-01-01,Shell,Energy,2844
+2001-01-01,Smirnoff,Alcohol,2594
+2001-01-01,Moët & Chandon,Alcohol,2470
+2001-01-01,Burger King,Restaurants,2426
+2001-01-01,Mobil,Energy,2415
+2001-01-01,Heineken,Alcohol,2266
+2001-01-01,Wall Street Journal,Media,2184
+2001-01-01,Barbie,Toys & Games,2037
+2001-01-01,Ralph Lauren,Apparel,1910
+2001-01-01,FedEx,Logistics,1885
+2001-01-01,Nivea,FMCG,1782
+2001-01-01,Starbucks,Restaurants,1757
+2001-01-01,Johnnie Walker,Alcohol,1649
+2001-01-01,Jack Daniel's,Alcohol,1583
+2001-01-01,Armani,Luxury,1490
+2001-01-01,Pampers,FMCG,1410
+2001-01-01,Absolut,Alcohol,1378
+2001-01-01,Guinness,Alcohol,1357
+2001-01-01,Financial Times,Media,1310
+2001-01-01,Hilton,Hospitality,1235
+2001-01-01,Carlsberg,Alcohol,1075
+2001-01-01,Siemens,Diversified,1029
+2001-01-01,Swatch,FMCG,1004
+2001-01-01,Benetton,Apparel,1002
+2002-01-01,Coca-Cola,Beverages,69637
+2002-01-01,Microsoft,Technology,64091
+2002-01-01,IBM,Business Services,51188
+2002-01-01,GE,Diversified,41311
+2002-01-01,Intel,Technology,30861
+2002-01-01,Nokia,Technology,29970
+2002-01-01,Disney,Media,29256
+2002-01-01,McDonald's,Restaurants,26375
+2002-01-01,Marlboro,Tobacco,24151
+2002-01-01,Mercedes-Benz,Automotive,21010
+2002-01-01,Ford,Automotive,20403
+2002-01-01,Toyota,Automotive,19448
+2002-01-01,Citi,Financial Services,18066
+2002-01-01,HP,Electronics,16776
+2002-01-01,American Express,Financial Services,16287
+2002-01-01,Cisco,Business Services,16222
+2002-01-01,AT&T,Telecommunications,16059
+2002-01-01,Honda,Automotive,15064
+2002-01-01,Gillette,FMCG,14959
+2002-01-01,BMW,Automotive,14425
+2002-01-01,Sony,Electronics,13899
+2002-01-01,NESCAFÉ,Beverages,12843
+2002-01-01,Oracle,Business Services,11510
+2002-01-01,Budweiser,Alcohol,11349
+2002-01-01,Merrill Lynch,Financial Services,11230
+2002-01-01,Morgan Stanley,Financial Services,11205
+2002-01-01,Compaq,Technology,9803
+2002-01-01,Pfizer,Pharmaceuticals,9770
+2002-01-01,J.P. Morgan,Financial Services,9693
+2002-01-01,Kodak,Electronics,9671
+2002-01-01,Dell,Electronics,9237
+2002-01-01,Nintendo,Electronics,9219
+2002-01-01,"Merck & Co., Inc.",Pharmaceuticals,9138
+2002-01-01,Samsung,Technology,8310
+2002-01-01,Nike,Sporting Goods,7724
+2002-01-01,Gap,Apparel,7406
+2002-01-01,Heinz,FMCG,7347
+2002-01-01,Volkswagen,Automotive,7209
+2002-01-01,Goldman Sachs,Financial Services,7194
+2002-01-01,Kellogg's,FMCG,7191
+2002-01-01,Louis Vuitton,Luxury,7054
+2002-01-01,SAP,Business Services,6775
+2002-01-01,Canon,Electronics,6721
+2002-01-01,IKEA,Retail,6545
+2002-01-01,Pepsi,Beverages,6394
+2002-01-01,Harley-Davidson,Automotive,6266
+2002-01-01,MTV,Media,6078
+2002-01-01,Pizza Hut,Restaurants,6046
+2002-01-01,KFC,Restaurants,5346
+2002-01-01,Apple,Technology,5316
+2002-01-01,Xerox,Business Services,5308
+2002-01-01,Gucci,Luxury,5304
+2002-01-01,Accenture,Business Services,5182
+2002-01-01,L'Oréal,FMCG,5079
+2002-01-01,Kleenex,FMCG,5039
+2002-01-01,Sun Microsystems,Business Services,4773
+2002-01-01,Wrigley,FMCG,4747
+2002-01-01,Reuters,Media,4611
+2002-01-01,Colgate,FMCG,4602
+2002-01-01,Philips,Electronics,4561
+2002-01-01,Nestlé,FMCG,4430
+2002-01-01,Avon,FMCG,4399
+2002-01-01,AOL,Media,4326
+2002-01-01,Chanel,Luxury,4272
+2002-01-01,Kraft,FMCG,4079
+2002-01-01,Danone,FMCG,4054
+2002-01-01,Yahoo!,Media,3855
+2002-01-01,adidas,Sporting Goods,3690
+2002-01-01,Rolex,Luxury,3686
+2002-01-01,Time,Media,3682
+2002-01-01,Ericsson,Electronics,3589
+2002-01-01,Tiffany & Co.,Luxury,3482
+2002-01-01,Levi's,Apparel,3454
+2002-01-01,Motorola,Electronics,3416
+2002-01-01,Duracell,FMCG,3409
+2002-01-01,BP,Energy,3390
+2002-01-01,Hertz,Automotive,3362
+2002-01-01,Bacardi,Alcohol,3341
+2002-01-01,Caterpillar,Diversified,3218
+2002-01-01,Amazon,Technology,3175
+2002-01-01,Panasonic,Electronics,3141
+2002-01-01,Boeing,Diversified,2973
+2002-01-01,Shell,Energy,2810
+2002-01-01,Smirnoff,Alcohol,2723
+2002-01-01,Johnson & Johnson,FMCG,2509
+2002-01-01,Prada,Luxury,2489
+2002-01-01,Moët & Chandon,Alcohol,2445
+2002-01-01,Heineken,Alcohol,2396
+2002-01-01,Mobil,Energy,2358
+2002-01-01,Burger King,Restaurants,2163
+2002-01-01,Nivea,FMCG,2059
+2002-01-01,Wall Street Journal,Media,1961
+2002-01-01,Starbucks,Restaurants,1961
+2002-01-01,Barbie,Toys & Games,1937
+2002-01-01,Ralph Lauren,Apparel,1928
+2002-01-01,FedEx,Logistics,1919
+2002-01-01,Johnnie Walker,Alcohol,1654
+2002-01-01,Jack Daniel's,Alcohol,1580
+2002-01-01,3M,Diversified,1579
+2002-01-01,Armani,Luxury,1509
+2003-01-01,Coca-Cola,Beverages,70453
+2003-01-01,Microsoft,Technology,65174
+2003-01-01,IBM,Business Services,51767
+2003-01-01,GE,Diversified,42340
+2003-01-01,Intel,Technology,31112
+2003-01-01,Nokia,Technology,29440
+2003-01-01,Disney,Media,28036
+2003-01-01,McDonald's,Restaurants,24699
+2003-01-01,Marlboro,Tobacco,22183
+2003-01-01,Mercedes-Benz,Automotive,21371
+2003-01-01,Toyota,Automotive,20784
+2003-01-01,HP,Electronics,19860
+2003-01-01,Citi,Financial Services,18571
+2003-01-01,Ford,Automotive,17066
+2003-01-01,American Express,Financial Services,16833
+2003-01-01,Gillette,FMCG,15978
+2003-01-01,Cisco,Business Services,15789
+2003-01-01,Honda,Automotive,15625
+2003-01-01,BMW,Automotive,15106
+2003-01-01,Sony,Electronics,13153
+2003-01-01,NESCAFÉ,Beverages,12336
+2003-01-01,Budweiser,Alcohol,11894
+2003-01-01,Pepsi,Beverages,11777
+2003-01-01,Oracle,Business Services,11263
+2003-01-01,Samsung,Technology,10846
+2003-01-01,Morgan Stanley,Financial Services,10691
+2003-01-01,Merrill Lynch,Financial Services,10521
+2003-01-01,Pfizer,Pharmaceuticals,10455
+2003-01-01,Dell,Electronics,10367
+2003-01-01,"Merck & Co., Inc.",Pharmaceuticals,9407
+2003-01-01,J.P. Morgan,Financial Services,9120
+2003-01-01,Nintendo,Electronics,8190
+2003-01-01,Nike,Sporting Goods,8167
+2003-01-01,Kodak,Electronics,7826
+2003-01-01,SAP,Business Services,7714
+2003-01-01,Gap,Apparel,7688
+2003-01-01,HSBC,Financial Services,7565
+2003-01-01,Kellogg's,FMCG,7438
+2003-01-01,Canon,Electronics,7192
+2003-01-01,Heinz,FMCG,7097
+2003-01-01,Goldman Sachs,Financial Services,7039
+2003-01-01,Volkswagen,Automotive,6938
+2003-01-01,IKEA,Retail,6918
+2003-01-01,Harley-Davidson,Automotive,6775
+2003-01-01,Louis Vuitton,Luxury,6708
+2003-01-01,MTV,Media,6278
+2003-01-01,L'Oréal,FMCG,5600
+2003-01-01,Xerox,Business Services,5578
+2003-01-01,KFC,Restaurants,5576
+2003-01-01,Apple,Technology,5554
+2003-01-01,Pizza Hut,Restaurants,5312
+2003-01-01,Accenture,Business Services,5301
+2003-01-01,Gucci,Luxury,5100
+2003-01-01,Kleenex,FMCG,5057
+2003-01-01,Wrigley,FMCG,5057
+2003-01-01,Colgate,FMCG,4686
+2003-01-01,Avon,FMCG,4631
+2003-01-01,Sun Microsystems,Business Services,4465
+2003-01-01,Philips,Electronics,4464
+2003-01-01,Nestlé,FMCG,4460
+2003-01-01,Chanel,Luxury,4315
+2003-01-01,Danone,FMCG,4237
+2003-01-01,Kraft,FMCG,4171
+2003-01-01,AOL,Media,3961
+2003-01-01,Yahoo!,Media,3895
+2003-01-01,Time,Media,3784
+2003-01-01,adidas,Sporting Goods,3679
+2003-01-01,Rolex,Luxury,3673
+2003-01-01,BP,Energy,3582
+2003-01-01,Tiffany & Co.,Luxury,3540
+2003-01-01,Duracell,FMCG,3438
+2003-01-01,Bacardi,Alcohol,3431
+2003-01-01,Hermès,Luxury,3416
+2003-01-01,Amazon,Technology,3403
+2003-01-01,Caterpillar,Diversified,3363
+2003-01-01,Reuters,Media,3300
+2003-01-01,Levi's,Apparel,3298
+2003-01-01,Hertz,Automotive,3288
+2003-01-01,Panasonic,Electronics,3257
+2003-01-01,Ericsson,Electronics,3153
+2003-01-01,Motorola,Electronics,3103
+2003-01-01,Hennessy,Alcohol,2996
+2003-01-01,Shell,Energy,2983
+2003-01-01,Boeing,Diversified,2864
+2003-01-01,Smirnoff,Alcohol,2806
+2003-01-01,Johnson & Johnson,FMCG,2706
+2003-01-01,Prada,Luxury,2535
+2003-01-01,Moët & Chandon,Alcohol,2524
+2003-01-01,Nissan,Automotive,2495
+2003-01-01,Heineken,Alcohol,2431
+2003-01-01,Mobil,Energy,2407
+2003-01-01,Nivea,FMCG,2221
+2003-01-01,Starbucks,Restaurants,2136
+2003-01-01,Burger King,Restaurants,2121
+2003-01-01,Ralph Lauren,Apparel,2048
+2003-01-01,FedEx,Logistics,2032
+2003-01-01,Barbie,Toys & Games,1873
+2003-01-01,Wall Street Journal,Media,1763
+2003-01-01,Johnnie Walker,Alcohol,1724
+2003-01-01,Jack Daniel's,Alcohol,1612
+2004-01-01,Coca-Cola,Beverages,67394
+2004-01-01,Microsoft,Technology,61372
+2004-01-01,IBM,Business Services,53791
+2004-01-01,GE,Diversified,44111
+2004-01-01,Intel,Technology,33499
+2004-01-01,Disney,Media,27113
+2004-01-01,McDonald's,Restaurants,25001
+2004-01-01,Nokia,Technology,24041
+2004-01-01,Toyota,Automotive,22673
+2004-01-01,Marlboro,Tobacco,22128
+2004-01-01,Mercedes-Benz,Automotive,21331
+2004-01-01,HP,Electronics,20978
+2004-01-01,Citi,Financial Services,19971
+2004-01-01,American Express,Financial Services,17683
+2004-01-01,Gillette,FMCG,16723
+2004-01-01,Cisco,Business Services,15948
+2004-01-01,BMW,Automotive,15886
+2004-01-01,Honda,Automotive,14874
+2004-01-01,Ford,Automotive,14475
+2004-01-01,Sony,Electronics,12759
+2004-01-01,Samsung,Technology,12553
+2004-01-01,Pepsi,Beverages,12066
+2004-01-01,NESCAFÉ,Beverages,11892
+2004-01-01,Budweiser,Alcohol,11846
+2004-01-01,Dell,Electronics,11500
+2004-01-01,Merrill Lynch,Financial Services,11499
+2004-01-01,Morgan Stanley,Financial Services,11498
+2004-01-01,Oracle,Business Services,10935
+2004-01-01,Pfizer,Pharmaceuticals,10635
+2004-01-01,J.P. Morgan,Financial Services,9782
+2004-01-01,Nike,Sporting Goods,9260
+2004-01-01,"Merck & Co., Inc.",Pharmaceuticals,8811
+2004-01-01,HSBC,Financial Services,8671
+2004-01-01,SAP,Business Services,8323
+2004-01-01,Canon,Electronics,8055
+2004-01-01,Kellogg's,FMCG,8029
+2004-01-01,Goldman Sachs,Financial Services,7954
+2004-01-01,Gap,Apparel,7873
+2004-01-01,Siemens,Diversified,7470
+2004-01-01,IKEA,Retail,7182
+2004-01-01,Harley-Davidson,Automotive,7057
+2004-01-01,Heinz,FMCG,7026
+2004-01-01,Apple,Technology,6871
+2004-01-01,Louis Vuitton,Luxury,6602
+2004-01-01,UBS,Financial Services,6526
+2004-01-01,Nintendo,Electronics,6479
+2004-01-01,MTV,Media,6456
+2004-01-01,Volkswagen,Automotive,6410
+2004-01-01,L'Oréal,FMCG,5902
+2004-01-01,Accenture,Business Services,5772
+2004-01-01,Xerox,Business Services,5696
+2004-01-01,Wrigley,FMCG,5424
+2004-01-01,Kodak,Electronics,5231
+2004-01-01,KFC,Restaurants,5118
+2004-01-01,Pizza Hut,Restaurants,5050
+2004-01-01,Colgate,FMCG,4929
+2004-01-01,Kleenex,FMCG,4881
+2004-01-01,Avon,FMCG,4849
+2004-01-01,Gucci,Luxury,4715
+2004-01-01,eBay,Retail,4700
+2004-01-01,Yahoo!,Media,4545
+2004-01-01,Nestlé,FMCG,4529
+2004-01-01,Danone,FMCG,4488
+2004-01-01,Chanel,Luxury,4416
+2004-01-01,Philips,Electronics,4378
+2004-01-01,Amazon,Technology,4156
+2004-01-01,Kraft,FMCG,4112
+2004-01-01,Caterpillar,Diversified,3801
+2004-01-01,adidas,Sporting Goods,3740
+2004-01-01,Rolex,Luxury,3720
+2004-01-01,Reuters,Media,3691
+2004-01-01,BP,Energy,3662
+2004-01-01,Time,Media,3651
+2004-01-01,Porsche,Automotive,3646
+2004-01-01,Tiffany & Co.,Luxury,3638
+2004-01-01,Motorola,Electronics,3483
+2004-01-01,Panasonic,Electronics,3480
+2004-01-01,Hertz,Automotive,3411
+2004-01-01,Hermès,Luxury,3376
+2004-01-01,Duracell,FMCG,3362
+2004-01-01,Audi,Automotive,3288
+2004-01-01,AOL,Media,3248
+2004-01-01,Hennessy,Alcohol,3084
+2004-01-01,Shell,Energy,2985
+2004-01-01,Levi's,Apparel,2979
+2004-01-01,Smirnoff,Alcohol,2975
+2004-01-01,Johnson & Johnson,FMCG,2952
+2004-01-01,ING,Financial Services,2864
+2004-01-01,Moët & Chandon,Alcohol,2861
+2004-01-01,Nissan,Automotive,2833
+2004-01-01,Cartier,Luxury,2749
+2004-01-01,Estee Lauder,FMCG,2634
+2004-01-01,Armani,Luxury,2613
+2004-01-01,Boeing,Diversified,2576
+2004-01-01,Prada,Luxury,2568
+2004-01-01,Mobil,Energy,2492
+2004-01-01,Nivea,FMCG,2409
+2004-01-01,Starbucks,Restaurants,2400
+2004-01-01,Heineken,Alcohol,2380
+2004-01-01,Ralph Lauren,Apparel,2147
+2005-01-01,Coca-Cola,Beverages,67525
+2005-01-01,Microsoft,Technology,59941
+2005-01-01,IBM,Business Services,53376
+2005-01-01,GE,Diversified,46996
+2005-01-01,Intel,Technology,35588
+2005-01-01,Nokia,Technology,26452
+2005-01-01,Disney,Media,26441
+2005-01-01,McDonald's,Restaurants,26014
+2005-01-01,Toyota,Automotive,24837
+2005-01-01,Marlboro,Tobacco,21189
+2005-01-01,Mercedes-Benz,Automotive,20006
+2005-01-01,Citi,Financial Services,19967
+2005-01-01,HP,Electronics,18866
+2005-01-01,American Express,Financial Services,18559
+2005-01-01,Gillette,FMCG,17534
+2005-01-01,BMW,Automotive,17126
+2005-01-01,Cisco,Business Services,16592
+2005-01-01,Louis Vuitton,Luxury,16077
+2005-01-01,Honda,Automotive,15788
+2005-01-01,Samsung,Technology,14956
+2005-01-01,Dell,Electronics,13231
+2005-01-01,Ford,Automotive,13159
+2005-01-01,Pepsi,Beverages,12399
+2005-01-01,NESCAFÉ,Beverages,12241
+2005-01-01,Merrill Lynch,Financial Services,12018
+2005-01-01,Budweiser,Alcohol,11878
+2005-01-01,Oracle,Business Services,10887
+2005-01-01,Sony,Electronics,10754
+2005-01-01,HSBC,Financial Services,10429
+2005-01-01,Nike,Sporting Goods,10114
+2005-01-01,Pfizer,Pharmaceuticals,9981
+2005-01-01,UPS,Logistics,9923
+2005-01-01,Morgan Stanley,Financial Services,9777
+2005-01-01,J.P. Morgan,Financial Services,9455
+2005-01-01,Canon,Electronics,9044
+2005-01-01,SAP,Business Services,9006
+2005-01-01,Goldman Sachs,Financial Services,8495
+2005-01-01,Google,Technology,8461
+2005-01-01,Kellogg's,FMCG,8306
+2005-01-01,Gap,Apparel,8195
+2005-01-01,Apple,Technology,7985
+2005-01-01,IKEA,Retail,7817
+2005-01-01,Novartis,Pharmaceuticals,7746
+2005-01-01,UBS,Financial Services,7565
+2005-01-01,Siemens,Diversified,7507
+2005-01-01,Harley-Davidson,Automotive,7346
+2005-01-01,Heinz,FMCG,6932
+2005-01-01,MTV,Media,6647
+2005-01-01,Gucci,Luxury,6619
+2005-01-01,Nintendo,Electronics,6470
+2005-01-01,Accenture,Business Services,6142
+2005-01-01,L'Oréal,FMCG,6005
+2005-01-01,Philips,Electronics,5901
+2005-01-01,Xerox,Business Services,5705
+2005-01-01,eBay,Retail,5701
+2005-01-01,Volkswagen,Automotive,5617
+2005-01-01,Wrigley,FMCG,5543
+2005-01-01,Yahoo!,Media,5256
+2005-01-01,Avon,FMCG,5213
+2005-01-01,Colgate,FMCG,5186
+2005-01-01,KFC,Restaurants,5112
+2005-01-01,Kodak,Electronics,4979
+2005-01-01,Pizza Hut,Restaurants,4963
+2005-01-01,Kleenex,FMCG,4922
+2005-01-01,Chanel,Luxury,4778
+2005-01-01,Nestlé,FMCG,4744
+2005-01-01,Danone,FMCG,4513
+2005-01-01,Amazon,Technology,4248
+2005-01-01,Kraft,FMCG,4238
+2005-01-01,Caterpillar,Diversified,4085
+2005-01-01,adidas,Sporting Goods,4033
+2005-01-01,Rolex,Luxury,3906
+2005-01-01,Motorola,Electronics,3877
+2005-01-01,Reuters,Media,3866
+2005-01-01,BP,Energy,3802
+2005-01-01,Porsche,Automotive,3777
+2005-01-01,Zara,Apparel,3730
+2005-01-01,Panasonic,Electronics,3714
+2005-01-01,Audi,Automotive,3686
+2005-01-01,Duracell,FMCG,3679
+2005-01-01,Tiffany & Co.,Luxury,3618
+2005-01-01,Hermès,Luxury,3540
+2005-01-01,Hertz,Automotive,3521
+2005-01-01,Hyundai,Automotive,3480
+2005-01-01,Nissan,Automotive,3203
+2005-01-01,Hennessy,Alcohol,3201
+2005-01-01,ING,Financial Services,3177
+2005-01-01,Smirnoff,Alcohol,3097
+2005-01-01,Cartier,Luxury,3050
+2005-01-01,Shell,Energy,3048
+2005-01-01,Johnson & Johnson,FMCG,3040
+2005-01-01,Moët & Chandon,Alcohol,2991
+2005-01-01,Prada,Luxury,2760
+2005-01-01,Bulgari,Luxury,2715
+2005-01-01,Armani,Luxury,2677
+2005-01-01,Levi's,Apparel,2655
+2005-01-01,LG,Electronics,2645
+2005-01-01,Nivea,FMCG,2576
+2005-01-01,Starbucks,Restaurants,2576
+2005-01-01,Heineken,Alcohol,2357
+2006-01-01,Coca-Cola,Beverages,67000
+2006-01-01,Microsoft,Technology,56926
+2006-01-01,IBM,Business Services,56201
+2006-01-01,GE,Diversified,48907
+2006-01-01,Intel,Technology,32319
+2006-01-01,Nokia,Technology,30131
+2006-01-01,Toyota,Automotive,27941
+2006-01-01,Disney,Media,27848
+2006-01-01,McDonald's,Restaurants,27501
+2006-01-01,Mercedes-Benz,Automotive,21795
+2006-01-01,Citi,Financial Services,21458
+2006-01-01,Marlboro,Tobacco,21350
+2006-01-01,HP,Electronics,20458
+2006-01-01,American Express,Financial Services,19641
+2006-01-01,BMW,Automotive,19617
+2006-01-01,Gillette,FMCG,19579
+2006-01-01,Louis Vuitton,Luxury,17606
+2006-01-01,Cisco,Business Services,17532
+2006-01-01,Honda,Automotive,17049
+2006-01-01,Samsung,Technology,16169
+2006-01-01,Merrill Lynch,Financial Services,13001
+2006-01-01,Pepsi,Beverages,12690
+2006-01-01,NESCAFÉ,Beverages,12507
+2006-01-01,Google,Technology,12376
+2006-01-01,Dell,Electronics,12256
+2006-01-01,Sony,Electronics,11695
+2006-01-01,Budweiser,Alcohol,11662
+2006-01-01,HSBC,Financial Services,11622
+2006-01-01,Oracle,Business Services,11459
+2006-01-01,Ford,Automotive,11056
+2006-01-01,Nike,Sporting Goods,10897
+2006-01-01,UPS,Logistics,10712
+2006-01-01,J.P. Morgan,Financial Services,10205
+2006-01-01,SAP,Business Services,10007
+2006-01-01,Canon,Electronics,9968
+2006-01-01,Morgan Stanley,Financial Services,9762
+2006-01-01,Goldman Sachs,Financial Services,9640
+2006-01-01,Pfizer,Pharmaceuticals,9591
+2006-01-01,Apple,Technology,9130
+2006-01-01,Kellogg's,FMCG,8776
+2006-01-01,IKEA,Retail,8763
+2006-01-01,UBS,Financial Services,8734
+2006-01-01,Novartis,Pharmaceuticals,7880
+2006-01-01,Siemens,Diversified,7828
+2006-01-01,Harley-Davidson,Automotive,7739
+2006-01-01,Gucci,Luxury,7158
+2006-01-01,eBay,Retail,6755
+2006-01-01,Philips,Electronics,6730
+2006-01-01,Accenture,Business Services,6728
+2006-01-01,MTV,Media,6627
+2006-01-01,Nintendo,Electronics,6559
+2006-01-01,Gap,Apparel,6416
+2006-01-01,L'Oréal,FMCG,6392
+2006-01-01,Heinz,FMCG,6223
+2006-01-01,Yahoo!,Media,6056
+2006-01-01,Volkswagen,Automotive,6032
+2006-01-01,Xerox,Business Services,5918
+2006-01-01,Colgate,FMCG,5633
+2006-01-01,Wrigley,FMCG,5449
+2006-01-01,KFC,Restaurants,5350
+2006-01-01,Chanel,Luxury,5156
+2006-01-01,Avon,FMCG,5040
+2006-01-01,Nestlé,FMCG,4932
+2006-01-01,Kleenex,FMCG,4842
+2006-01-01,Amazon,Technology,4707
+2006-01-01,Pizza Hut,Restaurants,4694
+2006-01-01,Danone,FMCG,4638
+2006-01-01,Caterpillar,Diversified,4580
+2006-01-01,Motorola,Electronics,4569
+2006-01-01,Kodak,Electronics,4406
+2006-01-01,adidas,Sporting Goods,4290
+2006-01-01,Rolex,Luxury,4237
+2006-01-01,Zara,Apparel,4235
+2006-01-01,Audi,Automotive,4165
+2006-01-01,Hyundai,Automotive,4078
+2006-01-01,BP,Energy,4010
+2006-01-01,Panasonic,Electronics,3977
+2006-01-01,Reuters,Media,3961
+2006-01-01,Kraft,FMCG,3943
+2006-01-01,Porsche,Automotive,3927
+2006-01-01,Hermès,Luxury,3854
+2006-01-01,Tiffany & Co.,Luxury,3819
+2006-01-01,Hennessy,Alcohol,3576
+2006-01-01,Duracell,FMCG,3576
+2006-01-01,ING,Financial Services,3474
+2006-01-01,Cartier,Luxury,3360
+2006-01-01,Moët & Chandon,Alcohol,3257
+2006-01-01,Johnson & Johnson,FMCG,3193
+2006-01-01,Shell,Energy,3173
+2006-01-01,Nissan,Automotive,3108
+2006-01-01,Starbucks,Restaurants,3099
+2006-01-01,Lexus,Automotive,3070
+2006-01-01,Smirnoff,Alcohol,3032
+2006-01-01,LG,Electronics,3010
+2006-01-01,Bulgari,Luxury,2875
+2006-01-01,Prada,Luxury,2874
+2006-01-01,Armani,Luxury,2783
+2006-01-01,Burberry,Luxury,2783
+2006-01-01,Nivea,FMCG,2692
+2006-01-01,Levi's,Apparel,2689
+2007-01-01,Coca-Cola,Beverages,65324
+2007-01-01,Microsoft,Technology,58709
+2007-01-01,IBM,Business Services,57090
+2007-01-01,GE,Diversified,51569
+2007-01-01,Nokia,Technology,33696
+2007-01-01,Toyota,Automotive,32070
+2007-01-01,Intel,Technology,30954
+2007-01-01,McDonald's,Restaurants,29398
+2007-01-01,Disney,Media,29210
+2007-01-01,Mercedes-Benz,Automotive,23568
+2007-01-01,Citi,Financial Services,23442
+2007-01-01,HP,Electronics,22197
+2007-01-01,BMW,Automotive,21612
+2007-01-01,Marlboro,Tobacco,21282
+2007-01-01,American Express,Financial Services,20827
+2007-01-01,Gillette,FMCG,20415
+2007-01-01,Louis Vuitton,Luxury,20321
+2007-01-01,Cisco,Business Services,19099
+2007-01-01,Honda,Automotive,17998
+2007-01-01,Google,Technology,17837
+2007-01-01,Samsung,Technology,16853
+2007-01-01,Merrill Lynch,Financial Services,14343
+2007-01-01,HSBC,Financial Services,13563
+2007-01-01,NESCAFÉ,Beverages,12950
+2007-01-01,Sony,Electronics,12907
+2007-01-01,Pepsi,Beverages,12888
+2007-01-01,Oracle,Business Services,12448
+2007-01-01,UPS,Logistics,12013
+2007-01-01,Nike,Sporting Goods,12003
+2007-01-01,Budweiser,Alcohol,11652
+2007-01-01,Dell,Electronics,11554
+2007-01-01,J.P. Morgan,Financial Services,11433
+2007-01-01,Apple,Technology,11037
+2007-01-01,SAP,Business Services,10850
+2007-01-01,Goldman Sachs,Financial Services,10663
+2007-01-01,Canon,Electronics,10581
+2007-01-01,Morgan Stanley,Financial Services,10340
+2007-01-01,IKEA,Retail,10087
+2007-01-01,UBS,Financial Services,9838
+2007-01-01,Kellogg's,FMCG,9341
+2007-01-01,Ford,Automotive,8982
+2007-01-01,Philips,Electronics,7741
+2007-01-01,Siemens,Diversified,7737
+2007-01-01,Nintendo,Electronics,7730
+2007-01-01,Harley-Davidson,Automotive,7718
+2007-01-01,Gucci,Luxury,7697
+2007-01-01,AIG,Financial Services,7490
+2007-01-01,eBay,Retail,7456
+2007-01-01,AXA,Financial Services,7327
+2007-01-01,Accenture,Business Services,7296
+2007-01-01,L'Oréal,FMCG,7045
+2007-01-01,MTV,Media,6907
+2007-01-01,Heinz,FMCG,6544
+2007-01-01,Volkswagen,Automotive,6511
+2007-01-01,Yahoo!,Media,6067
+2007-01-01,Xerox,Business Services,6050
+2007-01-01,Colgate,FMCG,6025
+2007-01-01,Chanel,Luxury,5830
+2007-01-01,Wrigley,FMCG,5777
+2007-01-01,KFC,Restaurants,5682
+2007-01-01,Gap,Apparel,5481
+2007-01-01,Amazon,Technology,5411
+2007-01-01,Nestlé,FMCG,5314
+2007-01-01,Zara,Apparel,5165
+2007-01-01,Avon,FMCG,5103
+2007-01-01,Caterpillar,Diversified,5059
+2007-01-01,Danone,FMCG,5019
+2007-01-01,Audi,Automotive,4866
+2007-01-01,adidas,Sporting Goods,4767
+2007-01-01,Kleenex,FMCG,4600
+2007-01-01,Rolex,Luxury,4589
+2007-01-01,Hyundai,Automotive,4453
+2007-01-01,Hermès,Luxury,4255
+2007-01-01,Pizza Hut,Restaurants,4254
+2007-01-01,Porsche,Automotive,4235
+2007-01-01,Reuters,Media,4197
+2007-01-01,Motorola,Electronics,4149
+2007-01-01,Panasonic,Electronics,4135
+2007-01-01,Tiffany & Co.,Luxury,4003
+2007-01-01,Allianz,Financial Services,3957
+2007-01-01,ING,Financial Services,3880
+2007-01-01,Kodak,Electronics,3874
+2007-01-01,Cartier,Luxury,3852
+2007-01-01,BP,Energy,3794
+2007-01-01,Moët & Chandon,Alcohol,3739
+2007-01-01,Kraft,FMCG,3732
+2007-01-01,Hennessy,Alcohol,3638
+2007-01-01,Starbucks,Restaurants,3631
+2007-01-01,Duracell,FMCG,3605
+2007-01-01,Johnson & Johnson,FMCG,3445
+2007-01-01,Smirnoff,Alcohol,3379
+2007-01-01,Lexus,Automotive,3354
+2007-01-01,Shell,Energy,3331
+2007-01-01,Prada,Luxury,3286
+2007-01-01,Burberry,Luxury,3221
+2007-01-01,Nivea,FMCG,3116
+2007-01-01,LG,Electronics,3100
+2007-01-01,Nissan,Automotive,3072
+2007-01-01,Ralph Lauren,Apparel,3046
+2007-01-01,Hertz,Automotive,3026
+2008-01-01,Coca-Cola,Beverages,66667
+2008-01-01,IBM,Business Services,59031
+2008-01-01,Microsoft,Technology,59007
+2008-01-01,GE,Diversified,53086
+2008-01-01,Nokia,Technology,35942
+2008-01-01,Toyota,Automotive,34050
+2008-01-01,Intel,Technology,31261
+2008-01-01,McDonald's,Restaurants,31049
+2008-01-01,Disney,Media,29251
+2008-01-01,Google,Technology,25590
+2008-01-01,Mercedes-Benz,Automotive,25577
+2008-01-01,HP,Electronics,23509
+2008-01-01,BMW,Automotive,23298
+2008-01-01,Gillette,FMCG,22689
+2008-01-01,American Express,Financial Services,21940
+2008-01-01,Louis Vuitton,Luxury,21602
+2008-01-01,Cisco,Business Services,21306
+2008-01-01,Marlboro,Tobacco,21300
+2008-01-01,Citi,Financial Services,20174
+2008-01-01,Honda,Automotive,19079
+2008-01-01,Samsung,Technology,17689
+2008-01-01,H&M,Apparel,13840
+2008-01-01,Oracle,Business Services,13831
+2008-01-01,Apple,Technology,13724
+2008-01-01,Sony,Electronics,13583
+2008-01-01,Pepsi,Beverages,13249
+2008-01-01,HSBC,Financial Services,13143
+2008-01-01,NESCAFÉ,Beverages,13056
+2008-01-01,Nike,Sporting Goods,12672
+2008-01-01,UPS,Logistics,12621
+2008-01-01,SAP,Business Services,12228
+2008-01-01,Dell,Electronics,11695
+2008-01-01,Budweiser,Alcohol,11438
+2008-01-01,Merrill Lynch,Financial Services,11399
+2008-01-01,IKEA,Retail,10913
+2008-01-01,Canon,Electronics,10876
+2008-01-01,J.P. Morgan,Financial Services,10773
+2008-01-01,Goldman Sachs,Financial Services,10331
+2008-01-01,Kellogg's,FMCG,9710
+2008-01-01,Nintendo,Electronics,8772
+2008-01-01,UBS,Financial Services,8740
+2008-01-01,Morgan Stanley,Financial Services,8696
+2008-01-01,Philips,Electronics,8325
+2008-01-01,Thomson Reuters,Media,8313
+2008-01-01,Gucci,Luxury,8254
+2008-01-01,eBay,Retail,7991
+2008-01-01,Accenture,Business Services,7948
+2008-01-01,Siemens,Diversified,7943
+2008-01-01,Ford,Automotive,7896
+2008-01-01,Harley-Davidson,Automotive,7609
+2008-01-01,L'Oréal,FMCG,7508
+2008-01-01,MTV,Media,7193
+2008-01-01,Volkswagen,Automotive,7047
+2008-01-01,AIG,Financial Services,7022
+2008-01-01,AXA,Financial Services,7001
+2008-01-01,Heinz,FMCG,6646
+2008-01-01,Colgate,FMCG,6437
+2008-01-01,Amazon,Technology,6434
+2008-01-01,Xerox,Business Services,6393
+2008-01-01,Chanel,Luxury,6355
+2008-01-01,Wrigley,FMCG,6105
+2008-01-01,Zara,Apparel,5955
+2008-01-01,Nestlé,FMCG,5592
+2008-01-01,KFC,Restaurants,5582
+2008-01-01,Yahoo!,Media,5496
+2008-01-01,Danone,FMCG,5408
+2008-01-01,Audi,Automotive,5407
+2008-01-01,Caterpillar,Diversified,5288
+2008-01-01,Avon,FMCG,5264
+2008-01-01,adidas,Sporting Goods,5072
+2008-01-01,Rolex,Luxury,4956
+2008-01-01,Hyundai,Automotive,4846
+2008-01-01,BlackBerry,Electronics,4802
+2008-01-01,Kleenex,FMCG,4636
+2008-01-01,Porsche,Automotive,4603
+2008-01-01,Hermès,Luxury,4575
+2008-01-01,Gap,Apparel,4357
+2008-01-01,Panasonic,Electronics,4281
+2008-01-01,Cartier,Luxury,4236
+2008-01-01,Tiffany & Co.,Luxury,4208
+2008-01-01,Pizza Hut,Restaurants,4097
+2008-01-01,Allianz,Financial Services,4033
+2008-01-01,Moët & Chandon,Alcohol,3951
+2008-01-01,BP,Energy,3911
+2008-01-01,Starbucks,Restaurants,3879
+2008-01-01,ING,Financial Services,3768
+2008-01-01,Motorola,Electronics,3721
+2008-01-01,Duracell,FMCG,3682
+2008-01-01,Smirnoff,Alcohol,3590
+2008-01-01,Lexus,Automotive,3588
+2008-01-01,Prada,Luxury,3585
+2008-01-01,Johnson & Johnson,FMCG,3582
+2008-01-01,Ferrari,Automotive,3527
+2008-01-01,Armani,Luxury,3526
+2008-01-01,Hennessy,Alcohol,3513
+2008-01-01,Marriott,Hospitality,3502
+2008-01-01,Shell,Energy,3471
+2008-01-01,Nivea,FMCG,3401
+2008-01-01,FedEx,Logistics,3359
+2008-01-01,Visa,Financial Services,3338
+2009-01-01,Coca-Cola,Beverages,68734
+2009-01-01,IBM,Business Services,60211
+2009-01-01,Microsoft,Technology,56647
+2009-01-01,GE,Diversified,47777
+2009-01-01,Nokia,Technology,34864
+2009-01-01,McDonald's,Restaurants,32275
+2009-01-01,Google,Technology,31980
+2009-01-01,Toyota,Automotive,31330
+2009-01-01,Intel,Technology,30636
+2009-01-01,Disney,Media,28447
+2009-01-01,HP,Electronics,24096
+2009-01-01,Mercedes-Benz,Automotive,23867
+2009-01-01,Gillette,FMCG,22841
+2009-01-01,Cisco,Business Services,22030
+2009-01-01,BMW,Automotive,21671
+2009-01-01,Louis Vuitton,Luxury,21120
+2009-01-01,Marlboro,Tobacco,19010
+2009-01-01,Honda,Automotive,17803
+2009-01-01,Samsung,Technology,17518
+2009-01-01,Apple,Technology,15433
+2009-01-01,H&M,Apparel,15375
+2009-01-01,American Express,Financial Services,14971
+2009-01-01,Pepsi,Beverages,13706
+2009-01-01,Oracle,Business Services,13699
+2009-01-01,NESCAFÉ,Beverages,13317
+2009-01-01,Nike,Sporting Goods,13179
+2009-01-01,SAP,Business Services,12106
+2009-01-01,IKEA,Retail,12004
+2009-01-01,Sony,Electronics,11953
+2009-01-01,Budweiser,Alcohol,11833
+2009-01-01,UPS,Logistics,11594
+2009-01-01,HSBC,Financial Services,10510
+2009-01-01,Canon,Electronics,10441
+2009-01-01,Kellogg's,FMCG,10428
+2009-01-01,Dell,Electronics,10291
+2009-01-01,Citi,Financial Services,10254
+2009-01-01,J.P. Morgan,Financial Services,9550
+2009-01-01,Goldman Sachs,Financial Services,9248
+2009-01-01,Nintendo,Electronics,9210
+2009-01-01,Thomson Reuters,Media,8434
+2009-01-01,Gucci,Luxury,8182
+2009-01-01,Philips,Electronics,8121
+2009-01-01,Amazon,Technology,7858
+2009-01-01,L'Oréal,FMCG,7748
+2009-01-01,Accenture,Business Services,7710
+2009-01-01,eBay,Retail,7350
+2009-01-01,Siemens,Diversified,7308
+2009-01-01,Heinz,FMCG,7244
+2009-01-01,Ford,Automotive,7005
+2009-01-01,Zara,Apparel,6789
+2009-01-01,Wrigley,FMCG,6731
+2009-01-01,Colgate,FMCG,6550
+2009-01-01,AXA,Financial Services,6525
+2009-01-01,MTV,Media,6523
+2009-01-01,Volkswagen,Automotive,6484
+2009-01-01,Xerox,Business Services,6431
+2009-01-01,Morgan Stanley,Financial Services,6399
+2009-01-01,Nestlé,FMCG,6319
+2009-01-01,Chanel,Luxury,6040
+2009-01-01,Danone,FMCG,5960
+2009-01-01,KFC,Restaurants,5722
+2009-01-01,adidas,Sporting Goods,5397
+2009-01-01,BlackBerry,Electronics,5138
+2009-01-01,Yahoo!,Media,5111
+2009-01-01,Audi,Automotive,5010
+2009-01-01,Caterpillar,Diversified,5004
+2009-01-01,Avon,FMCG,4917
+2009-01-01,Rolex,Luxury,4609
+2009-01-01,Hyundai,Automotive,4604
+2009-01-01,Hermès,Luxury,4598
+2009-01-01,Kleenex,FMCG,4404
+2009-01-01,UBS,Financial Services,4370
+2009-01-01,Harley-Davidson,Automotive,4337
+2009-01-01,Porsche,Automotive,4234
+2009-01-01,Panasonic,Electronics,4225
+2009-01-01,Tiffany & Co.,Luxury,4000
+2009-01-01,Cartier,Luxury,3968
+2009-01-01,Gap,Apparel,3922
+2009-01-01,Pizza Hut,Restaurants,3876
+2009-01-01,Johnson & Johnson,FMCG,3847
+2009-01-01,Allianz,Financial Services,3831
+2009-01-01,Moët & Chandon,Alcohol,3754
+2009-01-01,BP,Energy,3716
+2009-01-01,Smirnoff,Alcohol,3698
+2009-01-01,Duracell,FMCG,3563
+2009-01-01,Nivea,FMCG,3557
+2009-01-01,Prada,Luxury,3530
+2009-01-01,Ferrari,Automotive,3527
+2009-01-01,Armani,Luxury,3303
+2009-01-01,Starbucks,Restaurants,3263
+2009-01-01,Lancôme,FMCG,3235
+2009-01-01,Shell,Energy,3228
+2009-01-01,Burger King,Restaurants,3223
+2009-01-01,Visa,Financial Services,3170
+2009-01-01,Adobe,Business Services,3161
+2009-01-01,Lexus,Automotive,3158
+2009-01-01,Puma,Sporting Goods,3154
+2009-01-01,Burberry,Luxury,3095
+2009-01-01,Ralph Lauren,Apparel,3094
+2009-01-01,Campbell's,FMCG,3081
+2010-01-01,Coca-Cola,Beverages,70452
+2010-01-01,IBM,Business Services,64727
+2010-01-01,Microsoft,Technology,60895
+2010-01-01,Google,Technology,43557
+2010-01-01,GE,Diversified,42808
+2010-01-01,McDonald's,Restaurants,33578
+2010-01-01,Intel,Technology,32015
+2010-01-01,Nokia,Technology,29495
+2010-01-01,Disney,Media,28731
+2010-01-01,HP,Electronics,26867
+2010-01-01,Toyota,Automotive,26192
+2010-01-01,Mercedes-Benz,Automotive,25179
+2010-01-01,Gillette,FMCG,23298
+2010-01-01,Cisco,Business Services,23219
+2010-01-01,BMW,Automotive,22322
+2010-01-01,Louis Vuitton,Luxury,21860
+2010-01-01,Apple,Technology,21143
+2010-01-01,Marlboro,Tobacco,19961
+2010-01-01,Samsung,Technology,19491
+2010-01-01,Honda,Automotive,18506
+2010-01-01,H&M,Apparel,16136
+2010-01-01,Oracle,Business Services,14881
+2010-01-01,Pepsi,Beverages,14061
+2010-01-01,American Express,Financial Services,13944
+2010-01-01,Nike,Sporting Goods,13706
+2010-01-01,SAP,Business Services,12756
+2010-01-01,NESCAFÉ,Beverages,12753
+2010-01-01,IKEA,Retail,12487
+2010-01-01,J.P. Morgan,Financial Services,12314
+2010-01-01,Budweiser,Alcohol,12252
+2010-01-01,UPS,Logistics,11826
+2010-01-01,HSBC,Financial Services,11561
+2010-01-01,Canon,Electronics,11485
+2010-01-01,Sony,Electronics,11356
+2010-01-01,Kellogg's,FMCG,11041
+2010-01-01,Amazon,Technology,9665
+2010-01-01,Goldman Sachs,Financial Services,9372
+2010-01-01,Nintendo,Electronics,8990
+2010-01-01,Thomson Reuters,Media,8976
+2010-01-01,Citi,Financial Services,8887
+2010-01-01,Dell,Electronics,8880
+2010-01-01,Philips,Electronics,8696
+2010-01-01,eBay,Retail,8453
+2010-01-01,Gucci,Luxury,8346
+2010-01-01,L'Oréal,FMCG,7981
+2010-01-01,Heinz,FMCG,7534
+2010-01-01,Accenture,Business Services,7481
+2010-01-01,Zara,Apparel,7468
+2010-01-01,Siemens,Diversified,7315
+2010-01-01,Ford,Automotive,7195
+2010-01-01,Colgate,FMCG,6919
+2010-01-01,Morgan Stanley,Financial Services,6911
+2010-01-01,Volkswagen,Automotive,6892
+2010-01-01,BlackBerry,Electronics,6762
+2010-01-01,MTV,Media,6719
+2010-01-01,AXA,Financial Services,6694
+2010-01-01,Nestlé,FMCG,6548
+2010-01-01,Danone,FMCG,6363
+2010-01-01,Xerox,Business Services,6109
+2010-01-01,KFC,Restaurants,5844
+2010-01-01,Sprite,Beverages,5777
+2010-01-01,adidas,Sporting Goods,5495
+2010-01-01,Audi,Automotive,5461
+2010-01-01,Avon,FMCG,5072
+2010-01-01,Hyundai,Automotive,5033
+2010-01-01,Yahoo!,Media,4958
+2010-01-01,Allianz,Financial Services,4904
+2010-01-01,Banco Santander,Financial Services,4846
+2010-01-01,Hermès,Luxury,4782
+2010-01-01,Caterpillar,Diversified,4704
+2010-01-01,Kleenex,FMCG,4536
+2010-01-01,Porsche,Automotive,4404
+2010-01-01,Panasonic,Electronics,4351
+2010-01-01,BARCLAYS,Financial Services,4218
+2010-01-01,Johnson & Johnson,FMCG,4155
+2010-01-01,Tiffany & Co.,Luxury,4127
+2010-01-01,Cartier,Luxury,4052
+2010-01-01,Jack Daniel's,Alcohol,4036
+2010-01-01,Moët & Chandon,Alcohol,4021
+2010-01-01,Credit Suisse,Financial Services,4010
+2010-01-01,Shell,Energy,4003
+2010-01-01,Visa,Financial Services,3998
+2010-01-01,Pizza Hut,Restaurants,3973
+2010-01-01,Gap,Apparel,3961
+2010-01-01,Corona,Alcohol,3847
+2010-01-01,UBS,Financial Services,3812
+2010-01-01,Nivea,FMCG,3734
+2010-01-01,Adobe,Business Services,3626
+2010-01-01,Smirnoff,Alcohol,3624
+2010-01-01,3M,Diversified,3586
+2010-01-01,Ferrari,Automotive,3562
+2010-01-01,Johnnie Walker,Alcohol,3557
+2010-01-01,Heineken,Alcohol,3516
+2010-01-01,ZURICH,Financial Services,3496
+2010-01-01,Armani,Luxury,3443
+2010-01-01,Lancôme,FMCG,3403
+2010-01-01,Starbucks,Restaurants,3339
+2010-01-01,Harley-Davidson,Automotive,3281
+2010-01-01,Campbell's,FMCG,3241
+2010-01-01,Burberry,Luxury,3110
+2011-01-01,Coca-Cola,Beverages,71861
+2011-01-01,IBM,Business Services,69905
+2011-01-01,Microsoft,Technology,59087
+2011-01-01,Google,Technology,55317
+2011-01-01,GE,Diversified,42808
+2011-01-01,McDonald's,Restaurants,35593
+2011-01-01,Intel,Technology,35217
+2011-01-01,Apple,Technology,33492
+2011-01-01,Disney,Media,29018
+2011-01-01,HP,Electronics,28479
+2011-01-01,Toyota,Automotive,27764
+2011-01-01,Mercedes-Benz,Automotive,27445
+2011-01-01,Cisco,Business Services,25309
+2011-01-01,Nokia,Technology,25071
+2011-01-01,BMW,Automotive,24554
+2011-01-01,Gillette,FMCG,23997
+2011-01-01,Samsung,Technology,23430
+2011-01-01,Louis Vuitton,Luxury,23172
+2011-01-01,Honda,Automotive,19431
+2011-01-01,Oracle,Business Services,17262
+2011-01-01,H&M,Apparel,16459
+2011-01-01,Pepsi,Beverages,14590
+2011-01-01,American Express,Financial Services,14572
+2011-01-01,SAP,Business Services,14542
+2011-01-01,Nike,Sporting Goods,14528
+2011-01-01,Amazon,Technology,12758
+2011-01-01,UPS,Logistics,12536
+2011-01-01,J.P. Morgan,Financial Services,12437
+2011-01-01,Budweiser,Alcohol,12252
+2011-01-01,NESCAFÉ,Beverages,12115
+2011-01-01,IKEA,Retail,11863
+2011-01-01,HSBC,Financial Services,11792
+2011-01-01,Canon,Electronics,11715
+2011-01-01,Kellogg's,FMCG,11372
+2011-01-01,Sony,Electronics,9880
+2011-01-01,eBay,Retail,9805
+2011-01-01,Thomson Reuters,Media,9515
+2011-01-01,Goldman Sachs,Financial Services,9091
+2011-01-01,Gucci,Luxury,8763
+2011-01-01,L'Oréal,FMCG,8699
+2011-01-01,Philips,Electronics,8658
+2011-01-01,Citi,Financial Services,8620
+2011-01-01,Dell,Electronics,8347
+2011-01-01,Zara,Apparel,8065
+2011-01-01,Accenture,Business Services,8005
+2011-01-01,Siemens,Diversified,7900
+2011-01-01,Volkswagen,Automotive,7857
+2011-01-01,Nintendo,Electronics,7731
+2011-01-01,Heinz,FMCG,7609
+2011-01-01,Ford,Automotive,7483
+2011-01-01,Colgate,FMCG,7127
+2011-01-01,Danone,FMCG,6936
+2011-01-01,AXA,Financial Services,6694
+2011-01-01,Morgan Stanley,Financial Services,6634
+2011-01-01,Nestlé,FMCG,6613
+2011-01-01,BlackBerry,Electronics,6424
+2011-01-01,Xerox,Business Services,6414
+2011-01-01,MTV,Media,6383
+2011-01-01,Audi,Automotive,6171
+2011-01-01,adidas,Sporting Goods,6154
+2011-01-01,Hyundai,Automotive,6005
+2011-01-01,KFC,Restaurants,5902
+2011-01-01,Sprite,Beverages,5604
+2011-01-01,Caterpillar,Diversified,5598
+2011-01-01,Avon,FMCG,5376
+2011-01-01,Hermès,Luxury,5356
+2011-01-01,Allianz,Financial Services,5345
+2011-01-01,Banco Santander,Financial Services,5088
+2011-01-01,Panasonic,Electronics,5047
+2011-01-01,Cartier,Luxury,4781
+2011-01-01,Kleenex,FMCG,4672
+2011-01-01,Porsche,Automotive,4580
+2011-01-01,Tiffany & Co.,Luxury,4498
+2011-01-01,Shell,Energy,4483
+2011-01-01,Visa,Financial Services,4478
+2011-01-01,Yahoo!,Media,4413
+2011-01-01,Moët & Chandon,Alcohol,4383
+2011-01-01,Jack Daniel's,Alcohol,4319
+2011-01-01,BARCLAYS,Financial Services,4259
+2011-01-01,Adobe,Business Services,4170
+2011-01-01,Pizza Hut,Restaurants,4092
+2011-01-01,Credit Suisse,Financial Services,4090
+2011-01-01,Johnson & Johnson,FMCG,4072
+2011-01-01,Gap,Apparel,4040
+2011-01-01,3M,Diversified,3945
+2011-01-01,Corona,Alcohol,3924
+2011-01-01,Nivea,FMCG,3883
+2011-01-01,Johnnie Walker,Alcohol,3842
+2011-01-01,Smirnoff,Alcohol,3841
+2011-01-01,Nissan,Automotive,3819
+2011-01-01,Heineken,Alcohol,3809
+2011-01-01,UBS,Financial Services,3799
+2011-01-01,Armani,Luxury,3794
+2011-01-01,ZURICH,Financial Services,3769
+2011-01-01,Burberry,Luxury,3732
+2011-01-01,Starbucks,Restaurants,3663
+2011-01-01,John Deere,Diversified,3651
+2011-01-01,HTC,Electronics,3605
+2011-01-01,Ferrari,Automotive,3591
+2011-01-01,Harley-Davidson,Automotive,3512
+2012-01-01,Coca-Cola,Beverages,77839
+2012-01-01,Apple,Technology,76568
+2012-01-01,IBM,Business Services,75532
+2012-01-01,Google,Technology,69726
+2012-01-01,Microsoft,Technology,57853
+2012-01-01,GE,Diversified,43682
+2012-01-01,McDonald's,Restaurants,40062
+2012-01-01,Intel,Technology,39385
+2012-01-01,Samsung,Technology,32893
+2012-01-01,Toyota,Automotive,30280
+2012-01-01,Mercedes-Benz,Automotive,30097
+2012-01-01,BMW,Automotive,29052
+2012-01-01,Disney,Media,27438
+2012-01-01,Cisco,Business Services,27197
+2012-01-01,HP,Electronics,26087
+2012-01-01,Gillette,FMCG,24898
+2012-01-01,Louis Vuitton,Luxury,23577
+2012-01-01,Oracle,Business Services,22126
+2012-01-01,Nokia,Technology,21009
+2012-01-01,Amazon,Technology,18625
+2012-01-01,Honda,Automotive,17280
+2012-01-01,Pepsi,Beverages,16594
+2012-01-01,H&M,Apparel,16571
+2012-01-01,American Express,Financial Services,15702
+2012-01-01,SAP,Business Services,15641
+2012-01-01,Nike,Sporting Goods,15126
+2012-01-01,UPS,Logistics,13088
+2012-01-01,IKEA,Retail,12808
+2012-01-01,Kellogg's,FMCG,12068
+2012-01-01,Canon,Electronics,12029
+2012-01-01,Budweiser,Alcohol,11872
+2012-01-01,J.P. Morgan,Financial Services,11471
+2012-01-01,HSBC,Financial Services,11378
+2012-01-01,Pampers,FMCG,11296
+2012-01-01,NESCAFÉ,Beverages,11089
+2012-01-01,eBay,Retail,10947
+2012-01-01,Zara,Apparel,9488
+2012-01-01,Gucci,Luxury,9446
+2012-01-01,Volkswagen,Automotive,9252
+2012-01-01,Sony,Electronics,9111
+2012-01-01,Philips,Electronics,9066
+2012-01-01,L'Oréal,FMCG,8821
+2012-01-01,Accenture,Business Services,8745
+2012-01-01,Thomson Reuters,Media,8444
+2012-01-01,Ford,Automotive,7958
+2012-01-01,Heinz,FMCG,7722
+2012-01-01,Colgate,FMCG,7643
+2012-01-01,Goldman Sachs,Financial Services,7599
+2012-01-01,Dell,Electronics,7591
+2012-01-01,Citi,Financial Services,7570
+2012-01-01,Siemens,Diversified,7534
+2012-01-01,Danone,FMCG,7498
+2012-01-01,Hyundai,Automotive,7473
+2012-01-01,Morgan Stanley,Financial Services,7218
+2012-01-01,Audi,Automotive,7196
+2012-01-01,Nintendo,Electronics,7082
+2012-01-01,Nestlé,FMCG,6916
+2012-01-01,AXA,Financial Services,6748
+2012-01-01,Xerox,Business Services,6714
+2012-01-01,adidas,Sporting Goods,6699
+2012-01-01,Caterpillar,Diversified,6306
+2012-01-01,Allianz,Financial Services,6184
+2012-01-01,Hermès,Luxury,6182
+2012-01-01,KFC,Restaurants,5994
+2012-01-01,Panasonic,Electronics,5765
+2012-01-01,Sprite,Beverages,5709
+2012-01-01,MTV,Media,5648
+2012-01-01,Cartier,Luxury,5495
+2012-01-01,Facebook,Technology,5421
+2012-01-01,Tiffany & Co.,Luxury,5159
+2012-01-01,Avon,FMCG,5151
+2012-01-01,Porsche,Automotive,5149
+2012-01-01,Nissan,Automotive,4969
+2012-01-01,Visa,Financial Services,4944
+2012-01-01,Shell,Energy,4788
+2012-01-01,Banco Santander,Financial Services,4771
+2012-01-01,3M,Diversified,4656
+2012-01-01,Adobe,Business Services,4557
+2012-01-01,Johnson & Johnson,FMCG,4378
+2012-01-01,Kleenex,FMCG,4360
+2012-01-01,Jack Daniel's,Alcohol,4352
+2012-01-01,Burberry,Luxury,4342
+2012-01-01,Johnnie Walker,Alcohol,4301
+2012-01-01,Prada,Luxury,4271
+2012-01-01,John Deere,Diversified,4221
+2012-01-01,Pizza Hut,Restaurants,4193
+2012-01-01,Kia,Automotive,4089
+2012-01-01,Starbucks,Restaurants,4062
+2012-01-01,Corona,Alcohol,4061
+2012-01-01,Smirnoff,Alcohol,4050
+2012-01-01,Ralph Lauren,Apparel,4038
+2012-01-01,Heineken,Alcohol,3939
+2012-01-01,BlackBerry,Electronics,3922
+2012-01-01,Mastercard,Financial Services,3896
+2012-01-01,Credit Suisse,Financial Services,3866
+2012-01-01,Harley-Davidson,Automotive,3857
+2012-01-01,Yahoo!,Media,3851
+2012-01-01,Moët & Chandon,Alcohol,3824
+2012-01-01,Ferrari,Automotive,3770
+2012-01-01,Gap,Apparel,3731
+2013-01-01,Apple,Technology,98316
+2013-01-01,Google,Technology,93291
+2013-01-01,Coca-Cola,Beverages,79213
+2013-01-01,IBM,Business Services,78808
+2013-01-01,Microsoft,Technology,59546
+2013-01-01,GE,Diversified,46947
+2013-01-01,McDonald's,Restaurants,41992
+2013-01-01,Samsung,Technology,39610
+2013-01-01,Intel,Technology,37257
+2013-01-01,Toyota,Automotive,35346
+2013-01-01,Mercedes-Benz,Automotive,31904
+2013-01-01,BMW,Automotive,31839
+2013-01-01,Cisco,Business Services,29053
+2013-01-01,Disney,Media,28147
+2013-01-01,HP,Electronics,25843
+2013-01-01,Gillette,FMCG,25105
+2013-01-01,Louis Vuitton,Luxury,24893
+2013-01-01,Oracle,Business Services,24088
+2013-01-01,Amazon,Technology,23620
+2013-01-01,Honda,Automotive,18490
+2013-01-01,H&M,Apparel,18168
+2013-01-01,Pepsi,Beverages,17892
+2013-01-01,American Express,Financial Services,17646
+2013-01-01,Nike,Sporting Goods,17085
+2013-01-01,SAP,Business Services,16676
+2013-01-01,IKEA,Retail,13818
+2013-01-01,UPS,Logistics,13763
+2013-01-01,eBay,Retail,13162
+2013-01-01,Pampers,FMCG,13035
+2013-01-01,Kellogg's,FMCG,12987
+2013-01-01,Budweiser,Alcohol,12614
+2013-01-01,HSBC,Financial Services,12183
+2013-01-01,J.P. Morgan,Financial Services,11456
+2013-01-01,Volkswagen,Automotive,11120
+2013-01-01,Canon,Electronics,10989
+2013-01-01,Zara,Apparel,10821
+2013-01-01,NESCAFÉ,Beverages,10651
+2013-01-01,Gucci,Luxury,10151
+2013-01-01,L'Oréal,FMCG,9874
+2013-01-01,Philips,Electronics,9813
+2013-01-01,Accenture,Business Services,9471
+2013-01-01,Ford,Automotive,9181
+2013-01-01,Hyundai,Automotive,9004
+2013-01-01,Goldman Sachs,Financial Services,8536
+2013-01-01,Siemens,Diversified,8503
+2013-01-01,Sony,Electronics,8408
+2013-01-01,Thomson Reuters,Media,8103
+2013-01-01,Citi,Financial Services,7973
+2013-01-01,Danone,FMCG,7968
+2013-01-01,Colgate,FMCG,7833
+2013-01-01,Audi,Automotive,7767
+2013-01-01,Facebook,Technology,7732
+2013-01-01,Heinz,FMCG,7648
+2013-01-01,Hermès,Luxury,7616
+2013-01-01,adidas,Sporting Goods,7535
+2013-01-01,Nestlé,FMCG,7527
+2013-01-01,Nokia,Technology,7444
+2013-01-01,Caterpillar,Diversified,7125
+2013-01-01,AXA,Financial Services,7096
+2013-01-01,Cartier,Luxury,6897
+2013-01-01,Dell,Electronics,6845
+2013-01-01,Xerox,Business Services,6779
+2013-01-01,Allianz,Financial Services,6710
+2013-01-01,Porsche,Automotive,6471
+2013-01-01,Nissan,Automotive,6203
+2013-01-01,KFC,Restaurants,6192
+2013-01-01,Nintendo,Electronics,6086
+2013-01-01,Panasonic,Electronics,5821
+2013-01-01,Sprite,Beverages,5811
+2013-01-01,Discovery,Media,5756
+2013-01-01,Morgan Stanley,Financial Services,5724
+2013-01-01,Prada,Luxury,5570
+2013-01-01,Shell,Energy,5535
+2013-01-01,Visa,Financial Services,5465
+2013-01-01,Tiffany & Co.,Luxury,5440
+2013-01-01,3M,Diversified,5413
+2013-01-01,Burberry,Luxury,5189
+2013-01-01,MTV,Media,4980
+2013-01-01,Adobe,Business Services,4899
+2013-01-01,John Deere,Diversified,4865
+2013-01-01,Johnson & Johnson,FMCG,4777
+2013-01-01,Johnnie Walker,Alcohol,4745
+2013-01-01,Kia,Automotive,4708
+2013-01-01,Banco Santander,Financial Services,4660
+2013-01-01,Duracell,FMCG,4645
+2013-01-01,Jack Daniel's,Alcohol,4642
+2013-01-01,Avon,FMCG,4610
+2013-01-01,Ralph Lauren,Apparel,4584
+2013-01-01,Chevrolet,Automotive,4578
+2013-01-01,Kleenex,FMCG,4428
+2013-01-01,Starbucks,Restaurants,4399
+2013-01-01,Heineken,Alcohol,4331
+2013-01-01,Corona,Alcohol,4276
+2013-01-01,Pizza Hut,Restaurants,4269
+2013-01-01,Smirnoff,Alcohol,4262
+2013-01-01,Harley-Davidson,Automotive,4230
+2013-01-01,Mastercard,Financial Services,4206
+2013-01-01,Ferrari,Automotive,4013
+2013-01-01,Moët & Chandon,Alcohol,3943
+2013-01-01,Gap,Apparel,3920
+2014-01-01,Apple,Technology,118863
+2014-01-01,Google,Technology,107439
+2014-01-01,Coca-Cola,Beverages,81563
+2014-01-01,IBM,Business Services,72244
+2014-01-01,Microsoft,Technology,61154
+2014-01-01,GE,Diversified,45480
+2014-01-01,Samsung,Technology,45462
+2014-01-01,Toyota,Automotive,42392
+2014-01-01,McDonald's,Restaurants,42254
+2014-01-01,Mercedes-Benz,Automotive,34338
+2014-01-01,BMW,Automotive,34214
+2014-01-01,Intel,Technology,34153
+2014-01-01,Disney,Media,32223
+2014-01-01,Cisco,Business Services,30936
+2014-01-01,Amazon,Technology,29478
+2014-01-01,Oracle,Business Services,25980
+2014-01-01,HP,Electronics,23758
+2014-01-01,Gillette,FMCG,22845
+2014-01-01,Louis Vuitton,Luxury,22552
+2014-01-01,Honda,Automotive,21673
+2014-01-01,H&M,Apparel,21083
+2014-01-01,Nike,Sporting Goods,19875
+2014-01-01,American Express,Financial Services,19510
+2014-01-01,Pepsi,Beverages,19119
+2014-01-01,SAP,Business Services,17340
+2014-01-01,IKEA,Retail,15885
+2014-01-01,UPS,Logistics,14470
+2014-01-01,eBay,Retail,14358
+2014-01-01,Facebook,Technology,14349
+2014-01-01,Pampers,FMCG,14078
+2014-01-01,Volkswagen,Automotive,13716
+2014-01-01,Kellogg's,FMCG,13442
+2014-01-01,HSBC,Financial Services,13142
+2014-01-01,Budweiser,Alcohol,13024
+2014-01-01,J.P. Morgan,Financial Services,12456
+2014-01-01,Zara,Apparel,12126
+2014-01-01,Canon,Electronics,11702
+2014-01-01,NESCAFÉ,Beverages,11406
+2014-01-01,Ford,Automotive,10876
+2014-01-01,Hyundai,Automotive,10409
+2014-01-01,Gucci,Luxury,10385
+2014-01-01,Philips,Electronics,10264
+2014-01-01,L'Oréal,FMCG,10162
+2014-01-01,Accenture,Business Services,9882
+2014-01-01,Audi,Automotive,9831
+2014-01-01,Hermès,Luxury,8977
+2014-01-01,Goldman Sachs,Financial Services,8758
+2014-01-01,Citi,Financial Services,8737
+2014-01-01,Siemens,Diversified,8672
+2014-01-01,Colgate,FMCG,8215
+2014-01-01,Danone,FMCG,8205
+2014-01-01,Sony,Electronics,8133
+2014-01-01,AXA,Financial Services,8120
+2014-01-01,Nestlé,FMCG,8000
+2014-01-01,Allianz,Financial Services,7702
+2014-01-01,Nissan,Automotive,7623
+2014-01-01,Thomson Reuters,Media,7472
+2014-01-01,Cartier,Luxury,7449
+2014-01-01,adidas,Sporting Goods,7378
+2014-01-01,Porsche,Automotive,7171
+2014-01-01,Caterpillar,Diversified,6812
+2014-01-01,Xerox,Business Services,6641
+2014-01-01,Morgan Stanley,Financial Services,6334
+2014-01-01,Panasonic,Electronics,6303
+2014-01-01,Shell,Energy,6288
+2014-01-01,3M,Diversified,6177
+2014-01-01,Discovery,Media,6143
+2014-01-01,KFC,Restaurants,6059
+2014-01-01,Visa,Financial Services,5998
+2014-01-01,Prada,Luxury,5977
+2014-01-01,Tiffany & Co.,Luxury,5936
+2014-01-01,Sprite,Beverages,5646
+2014-01-01,Burberry,Luxury,5594
+2014-01-01,Kia,Automotive,5396
+2014-01-01,Banco Santander,Financial Services,5382
+2014-01-01,Starbucks,Restaurants,5382
+2014-01-01,Adobe,Business Services,5333
+2014-01-01,Johnson & Johnson,FMCG,5194
+2014-01-01,John Deere,Diversified,5124
+2014-01-01,MTV,Media,5102
+2014-01-01,DHL,Logistics,5084
+2014-01-01,Chevrolet,Automotive,5036
+2014-01-01,Ralph Lauren,Apparel,4979
+2014-01-01,Duracell,FMCG,4935
+2014-01-01,Jack Daniel's,Alcohol,4884
+2014-01-01,Johnnie Walker,Alcohol,4842
+2014-01-01,Harley-Davidson,Automotive,4772
+2014-01-01,Mastercard,Financial Services,4758
+2014-01-01,Kleenex,FMCG,4643
+2014-01-01,Smirnoff,Alcohol,4609
+2014-01-01,Land Rover,Automotive,4473
+2014-01-01,FedEx,Logistics,4414
+2014-01-01,Corona,Alcohol,4387
+2014-01-01,Huawei,Technology,4313
+2014-01-01,Heineken,Alcohol,4221
+2014-01-01,Pizza Hut,Restaurants,4196
+2014-01-01,Hugo Boss,Apparel,4143
+2014-01-01,Nokia,Technology,4138
+2014-01-01,Gap,Apparel,4122
+2014-01-01,Nintendo,Electronics,4103
+2015-01-01,Apple,Technology,170276
+2015-01-01,Google,Technology,120314
+2015-01-01,Coca-Cola,Beverages,78423
+2015-01-01,Microsoft,Technology,67670
+2015-01-01,IBM,Business Services,65095
+2015-01-01,Toyota,Automotive,49048
+2015-01-01,Samsung,Technology,45297
+2015-01-01,GE,Diversified,42267
+2015-01-01,McDonald's,Restaurants,39809
+2015-01-01,Amazon,Technology,37948
+2015-01-01,BMW,Automotive,37212
+2015-01-01,Mercedes-Benz,Automotive,36711
+2015-01-01,Disney,Media,36514
+2015-01-01,Intel,Technology,35415
+2015-01-01,Cisco,Business Services,29854
+2015-01-01,Oracle,Business Services,27283
+2015-01-01,Nike,Sporting Goods,23070
+2015-01-01,HP,Electronics,23056
+2015-01-01,Honda,Automotive,22975
+2015-01-01,Louis Vuitton,Luxury,22250
+2015-01-01,H&M,Apparel,22222
+2015-01-01,Gillette,FMCG,22218
+2015-01-01,Facebook,Technology,22029
+2015-01-01,Pepsi,Beverages,19622
+2015-01-01,American Express,Financial Services,18922
+2015-01-01,SAP,Business Services,18768
+2015-01-01,IKEA,Retail,16541
+2015-01-01,Pampers,FMCG,15267
+2015-01-01,UPS,Logistics,14723
+2015-01-01,Zara,Apparel,14031
+2015-01-01,Budweiser,Alcohol,13943
+2015-01-01,eBay,Retail,13940
+2015-01-01,J.P. Morgan,Financial Services,13749
+2015-01-01,Kellogg's,FMCG,12637
+2015-01-01,Volkswagen,Automotive,12545
+2015-01-01,NESCAFÉ,Beverages,12257
+2015-01-01,HSBC,Financial Services,11656
+2015-01-01,Ford,Automotive,11578
+2015-01-01,Hyundai,Automotive,11293
+2015-01-01,Canon,Electronics,11278
+2015-01-01,Hermès,Luxury,10944
+2015-01-01,Accenture,Business Services,10800
+2015-01-01,L'Oréal,FMCG,10798
+2015-01-01,Audi,Automotive,10328
+2015-01-01,Citi,Financial Services,9784
+2015-01-01,Goldman Sachs,Financial Services,9526
+2015-01-01,Philips,Electronics,9400
+2015-01-01,AXA,Financial Services,9254
+2015-01-01,Nissan,Automotive,9082
+2015-01-01,Gucci,Luxury,8882
+2015-01-01,Danone,FMCG,8632
+2015-01-01,Nestlé,FMCG,8588
+2015-01-01,Siemens,Diversified,8553
+2015-01-01,Allianz,Financial Services,8498
+2015-01-01,Colgate,FMCG,8464
+2015-01-01,Porsche,Automotive,8055
+2015-01-01,Cartier,Luxury,7924
+2015-01-01,Sony,Electronics,7702
+2015-01-01,3M,Diversified,7243
+2015-01-01,Morgan Stanley,Financial Services,7083
+2015-01-01,Visa,Financial Services,6870
+2015-01-01,adidas,Sporting Goods,6811
+2015-01-01,Thomson Reuters,Media,6583
+2015-01-01,Discovery,Media,6509
+2015-01-01,Panasonic,Electronics,6436
+2015-01-01,Tiffany & Co.,Luxury,6306
+2015-01-01,Starbucks,Restaurants,6266
+2015-01-01,Adobe,Business Services,6257
+2015-01-01,Prada,Luxury,6222
+2015-01-01,Banco Santander,Financial Services,6097
+2015-01-01,Xerox,Business Services,6033
+2015-01-01,Caterpillar,Diversified,5976
+2015-01-01,Burberry,Luxury,5873
+2015-01-01,Kia,Automotive,5666
+2015-01-01,KFC,Restaurants,5639
+2015-01-01,Mastercard,Financial Services,5551
+2015-01-01,Johnson & Johnson,FMCG,5533
+2015-01-01,Shell,Energy,5530
+2015-01-01,Harley-Davidson,Automotive,5460
+2015-01-01,DHL,Logistics,5391
+2015-01-01,Sprite,Beverages,5365
+2015-01-01,LEGO,FMCG,5362
+2015-01-01,John Deere,Diversified,5208
+2015-01-01,Jack Daniel's,Alcohol,5161
+2015-01-01,Chevrolet,Automotive,5133
+2015-01-01,FedEx,Logistics,5130
+2015-01-01,Land Rover,Automotive,5109
+2015-01-01,Huawei,Technology,4952
+2015-01-01,Heineken,Alcohol,4822
+2015-01-01,MTV,Media,4763
+2015-01-01,Ralph Lauren,Apparel,4629
+2015-01-01,Johnnie Walker,Alcohol,4540
+2015-01-01,Corona,Alcohol,4456
+2015-01-01,Smirnoff,Alcohol,4407
+2015-01-01,Kleenex,FMCG,4330
+2015-01-01,Hugo Boss,Apparel,4270
+2015-01-01,PayPal,Financial Services,4251
+2015-01-01,MINI,Automotive,4243
+2015-01-01,Moët & Chandon,Alcohol,4131
+2015-01-01,Lenovo,Technology,4114
+2016-01-01,Apple,Technology,178119
+2016-01-01,Google,Technology,133252
+2016-01-01,Coca-Cola,Beverages,73102
+2016-01-01,Microsoft,Technology,72795
+2016-01-01,Toyota,Automotive,53580
+2016-01-01,IBM,Business Services,52500
+2016-01-01,Samsung,Technology,51808
+2016-01-01,Amazon,Technology,50338
+2016-01-01,Mercedes-Benz,Automotive,43490
+2016-01-01,GE,Diversified,43130
+2016-01-01,BMW,Automotive,41535
+2016-01-01,McDonald's,Restaurants,39381
+2016-01-01,Disney,Media,38790
+2016-01-01,Intel,Technology,36952
+2016-01-01,Facebook,Technology,32593
+2016-01-01,Cisco,Business Services,30948
+2016-01-01,Oracle,Business Services,26552
+2016-01-01,Nike,Sporting Goods,25034
+2016-01-01,Louis Vuitton,Luxury,23998
+2016-01-01,H&M,Apparel,22681
+2016-01-01,Honda,Automotive,22106
+2016-01-01,SAP,Business Services,21293
+2016-01-01,Pepsi,Beverages,20265
+2016-01-01,Gillette,FMCG,19950
+2016-01-01,American Express,Financial Services,18358
+2016-01-01,IKEA,Retail,17834
+2016-01-01,Zara,Apparel,16766
+2016-01-01,Pampers,FMCG,16134
+2016-01-01,UPS,Logistics,15333
+2016-01-01,Budweiser,Alcohol,15099
+2016-01-01,J.P. Morgan,Financial Services,14227
+2016-01-01,eBay,Retail,13136
+2016-01-01,Ford,Automotive,12962
+2016-01-01,Hermès,Luxury,12833
+2016-01-01,Hyundai,Automotive,12547
+2016-01-01,NESCAFÉ,Beverages,12517
+2016-01-01,Accenture,Business Services,12033
+2016-01-01,Audi,Automotive,11799
+2016-01-01,Kellogg's,FMCG,11711
+2016-01-01,Volkswagen,Automotive,11436
+2016-01-01,Philips,Electronics,11336
+2016-01-01,Canon,Electronics,11081
+2016-01-01,Nissan,Automotive,11066
+2016-01-01,Hewlett Packard Enterprise,Business Services,11027
+2016-01-01,L'Oréal,FMCG,10930
+2016-01-01,AXA,Financial Services,10579
+2016-01-01,HSBC,Financial Services,10458
+2016-01-01,HP,Electronics,10386
+2016-01-01,Citi,Financial Services,10276
+2016-01-01,Porsche,Automotive,9537
+2016-01-01,Allianz,Financial Services,9528
+2016-01-01,Siemens,Diversified,9415
+2016-01-01,Gucci,Luxury,9385
+2016-01-01,Goldman Sachs,Financial Services,9378
+2016-01-01,Danone,FMCG,9197
+2016-01-01,Nestlé,FMCG,8708
+2016-01-01,Colgate,FMCG,8413
+2016-01-01,Sony,Electronics,8315
+2016-01-01,3M,Diversified,8199
+2016-01-01,adidas,Sporting Goods,7885
+2016-01-01,Visa,Financial Services,7747
+2016-01-01,Cartier,Luxury,7738
+2016-01-01,Adobe,Business Services,7586
+2016-01-01,Starbucks,Restaurants,7490
+2016-01-01,Morgan Stanley,Financial Services,7200
+2016-01-01,Thomson Reuters,Media,6830
+2016-01-01,LEGO,FMCG,6691
+2016-01-01,Panasonic,Electronics,6365
+2016-01-01,Kia,Automotive,6326
+2016-01-01,Banco Santander,Financial Services,6223
+2016-01-01,Discovery,Media,5944
+2016-01-01,Huawei,Technology,5835
+2016-01-01,Johnson & Johnson,FMCG,5790
+2016-01-01,Tiffany & Co.,Luxury,5761
+2016-01-01,KFC,Restaurants,5742
+2016-01-01,Mastercard,Financial Services,5736
+2016-01-01,DHL,Logistics,5708
+2016-01-01,Land Rover,Automotive,5696
+2016-01-01,FedEx,Logistics,5579
+2016-01-01,Harley-Davidson,Automotive,5527
+2016-01-01,Prada,Luxury,5504
+2016-01-01,Caterpillar,Diversified,5425
+2016-01-01,Burberry,Luxury,5362
+2016-01-01,Xerox,Business Services,5290
+2016-01-01,Jack Daniel's,Alcohol,5193
+2016-01-01,Sprite,Beverages,5148
+2016-01-01,Heineken,Alcohol,5123
+2016-01-01,MINI,Automotive,4986
+2016-01-01,Dior,Luxury,4909
+2016-01-01,PayPal,Financial Services,4839
+2016-01-01,John Deere,Diversified,4815
+2016-01-01,Shell,Energy,4599
+2016-01-01,Corona,Alcohol,4509
+2016-01-01,MTV,Media,4320
+2016-01-01,Johnnie Walker,Alcohol,4317
+2016-01-01,Smirnoff,Alcohol,4252
+2016-01-01,Moët & Chandon,Alcohol,4118
+2016-01-01,Ralph Lauren,Apparel,4092
+2016-01-01,Lenovo,Technology,4045
+2016-01-01,Tesla,Automotive,4011
+2017-01-01,Apple,Technology,184154
+2017-01-01,Google,Technology,141703
+2017-01-01,Microsoft,Technology,79999
+2017-01-01,Coca-Cola,Beverages,69733
+2017-01-01,Amazon,Technology,64796
+2017-01-01,Samsung,Technology,56249
+2017-01-01,Toyota,Automotive,50291
+2017-01-01,Facebook,Technology,48188
+2017-01-01,Mercedes-Benz,Automotive,47829
+2017-01-01,IBM,Business Services,46829
+2017-01-01,GE,Diversified,44208
+2017-01-01,McDonald's,Restaurants,41533
+2017-01-01,BMW,Automotive,41521
+2017-01-01,Disney,Media,40772
+2017-01-01,Intel,Technology,39459
+2017-01-01,Cisco,Business Services,31930
+2017-01-01,Oracle,Business Services,27466
+2017-01-01,Nike,Sporting Goods,27021
+2017-01-01,Louis Vuitton,Luxury,22919
+2017-01-01,Honda,Automotive,22696
+2017-01-01,SAP,Business Services,22635
+2017-01-01,Pepsi,Beverages,20491
+2017-01-01,H&M,Apparel,20488
+2017-01-01,Zara,Apparel,18573
+2017-01-01,IKEA,Retail,18472
+2017-01-01,Gillette,FMCG,18200
+2017-01-01,American Express,Financial Services,17787
+2017-01-01,Pampers,FMCG,16416
+2017-01-01,UPS,Logistics,16387
+2017-01-01,J.P. Morgan,Financial Services,15749
+2017-01-01,Budweiser,Alcohol,15375
+2017-01-01,Hermès,Luxury,14210
+2017-01-01,Ford,Automotive,13643
+2017-01-01,eBay,Retail,13224
+2017-01-01,Hyundai,Automotive,13193
+2017-01-01,NESCAFÉ,Beverages,12661
+2017-01-01,Accenture,Business Services,12471
+2017-01-01,Audi,Automotive,12023
+2017-01-01,Nissan,Automotive,11534
+2017-01-01,Volkswagen,Automotive,11522
+2017-01-01,Philips,Electronics,11519
+2017-01-01,AXA,Financial Services,11073
+2017-01-01,Kellogg's,FMCG,10972
+2017-01-01,Goldman Sachs,Financial Services,10864
+2017-01-01,L'Oréal,FMCG,10674
+2017-01-01,Citi,Financial Services,10599
+2017-01-01,HSBC,Financial Services,10534
+2017-01-01,Porsche,Automotive,10129
+2017-01-01,Allianz,Financial Services,10059
+2017-01-01,Siemens,Diversified,9982
+2017-01-01,Gucci,Luxury,9969
+2017-01-01,Canon,Electronics,9788
+2017-01-01,HP,Electronics,9541
+2017-01-01,Danone,FMCG,9322
+2017-01-01,adidas,Sporting Goods,9216
+2017-01-01,Adobe,Business Services,9060
+2017-01-01,Hewlett Packard Enterprise,Business Services,8951
+2017-01-01,3M,Diversified,8947
+2017-01-01,Nestlé,FMCG,8728
+2017-01-01,Starbucks,Restaurants,8704
+2017-01-01,Sony,Electronics,8474
+2017-01-01,Colgate,FMCG,8325
+2017-01-01,Morgan Stanley,Financial Services,8205
+2017-01-01,Visa,Financial Services,7815
+2017-01-01,Cartier,Luxury,7547
+2017-01-01,Thomson Reuters,Media,7100
+2017-01-01,LEGO,FMCG,7024
+2017-01-01,Banco Santander,Financial Services,6702
+2017-01-01,Kia,Automotive,6683
+2017-01-01,Huawei,Technology,6676
+2017-01-01,Mastercard,Financial Services,6350
+2017-01-01,FedEx,Logistics,6255
+2017-01-01,Land Rover,Automotive,6095
+2017-01-01,Johnson & Johnson,FMCG,6041
+2017-01-01,Panasonic,Electronics,5983
+2017-01-01,DHL,Logistics,5715
+2017-01-01,Harley-Davidson,Automotive,5671
+2017-01-01,Netflix,Media,5592
+2017-01-01,Discovery,Media,5411
+2017-01-01,PayPal,Financial Services,5408
+2017-01-01,Tiffany & Co.,Luxury,5394
+2017-01-01,Jack Daniel's,Alcohol,5332
+2017-01-01,KFC,Restaurants,5313
+2017-01-01,Salesforce.com,Business Services,5224
+2017-01-01,Heineken,Alcohol,5181
+2017-01-01,Burberry,Luxury,5135
+2017-01-01,MINI,Automotive,5114
+2017-01-01,Ferrari,Automotive,4876
+2017-01-01,Caterpillar,Diversified,4868
+2017-01-01,Sprite,Beverages,4842
+2017-01-01,Shell,Energy,4823
+2017-01-01,John Deere,Diversified,4783
+2017-01-01,Corona,Alcohol,4776
+2017-01-01,Prada,Luxury,4716
+2017-01-01,Dior,Luxury,4587
+2017-01-01,Johnnie Walker,Alcohol,4405
+2017-01-01,Smirnoff,Alcohol,4288
+2017-01-01,Tesla,Automotive,4009
+2017-01-01,Moët & Chandon,Alcohol,4006
+2017-01-01,Lenovo,Technology,4004
+2018-01-01,Apple,Technology,214480
+2018-01-01,Google,Technology,155506
+2018-01-01,Amazon,Technology,100764
+2018-01-01,Microsoft,Technology,92715
+2018-01-01,Coca-Cola,Beverages,66341
+2018-01-01,Samsung,Technology,59890
+2018-01-01,Toyota,Automotive,53404
+2018-01-01,Mercedes-Benz,Automotive,48601
+2018-01-01,Facebook,Technology,45168
+2018-01-01,McDonald's,Restaurants,43417
+2018-01-01,Intel,Technology,43293
+2018-01-01,IBM,Business Services,42972
+2018-01-01,BMW,Automotive,41006
+2018-01-01,Disney,Media,39874
+2018-01-01,Cisco,Business Services,34575
+2018-01-01,GE,Diversified,32757
+2018-01-01,Nike,Sporting Goods,30120
+2018-01-01,Louis Vuitton,Luxury,28152
+2018-01-01,Oracle,Business Services,26133
+2018-01-01,Honda,Automotive,23682
+2018-01-01,SAP,Business Services,22885
+2018-01-01,Pepsi,Beverages,20798
+2018-01-01,Chanel,Luxury,20005
+2018-01-01,American Express,Financial Services,19139
+2018-01-01,Zara,Apparel,17712
+2018-01-01,J.P. Morgan,Financial Services,17567
+2018-01-01,IKEA,Retail,17458
+2018-01-01,Gillette,FMCG,16864
+2018-01-01,UPS,Logistics,16849
+2018-01-01,H&M,Apparel,16826
+2018-01-01,Pampers,FMCG,16617
+2018-01-01,Hermès,Luxury,16372
+2018-01-01,Budweiser,Alcohol,15627
+2018-01-01,Accenture,Business Services,14214
+2018-01-01,Ford,Automotive,13995
+2018-01-01,Hyundai,Automotive,13535
+2018-01-01,NESCAFÉ,Beverages,13053
+2018-01-01,eBay,Retail,13017
+2018-01-01,Gucci,Luxury,12942
+2018-01-01,Nissan,Automotive,12213
+2018-01-01,Volkswagen,Automotive,12201
+2018-01-01,Audi,Automotive,12187
+2018-01-01,Philips,Electronics,12104
+2018-01-01,Goldman Sachs,Financial Services,11769
+2018-01-01,Citi,Financial Services,11577
+2018-01-01,HSBC,Financial Services,11208
+2018-01-01,AXA,Financial Services,11118
+2018-01-01,L'Oréal,FMCG,11102
+2018-01-01,Allianz,Financial Services,10821
+2018-01-01,adidas,Sporting Goods,10772
+2018-01-01,Adobe,Business Services,10748
+2018-01-01,Porsche,Automotive,10707
+2018-01-01,Kellogg's,FMCG,10634
+2018-01-01,HP,Electronics,10433
+2018-01-01,Canon,Electronics,10380
+2018-01-01,Siemens,Diversified,10132
+2018-01-01,Starbucks,Restaurants,9615
+2018-01-01,Danone,FMCG,9533
+2018-01-01,Sony,Electronics,9316
+2018-01-01,3M,Diversified,9104
+2018-01-01,Visa,Financial Services,9021
+2018-01-01,Nestlé,FMCG,8938
+2018-01-01,Morgan Stanley,Financial Services,8802
+2018-01-01,Colgate,FMCG,8659
+2018-01-01,Hewlett Packard Enterprise,Business Services,8157
+2018-01-01,Netflix,Media,8111
+2018-01-01,Cartier,Luxury,7646
+2018-01-01,Huawei,Technology,7578
+2018-01-01,Banco Santander,Financial Services,7547
+2018-01-01,Mastercard,Financial Services,7545
+2018-01-01,Kia,Automotive,6925
+2018-01-01,FedEx,Logistics,6890
+2018-01-01,PayPal,Financial Services,6621
+2018-01-01,LEGO,FMCG,6533
+2018-01-01,Salesforce.com,Business Services,6432
+2018-01-01,Panasonic,Electronics,6293
+2018-01-01,Johnson & Johnson,FMCG,6231
+2018-01-01,Land Rover,Automotive,6221
+2018-01-01,DHL,Logistics,5881
+2018-01-01,Ferrari,Automotive,5760
+2018-01-01,Discovery,Media,5755
+2018-01-01,Caterpillar,Diversified,5730
+2018-01-01,Tiffany & Co.,Luxury,5642
+2018-01-01,Jack Daniel's,Alcohol,5641
+2018-01-01,Corona,Alcohol,5517
+2018-01-01,KFC,Restaurants,5481
+2018-01-01,Heineken,Alcohol,5393
+2018-01-01,John Deere,Diversified,5375
+2018-01-01,Shell,Energy,5276
+2018-01-01,MINI,Automotive,5254
+2018-01-01,Dior,Luxury,5223
+2018-01-01,Spotify,Media,5176
+2018-01-01,Harley-Davidson,Automotive,5161
+2018-01-01,Burberry,Luxury,4989
+2018-01-01,Prada,Luxury,4812
+2018-01-01,Sprite,Beverages,4733
+2018-01-01,Johnnie Walker,Alcohol,4731
+2018-01-01,Hennessy,Alcohol,4722
+2018-01-01,Nintendo,Electronics,4696
+2018-01-01,Subaru,Automotive,4214
+2019-01-01,Apple,Technology,234241
+2019-01-01,Google,Technology,167713
+2019-01-01,Amazon,Technology,125263
+2019-01-01,Microsoft,Technology,108847
+2019-01-01,Coca-Cola,Beverages,63365
+2019-01-01,Samsung,Technology,61098
+2019-01-01,Toyota,Automotive,56246
+2019-01-01,Mercedes-Benz,Automotive,50832
+2019-01-01,McDonald's,Restaurants,45362
+2019-01-01,Disney,Media,44352
+2019-01-01,BMW,Automotive,41440
+2019-01-01,IBM,Business Services,40381
+2019-01-01,Intel,Technology,40197
+2019-01-01,Facebook,Technology,39857
+2019-01-01,Cisco,Business Services,35559
+2019-01-01,Nike,Sporting Goods,32376
+2019-01-01,Louis Vuitton,Luxury,32223
+2019-01-01,Oracle,Business Services,26288
+2019-01-01,GE,Diversified,25566
+2019-01-01,SAP,Business Services,25092
+2019-01-01,Honda,Automotive,24422
+2019-01-01,Chanel,Luxury,22134
+2019-01-01,American Express,Financial Services,21629
+2019-01-01,Pepsi,Beverages,20488
+2019-01-01,J.P. Morgan,Financial Services,19044
+2019-01-01,IKEA,Retail,18407
+2019-01-01,UPS,Logistics,18072
+2019-01-01,Hermès,Luxury,17920
+2019-01-01,Zara,Apparel,17175
+2019-01-01,H&M,Apparel,16345
+2019-01-01,Accenture,Business Services,16205
+2019-01-01,Budweiser,Alcohol,16018
+2019-01-01,Gucci,Luxury,15949
+2019-01-01,Pampers,FMCG,15773
+2019-01-01,Ford,Automotive,14325
+2019-01-01,Hyundai,Automotive,14156
+2019-01-01,Gillette,FMCG,13753
+2019-01-01,NESCAFÉ,Beverages,13605
+2019-01-01,Adobe,Business Services,12937
+2019-01-01,Volkswagen,Automotive,12921
+2019-01-01,Citi,Financial Services,12697
+2019-01-01,Audi,Automotive,12689
+2019-01-01,Allianz,Financial Services,12078
+2019-01-01,eBay,Retail,12010
+2019-01-01,adidas,Sporting Goods,11992
+2019-01-01,AXA,Financial Services,11830
+2019-01-01,HSBC,Financial Services,11816
+2019-01-01,Starbucks,Restaurants,11798
+2019-01-01,Philips,Electronics,11661
+2019-01-01,Porsche,Automotive,11652
+2019-01-01,L'Oréal,FMCG,11589
+2019-01-01,Nissan,Automotive,11502
+2019-01-01,Goldman Sachs,Financial Services,11352
+2019-01-01,HP,Electronics,10891
+2019-01-01,Visa,Financial Services,10756
+2019-01-01,Sony,Electronics,10514
+2019-01-01,Kellogg's,FMCG,10419
+2019-01-01,Siemens,Diversified,10259
+2019-01-01,Danone,FMCG,9915
+2019-01-01,Nestlé,FMCG,9534
+2019-01-01,Canon,Electronics,9482
+2019-01-01,Mastercard,Financial Services,9430
+2019-01-01,Dell,Electronics,9086
+2019-01-01,3M,Diversified,9035
+2019-01-01,Netflix,Media,8963
+2019-01-01,Colgate,FMCG,8824
+2019-01-01,Banco Santander,Financial Services,8521
+2019-01-01,Cartier,Luxury,8192
+2019-01-01,Morgan Stanley,Financial Services,8185
+2019-01-01,Salesforce.com,Business Services,8004
+2019-01-01,Hewlett Packard Enterprise,Business Services,7909
+2019-01-01,PayPal,Financial Services,7604
+2019-01-01,FedEx,Logistics,6998
+2019-01-01,Huawei,Technology,6887
+2019-01-01,LEGO,FMCG,6884
+2019-01-01,Caterpillar,Diversified,6791
+2019-01-01,Ferrari,Automotive,6458
+2019-01-01,Kia,Automotive,6428
+2019-01-01,Corona,Alcohol,6369
+2019-01-01,Jack Daniel's,Alcohol,6347
+2019-01-01,Panasonic,Electronics,6189
+2019-01-01,Dior,Luxury,6045
+2019-01-01,DHL,Logistics,5987
+2019-01-01,John Deere,Diversified,5883
+2019-01-01,Land Rover,Automotive,5855
+2019-01-01,Johnson & Johnson,FMCG,5720
+2019-01-01,Uber,Technology,5714
+2019-01-01,Heineken,Alcohol,5626
+2019-01-01,Nintendo,Electronics,5550
+2019-01-01,MINI,Automotive,5532
+2019-01-01,Discovery,Media,5525
+2019-01-01,Spotify,Media,5516
+2019-01-01,KFC,Restaurants,5509
+2019-01-01,Tiffany & Co.,Luxury,5335
+2019-01-01,Hennessy,Alcohol,5297
+2019-01-01,Burberry,Luxury,5205
+2019-01-01,Shell,Energy,5105
+2019-01-01,LinkedIn,Media,4836
+2019-01-01,Harley-Davidson,Automotive,4793
+2019-01-01,Prada,Luxury,4781
diff --git a/test/data/historical-life-expectancy.tsv b/test/data/historical-life-expectancy.tsv
new file mode 100644
index 0000000000..32fa1062a5
--- /dev/null
+++ b/test/data/historical-life-expectancy.tsv
@@ -0,0 +1,27 @@
+era lifeexp
+Paleolithic 22 – 33
+Neolithic 20 – 33
+Bronze Age and Iron Age 26
+Classical Greece 25 – 28
+Classical Rome 20–33
+Vedic India 25-35
+Wang clan of China, 1st c. AD – 1749 35
+Early middle ages 30–35
+Medieval Islamic world >35
+Pre-Columbian Mesoamerica >40
+Late medieval English peerage 30–33
+Early modern England (16th - 18th cent.) 33–40
+18th-century England 25–40
+Pre-Champlain Canadian Maritimes 60
+18th-century Prussia 24.7
+18th-century France 27.5–30
+18th-century Qing China 39.6
+18th-century Edo Japan 41.1
+18th-century American colonies 28
+Beginning of the 19th century ~29
+Early 19th-century England 40
+19th-century British India 25.4
+19th-century world average 28.5–32
+1900 world average 31–32
+1950 world average 45.7 – 48
+2019-2020 world average 72.6–73.2
diff --git a/test/output/barChartRace-1220227200000.svg b/test/output/barChartRace-1220227200000.svg
new file mode 100644
index 0000000000..ab4592377e
--- /dev/null
+++ b/test/output/barChartRace-1220227200000.svg
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10,000
+
+
+ 20,000
+
+
+ 30,000
+
+
+ 40,000
+
+
+ 50,000
+
+
+ 60,000
+
+
+
+
+
+ 2008
+ American Express Coca-Cola IBM Microsoft GE Nokia Toyota Intel McDonald's Disney Google Mercedes-Benz HP BMW Gillette Cisco
+ 21,940 68,045 59,818 57,434 49,547 35,223 32,237 30,844 31,866 28,715 29,850 24,437 23,900 22,213 22,790 22,030
+
\ No newline at end of file
diff --git a/test/output/barChartRace-1525132800000.svg b/test/output/barChartRace-1525132800000.svg
new file mode 100644
index 0000000000..aa5ff85e29
--- /dev/null
+++ b/test/output/barChartRace-1525132800000.svg
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 20,000
+
+
+ 40,000
+
+
+ 60,000
+
+
+ 80,000
+
+
+ 100,000
+
+
+ 120,000
+
+
+ 140,000
+
+
+ 160,000
+
+
+ 180,000
+
+
+ 200,000
+
+
+ 220,000
+
+
+
+
+
+ 2018
+ Apple Google Amazon Microsoft Coca-Cola Samsung Toyota Mercedes-Benz Facebook McDonald's Intel IBM BMW Disney Cisco
+ 220,977 159,519 108,818 98,019 65,363 60,287 54,338 49,334 43,422 44,056 42,275 42,120 41,149 41,346 34,899
+
\ No newline at end of file
diff --git a/test/output/barChartRace-946684800000.svg b/test/output/barChartRace-946684800000.svg
new file mode 100644
index 0000000000..380565b8d9
--- /dev/null
+++ b/test/output/barChartRace-946684800000.svg
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10,000
+
+
+ 20,000
+
+
+ 30,000
+
+
+ 40,000
+
+
+ 50,000
+
+
+ 60,000
+
+
+ 70,000
+
+
+
+
+
+ 2000
+ Coca-Cola Microsoft IBM Intel Nokia GE Ford Disney McDonald's AT&T Marlboro Mercedes-Benz HP Cisco Toyota
+ 72,537 70,196 53,183 39,048 38,528 38,127 36,368 33,553 27,859 25,548 22,110 21,104 20,572 20,067 18,823
+
\ No newline at end of file
diff --git a/test/output/drivingAnimation.svg b/test/output/drivingAnimation-2009.3.svg
similarity index 73%
rename from test/output/drivingAnimation.svg
rename to test/output/drivingAnimation-2009.3.svg
index 9bb7fac98a..0a6a0a7e2c 100644
--- a/test/output/drivingAnimation.svg
+++ b/test/output/drivingAnimation-2009.3.svg
@@ -85,5 +85,7 @@
10,000
miles →
-
+
+
+
\ No newline at end of file
diff --git a/test/output/gapminder-1952.svg b/test/output/gapminder-1952.svg
new file mode 100644
index 0000000000..edde8b977e
--- /dev/null
+++ b/test/output/gapminder-1952.svg
@@ -0,0 +1,460 @@
+
+
+
+
+
+ 25
+
+
+
+ 30
+
+
+
+ 35
+
+
+
+ 40
+
+
+
+ 45
+
+
+
+ 50
+
+
+
+ 55
+
+
+
+ 60
+
+
+
+ 65
+
+
+
+ 70
+
+
+
+ 75
+
+
+
+ 80
+ ↑ lifeExp
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1k
+
+
+
+ 2k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10k
+
+
+
+ 20k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100k
+ gdpPercap →
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/output/gapminder-1997.3.svg b/test/output/gapminder-1997.3.svg
new file mode 100644
index 0000000000..9736a04e1b
--- /dev/null
+++ b/test/output/gapminder-1997.3.svg
@@ -0,0 +1,460 @@
+
+
+
+
+
+ 25
+
+
+
+ 30
+
+
+
+ 35
+
+
+
+ 40
+
+
+
+ 45
+
+
+
+ 50
+
+
+
+ 55
+
+
+
+ 60
+
+
+
+ 65
+
+
+
+ 70
+
+
+
+ 75
+
+
+
+ 80
+ ↑ lifeExp
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1k
+
+
+
+ 2k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10k
+
+
+
+ 20k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100k
+ gdpPercap →
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/output/gapminder-2020.svg b/test/output/gapminder-2020.svg
new file mode 100644
index 0000000000..6831cf47d5
--- /dev/null
+++ b/test/output/gapminder-2020.svg
@@ -0,0 +1,460 @@
+
+
+
+
+
+ 25
+
+
+
+ 30
+
+
+
+ 35
+
+
+
+ 40
+
+
+
+ 45
+
+
+
+ 50
+
+
+
+ 55
+
+
+
+ 60
+
+
+
+ 65
+
+
+
+ 70
+
+
+
+ 75
+
+
+
+ 80
+ ↑ lifeExp
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1k
+
+
+
+ 2k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10k
+
+
+
+ 20k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100k
+ gdpPercap →
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/output/gapminder.svg b/test/output/gapminder.svg
deleted file mode 100644
index a2fbfbd674..0000000000
--- a/test/output/gapminder.svg
+++ /dev/null
@@ -1,174 +0,0 @@
-
-
-
-
-
- 25
-
-
-
- 30
-
-
-
- 35
-
-
-
- 40
-
-
-
- 45
-
-
-
- 50
-
-
-
- 55
-
-
-
- 60
-
-
-
- 65
-
-
-
- 70
-
-
-
- 75
-
-
-
- 80
- ↑ lifeExp
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 1k
-
-
-
- 2k
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 10k
-
-
-
- 20k
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 100k
- gdpPercap →
-
-
-
-
\ No newline at end of file
diff --git a/test/output/gapminderBin-1952.html b/test/output/gapminderBin-1952.html
new file mode 100644
index 0000000000..6e9ec4b6ab
--- /dev/null
+++ b/test/output/gapminderBin-1952.html
@@ -0,0 +1,272 @@
+
+
+
+
+ Africa
+
+ Americas
+
+ Asia
+
+ Europe
+
+ Oceania
+
+
+
+
+
+ 0
+
+
+
+ 200
+
+
+
+ 400
+
+
+
+ 600
+
+
+
+ 800
+
+
+
+ 1,000
+
+
+
+ 1,200
+
+
+
+ 1,400
+
+
+
+ 1,600
+
+
+
+ 1,800
+
+
+
+ 2,000
+ ↑ population (millions)
+
+
+
+
+ 200
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1k
+
+
+
+ 2k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10k
+
+
+
+ 20k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100k
+ GDP per capita (US$) →
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1952
+
+
\ No newline at end of file
diff --git a/test/output/gapminderBin-1997.3.html b/test/output/gapminderBin-1997.3.html
new file mode 100644
index 0000000000..9ba1b110f0
--- /dev/null
+++ b/test/output/gapminderBin-1997.3.html
@@ -0,0 +1,271 @@
+
+
+
+
+ Africa
+
+ Americas
+
+ Asia
+
+ Europe
+
+ Oceania
+
+
+
+
+
+ 0
+
+
+
+ 200
+
+
+
+ 400
+
+
+
+ 600
+
+
+
+ 800
+
+
+
+ 1,000
+
+
+
+ 1,200
+
+
+
+ 1,400
+
+
+
+ 1,600
+
+
+
+ 1,800
+
+
+
+ 2,000
+ ↑ population (millions)
+
+
+
+
+ 200
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1k
+
+
+
+ 2k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10k
+
+
+
+ 20k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100k
+ GDP per capita (US$) →
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1997
+
+
\ No newline at end of file
diff --git a/test/output/gapminderBin-2020.html b/test/output/gapminderBin-2020.html
new file mode 100644
index 0000000000..407852c5ac
--- /dev/null
+++ b/test/output/gapminderBin-2020.html
@@ -0,0 +1,270 @@
+
+
+
+
+ Africa
+
+ Americas
+
+ Asia
+
+ Europe
+
+ Oceania
+
+
+
+
+
+ 0
+
+
+
+ 200
+
+
+
+ 400
+
+
+
+ 600
+
+
+
+ 800
+
+
+
+ 1,000
+
+
+
+ 1,200
+
+
+
+ 1,400
+
+
+
+ 1,600
+
+
+
+ 1,800
+
+
+
+ 2,000
+ ↑ population (millions)
+
+
+
+
+ 200
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1k
+
+
+
+ 2k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10k
+
+
+
+ 20k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100k
+ GDP per capita (US$) →
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2007
+
+
\ No newline at end of file
diff --git a/test/output/gapminderBox-1952.svg b/test/output/gapminderBox-1952.svg
new file mode 100644
index 0000000000..2e59f7d401
--- /dev/null
+++ b/test/output/gapminderBox-1952.svg
@@ -0,0 +1,170 @@
+
+
+
+
+
+ Africa
+
+
+
+ Americas
+
+
+
+ Asia
+
+
+
+ Europe
+
+
+
+ Oceania
+ continent
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1k
+
+
+
+ 2k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10k
+
+
+
+ 20k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/output/gapminderBox-1997.3.svg b/test/output/gapminderBox-1997.3.svg
new file mode 100644
index 0000000000..e204d57d44
--- /dev/null
+++ b/test/output/gapminderBox-1997.3.svg
@@ -0,0 +1,170 @@
+
+
+
+
+
+ Africa
+
+
+
+ Americas
+
+
+
+ Asia
+
+
+
+ Europe
+
+
+
+ Oceania
+ continent
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1k
+
+
+
+ 2k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10k
+
+
+
+ 20k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/output/gapminderBox-2020.svg b/test/output/gapminderBox-2020.svg
new file mode 100644
index 0000000000..0132af3079
--- /dev/null
+++ b/test/output/gapminderBox-2020.svg
@@ -0,0 +1,170 @@
+
+
+
+
+
+ Africa
+
+
+
+ Americas
+
+
+
+ Asia
+
+
+
+ Europe
+
+
+
+ Oceania
+ continent
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1k
+
+
+
+ 2k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10k
+
+
+
+ 20k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/output/gapminderBoxFacet-1952.svg b/test/output/gapminderBoxFacet-1952.svg
new file mode 100644
index 0000000000..648eb99cd1
--- /dev/null
+++ b/test/output/gapminderBoxFacet-1952.svg
@@ -0,0 +1,204 @@
+
+
+
+
+ Africa
+
+
+ Americas
+
+
+ Asia
+
+
+ Europe
+
+
+ Oceania
+ continent
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1k
+
+
+
+ 2k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10k
+
+
+
+ 20k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/output/gapminderBoxFacet-1997.3.svg b/test/output/gapminderBoxFacet-1997.3.svg
new file mode 100644
index 0000000000..040dd743b7
--- /dev/null
+++ b/test/output/gapminderBoxFacet-1997.3.svg
@@ -0,0 +1,203 @@
+
+
+
+
+ Africa
+
+
+ Americas
+
+
+ Asia
+
+
+ Europe
+
+
+ Oceania
+ continent
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1k
+
+
+
+ 2k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10k
+
+
+
+ 20k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/output/gapminderBoxFacet-2020.svg b/test/output/gapminderBoxFacet-2020.svg
new file mode 100644
index 0000000000..2fac49032e
--- /dev/null
+++ b/test/output/gapminderBoxFacet-2020.svg
@@ -0,0 +1,203 @@
+
+
+
+
+ Africa
+
+
+ Americas
+
+
+ Asia
+
+
+ Europe
+
+
+ Oceania
+ continent
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1k
+
+
+
+ 2k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10k
+
+
+
+ 20k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/output/gapminderContinent-1952.svg b/test/output/gapminderContinent-1952.svg
new file mode 100644
index 0000000000..d649e7e8cb
--- /dev/null
+++ b/test/output/gapminderContinent-1952.svg
@@ -0,0 +1,583 @@
+
+
+
+
+ Africa
+
+
+ Americas
+
+
+ Asia
+
+
+ Europe
+
+
+ Oceania
+ continent
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1k
+
+
+
+ 2k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10k
+
+
+
+ 20k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100k
+
+ gdpPercap →
+
+
+
+
+ 30
+
+
+
+ 40
+
+
+
+ 50
+
+
+
+ 60
+
+
+
+ 70
+
+
+
+ 80
+ ↑ lifeExp
+
+
+
+
+ 30
+
+
+
+ 40
+
+
+
+ 50
+
+
+
+ 60
+
+
+
+ 70
+
+
+
+ 80
+
+
+
+
+
+ 30
+
+
+
+ 40
+
+
+
+ 50
+
+
+
+ 60
+
+
+
+ 70
+
+
+
+ 80
+
+
+
+
+
+ 30
+
+
+
+ 40
+
+
+
+ 50
+
+
+
+ 60
+
+
+
+ 70
+
+
+
+ 80
+
+
+
+
+
+ 30
+
+
+
+ 40
+
+
+
+ 50
+
+
+
+ 60
+
+
+
+ 70
+
+
+
+ 80
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/output/gapminderContinent-1997.3.svg b/test/output/gapminderContinent-1997.3.svg
new file mode 100644
index 0000000000..b5225b56a8
--- /dev/null
+++ b/test/output/gapminderContinent-1997.3.svg
@@ -0,0 +1,583 @@
+
+
+
+
+ Africa
+
+
+ Americas
+
+
+ Asia
+
+
+ Europe
+
+
+ Oceania
+ continent
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1k
+
+
+
+ 2k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10k
+
+
+
+ 20k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100k
+
+ gdpPercap →
+
+
+
+
+ 30
+
+
+
+ 40
+
+
+
+ 50
+
+
+
+ 60
+
+
+
+ 70
+
+
+
+ 80
+ ↑ lifeExp
+
+
+
+
+ 30
+
+
+
+ 40
+
+
+
+ 50
+
+
+
+ 60
+
+
+
+ 70
+
+
+
+ 80
+
+
+
+
+
+ 30
+
+
+
+ 40
+
+
+
+ 50
+
+
+
+ 60
+
+
+
+ 70
+
+
+
+ 80
+
+
+
+
+
+ 30
+
+
+
+ 40
+
+
+
+ 50
+
+
+
+ 60
+
+
+
+ 70
+
+
+
+ 80
+
+
+
+
+
+ 30
+
+
+
+ 40
+
+
+
+ 50
+
+
+
+ 60
+
+
+
+ 70
+
+
+
+ 80
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/output/gapminderContinent-2020.svg b/test/output/gapminderContinent-2020.svg
new file mode 100644
index 0000000000..5f183c5275
--- /dev/null
+++ b/test/output/gapminderContinent-2020.svg
@@ -0,0 +1,583 @@
+
+
+
+
+ Africa
+
+
+ Americas
+
+
+ Asia
+
+
+ Europe
+
+
+ Oceania
+ continent
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1k
+
+
+
+ 2k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10k
+
+
+
+ 20k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100k
+
+ gdpPercap →
+
+
+
+
+ 30
+
+
+
+ 40
+
+
+
+ 50
+
+
+
+ 60
+
+
+
+ 70
+
+
+
+ 80
+ ↑ lifeExp
+
+
+
+
+ 30
+
+
+
+ 40
+
+
+
+ 50
+
+
+
+ 60
+
+
+
+ 70
+
+
+
+ 80
+
+
+
+
+
+ 30
+
+
+
+ 40
+
+
+
+ 50
+
+
+
+ 60
+
+
+
+ 70
+
+
+
+ 80
+
+
+
+
+
+ 30
+
+
+
+ 40
+
+
+
+ 50
+
+
+
+ 60
+
+
+
+ 70
+
+
+
+ 80
+
+
+
+
+
+ 30
+
+
+
+ 40
+
+
+
+ 50
+
+
+
+ 60
+
+
+
+ 70
+
+
+
+ 80
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/output/gapminderDodge-1952.svg b/test/output/gapminderDodge-1952.svg
new file mode 100644
index 0000000000..3513d781a3
--- /dev/null
+++ b/test/output/gapminderDodge-1952.svg
@@ -0,0 +1,551 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1k
+
+
+
+ 2k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10k
+
+
+
+ 20k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100k
+ GDP per capita →
+
+
+
+ China
+
+
+ India
+
+
+ United States
+
+
+ Japan
+
+
+ Indonesia
+
+
+ Germany
+
+
+ Brazil
+
+
+ United Kingdom
+
+
+ Italy
+
+
+ Bangladesh
+
+
+ France
+
+
+ Pakistan
+
+
+ Nigeria
+
+
+ Mexico
+
+
+ Spain
+
+
+ Vietnam
+
+
+ Poland
+
+
+ Philippines
+
+
+ Turkey
+
+
+ Egypt
+
+
+ Thailand
+
+
+ Korea, Rep.
+
+
+ Ethiopia
+
+
+ Myanmar
+
+
+ Argentina
+
+
+ Iran
+
+
+ Romania
+
+
+ Canada
+
+
+ South Africa
+
+
+ Congo, Dem. Rep.
+
+
+ Colombia
+
+
+ Netherlands
+
+
+ Morocco
+
+
+ Hungary
+
+
+ Algeria
+
+
+ Nepal
+
+
+ Czech Republic
+
+
+ Korea, Dem. Rep.
+
+
+ Belgium
+
+
+ Australia
+
+
+ Taiwan
+
+
+ Portugal
+
+
+ Sudan
+
+
+ Afghanistan
+
+
+ Tanzania
+
+
+ Peru
+
+
+ Sri Lanka
+
+
+ Greece
+
+
+ Bulgaria
+
+
+ Sweden
+
+
+ Austria
+
+
+ Serbia
+
+
+ Malaysia
+
+
+ Kenya
+
+
+ Mozambique
+
+
+ Chile
+
+
+ Cuba
+
+
+ Uganda
+
+
+ Ghana
+
+
+ Iraq
+
+
+ Venezuela
+
+
+ Cameroon
+
+
+ Yemen, Rep.
+
+
+ Switzerland
+
+
+ Madagascar
+
+
+ Cambodia
+
+
+ Burkina Faso
+
+
+ Denmark
+
+
+ Angola
+
+
+ Finland
+
+
+ Saudi Arabia
+
+
+ Croatia
+
+
+ Mali
+
+
+ Syria
+
+
+ Tunisia
+
+
+ Slovak Republic
+
+
+ Ecuador
+
+
+ Niger
+
+
+ Norway
+
+
+ Haiti
+
+
+ Guatemala
+
+
+ Zimbabwe
+
+
+ Cote d'Ivoire
+
+
+ Ireland
+
+
+ Malawi
+
+
+ Bolivia
+
+
+ Bosnia and Herzegovina
+
+
+ Senegal
+
+
+ Chad
+
+
+ Zambia
+
+
+ Guinea
+
+
+ Rwanda
+
+
+ Somalia
+
+
+ Dominican Republic
+
+
+ Burundi
+
+
+ Uruguay
+
+
+ Puerto Rico
+
+
+ Sierra Leone
+
+
+ Hong Kong, China
+
+
+ El Salvador
+
+
+ New Zealand
+
+
+ Benin
+
+
+ Israel
+
+
+ Paraguay
+
+
+ Honduras
+
+
+ Slovenia
+
+
+ Lebanon
+
+
+ Eritrea
+
+
+ Jamaica
+
+
+ Central African Republic
+
+
+ Albania
+
+
+ Togo
+
+
+ Nicaragua
+
+
+ Singapore
+
+
+ West Bank and Gaza
+
+
+ Mauritania
+
+
+ Libya
+
+
+ Panama
+
+
+ Costa Rica
+
+
+ Liberia
+
+
+ Congo, Rep.
+
+
+ Mongolia
+
+
+ Lesotho
+
+
+ Trinidad and Tobago
+
+
+ Jordan
+
+
+ Guinea-Bissau
+
+
+ Mauritius
+
+
+ Oman
+
+
+ Namibia
+
+
+ Botswana
+
+
+ Gabon
+
+
+ Montenegro
+
+
+ Swaziland
+
+
+ Gambia
+
+
+ Reunion
+
+
+ Equatorial Guinea
+
+
+ Kuwait
+
+
+ Comoros
+
+
+ Iceland
+
+
+ Bahrain
+
+
+ Djibouti
+
+
+ Sao Tome and Principe
+
+
+ 1952
+
\ No newline at end of file
diff --git a/test/output/gapminderDodge-1997.3.svg b/test/output/gapminderDodge-1997.3.svg
new file mode 100644
index 0000000000..a669ad01ee
--- /dev/null
+++ b/test/output/gapminderDodge-1997.3.svg
@@ -0,0 +1,551 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1k
+
+
+
+ 2k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10k
+
+
+
+ 20k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100k
+ GDP per capita →
+
+
+
+ China
+
+
+ India
+
+
+ United States
+
+
+ Indonesia
+
+
+ Brazil
+
+
+ Pakistan
+
+
+ Japan
+
+
+ Bangladesh
+
+
+ Nigeria
+
+
+ Mexico
+
+
+ Germany
+
+
+ Vietnam
+
+
+ Philippines
+
+
+ Egypt
+
+
+ Iran
+
+
+ Turkey
+
+
+ Thailand
+
+
+ Ethiopia
+
+
+ United Kingdom
+
+
+ France
+
+
+ Italy
+
+
+ Congo, Dem. Rep.
+
+
+ Korea, Rep.
+
+
+ Myanmar
+
+
+ South Africa
+
+
+ Spain
+
+
+ Poland
+
+
+ Colombia
+
+
+ Argentina
+
+
+ Sudan
+
+
+ Tanzania
+
+
+ Canada
+
+
+ Algeria
+
+
+ Morocco
+
+
+ Kenya
+
+
+ Peru
+
+
+ Nepal
+
+
+ Romania
+
+
+ Venezuela
+
+
+ Afghanistan
+
+
+ Taiwan
+
+
+ Korea, Dem. Rep.
+
+
+ Saudi Arabia
+
+
+ Uganda
+
+
+ Iraq
+
+
+ Malaysia
+
+
+ Sri Lanka
+
+
+ Australia
+
+
+ Ghana
+
+
+ Mozambique
+
+
+ Yemen, Rep.
+
+
+ Netherlands
+
+
+ Syria
+
+
+ Cote d'Ivoire
+
+
+ Chile
+
+
+ Cameroon
+
+
+ Madagascar
+
+
+ Ecuador
+
+
+ Cambodia
+
+
+ Zimbabwe
+
+
+ Cuba
+
+
+ Greece
+
+
+ Malawi
+
+
+ Burkina Faso
+
+
+ Serbia
+
+
+ Czech Republic
+
+
+ Hungary
+
+
+ Belgium
+
+
+ Portugal
+
+
+ Angola
+
+
+ Guatemala
+
+
+ Niger
+
+
+ Senegal
+
+
+ Zambia
+
+
+ Mali
+
+
+ Tunisia
+
+
+ Sweden
+
+
+ Austria
+
+
+ Bulgaria
+
+
+ Guinea
+
+
+ Dominican Republic
+
+
+ Bolivia
+
+
+ Chad
+
+
+ Rwanda
+
+
+ Switzerland
+
+
+ Haiti
+
+
+ Somalia
+
+
+ Hong Kong, China
+
+
+ Burundi
+
+
+ Benin
+
+
+ Honduras
+
+
+ El Salvador
+
+
+ Israel
+
+
+ Slovak Republic
+
+
+ Denmark
+
+
+ Paraguay
+
+
+ Finland
+
+
+ Libya
+
+
+ Nicaragua
+
+
+ Sierra Leone
+
+
+ Jordan
+
+
+ Croatia
+
+
+ Norway
+
+
+ Togo
+
+
+ Eritrea
+
+
+ Singapore
+
+
+ Puerto Rico
+
+
+ Central African Republic
+
+
+ New Zealand
+
+
+ Ireland
+
+
+ Bosnia and Herzegovina
+
+
+ Costa Rica
+
+
+ Lebanon
+
+
+ Albania
+
+
+ Uruguay
+
+
+ West Bank and Gaza
+
+
+ Congo, Rep.
+
+
+ Panama
+
+
+ Jamaica
+
+
+ Mongolia
+
+
+ Mauritania
+
+
+ Oman
+
+
+ Liberia
+
+
+ Slovenia
+
+
+ Lesotho
+
+
+ Namibia
+
+
+ Kuwait
+
+
+ Botswana
+
+
+ Gambia
+
+
+ Guinea-Bissau
+
+
+ Mauritius
+
+
+ Trinidad and Tobago
+
+
+ Gabon
+
+
+ Swaziland
+
+
+ Montenegro
+
+
+ Reunion
+
+
+ Bahrain
+
+
+ Comoros
+
+
+ Equatorial Guinea
+
+
+ Djibouti
+
+
+ Iceland
+
+
+ Sao Tome and Principe
+
+
+ 1997
+
\ No newline at end of file
diff --git a/test/output/gapminderDodge-2020.svg b/test/output/gapminderDodge-2020.svg
new file mode 100644
index 0000000000..c3742d8edb
--- /dev/null
+++ b/test/output/gapminderDodge-2020.svg
@@ -0,0 +1,551 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1k
+
+
+
+ 2k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10k
+
+
+
+ 20k
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100k
+ GDP per capita →
+
+
+
+ China
+
+
+ India
+
+
+ United States
+
+
+ Indonesia
+
+
+ Brazil
+
+
+ Pakistan
+
+
+ Bangladesh
+
+
+ Nigeria
+
+
+ Japan
+
+
+ Mexico
+
+
+ Philippines
+
+
+ Vietnam
+
+
+ Germany
+
+
+ Egypt
+
+
+ Ethiopia
+
+
+ Turkey
+
+
+ Iran
+
+
+ Thailand
+
+
+ Congo, Dem. Rep.
+
+
+ France
+
+
+ United Kingdom
+
+
+ Italy
+
+
+ Korea, Rep.
+
+
+ Myanmar
+
+
+ Colombia
+
+
+ South Africa
+
+
+ Sudan
+
+
+ Spain
+
+
+ Argentina
+
+
+ Poland
+
+
+ Tanzania
+
+
+ Kenya
+
+
+ Morocco
+
+
+ Canada
+
+
+ Algeria
+
+
+ Afghanistan
+
+
+ Uganda
+
+
+ Nepal
+
+
+ Peru
+
+
+ Saudi Arabia
+
+
+ Iraq
+
+
+ Venezuela
+
+
+ Malaysia
+
+
+ Korea, Dem. Rep.
+
+
+ Taiwan
+
+
+ Ghana
+
+
+ Romania
+
+
+ Yemen, Rep.
+
+
+ Australia
+
+
+ Sri Lanka
+
+
+ Mozambique
+
+
+ Syria
+
+
+ Madagascar
+
+
+ Cote d'Ivoire
+
+
+ Cameroon
+
+
+ Netherlands
+
+
+ Chile
+
+
+ Burkina Faso
+
+
+ Cambodia
+
+
+ Ecuador
+
+
+ Malawi
+
+
+ Niger
+
+
+ Guatemala
+
+
+ Angola
+
+
+ Zimbabwe
+
+
+ Senegal
+
+
+ Mali
+
+
+ Zambia
+
+
+ Cuba
+
+
+ Greece
+
+
+ Portugal
+
+
+ Belgium
+
+
+ Tunisia
+
+
+ Chad
+
+
+ Czech Republic
+
+
+ Serbia
+
+
+ Hungary
+
+
+ Guinea
+
+
+ Dominican Republic
+
+
+ Bolivia
+
+
+ Somalia
+
+
+ Sweden
+
+
+ Rwanda
+
+
+ Haiti
+
+
+ Burundi
+
+
+ Austria
+
+
+ Benin
+
+
+ Switzerland
+
+
+ Honduras
+
+
+ Bulgaria
+
+
+ Hong Kong, China
+
+
+ El Salvador
+
+
+ Paraguay
+
+
+ Israel
+
+
+ Sierra Leone
+
+
+ Jordan
+
+
+ Libya
+
+
+ Togo
+
+
+ Nicaragua
+
+
+ Denmark
+
+
+ Slovak Republic
+
+
+ Finland
+
+
+ Eritrea
+
+
+ Norway
+
+
+ Singapore
+
+
+ Bosnia and Herzegovina
+
+
+ Croatia
+
+
+ Central African Republic
+
+
+ Costa Rica
+
+
+ New Zealand
+
+
+ Ireland
+
+
+ West Bank and Gaza
+
+
+ Puerto Rico
+
+
+ Lebanon
+
+
+ Congo, Rep.
+
+
+ Albania
+
+
+ Uruguay
+
+
+ Mauritania
+
+
+ Panama
+
+
+ Oman
+
+
+ Liberia
+
+
+ Mongolia
+
+
+ Jamaica
+
+
+ Kuwait
+
+
+ Namibia
+
+
+ Lesotho
+
+
+ Slovenia
+
+
+ Gambia
+
+
+ Botswana
+
+
+ Guinea-Bissau
+
+
+ Gabon
+
+
+ Mauritius
+
+
+ Swaziland
+
+
+ Trinidad and Tobago
+
+
+ Reunion
+
+
+ Comoros
+
+
+ Bahrain
+
+
+ Montenegro
+
+
+ Equatorial Guinea
+
+
+ Djibouti
+
+
+ Iceland
+
+
+ Sao Tome and Principe
+
+
+ 2007
+
\ No newline at end of file
diff --git a/test/output/historicalLifeExpectancy.svg b/test/output/historicalLifeExpectancy.svg
new file mode 100644
index 0000000000..a59081d32c
--- /dev/null
+++ b/test/output/historicalLifeExpectancy.svg
@@ -0,0 +1,54 @@
+
+
+
+
+
+ 0
+
+
+
+ 10
+
+
+
+ 20
+
+
+
+ 30
+
+
+
+ 40
+
+
+
+ 50
+
+
+
+ 60
+
+
+
+ 70
+ le →
+
+
+
+
+ Paleolithic
+
\ No newline at end of file
diff --git a/test/plot.js b/test/plot.js
index bba9268ab6..2cc3ffab2a 100644
--- a/test/plot.js
+++ b/test/plot.js
@@ -16,40 +16,48 @@ for (const [name, plot] of Object.entries(plots)) {
reindexStyle(root);
reindexMarker(root);
reindexClip(root);
- const actual = beautify.html(root.outerHTML, {indent_size: 2});
- const outfile = path.resolve("./test/output", `${path.basename(name, ".js")}.${ext}`);
- const diffile = path.resolve("./test/output", `${path.basename(name, ".js")}-changed.${ext}`);
- let expected;
- try {
- expected = await fs.readFile(outfile, "utf8");
- } catch (error) {
- if (error.code === "ENOENT" && process.env.CI !== "true") {
- console.warn(`! generating ${outfile}`);
- await fs.writeFile(outfile, actual, "utf8");
- return;
- } else {
- throw error;
+ for (const currentTime of root.snapshots || [undefined]) {
+ let n = name;
+ if (currentTime !== undefined) {
+ root.currentTime = currentTime;
+ n = `${n}-${+currentTime}`;
+ }
+ const actual = beautify.html(root.outerHTML, {indent_size: 2});
+ const outfile = path.resolve("./test/output", `${path.basename(n, ".js")}.${ext}`);
+ const diffile = path.resolve("./test/output", `${path.basename(n, ".js")}-changed.${ext}`);
+ let expected;
+
+ try {
+ expected = await fs.readFile(outfile, "utf8");
+ } catch (error) {
+ if (error.code === "ENOENT" && process.env.CI !== "true") {
+ console.warn(`! generating ${outfile}`);
+ await fs.writeFile(outfile, actual, "utf8");
+ continue;
+ } else {
+ throw error;
+ }
}
- }
- if (actual === expected) {
- if (process.env.CI !== "true") {
- try {
- await fs.unlink(diffile);
- console.warn(`! deleted ${diffile}`);
- } catch (error) {
- if (error.code !== "ENOENT") {
- throw error;
+ if (actual === expected) {
+ if (process.env.CI !== "true") {
+ try {
+ await fs.unlink(diffile);
+ console.warn(`! deleted ${diffile}`);
+ } catch (error) {
+ if (error.code !== "ENOENT") {
+ throw error;
+ }
}
}
+ } else {
+ console.warn(`! generating ${diffile}`);
+ await fs.writeFile(diffile, actual, "utf8");
}
- } else {
- console.warn(`! generating ${diffile}`);
- await fs.writeFile(diffile, actual, "utf8");
- }
- assert(actual === expected, `${name} must match snapshot`);
+ assert(actual === expected, `${name} must match snapshot`);
+ }
});
}
diff --git a/test/plots/bar-chart-race.js b/test/plots/bar-chart-race.js
new file mode 100644
index 0000000000..7d3499ca3f
--- /dev/null
+++ b/test/plots/bar-chart-race.js
@@ -0,0 +1,152 @@
+import * as Plot from "@observablehq/plot";
+import * as d3 from "d3";
+
+export default async function () {
+ const brands = await d3.csv("data/category-brands.csv", d3.autoType);
+ const format = d3.format(",");
+ const tickFormat = d => d === 0 ? "" : format(d);
+ const chart = Plot.plot({
+ width: 980,
+ height: 15 * 40,
+ marginTop: 20,
+ marginLeft: 10,
+ marginBottom: 10,
+ x: { axis: null, domain: [0, 1] },
+ y: { domain: d3.range(0, 15), axis: null },
+ color: {
+ scheme: "tableau10",
+ domain: d3.groupSort(
+ brands,
+ (v) => d3.mean(v, (d) => d.value),
+ (d) => d.category
+ )
+ },
+ time: {
+ delay: 1000,
+ duration: 20000
+ },
+ marks: [
+ Plot.barX(
+ brands,
+ Plot.normalizeX(
+ Plot.mapY("rank", {
+ fillOpacity: 0.6,
+ mixBlendMode: "multiply",
+ x: "value",
+ y: (d) => -d.value,
+ fill: "category",
+ time: "date",
+ key: "name",
+ z: null
+ })
+ )
+ ),
+
+ // ticks
+ ((data, options) => {
+ options = Plot.mapY("rank", options);
+ const b = Plot.tickX(data, options);
+ b.render = (
+ index,
+ { x },
+ { x: X },
+ { height, marginTop, marginBottom }
+ ) =>
+ d3
+ .create("svg:g")
+ .attr("transform", "translate(0, 20)")
+ .call(
+ d3
+ .axisTop(
+ d3.scaleLinear(
+ [0, x.invert(d3.max(index, (i) => X[i]))],
+ x.range()
+ )
+ )
+ .ticks(8)
+ .tickSize(-height + marginTop + marginBottom)
+ .tickFormat(tickFormat)
+ )
+ .call((g) => {
+ g.select(".domain").remove();
+ g.attr("stroke-width", 0.75).attr("font-size", 8);
+ g.selectAll("line").attr("stroke", "white");
+ })
+ .node();
+ return b;
+ })(brands, {
+ x: "value",
+ time: "date",
+ key: "name",
+ y: (d) => -d.value,
+ z: null
+ }),
+
+ Plot.tickX([0], {inset: 6, strokeWidth: .5}),
+
+ Plot.text(
+ brands,
+ Plot.selectMaxX({
+ text: (d) => `${d.date.getUTCFullYear()}`,
+ frameAnchor: "bottom-right",
+ time: "date",
+ x: (d) => d.value * 1e-9 + 1,
+ dy: -15,
+ fontSize: 40,
+ fontWeight: "bold",
+ fontVariant: "tabular-nums",
+ fill: "currentColor",
+ stroke: "white",
+ strokeWidth: 30
+ })
+ ),
+
+ Plot.textX(
+ brands,
+ Plot.normalizeX(
+ Plot.mapY("rank", {
+ x: "value",
+ y: (d) => -d.value,
+ text: "name",
+ textAnchor: "end",
+ fontSize: 12,
+ dx: -4,
+ dy: -7,
+ time: "date",
+ key: "name",
+ fill: "currentColor",
+ fontWeight: "bold",
+ z: null
+ })
+ )
+ ),
+
+ Plot.textX(
+ brands,
+ Plot.normalizeX(
+ Plot.mapY("rank", {
+ x: "value",
+ sort: (d) => -d.value,
+ y: (d) => -d.value,
+ text: "value",
+ textAnchor: "end",
+ dx: -4,
+ dy: 8,
+ time: "date",
+ key: "name",
+ fill: "currentColor",
+ z: null
+ })
+ )
+ )
+ ]
+ });
+
+ // for CI tests
+ chart.snapshots = [new Date(Date.UTC(2000, 0, 1)), new Date(Date.UTC(2008, 8, 1)), new Date(Date.UTC(2018, 4, 1))];
+
+ // animate on click
+ d3.select(chart).on("click", () => chart.paused ? chart.play() : chart.pause());
+
+ return chart;
+}
diff --git a/test/plots/driving-animation.js b/test/plots/driving-animation.js
index 4f47d65036..1cdba8e17b 100644
--- a/test/plots/driving-animation.js
+++ b/test/plots/driving-animation.js
@@ -1,13 +1,28 @@
import * as Plot from "@observablehq/plot";
import * as d3 from "d3";
-export default async function() {
+export default async function () {
const driving = await d3.csv("data/driving.csv", d3.autoType);
- return Plot.plot({
+ const chart = Plot.plot({
inset: 10,
grid: true,
marks: [
- Plot.line(driving, {x: "miles", y: "gas", time: "year", timeFilter: "lte"})
+ Plot.line(driving, {
+ x: "miles",
+ y: "gas",
+ time: "year",
+ timeFilter: "lte"
+ })
]
});
+
+ // for CI tests
+ chart.snapshots = [2009.3];
+
+ // animate on click
+ d3.select(chart).on("click", () =>
+ chart.paused ? chart.play() : chart.pause()
+ );
+
+ return chart;
}
diff --git a/test/plots/gapminder-bin.js b/test/plots/gapminder-bin.js
new file mode 100644
index 0000000000..7898e5489a
--- /dev/null
+++ b/test/plots/gapminder-bin.js
@@ -0,0 +1,78 @@
+import * as Plot from "@observablehq/plot";
+import * as d3 from "d3";
+
+export default async function () {
+ const gapminder = await d3.tsv("data/gapminder.tsv", d3.autoType);
+
+ // augment the data with countries for all continents with 0 population
+ // and every order of magnitude of gdpPercap, in order to avoid empty bars
+ const interval = 0.2; // powers of ten
+ for (const [continent, years] of d3.group(
+ gapminder,
+ (d) => d.continent,
+ (d) => d.year
+ )) {
+ for (const [year] of years) {
+ for (const m of d3.range(3 - interval, 5, interval)) {
+ gapminder.push({
+ pop: 0,
+ gdpPercap: 10 ** m,
+ continent,
+ year
+ });
+ }
+ }
+ }
+
+ const chart = Plot.plot({
+ grid: true,
+ x: {
+ label: "GDP per capita (US$) →",
+ type: "log",
+ transform: (d) => 10 ** d
+ },
+ y: {
+ label: "↑ population (millions)",
+ transform: (d) => d * 1e-6
+ },
+ color: { legend: true },
+ marks: [
+ Plot.rectY(
+ gapminder,
+ Plot.binX(
+ {
+ y: "sum",
+ interval
+ },
+ {
+ x: (d) => Math.log10(d.gdpPercap),
+ y: "pop",
+ fill: "continent",
+ time: "year",
+ order: "sum",
+ pointerEvents: "none"
+ }
+ )
+ ),
+ Plot.text(gapminder, Plot.selectFirst({
+ text: d => `${d.year}`, // TODO: should we use the tweening function even for round values?
+ frameAnchor: "top-right",
+ tween: (a, b) => {
+ const i = d3.interpolateRound(a, b);
+ return t => `${i(t)}`;
+ },
+ fontSize: 20,
+ fontVariant: "tabular-nums",
+ time: "year"
+ }))
+ ]
+ });
+
+ // for CI tests
+ chart.snapshots = [1952, 1997.3, 2020];
+
+ // animate on click
+ d3.select(chart).on("click", () => chart.paused ? chart.play() : chart.pause());
+
+ return chart;
+}
diff --git a/test/plots/gapminder-box-facet.js b/test/plots/gapminder-box-facet.js
new file mode 100644
index 0000000000..1e9ae305f2
--- /dev/null
+++ b/test/plots/gapminder-box-facet.js
@@ -0,0 +1,32 @@
+import * as Plot from "@observablehq/plot";
+import * as d3 from "d3";
+
+export default async function () {
+ const gapminder = await d3.tsv("data/gapminder.tsv", d3.autoType);
+ const chart = Plot.plot({
+ marginLeft: 70,
+ inset: 10,
+ grid: true,
+ facet: {data: gapminder, y: "continent"},
+ x: {
+ type: "log",
+ transform: d => Math.pow(10, d)
+ },
+ marks: [
+ Plot.boxX(gapminder, {
+ x: d => Math.log10(d.gdpPercap),
+ stroke: "continent",
+ strokeWidth: 0.5,
+ time: "year"
+ })
+ ]
+ });
+
+ // for CI tests
+ chart.snapshots = [1952, 1997.3, 2020];
+
+ // animate on click
+ d3.select(chart).on("click", () => chart.paused ? chart.play() : chart.pause());
+
+ return chart;
+}
diff --git a/test/plots/gapminder-box.js b/test/plots/gapminder-box.js
new file mode 100644
index 0000000000..b091301a3e
--- /dev/null
+++ b/test/plots/gapminder-box.js
@@ -0,0 +1,32 @@
+import * as Plot from "@observablehq/plot";
+import * as d3 from "d3";
+
+export default async function () {
+ const gapminder = await d3.tsv("data/gapminder.tsv", d3.autoType);
+ const chart = Plot.plot({
+ marginLeft: 70,
+ inset: 10,
+ grid: true,
+ x: {
+ type: "log",
+ transform: d => Math.pow(10, d)
+ },
+ marks: [
+ Plot.boxX(gapminder, {
+ x: d => Math.log10(d.gdpPercap),
+ y: "continent",
+ stroke: "continent",
+ strokeWidth: 0.5,
+ time: "year"
+ })
+ ]
+ });
+
+ // for CI tests
+ chart.snapshots = [1952, 1997.3, 2020];
+
+ // animate on click
+ d3.select(chart).on("click", () => chart.paused ? chart.play() : chart.pause());
+
+ return chart;
+}
diff --git a/test/plots/gapminder-continent.js b/test/plots/gapminder-continent.js
new file mode 100644
index 0000000000..e2860c641e
--- /dev/null
+++ b/test/plots/gapminder-continent.js
@@ -0,0 +1,27 @@
+import * as Plot from "@observablehq/plot";
+import * as d3 from "d3";
+
+export default async function() {
+ const gapminder = await d3.tsv("data/gapminder.tsv", d3.autoType);
+ const chart = Plot.plot({
+ height: 1200,
+ inset: 10,
+ grid: true,
+ facet: {data: gapminder, y: "continent", marginRight: 70},
+ x: {
+ type: "log"
+ },
+ marks: [
+ Plot.line(gapminder, {x: "gdpPercap", y: "lifeExp", z: "country", stroke: "continent", strokeWidth: 0.5, time: "year", timeFilter: "lte"}),
+ Plot.dot(gapminder, {x: "gdpPercap", y: "lifeExp", r: "pop", stroke: "continent", time: "year"})
+ ]
+ });
+
+ // for CI tests
+ chart.snapshots = [1952, 1997.3, 2020];
+
+ // animate on click
+ d3.select(chart).on("click", () => chart.paused ? chart.play() : chart.pause());
+
+ return chart;
+}
diff --git a/test/plots/gapminder-dodge.js b/test/plots/gapminder-dodge.js
new file mode 100644
index 0000000000..7184a6327b
--- /dev/null
+++ b/test/plots/gapminder-dodge.js
@@ -0,0 +1,37 @@
+import * as Plot from "@observablehq/plot";
+import * as d3 from "d3";
+
+export default async function () {
+ const gapminder = await d3.tsv("data/gapminder.tsv", d3.autoType);
+ const chart = Plot.plot({
+ height: 170,
+ marginLeft: 75,
+ inset: 10,
+ grid: true,
+ x: {
+ type: "log",
+ transform: d => Math.pow(10, d),
+ label: "GDP per capita →"
+ },
+ marks: [
+ Plot.dot(gapminder, Plot.dodgeY({
+ x: d => Math.log10(d.gdpPercap),
+ r: "pop",
+ fill: "continent",
+ fillOpacity: 0.3,
+ strokeWidth: 0.5,
+ time: "year",
+ title: "country"
+ })),
+ Plot.text(gapminder, Plot.selectFirst({frameAnchor: "top-left", text: d => `${d.year}`, time: "year"}))
+ ]
+ });
+
+ // for CI tests
+ chart.snapshots = [1952, 1997.3, 2020];
+
+ // animate on click
+ d3.select(chart).on("click", () => chart.paused ? chart.play() : chart.pause());
+
+ return chart;
+}
diff --git a/test/plots/gapminder.js b/test/plots/gapminder.js
index d33660f2f4..dfcb166463 100644
--- a/test/plots/gapminder.js
+++ b/test/plots/gapminder.js
@@ -3,15 +3,23 @@ import * as d3 from "d3";
export default async function() {
const gapminder = await d3.tsv("data/gapminder.tsv", d3.autoType);
- return Plot.plot({
+ const chart = Plot.plot({
inset: 10,
grid: true,
x: {
type: "log"
},
marks: [
- Plot.line(gapminder, {x: "gdpPercap", y: "lifeExp", z: "country", sort: null, stroke: "continent", strokeWidth: 0.5, time: "year", timeFilter: "lte"}),
- Plot.dot(gapminder, {x: "gdpPercap", y: "lifeExp", r: "pop", sort: null, stroke: "continent", time: "year"})
+ Plot.line(gapminder, {x: "gdpPercap", y: "lifeExp", z: "country", stroke: "continent", strokeWidth: 0.5, time: "year", timeFilter: "lte"}),
+ Plot.dot(gapminder, {x: "gdpPercap", y: "lifeExp", r: "pop", fill: "continent", fillOpacity: 0.5, stroke: "continent", time: "year"})
]
});
+
+ // for CI tests
+ chart.snapshots = [1952, 1997.3, 2020];
+
+ // animate on click
+ d3.select(chart).on("click", () => chart.paused ? chart.play() : chart.pause());
+
+ return chart;
}
diff --git a/test/plots/historical-life-expectancy.js b/test/plots/historical-life-expectancy.js
new file mode 100644
index 0000000000..a822784f75
--- /dev/null
+++ b/test/plots/historical-life-expectancy.js
@@ -0,0 +1,24 @@
+import * as Plot from "@observablehq/plot";
+import * as d3 from "d3";
+
+export default async function () {
+ const le = await d3.tsv("data/historical-life-expectancy.tsv").then((data) =>
+ data.map(({ era, lifeexp }, i) => ({
+ i,
+ era,
+ le: parseFloat(lifeexp.replace(/>/g, ""))
+ }))
+ );
+
+ return Plot.plot({
+ time: {
+ domain: [...new Set(le.map(d => d.era))],
+ duration: 13000
+ },
+ grid: true,
+ marks: [
+ Plot.barX(le, { time: "era", x: "le" }),
+ Plot.text(le, { time: "era", text: "era", fill: "white", frameAnchor: "left", dx: 14 })
+ ]
+ });
+}
diff --git a/test/plots/index.js b/test/plots/index.js
index c2bf783bc6..da2e43a6ef 100644
--- a/test/plots/index.js
+++ b/test/plots/index.js
@@ -24,6 +24,7 @@ export {default as athletesWeightCumulative} from "./athletes-weight-cumulative.
export {default as availability} from "./availability.js";
export {default as ballotStatusRace} from "./ballot-status-race.js";
export {default as bandClip} from "./band-clip.js";
+export {default as barChartRace} from "./bar-chart-race.js";
export {default as beckerBarley} from "./becker-barley.js";
export {default as binStrings} from "./bin-strings.js";
export {default as binTimestamps} from "./bin-timestamps.js";
@@ -76,6 +77,11 @@ export {default as frameCorners} from "./frame-corners.js";
export {default as fruitSales} from "./fruit-sales.js";
export {default as fruitSalesDate} from "./fruit-sales-date.js";
export {default as gapminder} from "./gapminder.js";
+export {default as gapminderBin} from "./gapminder-bin.js";
+export {default as gapminderBox} from "./gapminder-box.js";
+export {default as gapminderBoxFacet} from "./gapminder-box-facet.js";
+export {default as gapminderDodge} from "./gapminder-dodge.js";
+export {default as gapminderContinent} from "./gapminder-continent.js";
export {default as gistempAnomaly} from "./gistemp-anomaly.js";
export {default as gistempAnomalyMoving} from "./gistemp-anomaly-moving.js";
export {default as gistempAnomalyTransform} from "./gistemp-anomaly-transform.js";
@@ -93,6 +99,7 @@ export {default as hexbinText} from "./hexbin-text.js";
export {default as hexbinZ} from "./hexbin-z.js";
export {default as hexbinZNull} from "./hexbin-z-null.js";
export {default as highCardinalityOrdinal} from "./high-cardinality-ordinal.js";
+export {default as historicalLifeExpectancy} from "./historical-life-expectancy.js";
export {default as hrefFill} from "./href-fill.js";
export {default as identityScale} from "./identity-scale.js";
export {default as integerInterval} from "./integer-interval.js";
diff --git a/time.md b/time.md
new file mode 100644
index 0000000000..9af0980542
--- /dev/null
+++ b/time.md
@@ -0,0 +1,131 @@
+### Time options
+
+Each mark can index its data by time and animate the chart from one keyframe to
+the next. When possible, it maintains object consistency by interpolating the
+values of the same object between keyframes.
+
+The **time** option in a mark specifies the time channel for that mark, and
+defaults to null. Its values can be dates, numbers, or even ordinal
+("yesterday", "today", "tomorrow"…)
+
+The **key** option specifies the key channel, and defaults to the index of the
+datum for its time—in other words, given a repetitive array of data, Plot will
+interpolate the first line for each date as one object, the second line for each
+date as another object, etc.
+
+The layout (positions, colors…) of each keyframe are computed independently, and
+any data transforms are only aware of the tranche of data corresponding to that
+keyframe.
+
+Plot draws each time-aware mark, for any given time, by locating the preceding
+and following keyframes. It matches the objects which have the same *key*, and
+considers those which have no counterpart to be either *exiting* (if they belong
+to the preceding keyframe) or *entering* (if they belong to the following
+keyframe). A new set of channels is derived, with indices that contains all the
+original data points plus an appendix with additional exit, update and enter
+indices, that point to interpolated values.
+
+The interpolated values for the *exit* points are equal to the corresponding
+values in the preceding keyframe, except for the opacity channel, which goes
+from 1 when the time is very close to the preceding time to 0 when it is close
+to the following time. Conversely, the interpolated values for the *enter*
+points are equal to the corresponding values in the following keyframe, except
+for the opacity channel, which goes from 0 when the time is very close to the
+preceding time to 1 when it is close to the following time.
+
+The values for the *update* index are interpolated linearly in screen space,
+between the value for the matching object at the preceding keyframe and the
+value for the matching object at the following keyframe.
+
+Each channel has its own interpolator, which can be specified by the **tween**
+option, which is either a tweening function or an object where keys are channel
+names (such as text, x, x1…), and values are tweening function. A tweening
+function receives as arguments the previous and the next values *a* and *b*, and
+must return a function of *t* that is equal to *a* for *t*=0 and equal to *b*
+for *t*=1.
+
+TODO: shelve specific tweens for now.
+
+The default interpolator depends on the type of the value:
+- position channels default to [number
+ interpolation](https://github.com/d3/d3-interpolate/blob/main/README.md#interpolateHsl).
+- color channels default to [HSL
+ interpolation](https://github.com/d3/d3-interpolate/blob/main/README.md#interpolateHsl).
+- a text channel defaults to "first value" (*a*) if *a* or *b* are text values;
+ it defaults to number interpolation if *a* or *b* are fractional numbers, or
+ if they differ by less that a few units; and it defaults to [round number
+ interpolation](https://github.com/d3/d3-interpolate/blob/main/README.md#interpolateRound)
+ if the two values are integers with a larger difference.
+
+What the mark displays depends on the **timeFilter** option, which defaults to
+"eq".
+
+The support timeFilter options are:
+
+- **eq** (default) - displays the values with a time equal to the current time
+- **lte** - displays the values with a time lower than or equal to the current
+ time (the "past")
+- **gte** - displays the values with a time greater than or equal to the current
+ time (the "future")
+
+TODO: Some transforms do not propagate nor generate a correct *key* channel.
+They should be fixed or throw an error.
+
+TODO: A Plot.binTime transform with cumulative effect.
+
+TODO: scale/axis transition.
+
+
+### Animation options
+
+The top-level **time** option controls the time scale and animation parameters.
+The following parameters are supported:
+
+TODO: add scale options (domain, type, exponent…)
+
+- **duration** - the duration of the animation to cover the whole time domain,
+ in milliseconds; defaults to 5000
+- **direction** - specifies the direction of the playback: forward (positive) or
+ backward (neggative); defaults to 1
+- **playbackRate** - the rate at which the animation is played; redundant with
+ the duration and direction (a playbackRate of 0.5 results in a twice longer
+ duration, and a negative playbackRate results in the opposite direction)
+- **initial** - specifies the time at which the animation starts (must be valid
+ in the time domain); defaults to the start of time if the animation if played
+ forwards, and the end of time if played backwards
+- **autoplay** - a boolean that specifies if the animation should start when the
+ chart is created; defaults to true
+- **delay** - the delay before the animation autoplays, in milliseconds;
+ defaults to 0
+- **iterations** - if specified, the number of iterations before the animation
+ stops
+- **loop** - a boolean indicating if the animation loops; defaults to true if
+ the number of iterations is positive, false otherwise
+- **alternate** - a boolean indicating if every even iteration should be played
+ backwards, defaults to false
+- **loopDelay** - the delay between two iterations, in milliseconds; defaults to
+ 1000
+
+TODO: prefer Web animation API
+
+Some of these options correspond to the [HTMLMediaElement
+API](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement). When a
+figure has a time channel, the following properties can be used to
+programmatically control the animation.
+
+- **currentTime** - the current time, a value in the time domain; this property
+ is both readable and writable; note that in the case of ordinal time, this
+ indicates the time of the preceding keyframe
+- **timeupdate** - a [timeupdate
+ event](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/timeupdate_event)
+ is fired when the currentTime has been updated
+- **play** - a function to start the animation. It returns a Promise that
+ resolves immediately
+- **pause** - a function to pause the animation
+- **paused** - a readable boolean indicating whether the animation is paused
+- **ended** - a readable boolean indicating whether the animation is ended
+- **duration** - a readable number indicating the duration in seconds (not
+ milliseconds!) TODO: should all durations/delays be in seconds?
+- **playbackRate** - the playback rate, as a readable and writable number;
+ defaults to 1, negative if played in reverse
+- **loop** - a readable and writable boolean indicating if the animation loops