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 ExpressCoca-ColaIBMMicrosoftGENokiaToyotaIntelMcDonald'sDisneyGoogleMercedes-BenzHPBMWGilletteCisco + 21,94068,04559,81857,43449,54735,22332,23730,84431,86628,71529,85024,43723,90022,21322,79022,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 + AppleGoogleAmazonMicrosoftCoca-ColaSamsungToyotaMercedes-BenzFacebookMcDonald'sIntelIBMBMWDisneyCisco + 220,977159,519108,81898,01965,36360,28754,33849,33443,42244,05642,27542,12041,14941,34634,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-ColaMicrosoftIBMIntelNokiaGEFordDisneyMcDonald'sAT&TMarlboroMercedes-BenzHPCiscoToyota + 72,53770,19653,18339,04838,52838,12736,36833,55327,85925,54822,11021,10420,57220,06718,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