diff --git a/.gitignore b/.gitignore index 54fe83baed..2a169ecf82 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,6 @@ quicktests/overlaying/list_of_quicktests.json # Eclipse *.project + +# Visual Studio Code +.settings/ diff --git a/Gruntfile.js b/Gruntfile.js index e830b003ec..70b539c988 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -44,7 +44,8 @@ module.exports = function(grunt) { updateConfigs: ['pkg'], commit: false, createTag: false, - push: false + push: false, + prereleaseName: "rc" } }; diff --git a/bower.json b/bower.json index a444ddc2fe..954a4ef01f 100644 --- a/bower.json +++ b/bower.json @@ -1,7 +1,7 @@ { "name": "plottable", "description": "A library for creating charts out of D3", - "version": "0.54.0", + "version": "1.0.0-rc1", "main": ["plottable.js", "plottable.css"], "license": "MIT", "ignore": [ diff --git a/package.json b/package.json index 432e40afec..d834d00990 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "plottable.js", "description": "A library for creating charts out of D3", - "version": "0.54.0", + "version": "1.0.0-rc1", "repository": { "type": "git", "url": "https://github.com/palantir/plottable.git" @@ -16,7 +16,8 @@ "grunt-contrib-watch": "~0.5.3", "load-grunt-tasks": "~0.2.0", "grunt-ts": "~1.12.1", - "grunt-tslint": "2.0.0", + "grunt-tslint": "2.2.0-beta", + "tslint": "2.2.0-beta", "grunt-contrib-concat": "~0.3.0", "grunt-mocha-phantomjs": "0.6.1", "grunt-contrib-connect": "~0.6.0", @@ -26,7 +27,7 @@ "grunt-sed": "~0.1.1", "grunt-git": "~0.2.6", "grunt-contrib-clean": "~0.4.0", - "grunt-bump": "0.0.13", + "grunt-bump": "0.3.1", "grunt-contrib-compress": "~0.9.1", "grunt-contrib-uglify": "~0.4.0", "grunt-shell": "0.7.0", diff --git a/plottable.d.ts b/plottable.d.ts index 0735690a07..c927de2851 100644 --- a/plottable.d.ts +++ b/plottable.d.ts @@ -1,6 +1,6 @@ declare module Plottable { - module _Util { + module Utils { module Methods { /** * Checks if x is between a and b. @@ -43,11 +43,6 @@ declare module Plottable { * @return {D3.Set} A set that contains elements that appear in both set1 and set2 */ function intersection(set1: D3.Set, set2: D3.Set): D3.Set; - /** - * Take an accessor object (may be a string to be made into a key, or a value, or a color code) - * and "activate" it by turning it into a function in (datum, index, metadata) - */ - function accessorize(accessor: any): _Accessor; /** * Takes two sets and returns the union * @@ -101,19 +96,17 @@ declare module Plottable { */ function objEq(a: any, b: any): boolean; /** - * Computes the max value from the array. - * - * If type is not comparable then t will be converted to a comparable before computing max. + * Applies the accessor, if provided, to each element of `array` and returns the maximum value. + * If no maximum value can be computed, returns defaultValue. */ - function max(arr: C[], default_val: C): C; - function max(arr: T[], acc: (x?: T, i?: number) => C, default_val: C): C; + function max(array: C[], defaultValue: C): C; + function max(array: T[], accessor: (t?: T, i?: number) => C, defaultValue: C): C; /** - * Computes the min value from the array. - * - * If type is not comparable then t will be converted to a comparable before computing min. + * Applies the accessor, if provided, to each element of `array` and returns the minimum value. + * If no minimum value can be computed, returns defaultValue. */ - function min(arr: C[], default_val: C): C; - function min(arr: T[], acc: (x?: T, i?: number) => C, default_val: C): C; + function min(array: C[], defaultValue: C): C; + function min(array: T[], accessor: (t?: T, i?: number) => C, defaultValue: C): C; /** * Returns true **only** if x is NaN */ @@ -174,116 +167,87 @@ declare module Plottable { declare module Plottable { - module _Util { - module OpenSource { - /** - * Returns the sortedIndex for inserting a value into an array. - * Takes a number and an array of numbers OR an array of objects and an accessor that returns a number. - * @param {number} value: The numerical value to insert - * @param {any[]} arr: Array to find insertion index, can be number[] or any[] (if accessor provided) - * @param {_Accessor} accessor: If provided, this function is called on members of arr to determine insertion index - * @returns {number} The insertion index. - * The behavior is undefined for arrays that are unsorted - * If there are multiple valid insertion indices that maintain sorted order (e.g. addign 1 to [1,1,1,1,1]) then - * the behavior must satisfy that the array is sorted post-insertion, but is otherwise unspecified. - * This is a modified version of Underscore.js's implementation of sortedIndex. - * Underscore.js is released under the MIT License: - * Copyright (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative - * Reporters & Editors - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - function sortedIndex(val: number, arr: number[]): number; - function sortedIndex(val: number, arr: any[], accessor: _Accessor): number; - } - } -} - - -declare module Plottable { - module _Util { + module Utils { /** * An associative array that can be keyed by anything (inc objects). * Uses pointer equality checks which is why this works. * This power has a price: everything is linear time since it is actually backed by an array... */ - class StrictEqualityAssociativeArray { + class Map { /** * Set a new key/value pair in the store. * - * @param {any} key Key to set in the store - * @param {any} value Value to set in the store + * @param {K} key Key to set in the store + * @param {V} value Value to set in the store * @return {boolean} True if key already in store, false otherwise */ - set(key: any, value: any): boolean; + set(key: K, value: V): boolean; /** * Get a value from the store, given a key. * - * @param {any} key Key associated with value to retrieve - * @return {any} Value if found, undefined otherwise + * @param {K} key Key associated with value to retrieve + * @return {V} Value if found, undefined otherwise */ - get(key: any): any; + get(key: K): any; /** * Test whether store has a value associated with given key. * * Will return true if there is a key/value entry, * even if the value is explicitly `undefined`. * - * @param {any} key Key to test for presence of an entry + * @param {K} key Key to test for presence of an entry * @return {boolean} Whether there was a matching entry for that key */ - has(key: any): boolean; + has(key: K): boolean; /** * Return an array of the values in the key-value store * - * @return {any[]} The values in the store + * @return {V[]} The values in the store */ values(): any[]; /** * Return an array of keys in the key-value store * - * @return {any[]} The keys in the store + * @return {K[]} The keys in the store */ keys(): any[]; /** * Execute a callback for each entry in the array. * - * @param {(key: any, val?: any, index?: number) => any} callback The callback to eecute + * @param {(key: K, val?: V, index?: number) => any} callback The callback to execute * @return {any[]} The results of mapping the callback over the entries */ - map(cb: (key?: any, val?: any, index?: number) => any): any[]; + map(cb: (key?: K, val?: V, index?: number) => any): any[]; /** * Delete a key from the key-value store. Return whether the key was present. * - * @param {any} The key to remove + * @param {K} The key to remove * @return {boolean} Whether a matching entry was found and removed */ - delete(key: any): boolean; + delete(key: K): boolean; + } + } +} + + +declare module Plottable { + module Utils { + /** + * Shim for ES6 set. + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set + */ + class Set { + constructor(); + add(value: T): Set; + delete(value: T): boolean; + has(value: T): boolean; + values(): T[]; } } } declare module Plottable { - module _Util { + module Utils { module DOM { /** * Gets the bounding box of an element. @@ -301,14 +265,15 @@ declare module Plottable { function boxesOverlap(boxA: ClientRect, boxB: ClientRect): boolean; function boxIsInside(inner: ClientRect, outer: ClientRect): boolean; function getBoundingSVG(elem: SVGElement): SVGElement; + function getUniqueClipPathId(): string; } } } declare module Plottable { - module _Util { - module Color { + module Utils { + module Colors { /** * Return contrast ratio between two colors * Based on implementation from chroma.js by Gregor Aisch (gka) (licensed under BSD) @@ -322,6 +287,20 @@ declare module Plottable { } +declare module Plottable { + module Utils { + /** + * A set of callbacks which can be all invoked at once. + * Each callback exists at most once in the set (based on reference equality). + * All callbacks should have the same signature. + */ + class CallbackSet extends Set { + callCallbacks(...args: any[]): CallbackSet; + } + } +} + + declare module Plottable { type Formatter = (d: any) => string; var MILLISECONDS_IN_ONE_DAY: number; @@ -397,6 +376,11 @@ declare module Plottable { * @returns {Formatter} A formatter for time/date values. */ function time(specifier: string): Formatter; + /** + * Transforms the Plottable TimeInterval string into a d3 time interval equivalent. + * If the provided TimeInterval is incorrect, the default is d3.time.year + */ + function timeIntervalToD3Time(timeInterval: string): D3.Time.Interval; /** * Creates a formatter for relative dates. * @@ -430,7 +414,7 @@ declare module Plottable { declare module Plottable { - module _Util { + module Utils { class ClientToSVGTranslator { static getTranslator(elem: SVGElement): ClientToSVGTranslator; constructor(svg: SVGElement); @@ -444,7 +428,7 @@ declare module Plottable { declare module Plottable { - module Config { + module Configs { /** * Specifies if Plottable should show warnings. */ @@ -481,90 +465,8 @@ declare module Plottable { declare module Plottable { - module Core { - /** - * A class most other Plottable classes inherit from, in order to have a - * unique ID. - */ - class PlottableObject { - getID(): number; - } - } -} - - -declare module Plottable { - module Core { - /** - * A callback for a Broadcaster. The callback will be called with the Broadcaster's - * "listenable" as the first argument, with subsequent optional arguments depending - * on the listenable. - */ - interface BroadcasterCallback { - (listenable: L, ...args: any[]): any; - } - /** - * The Broadcaster holds a reference to a "listenable" object. - * Third parties can register and deregister listeners from the Broadcaster. - * When the broadcaster.broadcast() method is called, all registered callbacks - * are called with the Broadcaster's "listenable", along with optional - * arguments passed to the `broadcast` method. - * - * The listeners are called synchronously. - */ - class Broadcaster extends Core.PlottableObject { - /** - * Constructs a broadcaster, taking a "listenable" object to broadcast about. - * - * @constructor - * @param {L} listenable The listenable object to broadcast. - */ - constructor(listenable: L); - /** - * Registers a callback to be called when the broadcast method is called. Also takes a key which - * is used to support deregistering the same callback later, by passing in the same key. - * If there is already a callback associated with that key, then the callback will be replaced. - * The callback will be passed the Broadcaster's "listenable" as the `this` context. - * - * @param key The key associated with the callback. Key uniqueness is determined by deep equality. - * @param {BroadcasterCallback} callback A callback to be called. - * @returns {Broadcaster} The calling Broadcaster - */ - registerListener(key: any, callback: BroadcasterCallback): Broadcaster; - /** - * Call all listening callbacks, optionally with arguments passed through. - * - * @param ...args A variable number of optional arguments - * @returns {Broadcaster} The calling Broadcaster - */ - broadcast(...args: any[]): Broadcaster; - /** - * Deregisters the callback associated with a key. - * - * @param key The key to deregister. - * @returns {Broadcaster} The calling Broadcaster - */ - deregisterListener(key: any): Broadcaster; - /** - * Gets the keys for all listeners attached to the Broadcaster. - * - * @returns {any[]} An array of the keys. - */ - getListenerKeys(): any[]; - /** - * Deregisters all listeners and callbacks associated with the Broadcaster. - * - * @returns {Broadcaster} The calling Broadcaster - */ - deregisterAllListeners(): void; - } - } -} - - -declare module Plottable { - class Dataset extends Core.PlottableObject { - broadcaster: Core.Broadcaster; + type DatasetCallback = (dataset: Dataset) => any; + class Dataset { /** * Constructs a new set. * @@ -576,6 +478,8 @@ declare module Plottable { * @param {any} metadata An object containing additional information (default = {}). */ constructor(data?: any[], metadata?: any); + onUpdate(callback: DatasetCallback): void; + offUpdate(callback: DatasetCallback): void; /** * Gets the data. * @@ -603,95 +507,88 @@ declare module Plottable { * @returns {Dataset} The calling Dataset. */ metadata(metadata: any): Dataset; - _getExtent(accessor: _Accessor, typeCoercer: (d: any) => any, plotMetadata?: any): any[]; } } declare module Plottable { - module Core { - module RenderController { - module RenderPolicy { - /** - * A policy to render components. - */ - interface RenderPolicy { - render(): any; - } - /** - * Never queue anything, render everything immediately. Useful for - * debugging, horrible for performance. - */ - class Immediate implements RenderPolicy { - render(): void; - } - /** - * The default way to render, which only tries to render every frame - * (usually, 1/60th of a second). - */ - class AnimationFrame implements RenderPolicy { - render(): void; - } - /** - * Renders with `setTimeout`. This is generally an inferior way to render - * compared to `requestAnimationFrame`, but it's still there if you want - * it. - */ - class Timeout implements RenderPolicy { - _timeoutMsec: number; - render(): void; - } - } + module RenderPolicies { + /** + * A policy to render components. + */ + interface RenderPolicy { + render(): any; + } + /** + * Never queue anything, render everything immediately. Useful for + * debugging, horrible for performance. + */ + class Immediate implements RenderPolicy { + render(): void; + } + /** + * The default way to render, which only tries to render every frame + * (usually, 1/60th of a second). + */ + class AnimationFrame implements RenderPolicy { + render(): void; + } + /** + * Renders with `setTimeout`. This is generally an inferior way to render + * compared to `requestAnimationFrame`, but it's still there if you want + * it. + */ + class Timeout implements RenderPolicy { + _timeoutMsec: number; + render(): void; } } } declare module Plottable { - module Core { + /** + * The RenderController is responsible for enqueueing and synchronizing + * layout and render calls for Plottable components. + * + * Layouts and renders occur inside an animation callback + * (window.requestAnimationFrame if available). + * + * If you require immediate rendering, call RenderController.flush() to + * perform enqueued layout and rendering serially. + * + * If you want to always have immediate rendering (useful for debugging), + * call + * ```typescript + * Plottable.RenderController.setRenderPolicy( + * new Plottable.RenderPolicies.Immediate() + * ); + * ``` + */ + module RenderController { + var _renderPolicy: RenderPolicies.RenderPolicy; + function setRenderPolicy(policy: string | RenderPolicies.RenderPolicy): void; /** - * The RenderController is responsible for enqueueing and synchronizing - * layout and render calls for Plottable components. + * If the RenderController is enabled, we enqueue the component for + * render. Otherwise, it is rendered immediately. * - * Layouts and renders occur inside an animation callback - * (window.requestAnimationFrame if available). + * @param {Component} component Any Plottable component. + */ + function registerToRender(component: Component): void; + /** + * If the RenderController is enabled, we enqueue the component for + * layout and render. Otherwise, it is rendered immediately. * - * If you require immediate rendering, call RenderController.flush() to - * perform enqueued layout and rendering serially. + * @param {Component} component Any Plottable component. + */ + function registerToComputeLayout(component: Component): void; + /** + * Render everything that is waiting to be rendered right now, instead of + * waiting until the next frame. * - * If you want to always have immediate rendering (useful for debugging), - * call - * ```typescript - * Plottable.Core.RenderController.setRenderPolicy( - * new Plottable.Core.RenderController.RenderPolicy.Immediate() - * ); - * ``` + * Useful to call when debugging. */ - module RenderController { - var _renderPolicy: RenderPolicy.RenderPolicy; - function setRenderPolicy(policy: string | RenderPolicy.RenderPolicy): void; - /** - * If the RenderController is enabled, we enqueue the component for - * render. Otherwise, it is rendered immediately. - * - * @param {AbstractComponent} component Any Plottable component. - */ - function registerToRender(c: Component.AbstractComponent): void; - /** - * If the RenderController is enabled, we enqueue the component for - * layout and render. Otherwise, it is rendered immediately. - * - * @param {AbstractComponent} component Any Plottable component. - */ - function registerToComputeLayout(c: Component.AbstractComponent): void; - /** - * Render everything that is waiting to be rendered right now, instead of - * waiting until the next frame. - * - * Useful to call when debugging. - */ - function flush(): void; - } + function flush(): void; } } @@ -699,21 +596,23 @@ declare module Plottable { /** * Access specific datum property. */ - type _Accessor = (datum: any, index?: number, userMetadata?: any, plotMetadata?: Plot.PlotMetadata) => any; + interface Accessor { + (datum: any, index: number, dataset: Dataset, plotMetadata: Plots.PlotMetadata): T; + } /** * Retrieves scaled datum property. */ - type _Projector = (datum: any, index: number, userMetadata: any, plotMetadata: Plot.PlotMetadata) => any; + type _Projector = (datum: any, index: number, dataset: Dataset, plotMetadata: Plots.PlotMetadata) => any; /** - * Projector with applied user and plot metadata + * Projector with dataset and plot metadata */ type AppliedProjector = (datum: any, index: number) => any; /** * Defines a way how specific attribute needs be retrieved before rendering. */ type _Projection = { - accessor: _Accessor; - scale?: Scale.AbstractScale; + accessor: Accessor; + scale?: Scale; attribute: string; }; /** @@ -730,20 +629,9 @@ declare module Plottable { type AttributeToAppliedProjector = { [attrToSet: string]: AppliedProjector; }; - /** - * A simple bounding box. - */ - type SelectionArea = { - xMin: number; - xMax: number; - yMin: number; - yMax: number; - }; - type _SpaceRequest = { - width: number; - height: number; - wantsWidth: boolean; - wantsHeight: boolean; + type SpaceRequest = { + minWidth: number; + minHeight: number; }; /** * The range of your current data. For example, [1, 2, 6, -5] has the Extent @@ -799,7 +687,7 @@ declare module Plottable { * @returns {any[]} The domain, as a merging of all exents, as a [min, max] * pair. */ - computeDomain(extents: any[][], scale: Scale.AbstractQuantitative): any[]; + computeDomain(extents: any[][], scale: QuantitativeScale): any[]; /** * Sets the Domainer to pad by a given ratio. * @@ -819,46 +707,38 @@ declare module Plottable { * Adds a padding exception, a value that will not be padded at either end of the domain. * * Eg, if a padding exception is added at x=0, then [0, 100] will pad to [0, 105] instead of [-2.5, 102.5]. - * If a key is provided, it will be registered under that key with standard map semantics. (Overwrite / remove by key) - * If a key is not provided, it will be added with set semantics (Can be removed by value) + * The exception will be registered under the provided with standard map semantics. (Overwrite / remove by key). * * @param {any} exception The padding exception to add. - * @param {string} key The key to register the exception under. + * @param {any} key The key to register the exception under. * @returns {Domainer} The calling domainer */ - addPaddingException(exception: any, key?: string): Domainer; + addPaddingException(key: any, exception: any): Domainer; /** * Removes a padding exception, allowing the domain to pad out that value again. * - * If a string is provided, it is assumed to be a key and the exception associated with that key is removed. - * If a non-string is provdied, it is assumed to be an unkeyed exception and that exception is removed. - * - * @param {any} keyOrException The key for the value to remove, or the value to remove + * @param {any} key The key for the value to remove. * @return {Domainer} The calling domainer */ - removePaddingException(keyOrException: any): Domainer; + removePaddingException(key: any): Domainer; /** * Adds an included value, a value that must be included inside the domain. * * Eg, if a value exception is added at x=0, then [50, 100] will expand to [0, 100] rather than [50, 100]. - * If a key is provided, it will be registered under that key with standard map semantics. (Overwrite / remove by key) - * If a key is not provided, it will be added with set semantics (Can be removed by value) + * The value will be registered under that key with standard map semantics. (Overwrite / remove by key). * * @param {any} value The included value to add. - * @param {string} key The key to register the value under. + * @param {any} key The key to register the value under. * @returns {Domainer} The calling domainer */ - addIncludedValue(value: any, key?: string): Domainer; + addIncludedValue(key: any, value: any): Domainer; /** * Remove an included value, allowing the domain to not include that value gain again. * - * If a string is provided, it is assumed to be a key and the value associated with that key is removed. - * If a non-string is provdied, it is assumed to be an unkeyed value and that value is removed. - * - * @param {any} keyOrException The key for the value to remove, or the value to remove + * @param {any} key The key for the value to remove. * @return {Domainer} The calling domainer */ - removeIncludedValue(valueOrKey: any): Domainer; + removeIncludedValue(key: any): Domainer; /** * Extends the scale's domain so it starts and ends with "nice" values. * @@ -871,386 +751,292 @@ declare module Plottable { declare module Plottable { - module Scale { - class AbstractScale extends Core.PlottableObject { - protected _d3Scale: D3.Scale.Scale; - broadcaster: Core.Broadcaster>; - _typeCoercer: (d: any) => any; + interface ScaleCallback> { + (scale: S): any; + } + module Scales { + interface ExtentsProvider { + (scale: Scale): D[][]; + } + } + class Scale { + protected _d3Scale: D3.Scale.Scale; + /** + * Constructs a new Scale. + * + * A Scale is a wrapper around a D3.Scale.Scale. A Scale is really just a + * function. Scales have a domain (input), a range (output), and a function + * from domain to range. + * + * @constructor + * @param {D3.Scale.Scale} scale The D3 scale backing the Scale. + */ + constructor(scale: D3.Scale.Scale); + protected _getAllExtents(): D[][]; + protected _getExtent(): D[]; + onUpdate(callback: ScaleCallback>): Scale; + offUpdate(callback: ScaleCallback>): Scale; + protected _dispatchUpdate(): void; + /** + * Modifies the domain on the scale so that it includes the extent of all + * perspectives it depends on. This will normally happen automatically, but + * if you set domain explicitly with `plot.domain(x)`, you will need to + * call this function if you want the domain to neccessarily include all + * the data. + * + * Extent: The [min, max] pair for a Scale.QuantitativeScale, all covered + * strings for a Scale.Category. + * + * Perspective: A combination of a Dataset and an Accessor that + * represents a view in to the data. + * + * @returns {Scale} The calling Scale. + */ + autoDomain(): Scale; + _autoDomainIfAutomaticMode(): void; + /** + * Computes the range value corresponding to a given domain value. In other + * words, apply the function to value. + * + * @param {R} value A domain value to be scaled. + * @returns {R} The range value corresponding to the supplied domain value. + */ + scale(value: D): R; + /** + * Gets the domain. + * + * @returns {D[]} The current domain. + */ + domain(): D[]; + /** + * Sets the domain. + * + * @param {D[]} values If provided, the new value for the domain. On + * a QuantitativeScale, this is a [min, max] pair, or a [max, min] pair to + * make the function decreasing. On Scale.Ordinal, this is an array of all + * input values. + * @returns {Scale} The calling Scale. + */ + domain(values: D[]): Scale; + protected _getDomain(): any[]; + protected _setDomain(values: D[]): void; + /** + * Gets the range. + * + * In the case of having a numeric range, it will be a [min, max] pair. In + * the case of string range (e.g. Scale.InterpolatedColor), it will be a + * list of all possible outputs. + * + * @returns {R[]} The current range. + */ + range(): R[]; + /** + * Sets the range. + * + * In the case of having a numeric range, it will be a [min, max] pair. In + * the case of string range (e.g. Scale.InterpolatedColor), it will be a + * list of all possible outputs. + * + * @param {R[]} values If provided, the new values for the range. + * @returns {Scale} The calling Scale. + */ + range(values: R[]): Scale; + addExtentsProvider(provider: Scales.ExtentsProvider): Scale; + removeExtentsProvider(provider: Scales.ExtentsProvider): Scale; + } +} + + +declare module Plottable { + class QuantitativeScale extends Scale { + protected static _DEFAULT_NUM_TICKS: number; + protected _d3Scale: D3.Scale.QuantitativeScale; + _userSetDomainer: boolean; + /** + * Constructs a new QuantitativeScale. + * + * A QuantitativeScale is a Scale that maps anys to numbers. It + * is invertible and continuous. + * + * @constructor + * @param {D3.Scale.QuantitativeScale} scale The D3 QuantitativeScale + * backing the QuantitativeScale. + */ + constructor(scale: D3.Scale.QuantitativeScale); + protected _getExtent(): D[]; + /** + * Retrieves the domain value corresponding to a supplied range value. + * + * @param {number} value: A value from the Scale's range. + * @returns {D} The domain value corresponding to the supplied range value. + */ + invert(value: number): D; + domain(): D[]; + domain(values: D[]): QuantitativeScale; + protected _setDomain(values: D[]): void; + /** + * Gets ticks generated by the default algorithm. + */ + getDefaultTicks(): D[]; + /** + * Gets a set of tick values spanning the domain. + * + * @returns {D[]} The generated ticks. + */ + ticks(): D[]; + /** + * Given a domain, expands its domain onto "nice" values, e.g. whole + * numbers. + */ + _niceDomain(domain: D[], count?: number): D[]; + /** + * Gets a Domainer of a scale. A Domainer is responsible for combining + * multiple extents into a single domain. + * + * @return {Domainer} The scale's current domainer. + */ + domainer(): Domainer; + /** + * Sets a Domainer of a scale. A Domainer is responsible for combining + * multiple extents into a single domain. + * + * When you set domainer, we assume that you know what you want the domain + * to look like better that we do. Ensuring that the domain is padded, + * includes 0, etc., will be the responsability of the new domainer. + * + * @param {Domainer} domainer If provided, the new domainer. + * @return {QuantitativeScale} The calling QuantitativeScale. + */ + domainer(domainer: Domainer): QuantitativeScale; + _defaultExtent(): D[]; + /** + * Gets the tick generator of the QuantitativeScale. + * + * @returns {TickGenerator} The current tick generator. + */ + tickGenerator(): Scales.TickGenerators.TickGenerator; + /** + * Sets a tick generator + * + * @param {TickGenerator} generator, the new tick generator. + * @return {QuantitativeScale} The calling QuantitativeScale. + */ + tickGenerator(generator: Scales.TickGenerators.TickGenerator): QuantitativeScale; + } +} + + +declare module Plottable { + module Scales { + class Linear extends QuantitativeScale { /** - * Constructs a new Scale. + * Constructs a new LinearScale. * - * A Scale is a wrapper around a D3.Scale.Scale. A Scale is really just a - * function. Scales have a domain (input), a range (output), and a function - * from domain to range. + * This scale maps from domain to range with a simple `mx + b` formula. * * @constructor - * @param {D3.Scale.Scale} scale The D3 scale backing the Scale. - */ - constructor(scale: D3.Scale.Scale); - protected _getAllExtents(): D[][]; - protected _getExtent(): D[]; - /** - * Modifies the domain on the scale so that it includes the extent of all - * perspectives it depends on. This will normally happen automatically, but - * if you set domain explicitly with `plot.domain(x)`, you will need to - * call this function if you want the domain to neccessarily include all - * the data. - * - * Extent: The [min, max] pair for a Scale.Quantitative, all covered - * strings for a Scale.Category. - * - * Perspective: A combination of a Dataset and an Accessor that - * represents a view in to the data. - * - * @returns {Scale} The calling Scale. + * @param {D3.Scale.LinearScale} [scale] The D3 LinearScale backing the + * LinearScale. If not supplied, uses a default scale. */ - autoDomain(): AbstractScale; - _autoDomainIfAutomaticMode(): void; + constructor(); + constructor(scale: D3.Scale.LinearScale); + _defaultExtent(): number[]; + } + } +} + + +declare module Plottable { + module Scales { + class ModifiedLog extends QuantitativeScale { /** - * Computes the range value corresponding to a given domain value. In other - * words, apply the function to value. + * Creates a new Scale.ModifiedLog. * - * @param {R} value A domain value to be scaled. - * @returns {R} The range value corresponding to the supplied domain value. - */ - scale(value: D): R; - /** - * Gets the domain. + * A ModifiedLog scale acts as a regular log scale for large numbers. + * As it approaches 0, it gradually becomes linear. This means that the + * scale won't freak out if you give it 0 or a negative number, where an + * ordinary Log scale would. * - * @returns {D[]} The current domain. - */ - domain(): D[]; - /** - * Sets the domain. + * However, it does mean that scale will be effectively linear as values + * approach 0. If you want very small values on a log scale, you should use + * an ordinary Scale.Log instead. * - * @param {D[]} values If provided, the new value for the domain. On - * a QuantitativeScale, this is a [min, max] pair, or a [max, min] pair to - * make the function decreasing. On Scale.Ordinal, this is an array of all - * input values. - * @returns {Scale} The calling Scale. - */ - domain(values: D[]): AbstractScale; - protected _getDomain(): any[]; - protected _setDomain(values: D[]): void; - /** - * Gets the range. + * @constructor + * @param {number} [base] + * The base of the log. Defaults to 10, and must be > 1. * - * In the case of having a numeric range, it will be a [min, max] pair. In - * the case of string range (e.g. Scale.InterpolatedColor), it will be a - * list of all possible outputs. + * For base <= x, scale(x) = log(x). * - * @returns {R[]} The current range. - */ - range(): R[]; - /** - * Sets the range. + * For 0 < x < base, scale(x) will become more and more + * linear as it approaches 0. * - * In the case of having a numeric range, it will be a [min, max] pair. In - * the case of string range (e.g. Scale.InterpolatedColor), it will be a - * list of all possible outputs. + * At x == 0, scale(x) == 0. * - * @param {R[]} values If provided, the new values for the range. - * @returns {Scale} The calling Scale. + * For negative values, scale(-x) = -scale(x). */ - range(values: R[]): AbstractScale; + constructor(base?: number); + scale(x: number): number; + invert(x: number): number; + protected _getDomain(): number[]; + protected _setDomain(values: number[]): void; + ticks(): number[]; + _niceDomain(domain: number[], count?: number): number[]; /** - * Constructs a copy of the Scale with the same domain and range but without - * any registered listeners. + * Gets whether or not to return tick values other than powers of base. * - * @returns {Scale} A copy of the calling Scale. + * This defaults to false, so you'll normally only see ticks like + * [10, 100, 1000]. If you turn it on, you might see ticks values + * like [10, 50, 100, 500, 1000]. + * @returns {boolean} the current setting. */ - copy(): AbstractScale; + showIntermediateTicks(): boolean; /** - * When a renderer determines that the extent of a projector has changed, - * it will call this function. This function should ensure that - * the scale has a domain at least large enough to include extent. + * Sets whether or not to return ticks values other than powers or base. * - * @param {number} rendererID A unique indentifier of the renderer sending - * the new extent. - * @param {string} attr The attribute being projected, e.g. "x", "y0", "r" - * @param {D[]} extent The new extent to be included in the scale. + * @param {boolean} show If provided, the desired setting. + * @returns {ModifiedLog} The calling ModifiedLog. */ - _updateExtent(plotProvidedKey: string, attr: string, extent: D[]): AbstractScale; - _removeExtent(plotProvidedKey: string, attr: string): AbstractScale; + showIntermediateTicks(show: boolean): ModifiedLog; + _defaultExtent(): number[]; } } } declare module Plottable { - module Scale { - class AbstractQuantitative extends AbstractScale { - protected _d3Scale: D3.Scale.QuantitativeScale; - _userSetDomainer: boolean; - _typeCoercer: (d: any) => number; + module Scales { + class Category extends Scale { + protected _d3Scale: D3.Scale.OrdinalScale; /** - * Constructs a new QuantitativeScale. + * Creates a CategoryScale. * - * A QuantitativeScale is a Scale that maps anys to numbers. It - * is invertible and continuous. + * A CategoryScale maps strings to numbers. A common use is to map the + * labels of a bar plot (strings) to their pixel locations (numbers). * * @constructor - * @param {D3.Scale.QuantitativeScale} scale The D3 QuantitativeScale - * backing the QuantitativeScale. */ - constructor(scale: D3.Scale.QuantitativeScale); - protected _getExtent(): D[]; + constructor(scale?: D3.Scale.OrdinalScale); + protected _getExtent(): string[]; + domain(): string[]; + domain(values: string[]): Category; + protected _setDomain(values: string[]): void; + range(): number[]; + range(values: number[]): Category; /** - * Retrieves the domain value corresponding to a supplied range value. + * Returns the width of the range band. * - * @param {number} value: A value from the Scale's range. - * @returns {D} The domain value corresponding to the supplied range value. + * @returns {number} The range band width */ - invert(value: number): D; + rangeBand(): number; /** - * Creates a copy of the QuantitativeScale with the same domain and range but without any registered list. + * Returns the step width of the scale. * - * @returns {AbstractQuantitative} A copy of the calling QuantitativeScale. - */ - copy(): AbstractQuantitative; - domain(): D[]; - domain(values: D[]): AbstractQuantitative; - protected _setDomain(values: D[]): void; - /** - * Sets or gets the QuantitativeScale's output interpolator + * The step width is defined as the entire space for a band to occupy, + * including the padding in between the bands. * - * @param {D3.Transition.Interpolate} [factory] The output interpolator to use. - * @returns {D3.Transition.Interpolate|AbstractQuantitative} The current output interpolator, or the calling QuantitativeScale. + * @returns {number} the full band width of the scale */ - interpolate(): D3.Transition.Interpolate; - interpolate(factory: D3.Transition.Interpolate): AbstractQuantitative; - /** - * Sets the range of the QuantitativeScale and sets the interpolator to d3.interpolateRound. - * - * @param {number[]} values The new range value for the range. - */ - rangeRound(values: number[]): AbstractQuantitative; - /** - * Gets ticks generated by the default algorithm. - */ - getDefaultTicks(): D[]; - /** - * Gets the clamp status of the QuantitativeScale (whether to cut off values outside the ouput range). - * - * @returns {boolean} The current clamp status. - */ - clamp(): boolean; - /** - * Sets the clamp status of the QuantitativeScale (whether to cut off values outside the ouput range). - * - * @param {boolean} clamp Whether or not to clamp the QuantitativeScale. - * @returns {AbstractQuantitative} The calling QuantitativeScale. - */ - clamp(clamp: boolean): AbstractQuantitative; - /** - * Gets a set of tick values spanning the domain. - * - * @returns {any[]} The generated ticks. - */ - ticks(): any[]; - /** - * Gets the default number of ticks. - * - * @returns {number} The default number of ticks. - */ - numTicks(): number; - /** - * Sets the default number of ticks to generate. - * - * @param {number} count The new default number of ticks. - * @returns {Quantitative} The calling QuantitativeScale. - */ - numTicks(count: number): AbstractQuantitative; - /** - * Given a domain, expands its domain onto "nice" values, e.g. whole - * numbers. - */ - _niceDomain(domain: any[], count?: number): any[]; - /** - * Gets a Domainer of a scale. A Domainer is responsible for combining - * multiple extents into a single domain. - * - * @return {Domainer} The scale's current domainer. - */ - domainer(): Domainer; - /** - * Sets a Domainer of a scale. A Domainer is responsible for combining - * multiple extents into a single domain. - * - * When you set domainer, we assume that you know what you want the domain - * to look like better that we do. Ensuring that the domain is padded, - * includes 0, etc., will be the responsability of the new domainer. - * - * @param {Domainer} domainer If provided, the new domainer. - * @return {AbstractQuantitative} The calling QuantitativeScale. - */ - domainer(domainer: Domainer): AbstractQuantitative; - _defaultExtent(): any[]; - /** - * Gets the tick generator of the AbstractQuantitative. - * - * @returns {TickGenerator} The current tick generator. - */ - tickGenerator(): TickGenerators.TickGenerator; - /** - * Sets a tick generator - * - * @param {TickGenerator} generator, the new tick generator. - * @return {AbstractQuantitative} The calling AbstractQuantitative. - */ - tickGenerator(generator: TickGenerators.TickGenerator): AbstractQuantitative; - } - } -} - - -declare module Plottable { - module Scale { - class Linear extends AbstractQuantitative { - /** - * Constructs a new LinearScale. - * - * This scale maps from domain to range with a simple `mx + b` formula. - * - * @constructor - * @param {D3.Scale.LinearScale} [scale] The D3 LinearScale backing the - * LinearScale. If not supplied, uses a default scale. - */ - constructor(); - constructor(scale: D3.Scale.LinearScale); - /** - * Constructs a copy of the LinearScale with the same domain and range but - * without any registered listeners. - * - * @returns {Linear} A copy of the calling LinearScale. - */ - copy(): Linear; - } - } -} - - -declare module Plottable { - module Scale { - class Log extends AbstractQuantitative { - /** - * Constructs a new Scale.Log. - * - * Warning: Log is deprecated; if possible, use ModifiedLog. Log scales are - * very unstable due to the fact that they can't handle 0 or negative - * numbers. The only time when you would want to use a Log scale over a - * ModifiedLog scale is if you're plotting very small data, such as all - * data < 1. - * - * @constructor - * @param {D3.Scale.LogScale} [scale] The D3 Scale.Log backing the Scale.Log. If not supplied, uses a default scale. - */ - constructor(); - constructor(scale: D3.Scale.LogScale); - /** - * Creates a copy of the Scale.Log with the same domain and range but without any registered listeners. - * - * @returns {Log} A copy of the calling Log. - */ - copy(): Log; - _defaultExtent(): number[]; - } - } -} - - -declare module Plottable { - module Scale { - class ModifiedLog extends AbstractQuantitative { - /** - * Creates a new Scale.ModifiedLog. - * - * A ModifiedLog scale acts as a regular log scale for large numbers. - * As it approaches 0, it gradually becomes linear. This means that the - * scale won't freak out if you give it 0 or a negative number, where an - * ordinary Log scale would. - * - * However, it does mean that scale will be effectively linear as values - * approach 0. If you want very small values on a log scale, you should use - * an ordinary Scale.Log instead. - * - * @constructor - * @param {number} [base] - * The base of the log. Defaults to 10, and must be > 1. - * - * For base <= x, scale(x) = log(x). - * - * For 0 < x < base, scale(x) will become more and more - * linear as it approaches 0. - * - * At x == 0, scale(x) == 0. - * - * For negative values, scale(-x) = -scale(x). - */ - constructor(base?: number); - scale(x: number): number; - invert(x: number): number; - protected _getDomain(): number[]; - protected _setDomain(values: number[]): void; - ticks(count?: number): number[]; - copy(): ModifiedLog; - _niceDomain(domain: any[], count?: number): any[]; - /** - * Gets whether or not to return tick values other than powers of base. - * - * This defaults to false, so you'll normally only see ticks like - * [10, 100, 1000]. If you turn it on, you might see ticks values - * like [10, 50, 100, 500, 1000]. - * @returns {boolean} the current setting. - */ - showIntermediateTicks(): boolean; - /** - * Sets whether or not to return ticks values other than powers or base. - * - * @param {boolean} show If provided, the desired setting. - * @returns {ModifiedLog} The calling ModifiedLog. - */ - showIntermediateTicks(show: boolean): ModifiedLog; - } - } -} - - -declare module Plottable { - module Scale { - class Category extends AbstractScale { - protected _d3Scale: D3.Scale.OrdinalScale; - _typeCoercer: (d: any) => any; - /** - * Creates a CategoryScale. - * - * A CategoryScale maps strings to numbers. A common use is to map the - * labels of a bar plot (strings) to their pixel locations (numbers). - * - * @constructor - */ - constructor(scale?: D3.Scale.OrdinalScale); - protected _getExtent(): string[]; - domain(): string[]; - domain(values: string[]): Category; - protected _setDomain(values: string[]): void; - range(): number[]; - range(values: number[]): Category; - /** - * Returns the width of the range band. - * - * @returns {number} The range band width - */ - rangeBand(): number; - /** - * Returns the step width of the scale. - * - * The step width is defined as the entire space for a band to occupy, - * including the padding in between the bands. - * - * @returns {number} the full band width of the scale - */ - stepWidth(): number; + stepWidth(): number; /** * Returns the inner padding of the scale. * @@ -1287,7 +1073,6 @@ declare module Plottable { * @returns {Ordinal} The calling Scale.Ordinal */ outerPadding(outerPadding: number): Category; - copy(): Category; scale(value: string): number; } } @@ -1295,8 +1080,8 @@ declare module Plottable { declare module Plottable { - module Scale { - class Color extends AbstractScale { + module Scales { + class Color extends Scale { /** * Constructs a ColorScale. * @@ -1314,9 +1099,8 @@ declare module Plottable { declare module Plottable { - module Scale { - class Time extends AbstractQuantitative { - _typeCoercer: (d: any) => any; + module Scales { + class Time extends QuantitativeScale { /** * Constructs a TimeScale. * @@ -1327,17 +1111,24 @@ declare module Plottable { */ constructor(); constructor(scale: D3.Scale.LinearScale); - tickInterval(interval: D3.Time.Interval, step?: number): any[]; - protected _setDomain(values: any[]): void; - copy(): Time; - _defaultExtent(): any[]; + /** + * Specifies the interval between ticks + * + * @param {string} interval TimeInterval string specifying the interval unit measure + * @param {number?} step? The distance between adjacent ticks (using the interval unit measure) + * + * @return {Date[]} + */ + tickInterval(interval: string, step?: number): Date[]; + protected _setDomain(values: Date[]): void; + _defaultExtent(): Date[]; } } } declare module Plottable { - module Scale { + module Scales { /** * This class implements a color scale that takes quantitive input and * interpolates between a list of color values. It returns a hex string @@ -1345,21 +1136,20 @@ declare module Plottable { * * By default it generates a linear scale internally. */ - class InterpolatedColor extends AbstractScale { + class InterpolatedColor extends Scale { + static REDS: string[]; + static BLUES: string[]; + static POSNEG: string[]; /** - * Constructs an InterpolatedColorScale. + * An InterpolatedColorScale maps numbers to color strings. * - * An InterpolatedColorScale maps numbers evenly to color strings. - * - * @constructor - * @param {string|string[]} colorRange the type of color scale to - * create. Default is "reds". @see {@link colorRange} for further - * options. - * @param {string} scaleType the type of underlying scale to use - * (linear/pow/log/sqrt). Default is "linear". @see {@link scaleType} - * for further options. + * @param {string[]} colors an array of strings representing color values in hex + * ("#FFFFFF") or keywords ("white"). Defaults to InterpolatedColor.REDS + * @param {string} scaleType a string representing the underlying scale + * type ("linear"/"log"/"sqrt"/"pow"). Defaults to "linear" + * @returns {D3.Scale.QuantitativeScale} The converted QuantitativeScale d3 scale. */ - constructor(colorRange?: any, scaleType?: string); + constructor(colorRange?: string[], scaleType?: string); /** * Gets the color range. * @@ -1369,27 +1159,14 @@ declare module Plottable { /** * Sets the color range. * - * @param {string|string[]} [colorRange]. If provided and if colorRange is one of + * @param {string[]} [colorRange]. If provided and if colorRange is one of * (reds/blues/posneg), uses the built-in color groups. If colorRange is an * array of strings with at least 2 values (e.g. ["#FF00FF", "red", * "dodgerblue"], the resulting scale will interpolate between the color * values across the domain. * @returns {InterpolatedColor} The calling InterpolatedColor. */ - colorRange(colorRange: string | string[]): InterpolatedColor; - /** - * Gets the internal scale type. - * - * @returns {string} The current scale type. - */ - scaleType(): string; - /** - * Sets the internal scale type. - * - * @param {string} scaleType If provided, the type of d3 scale to use internally. (linear/log/sqrt/pow). - * @returns {InterpolatedColor} The calling InterpolatedColor. - */ - scaleType(scaleType: string): InterpolatedColor; + colorRange(colorRange: string[]): InterpolatedColor; autoDomain(): InterpolatedColor; } } @@ -1397,26 +1174,10 @@ declare module Plottable { declare module Plottable { - module _Util { - class ScaleDomainCoordinator { - /** - * Constructs a ScaleDomainCoordinator. - * - * @constructor - * @param {Scale[]} scales A list of scales whose domains should be linked. - */ - constructor(scales: Scale.AbstractScale[]); - rescale(scale: Scale.AbstractScale): void; - } - } -} - - -declare module Plottable { - module Scale { + module Scales { module TickGenerators { interface TickGenerator { - (scale: Plottable.Scale.AbstractQuantitative): D[]; + (scale: Plottable.QuantitativeScale): D[]; } /** * Creates a tick generator using the specified interval. @@ -1442,7 +1203,7 @@ declare module Plottable { declare module Plottable { - module _Drawer { + module Drawers { /** * A step for the drawer to draw. * @@ -1450,11 +1211,11 @@ declare module Plottable { */ type DrawStep = { attrToProjector: AttributeToProjector; - animator: Animator.PlotAnimator; + animator: Animators.PlotAnimator; }; type AppliedDrawStep = { attrToProjector: AttributeToAppliedProjector; - animator: Animator.PlotAnimator; + animator: Animators.PlotAnimator; }; class AbstractDrawer { protected _className: string; @@ -1498,10 +1259,10 @@ declare module Plottable { * * @param{any[]} data The data to be drawn * @param{DrawStep[]} drawSteps The list of steps, which needs to be drawn - * @param{any} userMetadata The metadata provided by user + * @param{Dataset} dataset The Dataset * @param{any} plotMetadata The metadata provided by plot */ - draw(data: any[], drawSteps: DrawStep[], userMetadata: any, plotMetadata: Plot.PlotMetadata): number; + draw(data: any[], drawSteps: DrawStep[], dataset: Dataset, plotMetadata: Plots.PlotMetadata): number; /** * Retrieves the renderArea selection for the drawer * @@ -1517,7 +1278,7 @@ declare module Plottable { declare module Plottable { - module _Drawer { + module Drawers { class Line extends AbstractDrawer { static LINE_CLASS: string; protected _enterData(data: any[]): void; @@ -1533,7 +1294,7 @@ declare module Plottable { declare module Plottable { - module _Drawer { + module Drawers { class Area extends Line { static AREA_CLASS: string; protected _enterData(data: any[]): void; @@ -1552,7 +1313,7 @@ declare module Plottable { declare module Plottable { - module _Drawer { + module Drawers { class Element extends AbstractDrawer { protected _svgElement: string; /** @@ -1572,26 +1333,26 @@ declare module Plottable { declare module Plottable { - module _Drawer { + module Drawers { class Rect extends Element { constructor(key: string, isVertical: boolean); setup(area: D3.Selection): void; removeLabels(): void; _getIfLabelsTooWide(): boolean; - drawText(data: any[], attrToProjector: AttributeToProjector, userMetadata: any, plotMetadata: Plot.PlotMetadata): void; + drawText(data: any[], attrToProjector: AttributeToProjector, userMetadata: any, plotMetadata: Plots.PlotMetadata): void; _getPixelPoint(datum: any, index: number): Point; - draw(data: any[], drawSteps: DrawStep[], userMetadata: any, plotMetadata: Plot.PlotMetadata): number; + draw(data: any[], drawSteps: DrawStep[], userMetadata: any, plotMetadata: Plots.PlotMetadata): number; } } } declare module Plottable { - module _Drawer { + module Drawers { class Arc extends Element { constructor(key: string); _drawStep(step: AppliedDrawStep): void; - draw(data: any[], drawSteps: DrawStep[], userMetadata: any, plotMetadata: Plot.PlotMetadata): number; + draw(data: any[], drawSteps: DrawStep[], dataset: Dataset, plotMetadata: Plots.PlotMetadata): number; _getPixelPoint(datum: any, index: number): Point; } } @@ -1599,7 +1360,7 @@ declare module Plottable { declare module Plottable { - module _Drawer { + module Drawers { class Symbol extends Element { constructor(key: string); protected _drawStep(step: AppliedDrawStep): void; @@ -1610,488 +1371,482 @@ declare module Plottable { declare module Plottable { - module Component { - class AbstractComponent extends Core.PlottableObject { - protected _element: D3.Selection; - protected _content: D3.Selection; - protected _boundingBox: D3.Selection; - clipPathEnabled: boolean; - protected _fixedHeightFlag: boolean; - protected _fixedWidthFlag: boolean; - protected _isSetup: boolean; - protected _isAnchored: boolean; - /** - * Attaches the Component as a child of a given a DOM element. Usually only directly invoked on root-level Components. - * - * @param {D3.Selection} element A D3 selection consisting of the element to anchor under. - */ - _anchor(element: D3.Selection): void; - /** - * Creates additional elements as necessary for the Component to function. - * Called during _anchor() if the Component's element has not been created yet. - * Override in subclasses to provide additional functionality. - */ - protected _setup(): void; - _requestedSpace(availableWidth: number, availableHeight: number): _SpaceRequest; - /** - * Computes the size, position, and alignment from the specified values. - * If no parameters are supplied and the Component is a root node, - * they are inferred from the size of the Component's element. - * - * @param {number} offeredXOrigin x-coordinate of the origin of the space offered the Component - * @param {number} offeredYOrigin y-coordinate of the origin of the space offered the Component - * @param {number} availableWidth available width for the Component to render in - * @param {number} availableHeight available height for the Component to render in - */ - _computeLayout(offeredXOrigin?: number, offeredYOrigin?: number, availableWidth?: number, availableHeight?: number): void; - protected _getSize(availableWidth: number, availableHeight: number): { - width: number; - height: number; - }; - _render(): void; - _doRender(): void; - _useLastCalculatedLayout(): boolean; - _useLastCalculatedLayout(useLast: boolean): AbstractComponent; - _invalidateLayout(): void; - /** - * Renders the Component into a given DOM element. The element must be as . - * - * @param {String|D3.Selection} element A D3 selection or a selector for getting the element to render into. - * @returns {Component} The calling component. - */ - renderTo(element: String | D3.Selection): AbstractComponent; - /** - * Causes the Component to recompute layout and redraw. - * - * This function should be called when CSS changes could influence the size - * of the components, e.g. changing the font size. - * - * @returns {Component} The calling component. - */ - redraw(): AbstractComponent; - /** - * Sets the x alignment of the Component. This will be used if the - * Component is given more space than it needs. - * - * For example, you may want to make a Legend postition itself it the top - * right, so you would call `legend.xAlign("right")` and - * `legend.yAlign("top")`. - * - * @param {string} alignment The x alignment of the Component (one of ["left", "center", "right"]). - * @returns {Component} The calling Component. - */ - xAlign(alignment: string): AbstractComponent; - /** - * Sets the y alignment of the Component. This will be used if the - * Component is given more space than it needs. - * - * For example, you may want to make a Legend postition itself it the top - * right, so you would call `legend.xAlign("right")` and - * `legend.yAlign("top")`. - * - * @param {string} alignment The x alignment of the Component (one of ["top", "center", "bottom"]). - * @returns {Component} The calling Component. - */ - yAlign(alignment: string): AbstractComponent; - /** - * Sets the x offset of the Component. This will be used if the Component - * is given more space than it needs. - * - * @param {number} offset The desired x offset, in pixels, from the left - * side of the container. - * @returns {Component} The calling Component. - */ - xOffset(offset: number): AbstractComponent; - /** - * Sets the y offset of the Component. This will be used if the Component - * is given more space than it needs. - * - * @param {number} offset The desired y offset, in pixels, from the top - * side of the container. - * @returns {Component} The calling Component. - */ - yOffset(offset: number): AbstractComponent; - /** - * Attaches an Interaction to the Component, so that the Interaction will listen for events on the Component. - * - * @param {Interaction} interaction The Interaction to attach to the Component. - * @returns {Component} The calling Component. - */ - registerInteraction(interaction: Interaction.AbstractInteraction): AbstractComponent; - /** - * Checks if the Component has a given CSS class. - * - * @param {string} cssClass The CSS class to check for. - * @returns {boolean} Whether the Component has the given CSS class. - */ - classed(cssClass: string): boolean; - /** - * Adds/removes a given CSS class to/from the Component. - * - * @param {string} cssClass The CSS class to add or remove. - * @param {boolean} addClass If true, adds the provided CSS class; otherwise, removes it. - * @returns {AbstractComponent} The calling Component. - */ - classed(cssClass: string, addClass: boolean): AbstractComponent; - /** - * Checks if the Component has a fixed width or false if it grows to fill available space. - * Returns false by default on the base Component class. - * - * @returns {boolean} Whether the component has a fixed width. - */ - _isFixedWidth(): boolean; - /** - * Checks if the Component has a fixed height or false if it grows to fill available space. - * Returns false by default on the base Component class. - * - * @returns {boolean} Whether the component has a fixed height. - */ - _isFixedHeight(): boolean; - _merge(c: AbstractComponent, below: boolean): Component.Group; - /** - * Merges this Component above another Component, returning a - * ComponentGroup. This is used to layer Components on top of each other. - * - * There are four cases: - * Component + Component: Returns a ComponentGroup with the first component after the second component. - * ComponentGroup + Component: Returns the ComponentGroup with the Component prepended. - * Component + ComponentGroup: Returns the ComponentGroup with the Component appended. - * ComponentGroup + ComponentGroup: Returns a new ComponentGroup with the first group after the second group. - * - * @param {Component} c The component to merge in. - * @returns {ComponentGroup} The relevant ComponentGroup out of the above four cases. - */ - above(c: AbstractComponent): Component.Group; - /** - * Merges this Component below another Component, returning a - * ComponentGroup. This is used to layer Components on top of each other. - * - * There are four cases: - * Component + Component: Returns a ComponentGroup with the first component before the second component. - * ComponentGroup + Component: Returns the ComponentGroup with the Component appended. - * Component + ComponentGroup: Returns the ComponentGroup with the Component prepended. - * ComponentGroup + ComponentGroup: Returns a new ComponentGroup with the first group before the second group. - * - * @param {Component} c The component to merge in. - * @returns {ComponentGroup} The relevant ComponentGroup out of the above four cases. - */ - below(c: AbstractComponent): Component.Group; - /** - * Detaches a Component from the DOM. The component can be reused. - * - * This should only be used if you plan on reusing the calling - * Components. Otherwise, use remove(). - * - * @returns The calling Component. - */ - detach(): AbstractComponent; - _parent(): AbstractComponentContainer; - _parent(parentElement: AbstractComponentContainer): any; - /** - * Removes a Component from the DOM and disconnects it from everything it's - * listening to (effectively destroying it). - */ - remove(): void; - /** - * Return the width of the component - * - * @return {number} width of the component - */ - width(): number; - /** - * Return the height of the component - * - * @return {number} height of the component - */ - height(): number; - /** - * Gets the origin of the Component relative to its parent. - * - * @return {Point} The x-y position of the Component relative to its parent. - */ - origin(): Point; - /** - * Gets the origin of the Component relative to the root . - * - * @return {Point} The x-y position of the Component relative to the root - */ - originToSVG(): Point; - /** - * Returns the foreground selection for the Component - * (A selection covering the front of the Component) - * - * Will return undefined if the Component has not been anchored. - * - * @return {D3.Selection} foreground selection for the Component - */ - foreground(): D3.Selection; - /** - * Returns the content selection for the Component - * (A selection containing the visual elements of the Component) - * - * Will return undefined if the Component has not been anchored. - * - * @return {D3.Selection} content selection for the Component - */ - content(): D3.Selection; - /** - * Returns the background selection for the Component - * (A selection appearing behind of the Component) - * - * Will return undefined if the Component has not been anchored. - * - * @return {D3.Selection} background selection for the Component - */ - background(): D3.Selection; - /** - * Returns the hitbox selection for the component - * (A selection in front of the foreground used mainly for interactions) - * - * Will return undefined if the component has not been anchored - * - * @return {D3.Selection} hitbox selection for the component - */ - hitBox(): D3.Selection; - } - } -} - - -declare module Plottable { - module Component { - class AbstractComponentContainer extends AbstractComponent { - _anchor(element: D3.Selection): void; - _render(): void; - _removeComponent(c: AbstractComponent): void; - _addComponent(c: AbstractComponent, prepend?: boolean): boolean; - /** - * Returns a list of components in the ComponentContainer. - * - * @returns {Component[]} the contained Components - */ - components(): AbstractComponent[]; - /** - * Returns true iff the ComponentContainer is empty. - * - * @returns {boolean} Whether the calling ComponentContainer is empty. - */ - empty(): boolean; - /** - * Detaches all components contained in the ComponentContainer, and - * empties the ComponentContainer. - * - * @returns {ComponentContainer} The calling ComponentContainer - */ - detachAll(): AbstractComponentContainer; - remove(): void; - _useLastCalculatedLayout(): boolean; - _useLastCalculatedLayout(calculated: boolean): AbstractComponent; - } - } -} - - -declare module Plottable { - module Component { - class Group extends AbstractComponentContainer { - /** - * Constructs a Component.Group. - * - * A Component.Group is a set of Components that will be rendered on top of - * each other. When you call Component.above(Component) or Component.below(Component), - * it creates and returns a Component.Group. - * - * Note that the order of the components will determine placement on the z-axis, - * with the previous items rendered below the later items. - * - * @constructor - * @param {Component[]} components The Components in the resultant Component.Group (default = []). - */ - constructor(components?: AbstractComponent[]); - _requestedSpace(offeredWidth: number, offeredHeight: number): _SpaceRequest; - _merge(c: AbstractComponent, below: boolean): Group; - _computeLayout(offeredXOrigin?: number, offeredYOrigin?: number, availableWidth?: number, availableHeight?: number): Group; - protected _getSize(availableWidth: number, availableHeight: number): { - width: number; - height: number; - }; - _isFixedWidth(): boolean; - _isFixedHeight(): boolean; + type ComponentCallback = (component: Component) => any; + module Components { + class Alignment { + static TOP: string; + static BOTTOM: string; + static LEFT: string; + static RIGHT: string; + static CENTER: string; } } -} - - -declare module Plottable { - module Axis { - class AbstractAxis extends Component.AbstractComponent { - /** - * The css class applied to each end tick mark (the line on the end tick). - */ - static END_TICK_MARK_CLASS: string; - /** - * The css class applied to each tick mark (the line on the tick). - */ - static TICK_MARK_CLASS: string; - /** - * The css class applied to each tick label (the text associated with the tick). - */ - static TICK_LABEL_CLASS: string; - protected _tickMarkContainer: D3.Selection; - protected _tickLabelContainer: D3.Selection; - protected _baseline: D3.Selection; - protected _scale: Scale.AbstractScale; - protected _computedWidth: number; - protected _computedHeight: number; - /** - * Constructs an axis. An axis is a wrapper around a scale for rendering. - * - * @constructor - * @param {Scale} scale The scale for this axis to render. - * @param {string} orientation One of ["top", "left", "bottom", "right"]; - * on which side the axis will appear. On most axes, this is either "left" - * or "bottom". - * @param {Formatter} Data is passed through this formatter before being - * displayed. - */ - constructor(scale: Scale.AbstractScale, orientation: string, formatter?: (d: any) => string); - remove(): void; - protected _isHorizontal(): boolean; - protected _computeWidth(): number; - protected _computeHeight(): number; - _requestedSpace(offeredWidth: number, offeredHeight: number): _SpaceRequest; - _isFixedHeight(): boolean; - _isFixedWidth(): boolean; - protected _rescale(): void; - _computeLayout(offeredXOrigin?: number, offeredYOrigin?: number, availableWidth?: number, availableHeight?: number): void; - protected _setup(): void; - protected _getTickValues(): any[]; - _doRender(): void; - protected _generateBaselineAttrHash(): { - x1: number; - y1: number; - x2: number; - y2: number; - }; - protected _generateTickMarkAttrHash(isEndTickMark?: boolean): { - x1: any; - y1: any; - x2: any; - y2: any; - }; - _invalidateLayout(): void; - protected _setDefaultAlignment(): void; - /** - * Gets the current formatter on the axis. Data is passed through the - * formatter before being displayed. - * - * @returns {Formatter} The calling Axis, or the current - * Formatter. - */ - formatter(): Formatter; - /** - * Sets the current formatter on the axis. Data is passed through the - * formatter before being displayed. - * - * @param {Formatter} formatter If provided, data will be passed though `formatter(data)`. - * @returns {Axis} The calling Axis. - */ - formatter(formatter: Formatter): AbstractAxis; - /** - * Gets the current tick mark length. - * - * @returns {number} the current tick mark length. - */ - tickLength(): number; - /** - * Sets the current tick mark length. - * - * @param {number} length If provided, length of each tick. - * @returns {Axis} The calling Axis. - */ - tickLength(length: number): AbstractAxis; - /** - * Gets the current end tick mark length. - * - * @returns {number} The current end tick mark length. - */ - endTickLength(): number; - /** - * Sets the end tick mark length. - * - * @param {number} length If provided, the length of the end ticks. - * @returns {BaseAxis} The calling Axis. - */ - endTickLength(length: number): AbstractAxis; - protected _maxLabelTickLength(): number; - /** - * Gets the padding between each tick mark and its associated label. - * - * @returns {number} the current padding. - * length. - */ - tickLabelPadding(): number; - /** - * Sets the padding between each tick mark and its associated label. - * - * @param {number} padding If provided, the desired padding. - * @returns {Axis} The calling Axis. - */ - tickLabelPadding(padding: number): AbstractAxis; - /** - * Gets the size of the gutter (the extra space between the tick - * labels and the outer edge of the axis). - * - * @returns {number} the current gutter. - * length. - */ - gutter(): number; - /** - * Sets the size of the gutter (the extra space between the tick - * labels and the outer edge of the axis). - * - * @param {number} size If provided, the desired gutter. - * @returns {Axis} The calling Axis. - */ - gutter(size: number): AbstractAxis; + class Component { + protected _element: D3.Selection; + protected _content: D3.Selection; + protected _boundingBox: D3.Selection; + protected _clipPathEnabled: boolean; + protected _isSetup: boolean; + protected _isAnchored: boolean; + /** + * Attaches the Component as a child of a given D3 Selection. + * + * @param {D3.Selection} selection The Selection containing the Element to anchor under. + * @returns {Component} The calling Component. + */ + anchor(selection: D3.Selection): Component; + /** + * Adds a callback to be called on anchoring the Component to the DOM. + * If the component is already anchored, the callback is called immediately. + * + * @param {ComponentCallback} callback The callback to be added. + * + * @return {Component} + */ + onAnchor(callback: ComponentCallback): Component; + /** + * Removes a callback to be called on anchoring the Component to the DOM. + * The callback is identified by reference equality. + * + * @param {ComponentCallback} callback The callback to be removed. + * + * @return {Component} + */ + offAnchor(callback: ComponentCallback): Component; + /** + * Creates additional elements as necessary for the Component to function. + * Called during anchor() if the Component's element has not been created yet. + * Override in subclasses to provide additional functionality. + */ + protected _setup(): void; + requestedSpace(availableWidth: number, availableHeight: number): SpaceRequest; + /** + * Computes the size, position, and alignment from the specified values. + * If no parameters are supplied and the Component is a root node, + * they are inferred from the size of the Component's element. + * + * @param {Point} origin Origin of the space offered to the Component. + * @param {number} availableWidth + * @param {number} availableHeight + * @returns {Component} The calling Component. + */ + computeLayout(origin?: Point, availableWidth?: number, availableHeight?: number): Component; + protected _getSize(availableWidth: number, availableHeight: number): { + width: number; + height: number; + }; + /** + * Queues the Component for rendering. Set immediately to true if the Component should be rendered + * immediately as opposed to queued to the RenderController. + * + * @returns {Component} The calling Component + */ + render(): Component; + renderImmediately(): Component; + /** + * Causes the Component to recompute layout and redraw. + * + * This function should be called when CSS changes could influence the size + * of the components, e.g. changing the font size. + * + * @returns {Component} The calling Component. + */ + redraw(): Component; + /** + * Renders the Component into a given DOM element. The element must be as . + * + * @param {String|D3.Selection} element A D3 selection or a selector for getting the element to render into. + * @returns {Component} The calling component. + */ + renderTo(element: String | D3.Selection): Component; + /** + * Gets the x alignment of the Component. + * + * @returns {string} The current x alignment. + */ + xAlignment(): string; + /** + * Sets the x alignment of the Component. + * + * @param {string} alignment The x alignment of the Component (one of ["left", "center", "right"]). + * @returns {Component} The calling Component. + */ + xAlignment(xAlignment: string): Component; + /** + * Gets the y alignment of the Component. + * + * @returns {string} The current y alignment. + */ + yAlignment(): string; + /** + * Sets the y alignment of the Component. + * + * @param {string} alignment The y alignment of the Component (one of ["top", "center", "bottom"]). + * @returns {Component} The calling Component. + */ + yAlignment(yAlignment: string): Component; + /** + * Checks if the Component has a given CSS class. + * + * @param {string} cssClass The CSS class to check for. + * @returns {boolean} Whether the Component has the given CSS class. + */ + classed(cssClass: string): boolean; + /** + * Adds/removes a given CSS class to/from the Component. + * + * @param {string} cssClass The CSS class to add or remove. + * @param {boolean} addClass If true, adds the provided CSS class; otherwise, removes it. + * @returns {Component} The calling Component. + */ + classed(cssClass: string, addClass: boolean): Component; + /** + * Checks if the Component has a fixed width or false if it grows to fill available space. + * Returns false by default on the base Component class. + * + * @returns {boolean} Whether the component has a fixed width. + */ + fixedWidth(): boolean; + /** + * Checks if the Component has a fixed height or false if it grows to fill available space. + * Returns false by default on the base Component class. + * + * @returns {boolean} Whether the component has a fixed height. + */ + fixedHeight(): boolean; + /** + * Detaches a Component from the DOM. The component can be reused. + * + * This should only be used if you plan on reusing the calling + * Components. Otherwise, use remove(). + * + * @returns The calling Component. + */ + detach(): Component; + /** + * Adds a callback to be called when th Component is detach()-ed. + * + * @param {ComponentCallback} callback The callback to be added. + * @return {Component} The calling Component. + */ + onDetach(callback: ComponentCallback): Component; + /** + * Removes a callback to be called when th Component is detach()-ed. + * The callback is identified by reference equality. + * + * @param {ComponentCallback} callback The callback to be removed. + * @return {Component} The calling Component. + */ + offDetach(callback: ComponentCallback): Component; + parent(): ComponentContainer; + parent(parent: ComponentContainer): Component; + /** + * Removes a Component from the DOM and disconnects it from everything it's + * listening to (effectively destroying it). + */ + destroy(): void; + /** + * Return the width of the component + * + * @return {number} width of the component + */ + width(): number; + /** + * Return the height of the component + * + * @return {number} height of the component + */ + height(): number; + /** + * Gets the origin of the Component relative to its parent. + * + * @return {Point} The x-y position of the Component relative to its parent. + */ + origin(): Point; + /** + * Gets the origin of the Component relative to the root . + * + * @return {Point} The x-y position of the Component relative to the root + */ + originToSVG(): Point; + /** + * Returns the foreground selection for the Component + * (A selection covering the front of the Component) + * + * Will return undefined if the Component has not been anchored. + * + * @return {D3.Selection} foreground selection for the Component + */ + foreground(): D3.Selection; + /** + * Returns the content selection for the Component + * (A selection containing the visual elements of the Component) + * + * Will return undefined if the Component has not been anchored. + * + * @return {D3.Selection} content selection for the Component + */ + content(): D3.Selection; + /** + * Returns the background selection for the Component + * (A selection appearing behind of the Component) + * + * Will return undefined if the Component has not been anchored. + * + * @return {D3.Selection} background selection for the Component + */ + background(): D3.Selection; + } +} + + +declare module Plottable { + class ComponentContainer extends Component { + constructor(); + anchor(selection: D3.Selection): ComponentContainer; + render(): ComponentContainer; + /** + * Checks whether the specified Component is in the ComponentContainer. + */ + has(component: Component): boolean; + protected _adoptAndAnchor(component: Component): void; + /** + * Removes the specified Component from the ComponentContainer. + */ + remove(component: Component): ComponentContainer; + /** + * Carry out the actual removal of a Component. + * Implementation dependent on the type of container. + * + * @return {boolean} true if the Component was successfully removed, false otherwise. + */ + protected _remove(component: Component): boolean; + /** + * Invokes a callback on each Component in the ComponentContainer. + */ + protected _forEach(callback: (component: Component) => void): void; + /** + * Destroys the ComponentContainer and all Components within it. + */ + destroy(): void; + } +} + + +declare module Plottable { + module Components { + class Group extends ComponentContainer { /** - * Gets the orientation of the Axis. + * Constructs a Component.Group. * - * @returns {number} the current orientation. - */ - orient(): string; - /** - * Sets the orientation of the Axis. + * A Component.Group is a set of Components that will be rendered on top of + * each other. Components added later will be rendered on top of existing Components. * - * @param {number} newOrientation If provided, the desired orientation - * (top/bottom/left/right). - * @returns {Axis} The calling Axis. + * @constructor + * @param {Component[]} components The Components in the resultant Component.Group (default = []). */ - orient(newOrientation: string): AbstractAxis; + constructor(components?: Component[]); + protected _forEach(callback: (component: Component) => any): void; /** - * Gets whether the Axis is currently set to show the first and last - * tick labels. - * - * @returns {boolean} whether or not the last - * tick labels are showing. + * Checks whether the specified Component is in the Group. */ - showEndTickLabels(): boolean; + has(component: Component): boolean; + requestedSpace(offeredWidth: number, offeredHeight: number): SpaceRequest; + computeLayout(origin?: Point, availableWidth?: number, availableHeight?: number): Group; + protected _getSize(availableWidth: number, availableHeight: number): { + width: number; + height: number; + }; + fixedWidth(): boolean; + fixedHeight(): boolean; /** - * Sets whether the Axis is currently set to show the first and last tick - * labels. - * - * @param {boolean} show Whether or not to show the first and last - * labels. - * @returns {Axis} The calling Axis. + * @return {Component[]} The Components in this Group. */ - showEndTickLabels(show: boolean): AbstractAxis; + components(): Component[]; + append(component: Component): Group; + protected _remove(component: Component): boolean; } } } declare module Plottable { - module Axis { + class Axis extends Component { + /** + * The css class applied to each end tick mark (the line on the end tick). + */ + static END_TICK_MARK_CLASS: string; + /** + * The css class applied to each tick mark (the line on the tick). + */ + static TICK_MARK_CLASS: string; + /** + * The css class applied to each tick label (the text associated with the tick). + */ + static TICK_LABEL_CLASS: string; + protected _tickMarkContainer: D3.Selection; + protected _tickLabelContainer: D3.Selection; + protected _baseline: D3.Selection; + protected _scale: Scale; + protected _computedWidth: number; + protected _computedHeight: number; + /** + * Constructs an axis. An axis is a wrapper around a scale for rendering. + * + * @constructor + * @param {Scale} scale The scale for this axis to render. + * @param {string} orientation One of ["top", "left", "bottom", "right"]; + * on which side the axis will appear. On most axes, this is either "left" + * or "bottom". + * @param {Formatter} Data is passed through this formatter before being + * displayed. + */ + constructor(scale: Scale, orientation: string, formatter?: (d: any) => string); + destroy(): void; + protected _isHorizontal(): boolean; + protected _computeWidth(): number; + protected _computeHeight(): number; + requestedSpace(offeredWidth: number, offeredHeight: number): SpaceRequest; + fixedHeight(): boolean; + fixedWidth(): boolean; + protected _rescale(): void; + computeLayout(origin?: Point, availableWidth?: number, availableHeight?: number): Axis; + protected _setup(): void; + protected _getTickValues(): D[]; + renderImmediately(): Axis; + protected _generateBaselineAttrHash(): { + x1: number; + y1: number; + x2: number; + y2: number; + }; + protected _generateTickMarkAttrHash(isEndTickMark?: boolean): { + x1: any; + y1: any; + x2: any; + y2: any; + }; + redraw(): Component; + protected _setDefaultAlignment(): void; + /** + * Gets the current formatter on the axis. Data is passed through the + * formatter before being displayed. + * + * @returns {Formatter} The calling Axis, or the current + * Formatter. + */ + formatter(): Formatter; + /** + * Sets the current formatter on the axis. Data is passed through the + * formatter before being displayed. + * + * @param {Formatter} formatter If provided, data will be passed though `formatter(data)`. + * @returns {Axis} The calling Axis. + */ + formatter(formatter: Formatter): Axis; + /** + * Gets the current tick mark length. + * + * @returns {number} the current tick mark length. + */ + tickLength(): number; + /** + * Sets the current tick mark length. + * + * @param {number} length If provided, length of each tick. + * @returns {Axis} The calling Axis. + */ + tickLength(length: number): Axis; + /** + * Gets the current end tick mark length. + * + * @returns {number} The current end tick mark length. + */ + endTickLength(): number; + /** + * Sets the end tick mark length. + * + * @param {number} length If provided, the length of the end ticks. + * @returns {BaseAxis} The calling Axis. + */ + endTickLength(length: number): Axis; + protected _maxLabelTickLength(): number; + /** + * Gets the padding between each tick mark and its associated label. + * + * @returns {number} the current padding. + * length. + */ + tickLabelPadding(): number; + /** + * Sets the padding between each tick mark and its associated label. + * + * @param {number} padding If provided, the desired padding. + * @returns {Axis} The calling Axis. + */ + tickLabelPadding(padding: number): Axis; + /** + * Gets the size of the gutter (the extra space between the tick + * labels and the outer edge of the axis). + * + * @returns {number} the current gutter. + * length. + */ + gutter(): number; + /** + * Sets the size of the gutter (the extra space between the tick + * labels and the outer edge of the axis). + * + * @param {number} size If provided, the desired gutter. + * @returns {Axis} The calling Axis. + */ + gutter(size: number): Axis; + /** + * Gets the orientation of the Axis. + * + * @returns {number} the current orientation. + */ + orientation(): string; + /** + * Sets the orientation of the Axis. + * + * @param {number} newOrientation If provided, the desired orientation + * (top/bottom/left/right). + * @returns {Axis} The calling Axis. + */ + orientation(orientation: string): Axis; + /** + * Gets whether the Axis is currently set to show the first and last + * tick labels. + * + * @returns {boolean} whether or not the last + * tick labels are showing. + */ + showEndTickLabels(): boolean; + /** + * Sets whether the Axis is currently set to show the first and last tick + * labels. + * + * @param {boolean} show Whether or not to show the first and last + * labels. + * @returns {Axis} The calling Axis. + */ + showEndTickLabels(show: boolean): Axis; + } +} + + +declare module Plottable { + module TimeInterval { + var second: string; + var minute: string; + var hour: string; + var day: string; + var week: string; + var month: string; + var year: string; + } + module Axes { /** * Defines a configuration for a time axis tier. * For details on how ticks are generated see: https://github.com/mbostock/d3/wiki/Time-Scales#ticks @@ -2100,7 +1855,7 @@ declare module Plottable { * formatter - formatter used to format tick labels. */ type TimeAxisTierConfiguration = { - interval: D3.Time.Interval; + interval: string; step: number; formatter: Formatter; }; @@ -2110,7 +1865,7 @@ declare module Plottable { * Currently, up to two tiers are supported. */ type TimeAxisConfiguration = TimeAxisTierConfiguration[]; - class Time extends AbstractAxis { + class Time extends Axis { /** * The css class applied to each time axis tier */ @@ -2124,7 +1879,7 @@ declare module Plottable { * @param {TimeScale} scale The scale to base the Axis on. * @param {string} orientation The orientation of the Axis (top/bottom) */ - constructor(scale: Scale.Time, orientation: string); + constructor(scale: Scales.Time, orientation: string); tierLabelPositions(): string[]; tierLabelPositions(newPositions: string[]): Time; /** @@ -2142,23 +1897,24 @@ declare module Plottable { * @returns {Axis.Time} The calling Axis.Time. */ axisConfigurations(configurations: TimeAxisConfiguration[]): Time; - orient(): string; - orient(orientation: string): Time; - _computeHeight(): number; + orientation(): string; + orientation(orientation: string): Time; + protected _computeHeight(): number; protected _getSize(availableWidth: number, availableHeight: number): { width: number; height: number; }; protected _setup(): void; protected _getTickValues(): any[]; - _doRender(): Time; + renderImmediately(): Time; } } } + declare module Plottable { - module Axis { - class Numeric extends AbstractAxis { + module Axes { + class Numeric extends Axis { /** * Constructs a NumericAxis. * @@ -2170,13 +1926,13 @@ declare module Plottable { * @param {string} orientation The orientation of the QuantitativeScale (top/bottom/left/right) * @param {Formatter} formatter A function to format tick labels (default Formatters.general()). */ - constructor(scale: Scale.AbstractQuantitative, orientation: string, formatter?: (d: any) => string); + constructor(scale: QuantitativeScale, orientation: string, formatter?: (d: any) => string); protected _setup(): void; - _computeWidth(): number; - _computeHeight(): number; - protected _getTickValues(): any[]; + protected _computeWidth(): number; + protected _computeHeight(): number; + protected _getTickValues(): number[]; protected _rescale(): void; - _doRender(): void; + renderImmediately(): Numeric; /** * Gets the tick label position relative to the tick marks. * @@ -2223,8 +1979,8 @@ declare module Plottable { declare module Plottable { - module Axis { - class Category extends AbstractAxis { + module Axes { + class Category extends Axis { /** * Constructs a CategoryAxis. * @@ -2237,11 +1993,16 @@ declare module Plottable { * @param {string} orientation The orientation of the Axis (top/bottom/left/right) (default = "bottom"). * @param {Formatter} formatter The Formatter for the Axis (default Formatters.identity()) */ - constructor(scale: Scale.Category, orientation?: string, formatter?: (d: any) => string); + constructor(scale: Scales.Category, orientation?: string, formatter?: (d: any) => string); protected _setup(): void; - protected _rescale(): void; - _requestedSpace(offeredWidth: number, offeredHeight: number): _SpaceRequest; + protected _rescale(): Component; + requestedSpace(offeredWidth: number, offeredHeight: number): SpaceRequest; protected _getTickValues(): string[]; + /** + * Gets the tick label angle + * @returns {number} the tick label angle + */ + tickLabelAngle(): number; /** * Sets the angle for the tick labels. Right now vertical-left (-90), horizontal (0), and vertical-right (90) are the only options. * @param {number} angle The angle for the ticks @@ -2251,21 +2012,18 @@ declare module Plottable { * See tracking at https://github.com/palantir/plottable/issues/504 */ tickLabelAngle(angle: number): Category; - /** - * Gets the tick label angle - * @returns {number} the tick label angle - */ - tickLabelAngle(): number; - _doRender(): Category; - _computeLayout(offeredXOrigin?: number, offeredYOrigin?: number, availableWidth?: number, availableHeight?: number): void; + renderImmediately(): Category; + computeLayout(origin?: Point, availableWidth?: number, availableHeight?: number): Axis; } } } declare module Plottable { - module Component { - class Label extends AbstractComponent { + module Components { + class Label extends Component { + static TITLE_LABEL_CLASS: string; + static AXIS_LABEL_CLASS: string; /** * Creates a Label. * @@ -2277,23 +2035,7 @@ declare module Plottable { * @param {string} orientation The orientation of the Label (horizontal/left/right) (default = "horizontal"). */ constructor(displayText?: string, orientation?: string); - /** - * Sets the horizontal side the label will go to given the label is given more space that it needs - * - * @param {string} alignment The new setting, one of `["left", "center", - * "right"]`. Defaults to `"center"`. - * @returns {Label} The calling Label. - */ - xAlign(alignment: string): Label; - /** - * Sets the vertical side the label will go to given the label is given more space that it needs - * - * @param {string} alignment The new setting, one of `["top", "center", - * "bottom"]`. Defaults to `"center"`. - * @returns {Label} The calling Label. - */ - yAlign(alignment: string): Label; - _requestedSpace(offeredWidth: number, offeredHeight: number): _SpaceRequest; + requestedSpace(offeredWidth: number, offeredHeight: number): SpaceRequest; protected _setup(): void; /** * Gets the current text on the Label. @@ -2313,7 +2055,7 @@ declare module Plottable { * * @returns {string} the current orientation. */ - orient(): string; + orientation(): string; /** * Sets the orientation of the Label. * @@ -2321,7 +2063,7 @@ declare module Plottable { * (horizontal/left/right). * @returns {Label} The calling Label. */ - orient(newOrientation: string): Label; + orientation(orientation: string): Label; /** * Gets the amount of padding in pixels around the Label. * @@ -2335,31 +2077,17 @@ declare module Plottable { * @returns {Label} The calling Label. */ padding(padAmount: number): Label; - _doRender(): void; - } - class TitleLabel extends Label { - /** - * Creates a TitleLabel, a type of label made for rendering titles. - * - * @constructor - */ - constructor(text?: string, orientation?: string); - } - class AxisLabel extends Label { - /** - * Creates a AxisLabel, a type of label made for rendering axis labels. - * - * @constructor - */ - constructor(text?: string, orientation?: string); + fixedWidth(): boolean; + fixedHeight(): boolean; + renderImmediately(): Label; } } } declare module Plottable { - module Component { - class Legend extends AbstractComponent { + module Components { + class Legend extends Component { /** * The css class applied to each legend row */ @@ -2381,7 +2109,7 @@ declare module Plottable { * @constructor * @param {Scale.Color} colorScale */ - constructor(colorScale: Scale.Color); + constructor(colorScale: Scales.Color); protected _setup(): void; /** * Gets the current max number of entries in Legend row. @@ -2412,16 +2140,16 @@ declare module Plottable { * * @returns {ColorScale} The current color scale. */ - scale(): Scale.Color; + scale(): Scales.Color; /** * Assigns a new color scale to the Legend. * * @param {Scale.Color} scale If provided, the new scale. * @returns {Legend} The calling Legend. */ - scale(scale: Scale.Color): Legend; - remove(): void; - _requestedSpace(offeredWidth: number, offeredHeight: number): _SpaceRequest; + scale(scale: Scales.Color): Legend; + destroy(): void; + requestedSpace(offeredWidth: number, offeredHeight: number): SpaceRequest; /** * Gets the legend entry under the given pixel position. * @@ -2429,7 +2157,7 @@ declare module Plottable { * @returns {D3.Selection} The selected entry, or null selection if no entry was selected. */ getEntry(position: Point): D3.Selection; - _doRender(): void; + renderImmediately(): Legend; /** * Gets the symbolFactoryAccessor of the legend, which dictates how * the symbol in each entry is drawn. @@ -2444,14 +2172,16 @@ declare module Plottable { * @returns {Legend} The calling Legend */ symbolFactoryAccessor(symbolFactoryAccessor: (datum: any, index: number) => SymbolFactory): Legend; + fixedWidth(): boolean; + fixedHeight(): boolean; } } } declare module Plottable { - module Component { - class InterpolatedColorLegend extends AbstractComponent { + module Components { + class InterpolatedColorLegend extends Component { /** * The css class applied to the legend labels. */ @@ -2468,8 +2198,8 @@ declare module Plottable { * @param {string} orientation (horizontal/left/right). * @param {Formatter} The labels are formatted using this function. */ - constructor(interpolatedColorScale: Scale.InterpolatedColor, orientation?: string, formatter?: (d: any) => string); - remove(): void; + constructor(interpolatedColorScale: Scales.InterpolatedColor, orientation?: string, formatter?: (d: any) => string); + destroy(): void; /** * Gets the current formatter on the InterpolatedColorLegend. * @@ -2488,7 +2218,7 @@ declare module Plottable { * * @returns {string} The current orientation. */ - orient(): string; + orientation(): string; /** * Sets the orientation of the InterpolatedColorLegend. * @@ -2496,18 +2226,20 @@ declare module Plottable { * * @returns {InterpolatedColorLegend} The calling InterpolatedColorLegend. */ - orient(newOrientation: string): InterpolatedColorLegend; + orientation(orientation: string): InterpolatedColorLegend; + fixedWidth(): boolean; + fixedHeight(): boolean; protected _setup(): void; - _requestedSpace(offeredWidth: number, offeredHeight: number): _SpaceRequest; - _doRender(): void; + requestedSpace(offeredWidth: number, offeredHeight: number): SpaceRequest; + renderImmediately(): InterpolatedColorLegend; } } } declare module Plottable { - module Component { - class Gridlines extends AbstractComponent { + module Components { + class Gridlines extends Component { /** * Creates a set of Gridlines. * @constructor @@ -2515,26 +2247,18 @@ declare module Plottable { * @param {QuantitativeScale} xScale The scale to base the x gridlines on. Pass null if no gridlines are desired. * @param {QuantitativeScale} yScale The scale to base the y gridlines on. Pass null if no gridlines are desired. */ - constructor(xScale: Scale.AbstractQuantitative, yScale: Scale.AbstractQuantitative); - remove(): Gridlines; + constructor(xScale: QuantitativeScale, yScale: QuantitativeScale); + destroy(): Gridlines; protected _setup(): void; - _doRender(): void; + renderImmediately(): Gridlines; } } } declare module Plottable { - module Component { - type _IterateLayoutResult = { - colProportionalSpace: number[]; - rowProportionalSpace: number[]; - guaranteedWidths: number[]; - guaranteedHeights: number[]; - wantsWidth: boolean; - wantsHeight: boolean; - }; - class Table extends AbstractComponentContainer { + module Components { + class Table extends ComponentContainer { /** * Constructs a Table. * @@ -2549,41 +2273,60 @@ declare module Plottable { * @param {Component[][]} [rows] A 2-D array of the Components to place in the table. * null can be used if a cell is empty. (default = []) */ - constructor(rows?: AbstractComponent[][]); + constructor(rows?: Component[][]); + protected _forEach(callback: (component: Component) => any): void; /** - * Adds a Component in the specified cell. - * - * If the cell is already occupied, there are 3 cases - * - Component + Component => Group containing both components - * - Component + Group => Component is added to the group - * - Group + Component => Component is added to the group + * Checks whether the specified Component is in the Table. + */ + has(component: Component): boolean; + /** + * Adds a Component in the specified row and column position. * * For example, instead of calling `new Table([[a, b], [null, c]])`, you * could call * ```typescript * var table = new Table(); - * table.addComponent(0, 0, a); - * table.addComponent(0, 1, b); - * table.addComponent(1, 1, c); + * table.add(a, 0, 0); + * table.add(b, 0, 1); + * table.add(c, 1, 1); * ``` * + * @param {Component} component The Component to be added. * @param {number} row The row in which to add the Component. * @param {number} col The column in which to add the Component. - * @param {Component} component The Component to be added. * @returns {Table} The calling Table. */ - addComponent(row: number, col: number, component: AbstractComponent): Table; - _removeComponent(component: AbstractComponent): void; - _requestedSpace(offeredWidth: number, offeredHeight: number): _SpaceRequest; - _computeLayout(offeredXOrigin?: number, offeredYOrigin?: number, availableWidth?: number, availableHeight?: number): void; + add(component: Component, row: number, col: number): Table; + protected _remove(component: Component): boolean; + requestedSpace(offeredWidth: number, offeredHeight: number): SpaceRequest; + computeLayout(origin?: Point, availableWidth?: number, availableHeight?: number): Table; /** - * Sets the row and column padding on the Table. + * Gets the row padding on the Table. + * + * @returns {number} the row padding. + */ + rowPadding(): number; + /** + * Sets the row padding on the Table. * * @param {number} rowPadding The padding above and below each row, in pixels. - * @param {number} colPadding the padding to the left and right of each column, in pixels. * @returns {Table} The calling Table. */ - padding(rowPadding: number, colPadding: number): Table; + rowPadding(rowPadding: number): Table; + /** + * Gets the column padding on the Table. + * + * @returns {number} the column padding. + */ + columnPadding(): number; + /** + * Sets the column padding on the Table. + * + * @param {number} columnPadding the padding to the left and right of each column, in pixels. + * @returns {Table} The calling Table. + */ + columnPadding(columnPadding: number): Table; + rowWeight(index: number): number; /** * Sets the layout weight of a particular row. * Space is allocated to rows based on their weight. Rows with higher weights receive proportionally more space. @@ -2610,6 +2353,7 @@ declare module Plottable { * @returns {Table} The calling Table. */ rowWeight(index: number, weight: number): Table; + columnWeight(index: number): number; /** * Sets the layout weight of a particular column. * Space is allocated to columns based on their weight. Columns with higher weights receive proportionally more space. @@ -2620,17 +2364,17 @@ declare module Plottable { * @param {number} weight The weight to be set on the column. * @returns {Table} The calling Table. */ - colWeight(index: number, weight: number): Table; - _isFixedWidth(): boolean; - _isFixedHeight(): boolean; + columnWeight(index: number, weight: number): Table; + fixedWidth(): boolean; + fixedHeight(): boolean; } } } declare module Plottable { - module Component { - class SelectionBoxLayer extends AbstractComponent { + module Components { + class SelectionBoxLayer extends Component { protected _box: D3.Selection; constructor(); protected _setup(): void; @@ -2652,7 +2396,7 @@ declare module Plottable { */ bounds(newBounds: Bounds): SelectionBoxLayer; protected _setBounds(newBounds: Bounds): void; - _doRender(): void; + renderImmediately(): SelectionBoxLayer; /** * Gets whether the box is being shown. * @@ -2666,19 +2410,21 @@ declare module Plottable { * @return {SelectionBoxLayer} The calling SelectionBoxLayer. */ boxVisible(show: boolean): SelectionBoxLayer; + fixedWidth(): boolean; + fixedHeight(): boolean; } } } declare module Plottable { - module Plot { + module Plots { /** * A key that is also coupled with a dataset, a drawer and a metadata in Plot. */ type PlotDatasetKey = { dataset: Dataset; - drawer: _Drawer.AbstractDrawer; + drawer: Drawers.AbstractDrawer; plotMetadata: PlotMetadata; key: string; }; @@ -2690,270 +2436,236 @@ declare module Plottable { pixelPoints: Point[]; selection: D3.Selection; }; - class AbstractPlot extends Component.AbstractComponent { - protected _dataChanged: boolean; - protected _key2PlotDatasetKey: D3.Map; - protected _datasetKeysInOrder: string[]; - protected _renderArea: D3.Selection; - protected _projections: { - [attrToSet: string]: _Projection; - }; - protected _animate: boolean; - protected _animateOnNextRender: boolean; - /** - * Constructs a Plot. - * - * Plots render data. Common example include Plot.Scatter, Plot.Bar, and Plot.Line. - * - * A bare Plot has a DataSource and any number of projectors, which take - * data and "project" it onto the Plot, such as "x", "y", "fill", "r". - * - * @constructor - * @param {any[]|Dataset} [dataset] If provided, the data or Dataset to be associated with this Plot. - */ - constructor(); - _anchor(element: D3.Selection): void; - protected _setup(): void; - remove(): void; - /** - * Adds a dataset to this plot. Identify this dataset with a key. - * - * A key is automatically generated if not supplied. - * - * @param {string} [key] The key of the dataset. - * @param {Dataset | any[]} dataset dataset to add. - * @returns {Plot} The calling Plot. - */ - addDataset(dataset: Dataset | any[]): AbstractPlot; - addDataset(key: string, dataset: Dataset | any[]): AbstractPlot; - protected _getDrawer(key: string): _Drawer.AbstractDrawer; - protected _getAnimator(key: string): Animator.PlotAnimator; - protected _onDatasetUpdate(): void; - /** - * Sets an attribute of every data point. - * - * Here's a common use case: - * ```typescript - * plot.attr("x", function(d) { return d.foo; }, xScale); - * ``` - * This will set the x accessor of each datum `d` to be `d.foo`, - * scaled in accordance with `xScale` - * - * @param {string} attrToSet The attribute to set across each data - * point. Popular examples include "x", "y". - * - * @param {Function|string|any} accessor Function to apply to each element - * of the dataSource. If a Function, use `accessor(d, i)`. If a string, - * `d[accessor]` is used. If anything else, use `accessor` as a constant - * across all data points. - * - * @param {Scale.AbstractScale} scale If provided, the result of the accessor - * is passed through the scale, such as `scale.scale(accessor(d, i))`. - * - * @returns {Plot} The calling Plot. - */ - attr(attrToSet: string, accessor: any, scale?: Scale.AbstractScale): AbstractPlot; - /** - * Identical to plot.attr - */ - project(attrToSet: string, accessor: any, scale?: Scale.AbstractScale): AbstractPlot; - protected _generateAttrToProjector(): AttributeToProjector; - /** - * Generates a dictionary mapping an attribute to a function that calculate that attribute's value - * in accordance with the given datasetKey. - * - * Note that this will return all of the data attributes, which may not perfectly align to svg attributes - * - * @param {datasetKey} the key of the dataset to generate the dictionary for - * @returns {AttributeToAppliedProjector} A dictionary mapping attributes to functions - */ - generateProjectors(datasetKey: string): AttributeToAppliedProjector; - _doRender(): void; - /** - * Enables or disables animation. - * - * @param {boolean} enabled Whether or not to animate. - */ - animate(enabled: boolean): AbstractPlot; - detach(): AbstractPlot; - /** - * This function makes sure that all of the scales in this._projections - * have an extent that includes all the data that is projected onto them. - */ - protected _updateScaleExtents(): void; - _updateScaleExtent(attr: string): void; - /** - * Get the animator associated with the specified Animator key. - * - * @return {PlotAnimator} The Animator for the specified key. - */ - animator(animatorKey: string): Animator.PlotAnimator; - /** - * Set the animator associated with the specified Animator key. - * - * @param {string} animatorKey The key for the Animator. - * @param {PlotAnimator} animator An Animator to be assigned to - * the specified key. - * @returns {Plot} The calling Plot. - */ - animator(animatorKey: string, animator: Animator.PlotAnimator): AbstractPlot; - /** - * Gets the dataset order by key - * - * @returns {string[]} A string array of the keys in order - */ - datasetOrder(): string[]; - /** - * Sets the dataset order by key - * - * @param {string[]} order If provided, a string array which represents the order of the keys. - * This must be a permutation of existing keys. - * - * @returns {Plot} The calling Plot. - */ - datasetOrder(order: string[]): AbstractPlot; - /** - * Removes a dataset by the given identifier - * - * @param {string | Dataset | any[]} datasetIdentifer The identifier as the key of the Dataset to remove - * If string is inputted, it is interpreted as the dataset key to remove. - * If Dataset is inputted, the first Dataset in the plot that is the same will be removed. - * If any[] is inputted, the first data array in the plot that is the same will be removed. - * @returns {AbstractPlot} The calling AbstractPlot. - */ - removeDataset(datasetIdentifier: string | Dataset | any[]): AbstractPlot; - datasets(): Dataset[]; - protected _getDrawersInOrder(): _Drawer.AbstractDrawer[]; - protected _generateDrawSteps(): _Drawer.DrawStep[]; - protected _additionalPaint(time: number): void; - protected _getDataToDraw(): D3.Map; - /** - * Gets the new plot metadata for new dataset with provided key - * - * @param {string} key The key of new dataset - */ - protected _getPlotMetadataForDataset(key: string): PlotMetadata; - /** - * Retrieves all of the selections of this plot for the specified dataset(s) - * - * @param {string | string[]} datasetKeys The dataset(s) to retrieve the selections from. - * If not provided, all selections will be retrieved. - * @param {boolean} exclude If set to true, all datasets will be queried excluding the keys referenced - * in the previous datasetKeys argument (default = false). - * @returns {D3.Selection} The retrieved selections. - */ - getAllSelections(datasetKeys?: string | string[], exclude?: boolean): D3.Selection; - /** - * Retrieves all of the PlotData of this plot for the specified dataset(s) - * - * @param {string | string[]} datasetKeys The dataset(s) to retrieve the selections from. - * If not provided, all selections will be retrieved. - * @returns {PlotData} The retrieved PlotData. - */ - getAllPlotData(datasetKeys?: string | string[]): PlotData; - protected _getAllPlotData(datasetKeys: string[]): PlotData; - /** - * Retrieves PlotData with the lowest distance, where distance is defined - * to be the Euclidiean norm. - * - * @param {Point} queryPoint The point to which plot data should be compared - * - * @returns {PlotData} The PlotData closest to queryPoint - */ - getClosestPlotData(queryPoint: Point): PlotData; - protected _isVisibleOnPlot(datum: any, pixelPoint: Point, selection: D3.Selection): boolean; - } + interface AccessorScaleBinding { + accessor: Accessor; + scale?: Scale; + } + } + class Plot extends Component { + protected _dataChanged: boolean; + protected _key2PlotDatasetKey: D3.Map; + protected _datasetKeysInOrder: string[]; + protected _renderArea: D3.Selection; + protected _attrBindings: D3.Map<_Projection>; + protected _attrExtents: D3.Map; + protected _animate: boolean; + protected _animateOnNextRender: boolean; + protected _propertyExtents: D3.Map; + protected _propertyBindings: D3.Map>; + /** + * Constructs a Plot. + * + * Plots render data. Common example include Plot.Scatter, Plot.Bar, and Plot.Line. + * + * A bare Plot has a DataSource and any number of projectors, which take + * data and "project" it onto the Plot, such as "x", "y", "fill", "r". + * + * @constructor + * @param {any[]|Dataset} [dataset] If provided, the data or Dataset to be associated with this Plot. + */ + constructor(); + anchor(selection: D3.Selection): Plot; + protected _setup(): void; + destroy(): void; + /** + * @param {Dataset} dataset + * @returns {Plot} The calling Plot. + */ + addDataset(dataset: Dataset): Plot; + protected _getDrawer(key: string): Drawers.AbstractDrawer; + protected _getAnimator(key: string): Animators.PlotAnimator; + protected _onDatasetUpdate(): void; + attr(attr: string): Plots.AccessorScaleBinding; + attr(attr: string, attrValue: number | string | Accessor | Accessor): Plot; + attr(attr: string, attrValue: A | Accessor, scale: Scale): Plot; + protected _bindProperty(property: string, value: any, scale: Scale): void; + protected _generateAttrToProjector(): AttributeToProjector; + /** + * Generates a dictionary mapping an attribute to a function that calculate that attribute's value + * in accordance with the given datasetKey. + * + * Note that this will return all of the data attributes, which may not perfectly align to svg attributes + * + * @param {Dataset} dataset The dataset to generate the dictionary for + * @returns {AttributeToAppliedProjector} A dictionary mapping attributes to functions + */ + generateProjectors(dataset: Dataset): AttributeToAppliedProjector; + renderImmediately(): Plot; + /** + * Enables or disables animation. + * + * @param {boolean} enabled Whether or not to animate. + */ + animate(enabled: boolean): Plot; + detach(): Plot; + /** + * Updates the extents associated with each attribute, then autodomains all scales the Plot uses. + */ + protected _updateExtents(): void; + protected _updateExtentsForProperty(property: string): void; + protected _filterForProperty(property: string): Accessor; + /** + * Override in subclass to add special extents, such as included values + */ + protected _extentsForProperty(property: string): any[]; + /** + * Get the animator associated with the specified Animator key. + * + * @return {PlotAnimator} The Animator for the specified key. + */ + animator(animatorKey: string): Animators.PlotAnimator; + /** + * Set the animator associated with the specified Animator key. + * + * @param {string} animatorKey The key for the Animator. + * @param {PlotAnimator} animator An Animator to be assigned to + * the specified key. + * @returns {Plot} The calling Plot. + */ + animator(animatorKey: string, animator: Animators.PlotAnimator): Plot; + /** + * @param {Dataset} dataset + * @returns {Plot} The calling Plot. + */ + removeDataset(dataset: Dataset): Plot; + /** + * Returns an array of internal keys corresponding to those Datasets actually on the plot + */ + protected _keysForDatasets(datasets: Dataset[]): string[]; + datasets(): Dataset[]; + datasets(datasets: Dataset[]): Plot; + protected _getDrawersInOrder(): Drawers.AbstractDrawer[]; + protected _generateDrawSteps(): Drawers.DrawStep[]; + protected _additionalPaint(time: number): void; + protected _getDataToDraw(): D3.Map; + /** + * Gets the new plot metadata for new dataset with provided key + * + * @param {string} key The key of new dataset + */ + protected _getPlotMetadataForDataset(key: string): Plots.PlotMetadata; + /** + * Retrieves all of the Selections of this Plot for the specified Datasets. + * + * @param {Dataset[]} datasets The Datasets to retrieve the selections from. + * If not provided, all selections will be retrieved. + * @param {boolean} exclude If set to true, all Datasets will be queried excluding the keys referenced + * in the previous datasetKeys argument (default = false). + * @returns {D3.Selection} The retrieved Selections. + */ + getAllSelections(datasets?: Dataset[], exclude?: boolean): D3.Selection; + /** + * Retrieves all of the PlotData of this plot for the specified dataset(s) + * + * @param {Dataset[]} datasets The Datasets to retrieve the PlotData from. + * If not provided, all PlotData will be retrieved. + * @returns {PlotData} The retrieved PlotData. + */ + getAllPlotData(datasets?: Dataset[]): Plots.PlotData; + /** + * Retrieves PlotData with the lowest distance, where distance is defined + * to be the Euclidiean norm. + * + * @param {Point} queryPoint The point to which plot data should be compared + * + * @returns {PlotData} The PlotData closest to queryPoint + */ + getClosestPlotData(queryPoint: Point): Plots.PlotData; + protected _isVisibleOnPlot(datum: any, pixelPoint: Point, selection: D3.Selection): boolean; + protected _uninstallScaleForKey(scale: Scale, key: string): void; + protected _installScaleForKey(scale: Scale, key: string): void; + protected _generatePropertyToProjectors(): AttributeToProjector; } } declare module Plottable { - module Plot { - class Pie extends AbstractPlot { + module Plots { + class Pie extends Plot { /** * Constructs a PiePlot. * * @constructor */ constructor(); - _computeLayout(offeredXOrigin?: number, offeredYOrigin?: number, availableWidth?: number, availableHeight?: number): void; - addDataset(keyOrDataset: any, dataset?: any): Pie; - protected _generateAttrToProjector(): AttributeToProjector; - protected _getDrawer(key: string): _Drawer.AbstractDrawer; - getAllPlotData(datasetKeys?: string | string[]): PlotData; + computeLayout(origin?: Point, availableWidth?: number, availableHeight?: number): Pie; + addDataset(dataset: Dataset): Pie; + protected _getDrawer(key: string): Drawers.AbstractDrawer; + getAllPlotData(datasets?: Dataset[]): Plots.PlotData; + sectorValue(): AccessorScaleBinding; + sectorValue(sectorValue: number | Accessor): Plots.Pie; + sectorValue(sectorValue: S | Accessor, scale: Scale): Plots.Pie; + innerRadius(): AccessorScaleBinding; + innerRadius(innerRadius: number | Accessor): Plots.Pie; + innerRadius(innerRadius: R | Accessor, scale: Scale): Plots.Pie; + outerRadius(): AccessorScaleBinding; + outerRadius(outerRadius: number | Accessor): Plots.Pie; + outerRadius(outerRadius: R | Accessor, scale: Scale): Plots.Pie; } } } declare module Plottable { - module Plot { - class AbstractXYPlot extends AbstractPlot { - protected _xScale: Scale.AbstractScale; - protected _yScale: Scale.AbstractScale; - /** - * Constructs an XYPlot. - * - * An XYPlot is a plot from drawing 2-dimensional data. Common examples - * include Scale.Line and Scale.Bar. - * - * @constructor - * @param {any[]|Dataset} [dataset] The data or Dataset to be associated with this Renderer. - * @param {Scale} xScale The x scale to use. - * @param {Scale} yScale The y scale to use. - */ - constructor(xScale: Scale.AbstractScale, yScale: Scale.AbstractScale); - /** - * @param {string} attrToSet One of ["x", "y"] which determines the point's - * x and y position in the Plot. - */ - project(attrToSet: string, accessor: any, scale?: Scale.AbstractScale): AbstractXYPlot; - remove(): AbstractXYPlot; - /** - * Sets the automatic domain adjustment over visible points for y scale. - * - * If autoAdjustment is true adjustment is immediately performend. - * - * @param {boolean} autoAdjustment The new value for the automatic adjustment domain for y scale. - * @returns {AbstractXYPlot} The calling AbstractXYPlot. - */ - automaticallyAdjustYScaleOverVisiblePoints(autoAdjustment: boolean): AbstractXYPlot; - /** - * Sets the automatic domain adjustment over visible points for x scale. - * - * If autoAdjustment is true adjustment is immediately performend. - * - * @param {boolean} autoAdjustment The new value for the automatic adjustment domain for x scale. - * @returns {AbstractXYPlot} The calling AbstractXYPlot. - */ - automaticallyAdjustXScaleOverVisiblePoints(autoAdjustment: boolean): AbstractXYPlot; - protected _generateAttrToProjector(): AttributeToProjector; - _computeLayout(offeredXOrigin?: number, offeredYOffset?: number, availableWidth?: number, availableHeight?: number): void; - protected _updateXDomainer(): void; - protected _updateYDomainer(): void; - /** - * Adjusts both domains' extents to show all datasets. - * - * This call does not override auto domain adjustment behavior over visible points. - */ - showAllData(): void; - protected _normalizeDatasets(fromX: boolean): { - a: A; - b: B; - }[]; - protected _projectorsReady(): { - accessor: (datum: any, index?: number, userMetadata?: any, plotMetadata?: PlotMetadata) => any; - scale?: Scale.AbstractScale; - attribute: string; - }; - } + class XYPlot extends Plot { + /** + * Constructs an XYPlot. + * + * An XYPlot is a plot from drawing 2-dimensional data. Common examples + * include Scale.Line and Scale.Bar. + * + * @constructor + * @param {any[]|Dataset} [dataset] The data or Dataset to be associated with this Renderer. + * @param {Scale} xScale The x scale to use. + * @param {Scale} yScale The y scale to use. + */ + constructor(xScale: Scale, yScale: Scale); + x(): Plots.AccessorScaleBinding; + x(x: number | Accessor): XYPlot; + x(x: X | Accessor, xScale: Scale): XYPlot; + y(): Plots.AccessorScaleBinding; + y(y: number | Accessor): XYPlot; + y(y: Y | Accessor, yScale: Scale): XYPlot; + protected _filterForProperty(property: string): (datum: any, index: number, dataset: Dataset, plotMetadata: Plots.PlotMetadata) => boolean; + protected _uninstallScaleForKey(scale: Scale, key: string): void; + protected _installScaleForKey(scale: Scale, key: string): void; + destroy(): XYPlot; + /** + * Sets the automatic domain adjustment over visible points for y scale. + * + * If autoAdjustment is true adjustment is immediately performend. + * + * @param {boolean} autoAdjustment The new value for the automatic adjustment domain for y scale. + * @returns {XYPlot} The calling XYPlot. + */ + automaticallyAdjustYScaleOverVisiblePoints(autoAdjustment: boolean): XYPlot; + /** + * Sets the automatic domain adjustment over visible points for x scale. + * + * If autoAdjustment is true adjustment is immediately performend. + * + * @param {boolean} autoAdjustment The new value for the automatic adjustment domain for x scale. + * @returns {XYPlot} The calling XYPlot. + */ + automaticallyAdjustXScaleOverVisiblePoints(autoAdjustment: boolean): XYPlot; + protected _generatePropertyToProjectors(): AttributeToProjector; + computeLayout(origin?: Point, availableWidth?: number, availableHeight?: number): XYPlot; + protected _updateXDomainer(): void; + protected _updateYDomainer(): void; + /** + * Adjusts both domains' extents to show all datasets. + * + * This call does not override auto domain adjustment behavior over visible points. + */ + showAllData(): XYPlot; + protected _projectorsReady(): boolean; } } declare module Plottable { - module Plot { - class Rectangle extends AbstractXYPlot { + module Plots { + class Rectangle extends XYPlot { /** * Constructs a RectanglePlot. * @@ -2962,23 +2674,35 @@ declare module Plottable { * as well as the bottom and top bounds (y1 and y2 respectively) * * @constructor - * @param {Scale.AbstractScale} xScale The x scale to use. - * @param {Scale.AbstractScale} yScale The y scale to use. + * @param {Scale.Scale} xScale The x scale to use. + * @param {Scale.Scale} yScale The y scale to use. */ - constructor(xScale: Scale.AbstractScale, yScale: Scale.AbstractScale); - protected _getDrawer(key: string): _Drawer.Rect; + constructor(xScale: Scale, yScale: Scale); + protected _getDrawer(key: string): Drawers.Rect; protected _generateAttrToProjector(): { - [attrToSet: string]: (datum: any, index: number, userMetadata: any, plotMetadata: PlotMetadata) => any; + [attrToSet: string]: (datum: any, index: number, dataset: Dataset, plotMetadata: PlotMetadata) => any; }; - protected _generateDrawSteps(): _Drawer.DrawStep[]; + protected _generateDrawSteps(): Drawers.DrawStep[]; + x1(): AccessorScaleBinding; + x1(x1: number | Accessor): Plots.Rectangle; + x1(x1: X | Accessor, scale: Scale): Plots.Rectangle; + x2(): AccessorScaleBinding; + x2(x2: number | Accessor): Plots.Rectangle; + x2(x2: X | Accessor, scale: Scale): Plots.Rectangle; + y1(): AccessorScaleBinding; + y1(y1: number | Accessor): Plots.Rectangle; + y1(y1: Y | Accessor, scale: Scale): Plots.Rectangle; + y2(): AccessorScaleBinding; + y2(y2: number | Accessor): Plots.Rectangle; + y2(y2: Y | Accessor, scale: Scale): Plots.Rectangle; } } } declare module Plottable { - module Plot { - class Scatter extends AbstractXYPlot implements Interaction.Hoverable { + module Plots { + class Scatter extends XYPlot { /** * Constructs a ScatterPlot. * @@ -2986,25 +2710,26 @@ declare module Plottable { * @param {Scale} xScale The x scale to use. * @param {Scale} yScale The y scale to use. */ - constructor(xScale: Scale.AbstractScale, yScale: Scale.AbstractScale); - protected _getDrawer(key: string): _Drawer.Symbol; + constructor(xScale: Scale, yScale: Scale); + protected _getDrawer(key: string): Drawers.Symbol; protected _generateAttrToProjector(): { - [attrToSet: string]: (datum: any, index: number, userMetadata: any, plotMetadata: PlotMetadata) => any; + [attrToSet: string]: (datum: any, index: number, dataset: Dataset, plotMetadata: PlotMetadata) => any; }; - protected _generateDrawSteps(): _Drawer.DrawStep[]; - protected _getClosestStruckPoint(p: Point, range: number): Interaction.HoverData; + size(): AccessorScaleBinding; + size(size: number | Accessor): Plots.Scatter; + size(size: S | Accessor, scale: Scale): Plots.Scatter; + symbol(): AccessorScaleBinding; + symbol(symbol: Accessor): Plots.Scatter; + protected _generateDrawSteps(): Drawers.DrawStep[]; protected _isVisibleOnPlot(datum: any, pixelPoint: Point, selection: D3.Selection): boolean; - _hoverOverComponent(p: Point): void; - _hoverOutComponent(p: Point): void; - _doHover(p: Point): Interaction.HoverData; } } } declare module Plottable { - module Plot { - class Grid extends Rectangle { + module Plots { + class Grid extends Rectangle { /** * Constructs a GridPlot. * @@ -3012,28 +2737,25 @@ declare module Plottable { * grid, and the datum can control what color it is. * * @constructor - * @param {Scale.AbstractScale} xScale The x scale to use. - * @param {Scale.AbstractScale} yScale The y scale to use. + * @param {Scale.Scale} xScale The x scale to use. + * @param {Scale.Scale} yScale The y scale to use. * @param {Scale.Color|Scale.InterpolatedColor} colorScale The color scale * to use for each grid cell. */ - constructor(xScale: Scale.AbstractScale, yScale: Scale.AbstractScale, colorScale: Scale.AbstractScale); - addDataset(keyOrDataset: any, dataset?: any): Grid; - protected _getDrawer(key: string): _Drawer.Rect; - /** - * @param {string} attrToSet One of ["x", "y", "x2", "y2", "fill"]. If "fill" is used, - * the data should return a valid CSS color. - */ - project(attrToSet: string, accessor: any, scale?: Scale.AbstractScale): Grid; - protected _generateDrawSteps(): _Drawer.DrawStep[]; + constructor(xScale: Scale, yScale: Scale); + addDataset(dataset: Dataset): Grid; + protected _getDrawer(key: string): Drawers.Rect; + protected _generateDrawSteps(): Drawers.DrawStep[]; + x(x?: number | Accessor | X | Accessor, scale?: Scale): any; + y(y?: number | Accessor | Y | Accessor, scale?: Scale): any; } } } declare module Plottable { - module Plot { - class Bar extends AbstractXYPlot implements Interaction.Hoverable { + module Plots { + class Bar extends XYPlot { protected static _BarAlignmentToFactor: { [alignment: string]: number; }; @@ -3047,8 +2769,8 @@ declare module Plottable { * @param {Scale} yScale The y scale to use. * @param {boolean} isVertical if the plot if vertical. */ - constructor(xScale: Scale.AbstractScale, yScale: Scale.AbstractScale, isVertical?: boolean); - protected _getDrawer(key: string): _Drawer.Rect; + constructor(xScale: Scale, yScale: Scale, isVertical?: boolean); + protected _getDrawer(key: string): Drawers.Rect; protected _setup(): void; /** * Gets the baseline value for the bars @@ -3081,27 +2803,27 @@ declare module Plottable { * * @returns {boolean} Whether bars should display labels or not. */ - barLabelsEnabled(): boolean; + labelsEnabled(): boolean; /** * Set whether bar labels are enabled. * @param {boolean} Whether bars should display labels or not. * * @returns {Bar} The calling plot. */ - barLabelsEnabled(enabled: boolean): Bar; + labelsEnabled(enabled: boolean): Bar; /** * Get the formatter for bar labels. * * @returns {Formatter} The formatting function for bar labels. */ - barLabelFormatter(): Formatter; + labelFormatter(): Formatter; /** * Change the formatting function for bar labels. * @param {Formatter} The formatting function for bar labels. * * @returns {Bar} The calling plot. */ - barLabelFormatter(formatter: Formatter): Bar; + labelFormatter(formatter: Formatter): Bar; /** * Retrieves the closest PlotData to queryPoint. * @@ -3126,14 +2848,14 @@ declare module Plottable { * @returns {D3.Selection} The selected bar, or null if no bar was selected. */ getBars(xValOrExtent: number | Extent, yValOrExtent: number | Extent): D3.Selection; - protected _updateDomainer(scale: Scale.AbstractScale): void; + protected _updateDomainer(scale: Scale): void; protected _updateYDomainer(): void; protected _updateXDomainer(): void; protected _additionalPaint(time: number): void; protected _drawLabels(): void; - protected _generateDrawSteps(): _Drawer.DrawStep[]; + protected _generateDrawSteps(): Drawers.DrawStep[]; protected _generateAttrToProjector(): { - [attrToSet: string]: (datum: any, index: number, userMetadata: any, plotMetadata: PlotMetadata) => any; + [attrToSet: string]: (datum: any, index: number, dataset: Dataset, plotMetadata: PlotMetadata) => any; }; /** * Computes the barPixelWidth of all the bars in the plot. @@ -3144,31 +2866,15 @@ declare module Plottable { * If the position scale of the plot is a QuantitativeScale, then _getMinimumDataWidth is scaled to compute the barPixelWidth */ protected _getBarPixelWidth(): number; - hoverMode(): string; - /** - * Sets the hover mode for hover interactions. There are two modes: - * - "point": Selects the bar under the mouse cursor (default). - * - "line" : Selects any bar that would be hit by a line extending - * in the same direction as the bar and passing through - * the cursor. - * - * @param {string} mode The desired hover mode. - * @return {Bar} The calling Bar Plot. - */ - hoverMode(mode: String): Bar; - _hoverOverComponent(p: Point): void; - _hoverOutComponent(p: Point): void; - _doHover(p: Point): Interaction.HoverData; - protected _getAllPlotData(datasetKeys: string[]): PlotData; + getAllPlotData(datasets?: Dataset[]): Plots.PlotData; } } } declare module Plottable { - module Plot { - class Line extends AbstractXYPlot implements Interaction.Hoverable { - protected _yScale: Scale.AbstractQuantitative; + module Plots { + class Line extends XYPlot { /** * Constructs a LinePlot. * @@ -3176,24 +2882,15 @@ declare module Plottable { * @param {QuantitativeScale} xScale The x scale to use. * @param {QuantitativeScale} yScale The y scale to use. */ - constructor(xScale: Scale.AbstractQuantitative, yScale: Scale.AbstractQuantitative); - protected _setup(): void; - protected _rejectNullsAndNaNs(d: any, i: number, userMetdata: any, plotMetadata: any, accessor: _Accessor): boolean; - protected _getDrawer(key: string): _Drawer.Line; - protected _getResetYFunction(): (d: any, i: number, u: any, m: PlotMetadata) => number; - protected _generateDrawSteps(): _Drawer.DrawStep[]; + constructor(xScale: QuantitativeScale, yScale: QuantitativeScale); + protected _getDrawer(key: string): Drawers.Line; + protected _getResetYFunction(): (d: any, i: number, dataset: Dataset, m: PlotMetadata) => number; + protected _generateDrawSteps(): Drawers.DrawStep[]; protected _generateAttrToProjector(): { - [attrToSet: string]: (datum: any, index: number, userMetadata: any, plotMetadata: PlotMetadata) => any; + [attrToSet: string]: (datum: any, index: number, dataset: Dataset, plotMetadata: PlotMetadata) => any; }; protected _wholeDatumAttributes(): string[]; - protected _getClosestWithinRange(p: Point, range: number): { - closestValue: any; - closestPoint: { - x: number; - y: number; - }; - }; - protected _getAllPlotData(datasetKeys: string[]): PlotData; + getAllPlotData(datasets?: Dataset[]): Plots.PlotData; /** * Retrieves the closest PlotData to queryPoint. * @@ -3205,16 +2902,13 @@ declare module Plottable { * @returns {PlotData} The PlotData closest to queryPoint */ getClosestPlotData(queryPoint: Point): PlotData; - _hoverOverComponent(p: Point): void; - _hoverOutComponent(p: Point): void; - _doHover(p: Point): Interaction.HoverData; } } } declare module Plottable { - module Plot { + module Plots { /** * An AreaPlot draws a filled region (area) between the plot's projected "y" and projected "y0" values. */ @@ -3226,23 +2920,22 @@ declare module Plottable { * @param {QuantitativeScale} xScale The x scale to use. * @param {QuantitativeScale} yScale The y scale to use. */ - constructor(xScale: Scale.AbstractQuantitative, yScale: Scale.AbstractQuantitative); + constructor(xScale: QuantitativeScale, yScale: QuantitativeScale); + y0(): Plots.AccessorScaleBinding; + y0(y0: number | Accessor): Area; + y0(y0: number | Accessor, y0Scale: Scale): Area; protected _onDatasetUpdate(): void; - protected _getDrawer(key: string): _Drawer.Area; + protected _getDrawer(key: string): Drawers.Area; protected _updateYDomainer(): void; - project(attrToSet: string, accessor: any, scale?: Scale.AbstractScale): Area; - protected _getResetYFunction(): (datum: any, index: number, userMetadata: any, plotMetadata: PlotMetadata) => any; + protected _getResetYFunction(): (datum: any, index: number, dataset: Dataset, plotMetadata: PlotMetadata) => any; protected _wholeDatumAttributes(): string[]; - protected _generateAttrToProjector(): { - [attrToSet: string]: (datum: any, index: number, userMetadata: any, plotMetadata: PlotMetadata) => any; - }; } } } declare module Plottable { - module Plot { + module Plots { interface ClusteredPlotMetadata extends PlotMetadata { position: number; } @@ -3259,9 +2952,9 @@ declare module Plottable { * @param {Scale} yScale The y scale to use. * @param {boolean} isVertical if the plot if vertical. */ - constructor(xScale: Scale.AbstractScale, yScale: Scale.AbstractScale, isVertical?: boolean); + constructor(xScale: Scale, yScale: Scale, isVertical?: boolean); protected _generateAttrToProjector(): { - [attrToSet: string]: (datum: any, index: number, userMetadata: any, plotMetadata: PlotMetadata) => any; + [attrToSet: string]: (datum: any, index: number, dataset: Dataset, plotMetadata: PlotMetadata) => any; }; protected _getDataToDraw(): D3.Map; protected _getPlotMetadataForDataset(key: string): ClusteredPlotMetadata; @@ -3271,7 +2964,7 @@ declare module Plottable { declare module Plottable { - module Plot { + module Plots { interface StackedPlotMetadata extends PlotMetadata { offsets: D3.Map; } @@ -3280,39 +2973,37 @@ declare module Plottable { value: number; offset?: number; }; - class AbstractStacked extends AbstractXYPlot { - protected _isVertical: boolean; - _getPlotMetadataForDataset(key: string): StackedPlotMetadata; - project(attrToSet: string, accessor: any, scale?: Scale.AbstractScale): AbstractStacked; - _onDatasetUpdate(): void; - _updateStackOffsets(): void; - _updateStackExtents(): void; - /** - * Feeds the data through d3's stack layout function which will calculate - * the stack offsets and use the the function declared in .out to set the offsets on the data. - */ - _stack(dataArray: D3.Map[]): D3.Map[]; - /** - * After the stack offsets have been determined on each separate dataset, the offsets need - * to be determined correctly on the overall datasets - */ - _setDatasetStackOffsets(positiveDataMapArray: D3.Map[], negativeDataMapArray: D3.Map[]): void; - _getDomainKeys(): string[]; - _generateDefaultMapArray(): D3.Map[]; - _updateScaleExtents(): void; - _normalizeDatasets(fromX: boolean): { - a: A; - b: B; - }[]; - _keyAccessor(): _Accessor; - _valueAccessor(): _Accessor; - } + } + class Stacked extends XYPlot { + protected _isVertical: boolean; + _getPlotMetadataForDataset(key: string): Plots.StackedPlotMetadata; + x(x?: number | Accessor | X | Accessor, scale?: Scale): any; + y(y?: number | Accessor | Y | Accessor, scale?: Scale): any; + _onDatasetUpdate(): void; + _updateStackOffsets(): void; + _updateStackExtents(): void; + /** + * Feeds the data through d3's stack layout function which will calculate + * the stack offsets and use the the function declared in .out to set the offsets on the data. + */ + _stack(dataArray: D3.Map[]): D3.Map[]; + /** + * After the stack offsets have been determined on each separate dataset, the offsets need + * to be determined correctly on the overall datasets + */ + _setDatasetStackOffsets(positiveDataMapArray: D3.Map[], negativeDataMapArray: D3.Map[]): void; + _getDomainKeys(): string[]; + _generateDefaultMapArray(): D3.Map[]; + protected _updateExtentsForProperty(property: string): void; + protected _extentsForProperty(attr: string): any[]; + _keyAccessor(): Accessor | Accessor; + _valueAccessor(): Accessor; } } declare module Plottable { - module Plot { + module Plots { class StackedArea extends Area { /** * Constructs a StackedArea plot. @@ -3321,16 +3012,17 @@ declare module Plottable { * @param {QuantitativeScale} xScale The x scale to use. * @param {QuantitativeScale} yScale The y scale to use. */ - constructor(xScale: Scale.AbstractQuantitative, yScale: Scale.AbstractQuantitative); - protected _getDrawer(key: string): _Drawer.Area; - _getAnimator(key: string): Animator.PlotAnimator; + constructor(xScale: QuantitativeScale, yScale: QuantitativeScale); + protected _getDrawer(key: string): Drawers.Area; + _getAnimator(key: string): Animators.PlotAnimator; protected _setup(): void; + x(x?: number | Accessor | X | Accessor, xScale?: Scale): any; + y(y?: number | Accessor, yScale?: Scale): any; protected _additionalPaint(): void; protected _updateYDomainer(): void; - project(attrToSet: string, accessor: any, scale?: Scale.AbstractScale): StackedArea; protected _onDatasetUpdate(): StackedArea; protected _generateAttrToProjector(): { - [attrToSet: string]: (datum: any, index: number, userMetadata: any, plotMetadata: PlotMetadata) => any; + [attrToSet: string]: (datum: any, index: number, dataset: Dataset, plotMetadata: PlotMetadata) => any; }; protected _wholeDatumAttributes(): string[]; _updateStackOffsets(): void; @@ -3339,21 +3031,18 @@ declare module Plottable { _setDatasetStackOffsets(positiveDataMapArray: D3.Map[], negativeDataMapArray: D3.Map[]): void; _getDomainKeys(): any; _generateDefaultMapArray(): D3.Map[]; - _updateScaleExtents(): void; - _keyAccessor(): _Accessor; - _valueAccessor(): _Accessor; + protected _extentsForProperty(attr: string): any; + _keyAccessor(): Accessor; + _valueAccessor(): Accessor; _getPlotMetadataForDataset(key: string): StackedPlotMetadata; - protected _normalizeDatasets(fromX: boolean): { - a: A; - b: B; - }[]; + protected _updateExtentsForProperty(property: string): void; } } } declare module Plottable { - module Plot { + module Plots { class StackedBar extends Bar { /** * Constructs a StackedBar plot. @@ -3364,35 +3053,33 @@ declare module Plottable { * @param {Scale} yScale the y scale of the plot. * @param {boolean} isVertical if the plot if vertical. */ - constructor(xScale?: Scale.AbstractScale, yScale?: Scale.AbstractScale, isVertical?: boolean); - protected _getAnimator(key: string): Animator.PlotAnimator; + constructor(xScale?: Scale, yScale?: Scale, isVertical?: boolean); + protected _getAnimator(key: string): Animators.PlotAnimator; + x(x?: number | Accessor | X | Accessor, xScale?: Scale): any; + y(y?: number | Accessor | Y | Accessor, yScale?: Scale): any; protected _generateAttrToProjector(): { - [attrToSet: string]: (datum: any, index: number, userMetadata: any, plotMetadata: PlotMetadata) => any; + [attrToSet: string]: (datum: any, index: number, dataset: Dataset, plotMetadata: PlotMetadata) => any; }; - protected _generateDrawSteps(): _Drawer.DrawStep[]; - project(attrToSet: string, accessor: any, scale?: Scale.AbstractScale): StackedBar; + protected _generateDrawSteps(): Drawers.DrawStep[]; protected _onDatasetUpdate(): StackedBar; protected _getPlotMetadataForDataset(key: string): StackedPlotMetadata; - protected _normalizeDatasets(fromX: boolean): { - a: A; - b: B; - }[]; + protected _updateExtentsForProperty(property: string): void; _updateStackOffsets(): void; _updateStackExtents(): void; _stack(dataArray: D3.Map[]): D3.Map[]; _setDatasetStackOffsets(positiveDataMapArray: D3.Map[], negativeDataMapArray: D3.Map[]): void; _getDomainKeys(): any; _generateDefaultMapArray(): D3.Map[]; - _updateScaleExtents(): void; - _keyAccessor(): _Accessor; - _valueAccessor(): _Accessor; + protected _extentsForProperty(attr: string): any; + _keyAccessor(): Accessor | Accessor; + _valueAccessor(): Accessor; } } } declare module Plottable { - module Animator { + module Animators { interface PlotAnimator { /** * Applies the supplied attributes to a D3.Selection with some animation. @@ -3420,7 +3107,7 @@ declare module Plottable { declare module Plottable { - module Animator { + module Animators { /** * An animator implementation with no animation. The attributes are * immediately set on the selection. @@ -3434,7 +3121,7 @@ declare module Plottable { declare module Plottable { - module Animator { + module Animators { /** * The base animator implementation with easing, duration, and delay. * @@ -3547,7 +3234,7 @@ declare module Plottable { declare module Plottable { - module Animator { + module Animators { /** * The default animator implementation with easing, duration, and delay. */ @@ -3557,14 +3244,14 @@ declare module Plottable { isReverse: boolean; constructor(isVertical?: boolean, isReverse?: boolean); animate(selection: any, attrToProjector: AttributeToProjector): D3.Transition.Transition; - protected _startMovingProjector(attrToProjector: AttributeToProjector): (datum: any, index: number, userMetadata: any, plotMetadata: Plot.PlotMetadata) => any; + protected _startMovingProjector(attrToProjector: AttributeToProjector): (datum: any, index: number, dataset: Dataset, plotMetadata: Plots.PlotMetadata) => any; } } } declare module Plottable { - module Animator { + module Animators { /** * A child class of RectAnimator that will move the rectangle * as well as animate its growth. @@ -3588,26 +3275,21 @@ declare module Plottable { declare module Plottable { - module Dispatcher { - class AbstractDispatcher extends Core.PlottableObject { - protected _event2Callback: { - [eventName: string]: (e: Event) => any; - }; - protected _broadcasters: Core.Broadcaster[]; - /** - * Creates a wrapped version of the callback that can be registered to a Broadcaster - */ - protected _getWrappedCallback(callback: Function): Core.BroadcasterCallback; - protected _setCallback(b: Core.Broadcaster, key: any, callback: Function): void; - } + class Dispatcher { + protected _event2Callback: { + [eventName: string]: (e: Event) => any; + }; + protected _callbacks: Utils.CallbackSet[]; + protected setCallback(callbackSet: Utils.CallbackSet, callback: Function): void; + protected unsetCallback(callbackSet: Utils.CallbackSet, callback: Function): void; } } declare module Plottable { - module Dispatcher { - type MouseCallback = (p: Point, e: MouseEvent) => any; - class Mouse extends AbstractDispatcher { + module Dispatchers { + type MouseCallback = (p: Point, event: MouseEvent) => any; + class Mouse extends Dispatcher { /** * Get a Dispatcher.Mouse for the containing elem. If one already exists * on that , it will be returned; otherwise, a new one will be created. @@ -3615,7 +3297,7 @@ declare module Plottable { * @param {SVGElement} elem A svg DOM element. * @return {Dispatcher.Mouse} A Dispatcher.Mouse */ - static getDispatcher(elem: SVGElement): Dispatcher.Mouse; + static getDispatcher(elem: SVGElement): Dispatchers.Mouse; /** * Creates a Dispatcher.Mouse. * This constructor not be invoked directly under most circumstances. @@ -3623,67 +3305,96 @@ declare module Plottable { * @param {SVGElement} svg The root element to attach to. */ constructor(svg: SVGElement); - protected _getWrappedCallback(callback: Function): Core.BroadcasterCallback; /** * Registers a callback to be called whenever the mouse position changes, - * or removes the callback if `null` is passed as the callback. * - * @param {any} key The key associated with the callback. - * Key uniqueness is determined by deep equality. * @param {(p: Point) => any} callback A callback that takes the pixel position * in svg-coordinate-space. Pass `null` * to remove a callback. * @return {Dispatcher.Mouse} The calling Dispatcher.Mouse. */ - onMouseMove(key: any, callback: MouseCallback): Dispatcher.Mouse; + onMouseMove(callback: MouseCallback): Dispatchers.Mouse; + /** + * Registers the callback to be called whenever the mouse position changes, + * + * @param {(p: Point) => any} callback A callback that takes the pixel position + * in svg-coordinate-space. Pass `null` + * to remove a callback. + * @return {Dispatcher.Mouse} The calling Dispatcher.Mouse. + */ + offMouseMove(callback: MouseCallback): Dispatchers.Mouse; + /** + * Registers a callback to be called whenever a mousedown occurs. + * + * @param {(p: Point) => any} callback A callback that takes the pixel position + * in svg-coordinate-space. Pass `null` + * to remove a callback. + * @return {Dispatcher.Mouse} The calling Dispatcher.Mouse. + */ + onMouseDown(callback: MouseCallback): Dispatchers.Mouse; + /** + * Registers the callback to be called whenever a mousedown occurs. + * + * @param {(p: Point) => any} callback A callback that takes the pixel position + * in svg-coordinate-space. Pass `null` + * to remove a callback. + * @return {Dispatcher.Mouse} The calling Dispatcher.Mouse. + */ + offMouseDown(callback: MouseCallback): Dispatchers.Mouse; /** - * Registers a callback to be called whenever a mousedown occurs, - * or removes the callback if `null` is passed as the callback. + * Registers a callback to be called whenever a mouseup occurs. * - * @param {any} key The key associated with the callback. - * Key uniqueness is determined by deep equality. * @param {(p: Point) => any} callback A callback that takes the pixel position * in svg-coordinate-space. Pass `null` * to remove a callback. * @return {Dispatcher.Mouse} The calling Dispatcher.Mouse. */ - onMouseDown(key: any, callback: MouseCallback): Dispatcher.Mouse; + onMouseUp(callback: MouseCallback): Dispatchers.Mouse; /** - * Registers a callback to be called whenever a mouseup occurs, - * or removes the callback if `null` is passed as the callback. + * Registers the callback to be called whenever a mouseup occurs. * - * @param {any} key The key associated with the callback. - * Key uniqueness is determined by deep equality. * @param {(p: Point) => any} callback A callback that takes the pixel position * in svg-coordinate-space. Pass `null` * to remove a callback. * @return {Dispatcher.Mouse} The calling Dispatcher.Mouse. */ - onMouseUp(key: any, callback: MouseCallback): Dispatcher.Mouse; + offMouseUp(callback: MouseCallback): Dispatchers.Mouse; /** - * Registers a callback to be called whenever a wheel occurs, - * or removes the callback if `null` is passed as the callback. + * Registers a callback to be called whenever a wheel occurs. * - * @param {any} key The key associated with the callback. - * Key uniqueness is determined by deep equality. * @param {MouseCallback} callback A callback that takes the pixel position * in svg-coordinate-space. * Pass `null` to remove a callback. * @return {Dispatcher.Mouse} The calling Dispatcher.Mouse. */ - onWheel(key: any, callback: MouseCallback): Dispatcher.Mouse; + onWheel(callback: MouseCallback): Dispatchers.Mouse; /** - * Registers a callback to be called whenever a dblClick occurs, - * or removes the callback if `null` is passed as the callback. + * Registers the callback to be called whenever a wheel occurs. * - * @param {any} key The key associated with the callback. - * Key uniqueness is determined by deep equality. * @param {MouseCallback} callback A callback that takes the pixel position * in svg-coordinate-space. * Pass `null` to remove a callback. * @return {Dispatcher.Mouse} The calling Dispatcher.Mouse. */ - onDblClick(key: any, callback: MouseCallback): Dispatcher.Mouse; + offWheel(callback: MouseCallback): Dispatchers.Mouse; + /** + * Registers a callback to be called whenever a dblClick occurs. + * + * @param {MouseCallback} callback A callback that takes the pixel position + * in svg-coordinate-space. + * Pass `null` to remove a callback. + * @return {Dispatcher.Mouse} The calling Dispatcher.Mouse. + */ + onDblClick(callback: MouseCallback): Dispatchers.Mouse; + /** + * Registers the callback to be called whenever a dblClick occurs. + * + * @param {MouseCallback} callback A callback that takes the pixel position + * in svg-coordinate-space. + * Pass `null` to remove a callback. + * @return {Dispatcher.Mouse} The calling Dispatcher.Mouse. + */ + offDblClick(callback: MouseCallback): Dispatchers.Mouse; /** * Returns the last computed mouse position. * @@ -3699,11 +3410,11 @@ declare module Plottable { declare module Plottable { - module Dispatcher { + module Dispatchers { type TouchCallback = (ids: number[], idToPoint: { [id: number]: Point; - }, e: TouchEvent) => any; - class Touch extends AbstractDispatcher { + }, event: TouchEvent) => any; + class Touch extends Dispatcher { /** * Get a Dispatcher.Touch for the containing elem. If one already exists * on that , it will be returned; otherwise, a new one will be created. @@ -3711,7 +3422,7 @@ declare module Plottable { * @param {SVGElement} elem A svg DOM element. * @return {Dispatcher.Touch} A Dispatcher.Touch */ - static getDispatcher(elem: SVGElement): Dispatcher.Touch; + static getDispatcher(elem: SVGElement): Dispatchers.Touch; /** * Creates a Dispatcher.Touch. * This constructor should not be invoked directly under most circumstances. @@ -3719,59 +3430,94 @@ declare module Plottable { * @param {SVGElement} svg The root element to attach to. */ constructor(svg: SVGElement); - protected _getWrappedCallback(callback: Function): Core.BroadcasterCallback; /** - * Registers a callback to be called whenever a touch starts, - * or removes the callback if `null` is passed as the callback. + * Registers a callback to be called whenever a touch starts. + * + * @param {TouchCallback} callback A callback that takes the pixel position + * in svg-coordinate-space. Pass `null` + * to remove a callback. + * @return {Dispatcher.Touch} The calling Dispatcher.Touch. + */ + onTouchStart(callback: TouchCallback): Dispatchers.Touch; + /** + * Removes the callback to be called whenever a touch starts. + * + * @param {TouchCallback} callback A callback that takes the pixel position + * in svg-coordinate-space. Pass `null` + * to remove a callback. + * @return {Dispatcher.Touch} The calling Dispatcher.Touch. + */ + offTouchStart(callback: TouchCallback): Dispatchers.Touch; + /** + * Registers a callback to be called whenever the touch position changes. + * + * @param {TouchCallback} callback A callback that takes the pixel position + * in svg-coordinate-space. Pass `null` + * to remove a callback. + * @return {Dispatcher.Touch} The calling Dispatcher.Touch. + */ + onTouchMove(callback: TouchCallback): Dispatchers.Touch; + /** + * Removes the callback to be called whenever the touch position changes. + * + * @param {TouchCallback} callback A callback that takes the pixel position + * in svg-coordinate-space. Pass `null` + * to remove a callback. + * @return {Dispatcher.Touch} The calling Dispatcher.Touch. + */ + offTouchMove(callback: TouchCallback): Dispatchers.Touch; + /** + * Registers a callback to be called whenever a touch ends. + * + * @param {TouchCallback} callback A callback that takes the pixel position + * in svg-coordinate-space. Pass `null` + * to remove a callback. + * @return {Dispatcher.Touch} The calling Dispatcher.Touch. + */ + onTouchEnd(callback: TouchCallback): Dispatchers.Touch; + /** + * Removes the callback to be called whenever a touch ends. * - * @param {any} key The key associated with the callback. - * Key uniqueness is determined by deep equality. * @param {TouchCallback} callback A callback that takes the pixel position * in svg-coordinate-space. Pass `null` * to remove a callback. * @return {Dispatcher.Touch} The calling Dispatcher.Touch. */ - onTouchStart(key: any, callback: TouchCallback): Dispatcher.Touch; + offTouchEnd(callback: TouchCallback): Dispatchers.Touch; /** - * Registers a callback to be called whenever the touch position changes, - * or removes the callback if `null` is passed as the callback. + * Registers a callback to be called whenever a touch is cancelled. * - * @param {any} key The key associated with the callback. - * Key uniqueness is determined by deep equality. * @param {TouchCallback} callback A callback that takes the pixel position * in svg-coordinate-space. Pass `null` * to remove a callback. * @return {Dispatcher.Touch} The calling Dispatcher.Touch. */ - onTouchMove(key: any, callback: TouchCallback): Dispatcher.Touch; + onTouchCancel(callback: TouchCallback): Dispatchers.Touch; /** - * Registers a callback to be called whenever a touch ends, - * or removes the callback if `null` is passed as the callback. + * Removes the callback to be called whenever a touch is cancelled. * - * @param {any} key The key associated with the callback. - * Key uniqueness is determined by deep equality. * @param {TouchCallback} callback A callback that takes the pixel position * in svg-coordinate-space. Pass `null` * to remove a callback. * @return {Dispatcher.Touch} The calling Dispatcher.Touch. */ - onTouchEnd(key: any, callback: TouchCallback): Dispatcher.Touch; + offTouchCancel(callback: TouchCallback): Dispatchers.Touch; } } } declare module Plottable { - module Dispatcher { - type KeyCallback = (keyCode: number, e: KeyboardEvent) => any; - class Key extends AbstractDispatcher { + module Dispatchers { + type KeyCallback = (keyCode: number, event: KeyboardEvent) => any; + class Key extends Dispatcher { /** * Get a Dispatcher.Key. If one already exists it will be returned; * otherwise, a new one will be created. * * @return {Dispatcher.Key} A Dispatcher.Key */ - static getDispatcher(): Dispatcher.Key; + static getDispatcher(): Dispatchers.Key; /** * Creates a Dispatcher.Key. * This constructor not be invoked directly under most circumstances. @@ -3779,170 +3525,206 @@ declare module Plottable { * @param {SVGElement} svg The root element to attach to. */ constructor(); - protected _getWrappedCallback(callback: Function): Core.BroadcasterCallback; /** - * Registers a callback to be called whenever a key is pressed, - * or removes the callback if `null` is passed as the callback. + * Registers a callback to be called whenever a key is pressed. + * + * @param {KeyCallback} callback + * @return {Dispatcher.Key} The calling Dispatcher.Key. + */ + onKeyDown(callback: KeyCallback): Key; + /** + * Removes the callback to be called whenever a key is pressed. * - * @param {any} key The registration key associated with the callback. - * Registration key uniqueness is determined by deep equality. * @param {KeyCallback} callback * @return {Dispatcher.Key} The calling Dispatcher.Key. */ - onKeyDown(key: any, callback: KeyCallback): Key; + offKeyDown(callback: KeyCallback): Key; } } } declare module Plottable { - module Interaction { - class AbstractInteraction extends Core.PlottableObject { - /** - * It maintains a 'hitBox' which is where all event listeners are - * attached. Due to cross- browser weirdness, the hitbox needs to be an - * opaque but invisible rectangle. TODO: We should give the interaction - * "foreground" and "background" elements where it can draw things, - * e.g. crosshairs. - */ - protected _hitBox: D3.Selection; - protected _componentToListenTo: Component.AbstractComponent; - _anchor(component: Component.AbstractComponent, hitBox: D3.Selection): void; - _requiresHitbox(): boolean; - /** - * Translates an -coordinate-space point to Component-space coordinates. - * - * @param {Point} p A Point in -space coordinates. - * - * @return {Point} The same location in Component-space coordinates. - */ - protected _translateToComponentSpace(p: Point): Point; - /** - * Checks whether a Component-coordinate-space Point is inside the Component. - * - * @param {Point} p A Point in Coordinate-space coordinates. - * - * @return {boolean} Whether or not the point is inside the Component. - */ - protected _isInsideComponent(p: Point): boolean; - } + class Interaction { + protected _componentAttachedTo: Component; + protected _anchor(component: Component): void; + protected _unanchor(): void; + /** + * Attaches this interaction to a Component. + * If the interaction was already attached to a Component, it first detaches itself from the old Component. + * + * @param {Component} component The component to which to attach the interaction. + * + * @return {Interaction} + */ + attachTo(component: Component): Interaction; + /** + * Detaches this interaction from the Component. + * This interaction can be reused. + * + * @param {Component} component The component from which to detach the interaction. + * + * @return {Interaction} + */ + detachFrom(component: Component): Interaction; + /** + * Translates an -coordinate-space point to Component-space coordinates. + * + * @param {Point} p A Point in -space coordinates. + * + * @return {Point} The same location in Component-space coordinates. + */ + protected _translateToComponentSpace(p: Point): Point; + /** + * Checks whether a Component-coordinate-space Point is inside the Component. + * + * @param {Point} p A Point in Coordinate-space coordinates. + * + * @return {boolean} Whether or not the point is inside the Component. + */ + protected _isInsideComponent(p: Point): boolean; } } declare module Plottable { - module Interaction { - class Click extends AbstractInteraction { - _anchor(component: Component.AbstractComponent, hitBox: D3.Selection): void; + type ClickCallback = (point: Point) => any; + module Interactions { + class Click extends Interaction { + protected _anchor(component: Component): void; + protected _unanchor(): void; /** - * Gets the callback called when the Component is clicked. + * Sets the callback called when the Component is clicked. * - * @return {(p: Point) => any} The current callback. + * @param {ClickCallback} callback The callback to set. + * @return {Interaction.Click} The calling Interaction.Click. */ - onClick(): (p: Point) => any; + onClick(callback: ClickCallback): Click; /** - * Sets the callback called when the Component is clicked. + * Removes the callback from click. * - * @param {(p: Point) => any} callback The callback to set. + * @param {ClickCallback} callback The callback to remove. * @return {Interaction.Click} The calling Interaction.Click. */ - onClick(callback: (p: Point) => any): Interaction.Click; + offClick(callback: ClickCallback): Click; } } } declare module Plottable { - module Interaction { - class DoubleClick extends AbstractInteraction { - _anchor(component: Component.AbstractComponent, hitBox: D3.Selection): void; + module Interactions { + class DoubleClick extends Interaction { + protected _anchor(component: Component): void; + protected _unanchor(): void; /** - * Gets the callback called when the Component is double-clicked. + * Sets the callback called when the Component is double-clicked. * - * @return {(p: Point) => any} The current callback. + * @param {ClickCallback} callback The callback to set. + * @return {Interaction.DoubleClick} The calling Interaction.DoubleClick. */ - onDoubleClick(): (p: Point) => any; + onDoubleClick(callback: ClickCallback): DoubleClick; /** - * Sets the callback called when the Component is double-clicked. + * Removes the callback called when the Component is double-clicked. * - * @param {(p: Point) => any} callback The callback to set. + * @param {ClickCallback} callback The callback to remove. * @return {Interaction.DoubleClick} The calling Interaction.DoubleClick. */ - onDoubleClick(callback: (p: Point) => any): Interaction.DoubleClick; + offDoubleClick(callback: ClickCallback): DoubleClick; } } } declare module Plottable { - module Interaction { - class Key extends AbstractInteraction { - _anchor(component: Component.AbstractComponent, hitBox: D3.Selection): void; + type KeyCallback = (keyCode: number) => void; + module Interactions { + class Key extends Interaction { + protected _anchor(component: Component): void; + protected _unanchor(): void; /** * Sets a callback to be called when the key with the given keyCode is * pressed and the user is moused over the Component. * * @param {number} keyCode The key code associated with the key. - * @param {() => void} callback Callback to be called. + * @param {KeyCallback} callback Callback to be set. + * @returns The calling Interaction.Key. + */ + onKey(keyCode: number, callback: KeyCallback): Key; + /** + * Removes the callback to be called when the key with the given keyCode is + * pressed and the user is moused over the Component. + * + * @param {number} keyCode The key code associated with the key. + * @param {KeyCallback} callback Callback to be removed. * @returns The calling Interaction.Key. */ - on(keyCode: number, callback: () => void): Key; + offKey(keyCode: number, callback: KeyCallback): Key; } } } declare module Plottable { - module Interaction { - class Pointer extends Interaction.AbstractInteraction { - _anchor(component: Component.AbstractComponent, hitBox: D3.Selection): void; - /** - * Gets the callback called when the pointer enters the Component. - * - * @return {(p: Point) => any} The current callback. - */ - onPointerEnter(): (p: Point) => any; + type PointerCallback = (point: Point) => any; + module Interactions { + class Pointer extends Interaction { + protected _anchor(component: Component): void; + protected _unanchor(): void; /** * Sets the callback called when the pointer enters the Component. * - * @param {(p: Point) => any} callback The callback to set. + * @param {PointerCallback} callback The callback to set. * @return {Interaction.Pointer} The calling Interaction.Pointer. */ - onPointerEnter(callback: (p: Point) => any): Interaction.Pointer; + onPointerEnter(callback: PointerCallback): Pointer; /** - * Gets the callback called when the pointer moves. + * Removes a callback called when the pointer enters the Component. * - * @return {(p: Point) => any} The current callback. + * @param {PointerCallback} callback The callback to remove. + * @return {Interaction.Pointer} The calling Interaction.Pointer. */ - onPointerMove(): (p: Point) => any; + offPointerEnter(callback: PointerCallback): Pointer; /** * Sets the callback called when the pointer moves. * - * @param {(p: Point) => any} callback The callback to set. + * @param {PointerCallback} callback The callback to set. * @return {Interaction.Pointer} The calling Interaction.Pointer. */ - onPointerMove(callback: (p: Point) => any): Interaction.Pointer; + onPointerMove(callback: PointerCallback): Pointer; /** - * Gets the callback called when the pointer exits the Component. + * Removes a callback called when the pointer moves. * - * @return {(p: Point) => any} The current callback. + * @param {PointerCallback} callback The callback to remove. + * @return {Interaction.Pointer} The calling Interaction.Pointer. */ - onPointerExit(): (p: Point) => any; + offPointerMove(callback: PointerCallback): Pointer; /** * Sets the callback called when the pointer exits the Component. * - * @param {(p: Point) => any} callback The callback to set. + * @param {PointerCallback} callback The callback to set. + * @return {Interaction.Pointer} The calling Interaction.Pointer. + */ + onPointerExit(callback: PointerCallback): Pointer; + /** + * Removes a callback called when the pointer exits the Component. + * + * @param {PointerCallback} callback The callback to remove. * @return {Interaction.Pointer} The calling Interaction.Pointer. */ - onPointerExit(callback: (p: Point) => any): Interaction.Pointer; + offPointerExit(callback: PointerCallback): Pointer; } } } declare module Plottable { - module Interaction { - class PanZoom extends AbstractInteraction { + module Interactions { + class PanZoom extends Interaction { + /** + * The number of pixels occupied in a line. + */ + static PIXELS_PER_LINE: number; /** * Creates a PanZoomInteraction. * @@ -3953,35 +3735,33 @@ declare module Plottable { * @param {QuantitativeScale} [xScale] The X scale to update on panning/zooming. * @param {QuantitativeScale} [yScale] The Y scale to update on panning/zooming. */ - constructor(xScale?: Scale.AbstractQuantitative, yScale?: Scale.AbstractQuantitative); - /** - * Sets the scales back to their original domains. - */ - resetZoom(): void; - _anchor(component: Component.AbstractComponent, hitBox: D3.Selection): void; - _requiresHitbox(): boolean; + constructor(xScale?: QuantitativeScale, yScale?: QuantitativeScale); + protected _anchor(component: Component): void; + protected _unanchor(): void; } } } declare module Plottable { - module Interaction { - class Drag extends AbstractInteraction { - _anchor(component: Component.AbstractComponent, hitBox: D3.Selection): void; + type DragCallback = (start: Point, end: Point) => any; + module Interactions { + class Drag extends Interaction { + protected _anchor(component: Component): void; + protected _unanchor(): void; /** - * Returns whether or not this Interaction constrains Points passed to its + * Returns whether or not this Interactions constrains Points passed to its * callbacks to lie inside its Component. * * If true, when the user drags outside of the Component, the closest Point * inside the Component will be passed to the callback instead of the actual * cursor position. * - * @return {boolean} Whether or not the Interaction.Drag constrains. + * @return {boolean} Whether or not the Interactions.Drag constrains. */ constrainToComponent(): boolean; /** - * Sets whether or not this Interaction constrains Points passed to its + * Sets whether or not this Interactions constrains Points passed to its * callbacks to lie inside its Component. * * If true, when the user drags outside of the Component, the closest Point @@ -3989,121 +3769,64 @@ declare module Plottable { * cursor position. * * @param {boolean} constrain Whether or not to constrain Points. - * @return {Interaction.Drag} The calling Interaction.Drag. + * @return {Interactions.Drag} The calling Interactions.Drag. */ constrainToComponent(constrain: boolean): Drag; - /** - * Gets the callback that is called when dragging starts. - * - * @returns {(start: Point) => any} The callback called when dragging starts. - */ - onDragStart(): (start: Point) => any; /** * Sets the callback to be called when dragging starts. * - * @param {(start: Point) => any} cb The callback to be called. Takes in a Point in pixels. - * @returns {Drag} The calling Interaction.Drag. + * @param {DragCallback} callback The callback to be called. Takes in a Point in pixels. + * @returns {Drag} The calling Interactions.Drag. */ - onDragStart(cb: (start: Point) => any): Drag; + onDragStart(callback: DragCallback): Drag; /** - * Gets the callback that is called during dragging. + * Removes the callback to be called when dragging starts. * - * @returns {(start: Point, end: Point) => any} The callback called during dragging. + * @param {DragCallback} callback The callback to be removed. + * @returns {Drag} The calling Interactions.Drag. */ - onDrag(): (start: Point, end: Point) => any; + offDragStart(callback: DragCallback): Drag; /** * Adds a callback to be called during dragging. * - * @param {(start: Point, end: Point) => any} cb The callback to be called. Takes in Points in pixels. - * @returns {Drag} The calling Interaction.Drag. + * @param {DragCallback} callback The callback to be called. Takes in Points in pixels. + * @returns {Drag} The calling Interactions.Drag. */ - onDrag(cb: (start: Point, end: Point) => any): Drag; + onDrag(callback: DragCallback): Drag; /** - * Gets the callback that is called when dragging ends. + * Removes a callback to be called during dragging. * - * @returns {(start: Point, end: Point) => any} The callback called when dragging ends. + * @param {DragCallback} callback The callback to be removed. + * @returns {Drag} The calling Interactions.Drag. */ - onDragEnd(): (start: Point, end: Point) => any; + offDrag(callback: DragCallback): Drag; /** * Adds a callback to be called when the dragging ends. * - * @param {(start: Point, end: Point) => any} cb The callback to be called. Takes in Points in pixels. - * @returns {Drag} The calling Interaction.Drag. - */ - onDragEnd(cb: (start: Point, end: Point) => any): Drag; - } - } -} - - -declare module Plottable { - module Interaction { - type HoverData = { - data: any[]; - pixelPositions: Point[]; - selection: D3.Selection; - }; - interface Hoverable extends Component.AbstractComponent { - /** - * Called when the user first mouses over the Component. - * - * @param {Point} The cursor's position relative to the Component's origin. - */ - _hoverOverComponent(p: Point): void; - /** - * Called when the user mouses out of the Component. - * - * @param {Point} The cursor's position relative to the Component's origin. - */ - _hoverOutComponent(p: Point): void; - /** - * Returns the HoverData associated with the given position, and performs - * any visual changes associated with hovering inside a Component. - * - * @param {Point} The cursor's position relative to the Component's origin. - * @return {HoverData} The HoverData associated with the given position. - */ - _doHover(p: Point): HoverData; - } - class Hover extends Interaction.AbstractInteraction { - _componentToListenTo: Hoverable; - constructor(); - _anchor(component: Hoverable, hitBox: D3.Selection): void; - /** - * Attaches an callback to be called when the user mouses over an element. - * - * @param {(hoverData: HoverData) => any} callback The callback to be called. - * The callback will be passed data for newly hovered-over elements. - * @return {Interaction.Hover} The calling Interaction.Hover. + * @param {DragCallback} callback The callback to be called. Takes in Points in pixels. + * @returns {Drag} The calling Interactions.Drag. */ - onHoverOver(callback: (hoverData: HoverData) => any): Hover; + onDragEnd(callback: DragCallback): Drag; /** - * Attaches a callback to be called when the user mouses off of an element. + * Removes a callback to be called when the dragging ends. * - * @param {(hoverData: HoverData) => any} callback The callback to be called. - * The callback will be passed data from the hovered-out elements. - * @return {Interaction.Hover} The calling Interaction.Hover. + * @param {DragCallback} callback The callback to be removed + * @returns {Drag} The calling Interactions.Drag. */ - onHoverOut(callback: (hoverData: HoverData) => any): Hover; - /** - * Retrieves the HoverData associated with the elements the user is currently hovering over. - * - * @return {HoverData} The data and selection corresponding to the elements - * the user is currently hovering over. - */ - getCurrentHoverData(): HoverData; + offDragEnd(callback: DragCallback): Drag; } } } declare module Plottable { - module Component { - class DragBoxLayer extends Component.SelectionBoxLayer { + type DragBoxCallback = (bounds: Bounds) => any; + module Components { + class DragBoxLayer extends Components.SelectionBoxLayer { protected _hasCorners: boolean; constructor(); protected _setup(): void; - _doRender(): void; + renderImmediately(): DragBoxLayer; /** * Gets the detection radius of the drag box. * @@ -4131,55 +3854,58 @@ declare module Plottable { */ resizable(canResize: boolean): DragBoxLayer; protected _setResizableClasses(canResize: boolean): void; - /** - * Gets the callback that is called when dragging starts. - * - * @returns {(b: Bounds) => any} The callback called when dragging starts. - */ - onDragStart(): (b: Bounds) => any; /** * Sets the callback to be called when dragging starts. * - * @param {(b: Bounds) => any} cb The callback to be called. Passed the current Bounds in pixels. + * @param {DragBoxCallback} callback The callback to be called. Passed the current Bounds in pixels. * @returns {DragBoxLayer} The calling DragBoxLayer. */ - onDragStart(cb: (b: Bounds) => any): DragBoxLayer; + onDragStart(callback: DragBoxCallback): DragBoxLayer; /** - * Gets the callback that is called during dragging. + * Removes a callback to be called when dragging starts. * - * @returns {(b: Bounds) => any} The callback called during dragging. + * @param {DragBoxCallback} callback The callback to be removed. + * @returns {DragBoxLayer} The calling DragBoxLayer. */ - onDrag(): (b: Bounds) => any; + offDragStart(callback: DragBoxCallback): DragBoxLayer; /** * Sets a callback to be called during dragging. * - * @param {(b: Bounds) => any} cb The callback to be called. Passed the current Bounds in pixels. + * @param {DragBoxCallback} callback The callback to be called. Passed the current Bounds in pixels. * @returns {DragBoxLayer} The calling DragBoxLayer. */ - onDrag(cb: (b: Bounds) => any): DragBoxLayer; + onDrag(callback: DragBoxCallback): DragBoxLayer; /** - * Gets the callback that is called when dragging ends. + * Removes a callback to be called during dragging. * - * @returns {(b: Bounds) => any} The callback called when dragging ends. + * @param {DragBoxCallback} callback The callback to be removed. + * @returns {DragBoxLayer} The calling DragBoxLayer. */ - onDragEnd(): (b: Bounds) => any; + offDrag(callback: DragBoxCallback): DragBoxLayer; /** * Sets a callback to be called when the dragging ends. * - * @param {(b: Bounds) => any} cb The callback to be called. Passed the current Bounds in pixels. + * @param {DragBoxCallback} callback The callback to be called. Passed the current Bounds in pixels. + * @returns {DragBoxLayer} The calling DragBoxLayer. + */ + onDragEnd(callback: DragBoxCallback): DragBoxLayer; + /** + * Removes a callback to be called when the dragging ends. + * + * @param {DragBoxCallback} callback The callback to be removed. * @returns {DragBoxLayer} The calling DragBoxLayer. */ - onDragEnd(cb: (b: Bounds) => any): DragBoxLayer; + offDragEnd(callback: DragBoxCallback): DragBoxLayer; } } } declare module Plottable { - module Component { + module Components { class XDragBoxLayer extends DragBoxLayer { constructor(); - _computeLayout(offeredXOrigin?: number, offeredYOrigin?: number, availableWidth?: number, availableHeight?: number): void; + computeLayout(origin?: Point, availableWidth?: number, availableHeight?: number): XDragBoxLayer; protected _setBounds(newBounds: Bounds): void; protected _setResizableClasses(canResize: boolean): void; } @@ -4188,10 +3914,10 @@ declare module Plottable { declare module Plottable { - module Component { + module Components { class YDragBoxLayer extends DragBoxLayer { constructor(); - _computeLayout(offeredXOrigin?: number, offeredYOrigin?: number, availableWidth?: number, availableHeight?: number): void; + computeLayout(origin?: Point, availableWidth?: number, availableHeight?: number): YDragBoxLayer; protected _setBounds(newBounds: Bounds): void; protected _setResizableClasses(canResize: boolean): void; } diff --git a/plottable.js b/plottable.js index ce11129efa..3f2ed002eb 100644 --- a/plottable.js +++ b/plottable.js @@ -1,5 +1,5 @@ /*! -Plottable 0.54.0 (https://github.com/palantir/plottable) +Plottable 1.0.0-rc1 (https://github.com/palantir/plottable) Copyright 2014 Palantir Technologies Licensed under MIT (https://github.com/palantir/plottable/blob/master/LICENSE) */ @@ -7,8 +7,8 @@ Licensed under MIT (https://github.com/palantir/plottable/blob/master/LICENSE) /// var Plottable; (function (Plottable) { - var _Util; - (function (_Util) { + var Utils; + (function (Utils) { var Methods; (function (Methods) { /** @@ -40,7 +40,7 @@ var Plottable; * @param {string} The warnings to print */ function warn(warning) { - if (!Plottable.Config.SHOW_WARNINGS) { + if (!Plottable.Configs.SHOW_WARNINGS) { return; } /* tslint:disable:no-console */ @@ -88,23 +88,6 @@ var Plottable; return set; } Methods.intersection = intersection; - /** - * Take an accessor object (may be a string to be made into a key, or a value, or a color code) - * and "activate" it by turning it into a function in (datum, index, metadata) - */ - function accessorize(accessor) { - if (typeof (accessor) === "function") { - return accessor; - } - else if (typeof (accessor) === "string" && accessor[0] !== "#") { - return function (d, i, s) { return d[accessor]; }; - } - else { - return function (d, i, s) { return accessor; }; - } - ; - } - Methods.accessorize = accessorize; /** * Takes two sets and returns the union * @@ -216,34 +199,22 @@ var Plottable; return arrayEq(keysA, keysB) && arrayEq(valuesA, valuesB); } Methods.objEq = objEq; - function max(arr, one, two) { - if (arr.length === 0) { - if (typeof (one) !== "function") { - return one; - } - else { - return two; - } - } + function max(array, firstArg, secondArg) { + var accessor = typeof (firstArg) === "function" ? firstArg : null; + var defaultValue = accessor == null ? firstArg : secondArg; /* tslint:disable:ban */ - var acc = typeof (one) === "function" ? one : typeof (two) === "function" ? two : undefined; - return acc === undefined ? d3.max(arr) : d3.max(arr, acc); + var maxValue = accessor == null ? d3.max(array) : d3.max(array, accessor); /* tslint:enable:ban */ + return maxValue !== undefined ? maxValue : defaultValue; } Methods.max = max; - function min(arr, one, two) { - if (arr.length === 0) { - if (typeof (one) !== "function") { - return one; - } - else { - return two; - } - } + function min(array, firstArg, secondArg) { + var accessor = typeof (firstArg) === "function" ? firstArg : null; + var defaultValue = accessor == null ? firstArg : secondArg; /* tslint:disable:ban */ - var acc = typeof (one) === "function" ? one : typeof (two) === "function" ? two : undefined; - return acc === undefined ? d3.min(arr) : d3.min(arr, acc); + var minValue = accessor == null ? d3.min(array) : d3.min(array, accessor); /* tslint:enable:ban */ + return minValue !== undefined ? minValue : defaultValue; } Methods.min = min; /** @@ -258,7 +229,7 @@ var Plottable; * Numbers represented as strings do not pass this function */ function isValidNumber(n) { - return typeof n === "number" && !Plottable._Util.Methods.isNaN(n) && isFinite(n); + return typeof n === "number" && !Plottable.Utils.Methods.isNaN(n) && isFinite(n); } Methods.isValidNumber = isValidNumber; /** @@ -383,65 +354,34 @@ var Plottable; } } Methods.parseExtent = parseExtent; - })(Methods = _Util.Methods || (_Util.Methods = {})); - })(_Util = Plottable._Util || (Plottable._Util = {})); -})(Plottable || (Plottable = {})); - -/// -// This file contains open source utilities, along with their copyright notices -var Plottable; -(function (Plottable) { - var _Util; - (function (_Util) { - var OpenSource; - (function (OpenSource) { - function sortedIndex(val, arr, accessor) { - var low = 0; - var high = arr.length; - while (low < high) { - /* tslint:disable:no-bitwise */ - var mid = (low + high) >>> 1; - /* tslint:enable:no-bitwise */ - var x = accessor == null ? arr[mid] : accessor(arr[mid]); - if (x < val) { - low = mid + 1; - } - else { - high = mid; - } - } - return low; - } - OpenSource.sortedIndex = sortedIndex; - ; - })(OpenSource = _Util.OpenSource || (_Util.OpenSource = {})); - })(_Util = Plottable._Util || (Plottable._Util = {})); + })(Methods = Utils.Methods || (Utils.Methods = {})); + })(Utils = Plottable.Utils || (Plottable.Utils = {})); })(Plottable || (Plottable = {})); /// var Plottable; (function (Plottable) { - var _Util; - (function (_Util) { + var Utils; + (function (Utils) { /** * An associative array that can be keyed by anything (inc objects). * Uses pointer equality checks which is why this works. * This power has a price: everything is linear time since it is actually backed by an array... */ - var StrictEqualityAssociativeArray = (function () { - function StrictEqualityAssociativeArray() { + var Map = (function () { + function Map() { this._keyValuePairs = []; } /** * Set a new key/value pair in the store. * - * @param {any} key Key to set in the store - * @param {any} value Value to set in the store + * @param {K} key Key to set in the store + * @param {V} value Value to set in the store * @return {boolean} True if key already in store, false otherwise */ - StrictEqualityAssociativeArray.prototype.set = function (key, value) { + Map.prototype.set = function (key, value) { if (key !== key) { - throw new Error("NaN may not be used as a key to the StrictEqualityAssociativeArray"); + throw new Error("NaN may not be used as a key to the Map"); } for (var i = 0; i < this._keyValuePairs.length; i++) { if (this._keyValuePairs[i][0] === key) { @@ -455,10 +395,10 @@ var Plottable; /** * Get a value from the store, given a key. * - * @param {any} key Key associated with value to retrieve - * @return {any} Value if found, undefined otherwise + * @param {K} key Key associated with value to retrieve + * @return {V} Value if found, undefined otherwise */ - StrictEqualityAssociativeArray.prototype.get = function (key) { + Map.prototype.get = function (key) { for (var i = 0; i < this._keyValuePairs.length; i++) { if (this._keyValuePairs[i][0] === key) { return this._keyValuePairs[i][1]; @@ -472,10 +412,10 @@ var Plottable; * Will return true if there is a key/value entry, * even if the value is explicitly `undefined`. * - * @param {any} key Key to test for presence of an entry + * @param {K} key Key to test for presence of an entry * @return {boolean} Whether there was a matching entry for that key */ - StrictEqualityAssociativeArray.prototype.has = function (key) { + Map.prototype.has = function (key) { for (var i = 0; i < this._keyValuePairs.length; i++) { if (this._keyValuePairs[i][0] === key) { return true; @@ -486,26 +426,26 @@ var Plottable; /** * Return an array of the values in the key-value store * - * @return {any[]} The values in the store + * @return {V[]} The values in the store */ - StrictEqualityAssociativeArray.prototype.values = function () { + Map.prototype.values = function () { return this._keyValuePairs.map(function (x) { return x[1]; }); }; /** * Return an array of keys in the key-value store * - * @return {any[]} The keys in the store + * @return {K[]} The keys in the store */ - StrictEqualityAssociativeArray.prototype.keys = function () { + Map.prototype.keys = function () { return this._keyValuePairs.map(function (x) { return x[0]; }); }; /** * Execute a callback for each entry in the array. * - * @param {(key: any, val?: any, index?: number) => any} callback The callback to eecute + * @param {(key: K, val?: V, index?: number) => any} callback The callback to execute * @return {any[]} The results of mapping the callback over the entries */ - StrictEqualityAssociativeArray.prototype.map = function (cb) { + Map.prototype.map = function (cb) { return this._keyValuePairs.map(function (kv, index) { return cb(kv[0], kv[1], index); }); @@ -513,10 +453,10 @@ var Plottable; /** * Delete a key from the key-value store. Return whether the key was present. * - * @param {any} The key to remove + * @param {K} The key to remove * @return {boolean} Whether a matching entry was found and removed */ - StrictEqualityAssociativeArray.prototype.delete = function (key) { + Map.prototype.delete = function (key) { for (var i = 0; i < this._keyValuePairs.length; i++) { if (this._keyValuePairs[i][0] === key) { this._keyValuePairs.splice(i, 1); @@ -525,16 +465,55 @@ var Plottable; } return false; }; - return StrictEqualityAssociativeArray; + return Map; + })(); + Utils.Map = Map; + })(Utils = Plottable.Utils || (Plottable.Utils = {})); +})(Plottable || (Plottable = {})); + +/// +var Plottable; +(function (Plottable) { + var Utils; + (function (Utils) { + /** + * Shim for ES6 set. + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set + */ + var Set = (function () { + function Set() { + this._values = []; + } + Set.prototype.add = function (value) { + if (!this.has(value)) { + this._values.push(value); + } + return this; + }; + Set.prototype.delete = function (value) { + var index = this._values.indexOf(value); + if (index !== -1) { + this._values.splice(index, 1); + return true; + } + return false; + }; + Set.prototype.has = function (value) { + return this._values.indexOf(value) !== -1; + }; + Set.prototype.values = function () { + return this._values; + }; + return Set; })(); - _Util.StrictEqualityAssociativeArray = StrictEqualityAssociativeArray; - })(_Util = Plottable._Util || (Plottable._Util = {})); + Utils.Set = Set; + })(Utils = Plottable.Utils || (Plottable.Utils = {})); })(Plottable || (Plottable = {})); var Plottable; (function (Plottable) { - var _Util; - (function (_Util) { + var Utils; + (function (Utils) { var DOM; (function (DOM) { /** @@ -660,17 +639,22 @@ var Plottable; return null; // not in the DOM } DOM.getBoundingSVG = getBoundingSVG; - })(DOM = _Util.DOM || (_Util.DOM = {})); - })(_Util = Plottable._Util || (Plottable._Util = {})); + var _latestClipPathId = 0; + function getUniqueClipPathId() { + return "plottableClipPath" + ++_latestClipPathId; + } + DOM.getUniqueClipPathId = getUniqueClipPathId; + })(DOM = Utils.DOM || (Utils.DOM = {})); + })(Utils = Plottable.Utils || (Plottable.Utils = {})); })(Plottable || (Plottable = {})); /// var Plottable; (function (Plottable) { - var _Util; - (function (_Util) { - var Color; - (function (Color) { + var Utils; + (function (Utils) { + var Colors; + (function (Colors) { /** * Return relative luminance (defined here: http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef) * Based on implementation from chroma.js by Gregor Aisch (gka) (licensed under BSD) @@ -700,9 +684,47 @@ var Plottable; var l2 = luminance(b) + 0.05; return l1 > l2 ? l1 / l2 : l2 / l1; } - Color.contrast = contrast; - })(Color = _Util.Color || (_Util.Color = {})); - })(_Util = Plottable._Util || (Plottable._Util = {})); + Colors.contrast = contrast; + })(Colors = Utils.Colors || (Utils.Colors = {})); + })(Utils = Plottable.Utils || (Plottable.Utils = {})); +})(Plottable || (Plottable = {})); + +/// +var __extends = this.__extends || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + __.prototype = b.prototype; + d.prototype = new __(); +}; +var Plottable; +(function (Plottable) { + var Utils; + (function (Utils) { + /** + * A set of callbacks which can be all invoked at once. + * Each callback exists at most once in the set (based on reference equality). + * All callbacks should have the same signature. + */ + var CallbackSet = (function (_super) { + __extends(CallbackSet, _super); + function CallbackSet() { + _super.apply(this, arguments); + } + CallbackSet.prototype.callCallbacks = function () { + var _this = this; + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i - 0] = arguments[_i]; + } + this.values().forEach(function (callback) { + callback.apply(_this, args); + }); + return this; + }; + return CallbackSet; + })(Utils.Set); + Utils.CallbackSet = CallbackSet; + })(Utils = Plottable.Utils || (Plottable.Utils = {})); })(Plottable || (Plottable = {})); /// @@ -889,6 +911,31 @@ var Plottable; return d3.time.format(specifier); } Formatters.time = time; + /** + * Transforms the Plottable TimeInterval string into a d3 time interval equivalent. + * If the provided TimeInterval is incorrect, the default is d3.time.year + */ + function timeIntervalToD3Time(timeInterval) { + switch (timeInterval) { + case Plottable.TimeInterval.second: + return d3.time.second; + case Plottable.TimeInterval.minute: + return d3.time.minute; + case Plottable.TimeInterval.hour: + return d3.time.hour; + case Plottable.TimeInterval.day: + return d3.time.day; + case Plottable.TimeInterval.week: + return d3.time.week; + case Plottable.TimeInterval.month: + return d3.time.month; + case Plottable.TimeInterval.year: + return d3.time.year; + default: + throw Error("TimeInterval specified does not exist: " + timeInterval); + } + } + Formatters.timeIntervalToD3Time = timeIntervalToD3Time; /** * Creates a formatter for relative dates. * @@ -951,8 +998,8 @@ var Plottable; /// var Plottable; (function (Plottable) { - var _Util; - (function (_Util) { + var Utils; + (function (Utils) { var ClientToSVGTranslator = (function () { function ClientToSVGTranslator(svg) { this._svg = svg; @@ -964,7 +1011,7 @@ var Plottable; this._svg.appendChild(this._measureRect); } ClientToSVGTranslator.getTranslator = function (elem) { - var svg = _Util.DOM.getBoundingSVG(elem); + var svg = Utils.DOM.getBoundingSVG(elem); var translator = svg[ClientToSVGTranslator._TRANSLATOR_KEY]; if (translator == null) { translator = new ClientToSVGTranslator(svg); @@ -1007,26 +1054,26 @@ var Plottable; ClientToSVGTranslator._TRANSLATOR_KEY = "__Plottable_ClientToSVGTranslator"; return ClientToSVGTranslator; })(); - _Util.ClientToSVGTranslator = ClientToSVGTranslator; - })(_Util = Plottable._Util || (Plottable._Util = {})); + Utils.ClientToSVGTranslator = ClientToSVGTranslator; + })(Utils = Plottable.Utils || (Plottable.Utils = {})); })(Plottable || (Plottable = {})); /// var Plottable; (function (Plottable) { - var Config; - (function (Config) { + var Configs; + (function (Configs) { /** * Specifies if Plottable should show warnings. */ - Config.SHOW_WARNINGS = true; - })(Config = Plottable.Config || (Plottable.Config = {})); + Configs.SHOW_WARNINGS = true; + })(Configs = Plottable.Configs || (Plottable.Configs = {})); })(Plottable || (Plottable = {})); /// var Plottable; (function (Plottable) { - Plottable.version = "0.54.0"; + Plottable.version = "1.0.0-rc1"; })(Plottable || (Plottable = {})); /// @@ -1071,134 +1118,7 @@ var Plottable; /// var Plottable; (function (Plottable) { - var Core; - (function (Core) { - /** - * A class most other Plottable classes inherit from, in order to have a - * unique ID. - */ - var PlottableObject = (function () { - function PlottableObject() { - this._plottableID = PlottableObject._nextID++; - } - PlottableObject.prototype.getID = function () { - return this._plottableID; - }; - PlottableObject._nextID = 0; - return PlottableObject; - })(); - Core.PlottableObject = PlottableObject; - })(Core = Plottable.Core || (Plottable.Core = {})); -})(Plottable || (Plottable = {})); - -/// -var __extends = this.__extends || function (d, b) { - for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; - function __() { this.constructor = d; } - __.prototype = b.prototype; - d.prototype = new __(); -}; -var Plottable; -(function (Plottable) { - var Core; - (function (Core) { - /** - * The Broadcaster holds a reference to a "listenable" object. - * Third parties can register and deregister listeners from the Broadcaster. - * When the broadcaster.broadcast() method is called, all registered callbacks - * are called with the Broadcaster's "listenable", along with optional - * arguments passed to the `broadcast` method. - * - * The listeners are called synchronously. - */ - var Broadcaster = (function (_super) { - __extends(Broadcaster, _super); - /** - * Constructs a broadcaster, taking a "listenable" object to broadcast about. - * - * @constructor - * @param {L} listenable The listenable object to broadcast. - */ - function Broadcaster(listenable) { - _super.call(this); - this._key2callback = new Plottable._Util.StrictEqualityAssociativeArray(); - this._listenable = listenable; - } - /** - * Registers a callback to be called when the broadcast method is called. Also takes a key which - * is used to support deregistering the same callback later, by passing in the same key. - * If there is already a callback associated with that key, then the callback will be replaced. - * The callback will be passed the Broadcaster's "listenable" as the `this` context. - * - * @param key The key associated with the callback. Key uniqueness is determined by deep equality. - * @param {BroadcasterCallback} callback A callback to be called. - * @returns {Broadcaster} The calling Broadcaster - */ - Broadcaster.prototype.registerListener = function (key, callback) { - this._key2callback.set(key, callback); - return this; - }; - /** - * Call all listening callbacks, optionally with arguments passed through. - * - * @param ...args A variable number of optional arguments - * @returns {Broadcaster} The calling Broadcaster - */ - Broadcaster.prototype.broadcast = function () { - var _this = this; - var args = []; - for (var _i = 0; _i < arguments.length; _i++) { - args[_i - 0] = arguments[_i]; - } - args.unshift(this._listenable); - this._key2callback.values().forEach(function (callback) { - callback.apply(_this._listenable, args); - }); - return this; - }; - /** - * Deregisters the callback associated with a key. - * - * @param key The key to deregister. - * @returns {Broadcaster} The calling Broadcaster - */ - Broadcaster.prototype.deregisterListener = function (key) { - this._key2callback.delete(key); - return this; - }; - /** - * Gets the keys for all listeners attached to the Broadcaster. - * - * @returns {any[]} An array of the keys. - */ - Broadcaster.prototype.getListenerKeys = function () { - return this._key2callback.keys(); - }; - /** - * Deregisters all listeners and callbacks associated with the Broadcaster. - * - * @returns {Broadcaster} The calling Broadcaster - */ - Broadcaster.prototype.deregisterAllListeners = function () { - this._key2callback = new Plottable._Util.StrictEqualityAssociativeArray(); - }; - return Broadcaster; - })(Core.PlottableObject); - Core.Broadcaster = Broadcaster; - })(Core = Plottable.Core || (Plottable.Core = {})); -})(Plottable || (Plottable = {})); - -/// -var __extends = this.__extends || function (d, b) { - for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; - function __() { this.constructor = d; } - __.prototype = b.prototype; - d.prototype = new __(); -}; -var Plottable; -(function (Plottable) { - var Dataset = (function (_super) { - __extends(Dataset, _super); + var Dataset = (function () { /** * Constructs a new set. * @@ -1212,20 +1132,25 @@ var Plottable; function Dataset(data, metadata) { if (data === void 0) { data = []; } if (metadata === void 0) { metadata = {}; } - _super.call(this); this._data = data; this._metadata = metadata; - this._accessor2cachedExtent = new Plottable._Util.StrictEqualityAssociativeArray(); - this.broadcaster = new Plottable.Core.Broadcaster(this); + this._accessor2cachedExtent = new Plottable.Utils.Map(); + this._callbacks = new Plottable.Utils.CallbackSet(); } + Dataset.prototype.onUpdate = function (callback) { + this._callbacks.add(callback); + }; + Dataset.prototype.offUpdate = function (callback) { + this._callbacks.delete(callback); + }; Dataset.prototype.data = function (data) { if (data == null) { return this._data; } else { this._data = data; - this._accessor2cachedExtent = new Plottable._Util.StrictEqualityAssociativeArray(); - this.broadcaster.broadcast(); + this._accessor2cachedExtent = new Plottable.Utils.Map(); + this._callbacks.callCallbacks(this); return this; } }; @@ -1235,225 +1160,180 @@ var Plottable; } else { this._metadata = metadata; - this._accessor2cachedExtent = new Plottable._Util.StrictEqualityAssociativeArray(); - this.broadcaster.broadcast(); + this._accessor2cachedExtent = new Plottable.Utils.Map(); + this._callbacks.callCallbacks(this); return this; } }; - Dataset.prototype._getExtent = function (accessor, typeCoercer, plotMetadata) { - if (plotMetadata === void 0) { plotMetadata = {}; } - var cachedExtent = this._accessor2cachedExtent.get(accessor); - if (cachedExtent === undefined) { - cachedExtent = this._computeExtent(accessor, typeCoercer, plotMetadata); - this._accessor2cachedExtent.set(accessor, cachedExtent); - } - return cachedExtent; - }; - Dataset.prototype._computeExtent = function (accessor, typeCoercer, plotMetadata) { - var _this = this; - var appliedAccessor = function (d, i) { return accessor(d, i, _this._metadata, plotMetadata); }; - var mappedData = this._data.map(appliedAccessor).map(typeCoercer); - if (mappedData.length === 0) { - return []; - } - else if (typeof (mappedData[0]) === "string") { - return Plottable._Util.Methods.uniq(mappedData); - } - else { - var extent = d3.extent(mappedData); - if (extent[0] == null || extent[1] == null) { - return []; - } - else { - return extent; - } - } - }; return Dataset; - })(Plottable.Core.PlottableObject); + })(); Plottable.Dataset = Dataset; })(Plottable || (Plottable = {})); /// var Plottable; (function (Plottable) { - var Core; - (function (Core) { - var RenderController; - (function (RenderController) { - var RenderPolicy; - (function (RenderPolicy) { - /** - * Never queue anything, render everything immediately. Useful for - * debugging, horrible for performance. - */ - var Immediate = (function () { - function Immediate() { - } - Immediate.prototype.render = function () { - RenderController.flush(); - }; - return Immediate; - })(); - RenderPolicy.Immediate = Immediate; - /** - * The default way to render, which only tries to render every frame - * (usually, 1/60th of a second). - */ - var AnimationFrame = (function () { - function AnimationFrame() { - } - AnimationFrame.prototype.render = function () { - Plottable._Util.DOM.requestAnimationFramePolyfill(RenderController.flush); - }; - return AnimationFrame; - })(); - RenderPolicy.AnimationFrame = AnimationFrame; - /** - * Renders with `setTimeout`. This is generally an inferior way to render - * compared to `requestAnimationFrame`, but it's still there if you want - * it. - */ - var Timeout = (function () { - function Timeout() { - this._timeoutMsec = Plottable._Util.DOM.POLYFILL_TIMEOUT_MSEC; - } - Timeout.prototype.render = function () { - setTimeout(RenderController.flush, this._timeoutMsec); - }; - return Timeout; - })(); - RenderPolicy.Timeout = Timeout; - })(RenderPolicy = RenderController.RenderPolicy || (RenderController.RenderPolicy = {})); - })(RenderController = Core.RenderController || (Core.RenderController = {})); - })(Core = Plottable.Core || (Plottable.Core = {})); + var RenderPolicies; + (function (RenderPolicies) { + /** + * Never queue anything, render everything immediately. Useful for + * debugging, horrible for performance. + */ + var Immediate = (function () { + function Immediate() { + } + Immediate.prototype.render = function () { + Plottable.RenderController.flush(); + }; + return Immediate; + })(); + RenderPolicies.Immediate = Immediate; + /** + * The default way to render, which only tries to render every frame + * (usually, 1/60th of a second). + */ + var AnimationFrame = (function () { + function AnimationFrame() { + } + AnimationFrame.prototype.render = function () { + Plottable.Utils.DOM.requestAnimationFramePolyfill(Plottable.RenderController.flush); + }; + return AnimationFrame; + })(); + RenderPolicies.AnimationFrame = AnimationFrame; + /** + * Renders with `setTimeout`. This is generally an inferior way to render + * compared to `requestAnimationFrame`, but it's still there if you want + * it. + */ + var Timeout = (function () { + function Timeout() { + this._timeoutMsec = Plottable.Utils.DOM.POLYFILL_TIMEOUT_MSEC; + } + Timeout.prototype.render = function () { + setTimeout(Plottable.RenderController.flush, this._timeoutMsec); + }; + return Timeout; + })(); + RenderPolicies.Timeout = Timeout; + })(RenderPolicies = Plottable.RenderPolicies || (Plottable.RenderPolicies = {})); })(Plottable || (Plottable = {})); /// var Plottable; (function (Plottable) { - var Core; - (function (Core) { + /** + * The RenderController is responsible for enqueueing and synchronizing + * layout and render calls for Plottable components. + * + * Layouts and renders occur inside an animation callback + * (window.requestAnimationFrame if available). + * + * If you require immediate rendering, call RenderController.flush() to + * perform enqueued layout and rendering serially. + * + * If you want to always have immediate rendering (useful for debugging), + * call + * ```typescript + * Plottable.RenderController.setRenderPolicy( + * new Plottable.RenderPolicies.Immediate() + * ); + * ``` + */ + var RenderController; + (function (RenderController) { + var _componentsNeedingRender = new Plottable.Utils.Set(); + var _componentsNeedingComputeLayout = new Plottable.Utils.Set(); + var _animationRequested = false; + var _isCurrentlyFlushing = false; + RenderController._renderPolicy = new Plottable.RenderPolicies.AnimationFrame(); + function setRenderPolicy(policy) { + if (typeof (policy) === "string") { + switch (policy.toLowerCase()) { + case "immediate": + policy = new Plottable.RenderPolicies.Immediate(); + break; + case "animationframe": + policy = new Plottable.RenderPolicies.AnimationFrame(); + break; + case "timeout": + policy = new Plottable.RenderPolicies.Timeout(); + break; + default: + Plottable.Utils.Methods.warn("Unrecognized renderPolicy: " + policy); + return; + } + } + RenderController._renderPolicy = policy; + } + RenderController.setRenderPolicy = setRenderPolicy; /** - * The RenderController is responsible for enqueueing and synchronizing - * layout and render calls for Plottable components. + * If the RenderController is enabled, we enqueue the component for + * render. Otherwise, it is rendered immediately. * - * Layouts and renders occur inside an animation callback - * (window.requestAnimationFrame if available). + * @param {Component} component Any Plottable component. + */ + function registerToRender(component) { + if (_isCurrentlyFlushing) { + Plottable.Utils.Methods.warn("Registered to render while other components are flushing: request may be ignored"); + } + _componentsNeedingRender.add(component); + requestRender(); + } + RenderController.registerToRender = registerToRender; + /** + * If the RenderController is enabled, we enqueue the component for + * layout and render. Otherwise, it is rendered immediately. * - * If you require immediate rendering, call RenderController.flush() to - * perform enqueued layout and rendering serially. + * @param {Component} component Any Plottable component. + */ + function registerToComputeLayout(component) { + _componentsNeedingComputeLayout.add(component); + _componentsNeedingRender.add(component); + requestRender(); + } + RenderController.registerToComputeLayout = registerToComputeLayout; + function requestRender() { + // Only run or enqueue flush on first request. + if (!_animationRequested) { + _animationRequested = true; + RenderController._renderPolicy.render(); + } + } + /** + * Render everything that is waiting to be rendered right now, instead of + * waiting until the next frame. * - * If you want to always have immediate rendering (useful for debugging), - * call - * ```typescript - * Plottable.Core.RenderController.setRenderPolicy( - * new Plottable.Core.RenderController.RenderPolicy.Immediate() - * ); - * ``` + * Useful to call when debugging. */ - var RenderController; - (function (RenderController) { - var _componentsNeedingRender = {}; - var _componentsNeedingComputeLayout = {}; - var _animationRequested = false; - var _isCurrentlyFlushing = false; - RenderController._renderPolicy = new RenderController.RenderPolicy.AnimationFrame(); - function setRenderPolicy(policy) { - if (typeof (policy) === "string") { - switch (policy.toLowerCase()) { - case "immediate": - policy = new RenderController.RenderPolicy.Immediate(); - break; - case "animationframe": - policy = new RenderController.RenderPolicy.AnimationFrame(); - break; - case "timeout": - policy = new RenderController.RenderPolicy.Timeout(); - break; - default: - Plottable._Util.Methods.warn("Unrecognized renderPolicy: " + policy); - return; + function flush() { + if (_animationRequested) { + // Layout + _componentsNeedingComputeLayout.values().forEach(function (component) { return component.computeLayout(); }); + // Top level render; Containers will put their children in the toRender queue + _componentsNeedingRender.values().forEach(function (component) { return component.render(); }); + _isCurrentlyFlushing = true; + var failed = new Plottable.Utils.Set(); + _componentsNeedingRender.values().forEach(function (component) { + try { + component.renderImmediately(); + } + catch (err) { + // throw error with timeout to avoid interrupting further renders + window.setTimeout(function () { + throw err; + }, 0); + failed.add(component); } - } - RenderController._renderPolicy = policy; - } - RenderController.setRenderPolicy = setRenderPolicy; - /** - * If the RenderController is enabled, we enqueue the component for - * render. Otherwise, it is rendered immediately. - * - * @param {AbstractComponent} component Any Plottable component. - */ - function registerToRender(c) { - if (_isCurrentlyFlushing) { - Plottable._Util.Methods.warn("Registered to render while other components are flushing: request may be ignored"); - } - _componentsNeedingRender[c.getID()] = c; - requestRender(); - } - RenderController.registerToRender = registerToRender; - /** - * If the RenderController is enabled, we enqueue the component for - * layout and render. Otherwise, it is rendered immediately. - * - * @param {AbstractComponent} component Any Plottable component. - */ - function registerToComputeLayout(c) { - _componentsNeedingComputeLayout[c.getID()] = c; - _componentsNeedingRender[c.getID()] = c; - requestRender(); - } - RenderController.registerToComputeLayout = registerToComputeLayout; - function requestRender() { - // Only run or enqueue flush on first request. - if (!_animationRequested) { - _animationRequested = true; - RenderController._renderPolicy.render(); - } - } - /** - * Render everything that is waiting to be rendered right now, instead of - * waiting until the next frame. - * - * Useful to call when debugging. - */ - function flush() { - if (_animationRequested) { - // Layout - var toCompute = d3.values(_componentsNeedingComputeLayout); - toCompute.forEach(function (c) { return c._computeLayout(); }); - // Top level render. - // Containers will put their children in the toRender queue - var toRender = d3.values(_componentsNeedingRender); - toRender.forEach(function (c) { return c._render(); }); - // now we are flushing - _isCurrentlyFlushing = true; - // Finally, perform render of all components - var failed = {}; - Object.keys(_componentsNeedingRender).forEach(function (k) { - try { - _componentsNeedingRender[k]._doRender(); - } - catch (err) { - // using setTimeout instead of console.log, we get the familiar red - // stack trace - setTimeout(function () { - throw err; - }, 0); - failed[k] = _componentsNeedingRender[k]; - } - }); - // Reset queues - _componentsNeedingComputeLayout = {}; - _componentsNeedingRender = failed; - _animationRequested = false; - _isCurrentlyFlushing = false; - } + }); + _componentsNeedingComputeLayout = new Plottable.Utils.Set(); + _componentsNeedingRender = failed; + _animationRequested = false; + _isCurrentlyFlushing = false; } - RenderController.flush = flush; - })(RenderController = Core.RenderController || (Core.RenderController = {})); - })(Core = Plottable.Core || (Plottable.Core = {})); + } + RenderController.flush = flush; + })(RenderController = Plottable.RenderController || (Plottable.RenderController = {})); })(Plottable || (Plottable = {})); var Plottable; @@ -1481,12 +1361,9 @@ var Plottable; function Domainer(combineExtents) { this._doNice = false; this._padProportion = 0.0; - this._paddingExceptions = d3.map(); - this._unregisteredPaddingExceptions = d3.set(); - this._includedValues = d3.map(); - // _includedValues needs to be a map, even unregistered, to support getting un-stringified values back out - this._unregisteredIncludedValues = d3.map(); this._combineExtents = combineExtents; + this._paddingExceptions = new Plottable.Utils.Map(); + this._includedValues = new Plottable.Utils.Map(); } /** * @param {any[][]} extents The list of extents to be reduced to a single @@ -1506,7 +1383,7 @@ var Plottable; domain = scale._defaultExtent(); } else { - domain = [Plottable._Util.Methods.min(extents, function (e) { return e[0]; }, 0), Plottable._Util.Methods.max(extents, function (e) { return e[1]; }, 0)]; + domain = [Plottable.Utils.Methods.min(extents, function (e) { return e[0]; }, 0), Plottable.Utils.Methods.max(extents, function (e) { return e[1]; }, 0)]; } domain = this._includeDomain(domain); domain = this._padDomain(scale, domain); @@ -1536,76 +1413,48 @@ var Plottable; * Adds a padding exception, a value that will not be padded at either end of the domain. * * Eg, if a padding exception is added at x=0, then [0, 100] will pad to [0, 105] instead of [-2.5, 102.5]. - * If a key is provided, it will be registered under that key with standard map semantics. (Overwrite / remove by key) - * If a key is not provided, it will be added with set semantics (Can be removed by value) + * The exception will be registered under the provided with standard map semantics. (Overwrite / remove by key). * * @param {any} exception The padding exception to add. - * @param {string} key The key to register the exception under. + * @param {any} key The key to register the exception under. * @returns {Domainer} The calling domainer */ - Domainer.prototype.addPaddingException = function (exception, key) { - if (key != null) { - this._paddingExceptions.set(key, exception); - } - else { - this._unregisteredPaddingExceptions.add(exception); - } + Domainer.prototype.addPaddingException = function (key, exception) { + this._paddingExceptions.set(key, exception); return this; }; /** * Removes a padding exception, allowing the domain to pad out that value again. * - * If a string is provided, it is assumed to be a key and the exception associated with that key is removed. - * If a non-string is provdied, it is assumed to be an unkeyed exception and that exception is removed. - * - * @param {any} keyOrException The key for the value to remove, or the value to remove + * @param {any} key The key for the value to remove. * @return {Domainer} The calling domainer */ - Domainer.prototype.removePaddingException = function (keyOrException) { - if (typeof (keyOrException) === "string") { - this._paddingExceptions.remove(keyOrException); - } - else { - this._unregisteredPaddingExceptions.remove(keyOrException); - } + Domainer.prototype.removePaddingException = function (key) { + this._paddingExceptions.delete(key); return this; }; /** * Adds an included value, a value that must be included inside the domain. * * Eg, if a value exception is added at x=0, then [50, 100] will expand to [0, 100] rather than [50, 100]. - * If a key is provided, it will be registered under that key with standard map semantics. (Overwrite / remove by key) - * If a key is not provided, it will be added with set semantics (Can be removed by value) + * The value will be registered under that key with standard map semantics. (Overwrite / remove by key). * * @param {any} value The included value to add. - * @param {string} key The key to register the value under. + * @param {any} key The key to register the value under. * @returns {Domainer} The calling domainer */ - Domainer.prototype.addIncludedValue = function (value, key) { - if (key != null) { - this._includedValues.set(key, value); - } - else { - this._unregisteredIncludedValues.set(value, value); - } + Domainer.prototype.addIncludedValue = function (key, value) { + this._includedValues.set(key, value); return this; }; /** * Remove an included value, allowing the domain to not include that value gain again. * - * If a string is provided, it is assumed to be a key and the value associated with that key is removed. - * If a non-string is provdied, it is assumed to be an unkeyed value and that value is removed. - * - * @param {any} keyOrException The key for the value to remove, or the value to remove + * @param {any} key The key for the value to remove. * @return {Domainer} The calling domainer */ - Domainer.prototype.removeIncludedValue = function (valueOrKey) { - if (typeof (valueOrKey) === "string") { - this._includedValues.remove(valueOrKey); - } - else { - this._unregisteredIncludedValues.remove(valueOrKey); - } + Domainer.prototype.removeIncludedValue = function (key) { + this._includedValues.delete(key); return this; }; /** @@ -1641,7 +1490,7 @@ var Plottable; // scales. A log scale should be padded more on the max than on the min. var newMin = scale.invert(scale.scale(min) - (scale.scale(max) - scale.scale(min)) * p); var newMax = scale.invert(scale.scale(max) + (scale.scale(max) - scale.scale(min)) * p); - var exceptionValues = this._paddingExceptions.values().concat(this._unregisteredPaddingExceptions.values()); + var exceptionValues = this._paddingExceptions.values(); var exceptionSet = d3.set(exceptionValues); if (exceptionSet.has(min)) { newMin = min; @@ -1660,7 +1509,7 @@ var Plottable; } }; Domainer.prototype._includeDomain = function (domain) { - var includedValues = this._includedValues.values().concat(this._unregisteredIncludedValues.values()); + var includedValues = this._includedValues.values(); return includedValues.reduce(function (domain, value) { return [Math.min(domain[0], value), Math.max(domain[1], value)]; }, domain); }; Domainer._PADDING_FOR_IDENTICAL_DOMAIN = 1; @@ -1671,141 +1520,120 @@ var Plottable; })(Plottable || (Plottable = {})); /// -var __extends = this.__extends || function (d, b) { - for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; - function __() { this.constructor = d; } - __.prototype = b.prototype; - d.prototype = new __(); -}; var Plottable; (function (Plottable) { - var Scale; - (function (Scale) { - var AbstractScale = (function (_super) { - __extends(AbstractScale, _super); - /** - * Constructs a new Scale. - * - * A Scale is a wrapper around a D3.Scale.Scale. A Scale is really just a - * function. Scales have a domain (input), a range (output), and a function - * from domain to range. - * - * @constructor - * @param {D3.Scale.Scale} scale The D3 scale backing the Scale. - */ - function AbstractScale(scale) { - _super.call(this); - this._autoDomainAutomatically = true; - this._rendererAttrID2Extent = {}; - this._typeCoercer = function (d) { return d; }; - this._domainModificationInProgress = false; - this._d3Scale = scale; - this.broadcaster = new Plottable.Core.Broadcaster(this); + var Scale = (function () { + /** + * Constructs a new Scale. + * + * A Scale is a wrapper around a D3.Scale.Scale. A Scale is really just a + * function. Scales have a domain (input), a range (output), and a function + * from domain to range. + * + * @constructor + * @param {D3.Scale.Scale} scale The D3 scale backing the Scale. + */ + function Scale(scale) { + this._autoDomainAutomatically = true; + this._domainModificationInProgress = false; + this._d3Scale = scale; + this._callbacks = new Plottable.Utils.CallbackSet(); + this._extentsProviders = new Plottable.Utils.Set(); + } + Scale.prototype._getAllExtents = function () { + var _this = this; + return d3.merge(this._extentsProviders.values().map(function (provider) { return provider(_this); })); + }; + Scale.prototype._getExtent = function () { + return []; // this should be overwritten + }; + Scale.prototype.onUpdate = function (callback) { + this._callbacks.add(callback); + return this; + }; + Scale.prototype.offUpdate = function (callback) { + this._callbacks.delete(callback); + return this; + }; + Scale.prototype._dispatchUpdate = function () { + this._callbacks.callCallbacks(this); + }; + /** + * Modifies the domain on the scale so that it includes the extent of all + * perspectives it depends on. This will normally happen automatically, but + * if you set domain explicitly with `plot.domain(x)`, you will need to + * call this function if you want the domain to neccessarily include all + * the data. + * + * Extent: The [min, max] pair for a Scale.QuantitativeScale, all covered + * strings for a Scale.Category. + * + * Perspective: A combination of a Dataset and an Accessor that + * represents a view in to the data. + * + * @returns {Scale} The calling Scale. + */ + Scale.prototype.autoDomain = function () { + this._autoDomainAutomatically = true; + this._setDomain(this._getExtent()); + return this; + }; + Scale.prototype._autoDomainIfAutomaticMode = function () { + if (this._autoDomainAutomatically) { + this.autoDomain(); } - AbstractScale.prototype._getAllExtents = function () { - return d3.values(this._rendererAttrID2Extent); - }; - AbstractScale.prototype._getExtent = function () { - return []; // this should be overwritten - }; - /** - * Modifies the domain on the scale so that it includes the extent of all - * perspectives it depends on. This will normally happen automatically, but - * if you set domain explicitly with `plot.domain(x)`, you will need to - * call this function if you want the domain to neccessarily include all - * the data. - * - * Extent: The [min, max] pair for a Scale.Quantitative, all covered - * strings for a Scale.Category. - * - * Perspective: A combination of a Dataset and an Accessor that - * represents a view in to the data. - * - * @returns {Scale} The calling Scale. - */ - AbstractScale.prototype.autoDomain = function () { - this._autoDomainAutomatically = true; - this._setDomain(this._getExtent()); - return this; - }; - AbstractScale.prototype._autoDomainIfAutomaticMode = function () { - if (this._autoDomainAutomatically) { - this.autoDomain(); - } - }; - /** - * Computes the range value corresponding to a given domain value. In other - * words, apply the function to value. - * - * @param {R} value A domain value to be scaled. - * @returns {R} The range value corresponding to the supplied domain value. - */ - AbstractScale.prototype.scale = function (value) { - return this._d3Scale(value); - }; - AbstractScale.prototype.domain = function (values) { - if (values == null) { - return this._getDomain(); - } - else { - this._autoDomainAutomatically = false; - this._setDomain(values); - return this; - } - }; - AbstractScale.prototype._getDomain = function () { - return this._d3Scale.domain(); - }; - AbstractScale.prototype._setDomain = function (values) { - if (!this._domainModificationInProgress) { - this._domainModificationInProgress = true; - this._d3Scale.domain(values); - this.broadcaster.broadcast(); - this._domainModificationInProgress = false; - } - }; - AbstractScale.prototype.range = function (values) { - if (values == null) { - return this._d3Scale.range(); - } - else { - this._d3Scale.range(values); - return this; - } - }; - /** - * Constructs a copy of the Scale with the same domain and range but without - * any registered listeners. - * - * @returns {Scale} A copy of the calling Scale. - */ - AbstractScale.prototype.copy = function () { - return new AbstractScale(this._d3Scale.copy()); - }; - /** - * When a renderer determines that the extent of a projector has changed, - * it will call this function. This function should ensure that - * the scale has a domain at least large enough to include extent. - * - * @param {number} rendererID A unique indentifier of the renderer sending - * the new extent. - * @param {string} attr The attribute being projected, e.g. "x", "y0", "r" - * @param {D[]} extent The new extent to be included in the scale. - */ - AbstractScale.prototype._updateExtent = function (plotProvidedKey, attr, extent) { - this._rendererAttrID2Extent[plotProvidedKey + attr] = extent; - this._autoDomainIfAutomaticMode(); + }; + /** + * Computes the range value corresponding to a given domain value. In other + * words, apply the function to value. + * + * @param {R} value A domain value to be scaled. + * @returns {R} The range value corresponding to the supplied domain value. + */ + Scale.prototype.scale = function (value) { + return this._d3Scale(value); + }; + Scale.prototype.domain = function (values) { + if (values == null) { + return this._getDomain(); + } + else { + this._autoDomainAutomatically = false; + this._setDomain(values); return this; - }; - AbstractScale.prototype._removeExtent = function (plotProvidedKey, attr) { - delete this._rendererAttrID2Extent[plotProvidedKey + attr]; - this._autoDomainIfAutomaticMode(); + } + }; + Scale.prototype._getDomain = function () { + return this._d3Scale.domain(); + }; + Scale.prototype._setDomain = function (values) { + if (!this._domainModificationInProgress) { + this._domainModificationInProgress = true; + this._d3Scale.domain(values); + this._dispatchUpdate(); + this._domainModificationInProgress = false; + } + }; + Scale.prototype.range = function (values) { + if (values == null) { + return this._d3Scale.range(); + } + else { + this._d3Scale.range(values); return this; - }; - return AbstractScale; - })(Plottable.Core.PlottableObject); - Scale.AbstractScale = AbstractScale; - })(Scale = Plottable.Scale || (Plottable.Scale = {})); + } + }; + Scale.prototype.addExtentsProvider = function (provider) { + this._extentsProviders.add(provider); + return this; + }; + Scale.prototype.removeExtentsProvider = function (provider) { + this._extentsProviders.delete(provider); + return this; + }; + return Scale; + })(); + Plottable.Scale = Scale; })(Plottable || (Plottable = {})); /// @@ -1817,138 +1645,95 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Scale; - (function (Scale) { - var AbstractQuantitative = (function (_super) { - __extends(AbstractQuantitative, _super); - /** - * Constructs a new QuantitativeScale. - * - * A QuantitativeScale is a Scale that maps anys to numbers. It - * is invertible and continuous. - * - * @constructor - * @param {D3.Scale.QuantitativeScale} scale The D3 QuantitativeScale - * backing the QuantitativeScale. - */ - function AbstractQuantitative(scale) { - _super.call(this, scale); - this._numTicks = 10; - this._PADDING_FOR_IDENTICAL_DOMAIN = 1; - this._userSetDomainer = false; - this._domainer = new Plottable.Domainer(); - this._typeCoercer = function (d) { return +d; }; - this._tickGenerator = function (scale) { return scale.getDefaultTicks(); }; + var QuantitativeScale = (function (_super) { + __extends(QuantitativeScale, _super); + /** + * Constructs a new QuantitativeScale. + * + * A QuantitativeScale is a Scale that maps anys to numbers. It + * is invertible and continuous. + * + * @constructor + * @param {D3.Scale.QuantitativeScale} scale The D3 QuantitativeScale + * backing the QuantitativeScale. + */ + function QuantitativeScale(scale) { + _super.call(this, scale); + this._userSetDomainer = false; + this._domainer = new Plottable.Domainer(); + this._tickGenerator = function (scale) { return scale.getDefaultTicks(); }; + } + QuantitativeScale.prototype._getExtent = function () { + return this._domainer.computeDomain(this._getAllExtents(), this); + }; + /** + * Retrieves the domain value corresponding to a supplied range value. + * + * @param {number} value: A value from the Scale's range. + * @returns {D} The domain value corresponding to the supplied range value. + */ + QuantitativeScale.prototype.invert = function (value) { + return this._d3Scale.invert(value); + }; + QuantitativeScale.prototype.domain = function (values) { + return _super.prototype.domain.call(this, values); // need to override type sig to enable method chaining:/ + }; + QuantitativeScale.prototype._setDomain = function (values) { + var isNaNOrInfinity = function (x) { return x !== x || x === Infinity || x === -Infinity; }; + if (isNaNOrInfinity(values[0]) || isNaNOrInfinity(values[1])) { + Plottable.Utils.Methods.warn("Warning: QuantitativeScales cannot take NaN or Infinity as a domain value. Ignoring."); + return; } - AbstractQuantitative.prototype._getExtent = function () { - return this._domainer.computeDomain(this._getAllExtents(), this); - }; - /** - * Retrieves the domain value corresponding to a supplied range value. - * - * @param {number} value: A value from the Scale's range. - * @returns {D} The domain value corresponding to the supplied range value. - */ - AbstractQuantitative.prototype.invert = function (value) { - return this._d3Scale.invert(value); - }; - /** - * Creates a copy of the QuantitativeScale with the same domain and range but without any registered list. - * - * @returns {AbstractQuantitative} A copy of the calling QuantitativeScale. - */ - AbstractQuantitative.prototype.copy = function () { - return new AbstractQuantitative(this._d3Scale.copy()); - }; - AbstractQuantitative.prototype.domain = function (values) { - return _super.prototype.domain.call(this, values); // need to override type sig to enable method chaining :/ - }; - AbstractQuantitative.prototype._setDomain = function (values) { - var isNaNOrInfinity = function (x) { return x !== x || x === Infinity || x === -Infinity; }; - if (isNaNOrInfinity(values[0]) || isNaNOrInfinity(values[1])) { - Plottable._Util.Methods.warn("Warning: QuantitativeScales cannot take NaN or Infinity as a domain value. Ignoring."); - return; - } - _super.prototype._setDomain.call(this, values); - }; - AbstractQuantitative.prototype.interpolate = function (factory) { - if (factory == null) { - return this._d3Scale.interpolate(); - } - this._d3Scale.interpolate(factory); - return this; - }; - /** - * Sets the range of the QuantitativeScale and sets the interpolator to d3.interpolateRound. - * - * @param {number[]} values The new range value for the range. - */ - AbstractQuantitative.prototype.rangeRound = function (values) { - this._d3Scale.rangeRound(values); - return this; - }; - /** - * Gets ticks generated by the default algorithm. - */ - AbstractQuantitative.prototype.getDefaultTicks = function () { - return this._d3Scale.ticks(this.numTicks()); - }; - AbstractQuantitative.prototype.clamp = function (clamp) { - if (clamp == null) { - return this._d3Scale.clamp(); - } - this._d3Scale.clamp(clamp); + _super.prototype._setDomain.call(this, values); + }; + /** + * Gets ticks generated by the default algorithm. + */ + QuantitativeScale.prototype.getDefaultTicks = function () { + return this._d3Scale.ticks(QuantitativeScale._DEFAULT_NUM_TICKS); + }; + /** + * Gets a set of tick values spanning the domain. + * + * @returns {D[]} The generated ticks. + */ + QuantitativeScale.prototype.ticks = function () { + return this._tickGenerator(this); + }; + /** + * Given a domain, expands its domain onto "nice" values, e.g. whole + * numbers. + */ + QuantitativeScale.prototype._niceDomain = function (domain, count) { + return this._d3Scale.copy().domain(domain).nice(count).domain(); + }; + QuantitativeScale.prototype.domainer = function (domainer) { + if (domainer == null) { + return this._domainer; + } + else { + this._domainer = domainer; + this._userSetDomainer = true; + this._autoDomainIfAutomaticMode(); return this; - }; - /** - * Gets a set of tick values spanning the domain. - * - * @returns {any[]} The generated ticks. - */ - AbstractQuantitative.prototype.ticks = function () { - return this._tickGenerator(this); - }; - AbstractQuantitative.prototype.numTicks = function (count) { - if (count == null) { - return this._numTicks; - } - this._numTicks = count; + } + }; + QuantitativeScale.prototype._defaultExtent = function () { + throw Error("The quantitative scale itself does not have a default extent"); + }; + QuantitativeScale.prototype.tickGenerator = function (generator) { + if (generator == null) { + return this._tickGenerator; + } + else { + this._tickGenerator = generator; return this; - }; - /** - * Given a domain, expands its domain onto "nice" values, e.g. whole - * numbers. - */ - AbstractQuantitative.prototype._niceDomain = function (domain, count) { - return this._d3Scale.copy().domain(domain).nice(count).domain(); - }; - AbstractQuantitative.prototype.domainer = function (domainer) { - if (domainer == null) { - return this._domainer; - } - else { - this._domainer = domainer; - this._userSetDomainer = true; - this._autoDomainIfAutomaticMode(); - return this; - } - }; - AbstractQuantitative.prototype._defaultExtent = function () { - return [0, 1]; - }; - AbstractQuantitative.prototype.tickGenerator = function (generator) { - if (generator == null) { - return this._tickGenerator; - } - else { - this._tickGenerator = generator; - return this; - } - }; - return AbstractQuantitative; - })(Scale.AbstractScale); - Scale.AbstractQuantitative = AbstractQuantitative; - })(Scale = Plottable.Scale || (Plottable.Scale = {})); + } + }; + QuantitativeScale._DEFAULT_NUM_TICKS = 10; + return QuantitativeScale; + })(Plottable.Scale); + Plottable.QuantitativeScale = QuantitativeScale; })(Plottable || (Plottable = {})); /// @@ -1960,64 +1745,20 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Scale; - (function (Scale) { + var Scales; + (function (Scales) { var Linear = (function (_super) { __extends(Linear, _super); function Linear(scale) { _super.call(this, scale == null ? d3.scale.linear() : scale); } - /** - * Constructs a copy of the LinearScale with the same domain and range but - * without any registered listeners. - * - * @returns {Linear} A copy of the calling LinearScale. - */ - Linear.prototype.copy = function () { - return new Linear(this._d3Scale.copy()); + Linear.prototype._defaultExtent = function () { + return [0, 1]; }; return Linear; - })(Scale.AbstractQuantitative); - Scale.Linear = Linear; - })(Scale = Plottable.Scale || (Plottable.Scale = {})); -})(Plottable || (Plottable = {})); - -/// -var __extends = this.__extends || function (d, b) { - for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; - function __() { this.constructor = d; } - __.prototype = b.prototype; - d.prototype = new __(); -}; -var Plottable; -(function (Plottable) { - var Scale; - (function (Scale) { - var Log = (function (_super) { - __extends(Log, _super); - function Log(scale) { - _super.call(this, scale == null ? d3.scale.log() : scale); - if (!Log.warned) { - Log.warned = true; - Plottable._Util.Methods.warn("Plottable.Scale.Log is deprecated. If possible, use Plottable.Scale.ModifiedLog instead."); - } - } - /** - * Creates a copy of the Scale.Log with the same domain and range but without any registered listeners. - * - * @returns {Log} A copy of the calling Log. - */ - Log.prototype.copy = function () { - return new Log(this._d3Scale.copy()); - }; - Log.prototype._defaultExtent = function () { - return [1, 10]; - }; - Log.warned = false; - return Log; - })(Scale.AbstractQuantitative); - Scale.Log = Log; - })(Scale = Plottable.Scale || (Plottable.Scale = {})); + })(Plottable.QuantitativeScale); + Scales.Linear = Linear; + })(Scales = Plottable.Scales || (Plottable.Scales = {})); })(Plottable || (Plottable = {})); /// @@ -2029,8 +1770,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Scale; - (function (Scale) { + var Scales; + (function (Scales) { var ModifiedLog = (function (_super) { __extends(ModifiedLog, _super); /** @@ -2062,10 +1803,9 @@ var Plottable; if (base === void 0) { base = 10; } _super.call(this, d3.scale.linear()); this._showIntermediateTicks = false; - this.base = base; - this.pivot = this.base; - this.untransformedDomain = this._defaultExtent(); - this.numTicks(10); + this._base = base; + this._pivot = this._base; + this._setDomain(this._defaultExtent()); if (base <= 1) { throw new Error("ModifiedLogScale: The base must be > 1"); } @@ -2081,19 +1821,19 @@ var Plottable; ModifiedLog.prototype.adjustedLog = function (x) { var negationFactor = x < 0 ? -1 : 1; x *= negationFactor; - if (x < this.pivot) { - x += (this.pivot - x) / this.pivot; + if (x < this._pivot) { + x += (this._pivot - x) / this._pivot; } - x = Math.log(x) / Math.log(this.base); + x = Math.log(x) / Math.log(this._base); x *= negationFactor; return x; }; ModifiedLog.prototype.invertedAdjustedLog = function (x) { var negationFactor = x < 0 ? -1 : 1; x *= negationFactor; - x = Math.pow(this.base, x); - if (x < this.pivot) { - x = (this.pivot * (x - 1)) / (this.pivot - 1); + x = Math.pow(this._base, x); + if (x < this._pivot) { + x = (this._pivot * (x - 1)) / (this._pivot - 1); } x *= negationFactor; return x; @@ -2105,33 +1845,31 @@ var Plottable; return this.invertedAdjustedLog(this._d3Scale.invert(x)); }; ModifiedLog.prototype._getDomain = function () { - return this.untransformedDomain; + return this._untransformedDomain; }; ModifiedLog.prototype._setDomain = function (values) { - this.untransformedDomain = values; + this._untransformedDomain = values; var transformedDomain = [this.adjustedLog(values[0]), this.adjustedLog(values[1])]; - this._d3Scale.domain(transformedDomain); - this.broadcaster.broadcast(); + _super.prototype._setDomain.call(this, transformedDomain); }; - ModifiedLog.prototype.ticks = function (count) { - if (count === void 0) { count = this.numTicks(); } + ModifiedLog.prototype.ticks = function () { // Say your domain is [-100, 100] and your pivot is 10. // then we're going to draw negative log ticks from -100 to -10, // linear ticks from -10 to 10, and positive log ticks from 10 to 100. var middle = function (x, y, z) { return [x, y, z].sort(function (a, b) { return a - b; })[1]; }; - var min = Plottable._Util.Methods.min(this.untransformedDomain, 0); - var max = Plottable._Util.Methods.max(this.untransformedDomain, 0); + var min = Plottable.Utils.Methods.min(this._untransformedDomain, 0); + var max = Plottable.Utils.Methods.max(this._untransformedDomain, 0); var negativeLower = min; - var negativeUpper = middle(min, max, -this.pivot); - var positiveLower = middle(min, max, this.pivot); + var negativeUpper = middle(min, max, -this._pivot); + var positiveLower = middle(min, max, this._pivot); var positiveUpper = max; var negativeLogTicks = this.logTicks(-negativeUpper, -negativeLower).map(function (x) { return -x; }).reverse(); var positiveLogTicks = this.logTicks(positiveLower, positiveUpper); - var linearTicks = this._showIntermediateTicks ? d3.scale.linear().domain([negativeUpper, positiveLower]).ticks(this.howManyTicks(negativeUpper, positiveLower)) : [-this.pivot, 0, this.pivot].filter(function (x) { return min <= x && x <= max; }); + var linearTicks = this._showIntermediateTicks ? d3.scale.linear().domain([negativeUpper, positiveLower]).ticks(this._howManyTicks(negativeUpper, positiveLower)) : [-this._pivot, 0, this._pivot].filter(function (x) { return min <= x && x <= max; }); var ticks = negativeLogTicks.concat(linearTicks).concat(positiveLogTicks); // If you only have 1 tick, you can't tell how big the scale is. if (ticks.length <= 1) { - ticks = d3.scale.linear().domain([min, max]).ticks(count); + ticks = d3.scale.linear().domain([min, max]).ticks(ModifiedLog._DEFAULT_NUM_TICKS); } return ticks; }; @@ -2150,18 +1888,18 @@ var Plottable; */ ModifiedLog.prototype.logTicks = function (lower, upper) { var _this = this; - var nTicks = this.howManyTicks(lower, upper); + var nTicks = this._howManyTicks(lower, upper); if (nTicks === 0) { return []; } - var startLogged = Math.floor(Math.log(lower) / Math.log(this.base)); - var endLogged = Math.ceil(Math.log(upper) / Math.log(this.base)); + var startLogged = Math.floor(Math.log(lower) / Math.log(this._base)); + var endLogged = Math.ceil(Math.log(upper) / Math.log(this._base)); var bases = d3.range(endLogged, startLogged, -Math.ceil((endLogged - startLogged) / nTicks)); var nMultiples = this._showIntermediateTicks ? Math.floor(nTicks / bases.length) : 1; - var multiples = d3.range(this.base, 1, -(this.base - 1) / nMultiples).map(Math.floor); - var uniqMultiples = Plottable._Util.Methods.uniq(multiples); - var clusters = bases.map(function (b) { return uniqMultiples.map(function (x) { return Math.pow(_this.base, b - 1) * x; }); }); - var flattened = Plottable._Util.Methods.flatten(clusters); + var multiples = d3.range(this._base, 1, -(this._base - 1) / nMultiples).map(Math.floor); + var uniqMultiples = Plottable.Utils.Methods.uniq(multiples); + var clusters = bases.map(function (b) { return uniqMultiples.map(function (x) { return Math.pow(_this._base, b - 1) * x; }); }); + var flattened = Plottable.Utils.Methods.flatten(clusters); var filtered = flattened.filter(function (x) { return lower <= x && x <= upper; }); var sorted = filtered.sort(function (x, y) { return x - y; }); return sorted; @@ -2169,22 +1907,19 @@ var Plottable; /** * How many ticks does the range [lower, upper] deserve? * - * e.g. if your domain was [10, 1000] and I asked howManyTicks(10, 100), + * e.g. if your domain was [10, 1000] and I asked _howManyTicks(10, 100), * I would get 1/2 of the ticks. The range 10, 100 takes up 1/2 of the * distance when plotted. */ - ModifiedLog.prototype.howManyTicks = function (lower, upper) { - var adjustedMin = this.adjustedLog(Plottable._Util.Methods.min(this.untransformedDomain, 0)); - var adjustedMax = this.adjustedLog(Plottable._Util.Methods.max(this.untransformedDomain, 0)); + ModifiedLog.prototype._howManyTicks = function (lower, upper) { + var adjustedMin = this.adjustedLog(Plottable.Utils.Methods.min(this._untransformedDomain, 0)); + var adjustedMax = this.adjustedLog(Plottable.Utils.Methods.max(this._untransformedDomain, 0)); var adjustedLower = this.adjustedLog(lower); var adjustedUpper = this.adjustedLog(upper); var proportion = (adjustedUpper - adjustedLower) / (adjustedMax - adjustedMin); - var ticks = Math.ceil(proportion * this.numTicks()); + var ticks = Math.ceil(proportion * ModifiedLog._DEFAULT_NUM_TICKS); return ticks; }; - ModifiedLog.prototype.copy = function () { - return new ModifiedLog(this.base); - }; ModifiedLog.prototype._niceDomain = function (domain, count) { return domain; }; @@ -2196,10 +1931,13 @@ var Plottable; this._showIntermediateTicks = show; } }; + ModifiedLog.prototype._defaultExtent = function () { + return [0, this._base]; + }; return ModifiedLog; - })(Scale.AbstractQuantitative); - Scale.ModifiedLog = ModifiedLog; - })(Scale = Plottable.Scale || (Plottable.Scale = {})); + })(Plottable.QuantitativeScale); + Scales.ModifiedLog = ModifiedLog; + })(Scales = Plottable.Scales || (Plottable.Scales = {})); })(Plottable || (Plottable = {})); /// @@ -2211,8 +1949,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Scale; - (function (Scale) { + var Scales; + (function (Scales) { var Category = (function (_super) { __extends(Category, _super); /** @@ -2227,14 +1965,13 @@ var Plottable; if (scale === void 0) { scale = d3.scale.ordinal(); } _super.call(this, scale); this._range = [0, 1]; - this._typeCoercer = function (d) { return d != null && d.toString ? d.toString() : d; }; var d3InnerPadding = 0.3; this._innerPadding = Category._convertToPlottableInnerPadding(d3InnerPadding); this._outerPadding = Category._convertToPlottableOuterPadding(0.5, d3InnerPadding); } Category.prototype._getExtent = function () { var extents = this._getAllExtents(); - return Plottable._Util.Methods.uniq(Plottable._Util.Methods.flatten(extents)); + return Plottable.Utils.Methods.uniq(Plottable.Utils.Methods.flatten(extents)); }; Category.prototype.domain = function (values) { return _super.prototype.domain.call(this, values); @@ -2286,7 +2023,7 @@ var Plottable; } this._innerPadding = innerPadding; this.range(this.range()); - this.broadcaster.broadcast(); + this._dispatchUpdate(); return this; }; Category.prototype.outerPadding = function (outerPadding) { @@ -2295,20 +2032,17 @@ var Plottable; } this._outerPadding = outerPadding; this.range(this.range()); - this.broadcaster.broadcast(); + this._dispatchUpdate(); return this; }; - Category.prototype.copy = function () { - return new Category(this._d3Scale.copy()); - }; Category.prototype.scale = function (value) { - //scale it to the middle + // scale it to the middle return _super.prototype.scale.call(this, value) + this.rangeBand() / 2; }; return Category; - })(Scale.AbstractScale); - Scale.Category = Category; - })(Scale = Plottable.Scale || (Plottable.Scale = {})); + })(Plottable.Scale); + Scales.Category = Category; + })(Scales = Plottable.Scales || (Plottable.Scales = {})); })(Plottable || (Plottable = {})); /// @@ -2320,8 +2054,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Scale; - (function (Scale) { + var Scales; + (function (Scales) { var Color = (function (_super) { __extends(Color, _super); /** @@ -2371,15 +2105,15 @@ var Plottable; extents.forEach(function (e) { concatenatedExtents = concatenatedExtents.concat(e); }); - return Plottable._Util.Methods.uniq(concatenatedExtents); + return Plottable.Utils.Methods.uniq(concatenatedExtents); }; Color._getPlottableColors = function () { var plottableDefaultColors = []; var colorTester = d3.select("body").append("plottable-color-tester"); - var defaultColorHex = Plottable._Util.Methods.colorTest(colorTester, ""); + var defaultColorHex = Plottable.Utils.Methods.colorTest(colorTester, ""); var i = 0; var colorHex; - while ((colorHex = Plottable._Util.Methods.colorTest(colorTester, "plottable-colors-" + i)) !== null && i < this.MAXIMUM_COLORS_FROM_CSS) { + while ((colorHex = Plottable.Utils.Methods.colorTest(colorTester, "plottable-colors-" + i)) !== null && i < this.MAXIMUM_COLORS_FROM_CSS) { if (colorHex === defaultColorHex && colorHex === plottableDefaultColors[plottableDefaultColors.length - 1]) { break; } @@ -2396,16 +2130,15 @@ var Plottable; var index = this.domain().indexOf(value); var numLooped = Math.floor(index / this.range().length); var modifyFactor = Math.log(numLooped * Color.LOOP_LIGHTEN_FACTOR + 1); - return Plottable._Util.Methods.lightenColor(color, modifyFactor); + return Plottable.Utils.Methods.lightenColor(color, modifyFactor); }; - Color.HEX_SCALE_FACTOR = 20; Color.LOOP_LIGHTEN_FACTOR = 1.6; - //The maximum number of colors we are getting from CSS stylesheets + // The maximum number of colors we are getting from CSS stylesheets Color.MAXIMUM_COLORS_FROM_CSS = 256; return Color; - })(Scale.AbstractScale); - Scale.Color = Color; - })(Scale = Plottable.Scale || (Plottable.Scale = {})); + })(Plottable.Scale); + Scales.Color = Color; + })(Scales = Plottable.Scales || (Plottable.Scales = {})); })(Plottable || (Plottable = {})); /// @@ -2417,42 +2150,45 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Scale; - (function (Scale) { + var Scales; + (function (Scales) { var Time = (function (_super) { __extends(Time, _super); function Time(scale) { - // need to cast since d3 time scales do not descend from Quantitative scales + // need to cast since d3 time scales do not descend from QuantitativeScale scales _super.call(this, scale == null ? d3.time.scale() : scale); - this._typeCoercer = function (d) { return d && d._isAMomentObject || d instanceof Date ? d : new Date(d); }; } + /** + * Specifies the interval between ticks + * + * @param {string} interval TimeInterval string specifying the interval unit measure + * @param {number?} step? The distance between adjacent ticks (using the interval unit measure) + * + * @return {Date[]} + */ Time.prototype.tickInterval = function (interval, step) { // temporarily creats a time scale from our linear scale into a time scale so we can get access to its api var tempScale = d3.time.scale(); + var d3Interval = Plottable.Formatters.timeIntervalToD3Time(interval); tempScale.domain(this.domain()); tempScale.range(this.range()); - return tempScale.ticks(interval.range, step); + return tempScale.ticks(d3Interval.range, step); }; Time.prototype._setDomain = function (values) { - // attempt to parse dates - values = values.map(this._typeCoercer); if (values[1] < values[0]) { throw new Error("Scale.Time domain values must be in chronological order"); } return _super.prototype._setDomain.call(this, values); }; - Time.prototype.copy = function () { - return new Time(this._d3Scale.copy()); - }; Time.prototype._defaultExtent = function () { - var endTime = new Date().valueOf(); - var startTime = endTime - Plottable.MILLISECONDS_IN_ONE_DAY; - return [startTime, endTime]; + var endTimeValue = new Date().valueOf(); + var startTimeValue = endTimeValue - Plottable.MILLISECONDS_IN_ONE_DAY; + return [new Date(startTimeValue), new Date(endTimeValue)]; }; return Time; - })(Scale.AbstractQuantitative); - Scale.Time = Time; - })(Scale = Plottable.Scale || (Plottable.Scale = {})); + })(Plottable.QuantitativeScale); + Scales.Time = Time; + })(Scales = Plottable.Scales || (Plottable.Scales = {})); })(Plottable || (Plottable = {})); /// @@ -2464,8 +2200,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Scale; - (function (Scale) { + var Scales; + (function (Scales) { /** * This class implements a color scale that takes quantitive input and * interpolates between a list of color values. It returns a hex string @@ -2476,65 +2212,52 @@ var Plottable; var InterpolatedColor = (function (_super) { __extends(InterpolatedColor, _super); /** - * Constructs an InterpolatedColorScale. - * - * An InterpolatedColorScale maps numbers evenly to color strings. + * An InterpolatedColorScale maps numbers to color strings. * - * @constructor - * @param {string|string[]} colorRange the type of color scale to - * create. Default is "reds". @see {@link colorRange} for further - * options. - * @param {string} scaleType the type of underlying scale to use - * (linear/pow/log/sqrt). Default is "linear". @see {@link scaleType} - * for further options. + * @param {string[]} colors an array of strings representing color values in hex + * ("#FFFFFF") or keywords ("white"). Defaults to InterpolatedColor.REDS + * @param {string} scaleType a string representing the underlying scale + * type ("linear"/"log"/"sqrt"/"pow"). Defaults to "linear" + * @returns {D3.Scale.QuantitativeScale} The converted QuantitativeScale d3 scale. */ function InterpolatedColor(colorRange, scaleType) { - if (colorRange === void 0) { colorRange = "reds"; } + if (colorRange === void 0) { colorRange = InterpolatedColor.REDS; } if (scaleType === void 0) { scaleType = "linear"; } - this._colorRange = this._resolveColorValues(colorRange); - this._scaleType = scaleType; - _super.call(this, InterpolatedColor._getD3InterpolatedScale(this._colorRange, this._scaleType)); - } - /** - * Converts the string array into a d3 scale. - * - * @param {string[]} colors an array of strings representing color - * values in hex ("#FFFFFF") or keywords ("white"). - * @param {string} scaleType a string representing the underlying scale - * type ("linear"/"log"/"sqrt"/"pow") - * @returns {D3.Scale.QuantitativeScale} The converted Quantitative d3 scale. - */ - InterpolatedColor._getD3InterpolatedScale = function (colors, scaleType) { - var scale; + this._colorRange = colorRange; switch (scaleType) { case "linear": - scale = d3.scale.linear(); + this._colorScale = d3.scale.linear(); break; case "log": - scale = d3.scale.log(); + this._colorScale = d3.scale.log(); break; case "sqrt": - scale = d3.scale.sqrt(); + this._colorScale = d3.scale.sqrt(); break; case "pow": - scale = d3.scale.pow(); + this._colorScale = d3.scale.pow(); break; } - if (scale == null) { - throw new Error("unknown Quantitative scale type " + scaleType); + if (this._colorScale == null) { + throw new Error("unknown QuantitativeScale scale type " + scaleType); } - return scale.range([0, 1]).interpolate(InterpolatedColor._interpolateColors(colors)); - }; + _super.call(this, this._D3InterpolatedScale()); + } /** - * Creates a d3 interpolator given the color array. + * Generates the converted QuantitativeScale. * - * This class implements a scale that maps numbers to strings. + * @returns {D3.Scale.QuantitativeScale} The converted d3 QuantitativeScale + */ + InterpolatedColor.prototype._D3InterpolatedScale = function () { + return this._colorScale.range([0, 1]).interpolate(this._interpolateColors()); + }; + /** + * Generates the d3 interpolator for colors. * - * @param {string[]} colors an array of strings representing color - * values in hex ("#FFFFFF") or keywords ("white"). - * @returns {D3.Transition.Interpolate} The d3 interpolator for colors. + * @return {D3.Transition.Interpolate} The d3 interpolator for colors. */ - InterpolatedColor._interpolateColors = function (colors) { + InterpolatedColor.prototype._interpolateColors = function () { + var colors = this._colorRange; if (colors.length < 2) { throw new Error("Color scale arrays must have at least two elements."); } @@ -2557,140 +2280,79 @@ var Plottable; if (colorRange == null) { return this._colorRange; } - this._colorRange = this._resolveColorValues(colorRange); - this._resetScale(); - return this; - }; - InterpolatedColor.prototype.scaleType = function (scaleType) { - if (scaleType == null) { - return this._scaleType; - } - this._scaleType = scaleType; + this._colorRange = colorRange; this._resetScale(); return this; }; InterpolatedColor.prototype._resetScale = function () { - this._d3Scale = InterpolatedColor._getD3InterpolatedScale(this._colorRange, this._scaleType); + this._d3Scale = this._D3InterpolatedScale(); this._autoDomainIfAutomaticMode(); - this.broadcaster.broadcast(); - }; - InterpolatedColor.prototype._resolveColorValues = function (colorRange) { - if (typeof (colorRange) === "object") { - return colorRange; - } - else if (InterpolatedColor._COLOR_SCALES[colorRange] != null) { - return InterpolatedColor._COLOR_SCALES[colorRange]; - } - else { - return InterpolatedColor._COLOR_SCALES["reds"]; - } + this._dispatchUpdate(); }; InterpolatedColor.prototype.autoDomain = function () { // unlike other QuantitativeScales, interpolatedColorScale ignores its domainer var extents = this._getAllExtents(); if (extents.length > 0) { - this._setDomain([Plottable._Util.Methods.min(extents, function (x) { return x[0]; }, 0), Plottable._Util.Methods.max(extents, function (x) { return x[1]; }, 0)]); + this._setDomain([Plottable.Utils.Methods.min(extents, function (x) { return x[0]; }, 0), Plottable.Utils.Methods.max(extents, function (x) { return x[1]; }, 0)]); } return this; }; - InterpolatedColor._COLOR_SCALES = { - reds: [ - "#FFFFFF", - "#FFF6E1", - "#FEF4C0", - "#FED976", - "#FEB24C", - "#FD8D3C", - "#FC4E2A", - "#E31A1C", - "#B10026" - ], - blues: [ - "#FFFFFF", - "#CCFFFF", - "#A5FFFD", - "#85F7FB", - "#6ED3EF", - "#55A7E0", - "#417FD0", - "#2545D3", - "#0B02E1" - ], - posneg: [ - "#0B02E1", - "#2545D3", - "#417FD0", - "#55A7E0", - "#6ED3EF", - "#85F7FB", - "#A5FFFD", - "#CCFFFF", - "#FFFFFF", - "#FFF6E1", - "#FEF4C0", - "#FED976", - "#FEB24C", - "#FD8D3C", - "#FC4E2A", - "#E31A1C", - "#B10026" - ] - }; + InterpolatedColor.REDS = [ + "#FFFFFF", + "#FFF6E1", + "#FEF4C0", + "#FED976", + "#FEB24C", + "#FD8D3C", + "#FC4E2A", + "#E31A1C", + "#B10026" + ]; + InterpolatedColor.BLUES = [ + "#FFFFFF", + "#CCFFFF", + "#A5FFFD", + "#85F7FB", + "#6ED3EF", + "#55A7E0", + "#417FD0", + "#2545D3", + "#0B02E1" + ]; + InterpolatedColor.POSNEG = [ + "#0B02E1", + "#2545D3", + "#417FD0", + "#55A7E0", + "#6ED3EF", + "#85F7FB", + "#A5FFFD", + "#CCFFFF", + "#FFFFFF", + "#FFF6E1", + "#FEF4C0", + "#FED976", + "#FEB24C", + "#FD8D3C", + "#FC4E2A", + "#E31A1C", + "#B10026" + ]; return InterpolatedColor; - })(Scale.AbstractScale); - Scale.InterpolatedColor = InterpolatedColor; - })(Scale = Plottable.Scale || (Plottable.Scale = {})); + })(Plottable.Scale); + Scales.InterpolatedColor = InterpolatedColor; + })(Scales = Plottable.Scales || (Plottable.Scales = {})); })(Plottable || (Plottable = {})); /// var Plottable; (function (Plottable) { - var _Util; - (function (_Util) { - var ScaleDomainCoordinator = (function () { + var Scales; + (function (Scales) { + var TickGenerators; + (function (TickGenerators) { /** - * Constructs a ScaleDomainCoordinator. - * - * @constructor - * @param {Scale[]} scales A list of scales whose domains should be linked. - */ - function ScaleDomainCoordinator(scales) { - var _this = this; - /* This class is responsible for maintaining coordination between linked scales. - It registers event listeners for when one of its scales changes its domain. When the scale - does change its domain, it re-propogates the change to every linked scale. - */ - this._rescaleInProgress = false; - if (scales == null) { - throw new Error("ScaleDomainCoordinator requires scales to coordinate"); - } - this._scales = scales; - this._scales.forEach(function (s) { return s.broadcaster.registerListener(_this, function (sx) { return _this.rescale(sx); }); }); - } - ScaleDomainCoordinator.prototype.rescale = function (scale) { - if (this._rescaleInProgress) { - return; - } - this._rescaleInProgress = true; - var newDomain = scale.domain(); - this._scales.forEach(function (s) { return s.domain(newDomain); }); - this._rescaleInProgress = false; - }; - return ScaleDomainCoordinator; - })(); - _Util.ScaleDomainCoordinator = ScaleDomainCoordinator; - })(_Util = Plottable._Util || (Plottable._Util = {})); -})(Plottable || (Plottable = {})); - -/// -var Plottable; -(function (Plottable) { - var Scale; - (function (Scale) { - var TickGenerators; - (function (TickGenerators) { - /** - * Creates a tick generator using the specified interval. + * Creates a tick generator using the specified interval. * * Generates ticks at multiples of the interval while also including the domain boundaries. * @@ -2709,7 +2371,7 @@ var Plottable; var firstTick = Math.ceil(low / interval) * interval; var numTicks = Math.floor((high - firstTick) / interval) + 1; var lowTicks = low % interval === 0 ? [] : [low]; - var middleTicks = Plottable._Util.Methods.range(0, numTicks).map(function (t) { return firstTick + t * interval; }); + var middleTicks = Plottable.Utils.Methods.range(0, numTicks).map(function (t) { return firstTick + t * interval; }); var highTicks = high % interval === 0 ? [] : [high]; return lowTicks.concat(middleTicks).concat(highTicks); }; @@ -2729,15 +2391,15 @@ var Plottable; }; } TickGenerators.integerTickGenerator = integerTickGenerator; - })(TickGenerators = Scale.TickGenerators || (Scale.TickGenerators = {})); - })(Scale = Plottable.Scale || (Plottable.Scale = {})); + })(TickGenerators = Scales.TickGenerators || (Scales.TickGenerators = {})); + })(Scales = Plottable.Scales || (Plottable.Scales = {})); })(Plottable || (Plottable = {})); /// var Plottable; (function (Plottable) { - var _Drawer; - (function (_Drawer) { + var Drawers; + (function (Drawers) { var AbstractDrawer = (function () { /** * Constructs a Drawer @@ -2787,10 +2449,10 @@ var Plottable; AbstractDrawer.prototype._numberOfAnimationIterations = function (data) { return data.length; }; - AbstractDrawer.prototype._applyMetadata = function (attrToProjector, userMetadata, plotMetadata) { + AbstractDrawer.prototype._applyMetadata = function (attrToProjector, dataset, plotMetadata) { var modifiedAttrToProjector = {}; d3.keys(attrToProjector).forEach(function (attr) { - modifiedAttrToProjector[attr] = function (datum, index) { return attrToProjector[attr](datum, index, userMetadata, plotMetadata); }; + modifiedAttrToProjector[attr] = function (datum, index) { return attrToProjector[attr](datum, index, dataset, plotMetadata); }; }); return modifiedAttrToProjector; }; @@ -2805,14 +2467,14 @@ var Plottable; * * @param{any[]} data The data to be drawn * @param{DrawStep[]} drawSteps The list of steps, which needs to be drawn - * @param{any} userMetadata The metadata provided by user + * @param{Dataset} dataset The Dataset * @param{any} plotMetadata The metadata provided by plot */ - AbstractDrawer.prototype.draw = function (data, drawSteps, userMetadata, plotMetadata) { + AbstractDrawer.prototype.draw = function (data, drawSteps, dataset, plotMetadata) { var _this = this; var appliedDrawSteps = drawSteps.map(function (dr) { - var appliedAttrToProjector = _this._applyMetadata(dr.attrToProjector, userMetadata, plotMetadata); - _this._attrToProjector = Plottable._Util.Methods.copyMap(appliedAttrToProjector); + var appliedAttrToProjector = _this._applyMetadata(dr.attrToProjector, dataset, plotMetadata); + _this._attrToProjector = Plottable.Utils.Methods.copyMap(appliedAttrToProjector); return { attrToProjector: appliedAttrToProjector, animator: dr.animator @@ -2824,7 +2486,7 @@ var Plottable; var numberOfIterations = this._numberOfAnimationIterations(preparedData); var delay = 0; appliedDrawSteps.forEach(function (drawStep, i) { - Plottable._Util.Methods.setTimeout(function () { return _this._drawStep(drawStep); }, delay); + Plottable.Utils.Methods.setTimeout(function () { return _this._drawStep(drawStep); }, delay); delay += drawStep.animator.getTiming(numberOfIterations); }); return delay; @@ -2849,8 +2511,8 @@ var Plottable; }; return AbstractDrawer; })(); - _Drawer.AbstractDrawer = AbstractDrawer; - })(_Drawer = Plottable._Drawer || (Plottable._Drawer = {})); + Drawers.AbstractDrawer = AbstractDrawer; + })(Drawers = Plottable.Drawers || (Plottable.Drawers = {})); })(Plottable || (Plottable = {})); /// @@ -2862,8 +2524,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var _Drawer; - (function (_Drawer) { + var Drawers; + (function (Drawers) { var Line = (function (_super) { __extends(Line, _super); function Line() { @@ -2890,8 +2552,8 @@ var Plottable; return 1; }; Line.prototype._drawStep = function (step) { - var baseTime = _super.prototype._drawStep.call(this, step); - var attrToProjector = Plottable._Util.Methods.copyMap(step.attrToProjector); + _super.prototype._drawStep.call(this, step); + var attrToProjector = Plottable.Utils.Methods.copyMap(step.attrToProjector); var definedFunction = attrToProjector["defined"]; var xProjector = attrToProjector["x"]; var yProjector = attrToProjector["y"]; @@ -2922,9 +2584,9 @@ var Plottable; }; Line.LINE_CLASS = "line"; return Line; - })(_Drawer.AbstractDrawer); - _Drawer.Line = Line; - })(_Drawer = Plottable._Drawer || (Plottable._Drawer = {})); + })(Drawers.AbstractDrawer); + Drawers.Line = Line; + })(Drawers = Plottable.Drawers || (Plottable.Drawers = {})); })(Plottable || (Plottable = {})); /// @@ -2936,8 +2598,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var _Drawer; - (function (_Drawer) { + var Drawers; + (function (Drawers) { var Area = (function (_super) { __extends(Area, _super); function Area() { @@ -2950,7 +2612,7 @@ var Plottable; } else { // HACKHACK Forced to use anycast to access protected var - _Drawer.AbstractDrawer.prototype._enterData.call(this, data); + Drawers.AbstractDrawer.prototype._enterData.call(this, data); } this._areaSelection.datum(data); }; @@ -2969,7 +2631,7 @@ var Plottable; _super.prototype.setup.call(this, area); } else { - _Drawer.AbstractDrawer.prototype.setup.call(this, area); + Drawers.AbstractDrawer.prototype.setup.call(this, area); } }; Area.prototype._createArea = function (xFunction, y0Function, y1Function, definedFunction) { @@ -2984,9 +2646,9 @@ var Plottable; } else { // HACKHACK Forced to use anycast to access protected var - _Drawer.AbstractDrawer.prototype._drawStep.call(this, step); + Drawers.AbstractDrawer.prototype._drawStep.call(this, step); } - var attrToProjector = Plottable._Util.Methods.copyMap(step.attrToProjector); + var attrToProjector = Plottable.Utils.Methods.copyMap(step.attrToProjector); var xFunction = attrToProjector["x"]; var y0Function = attrToProjector["y0"]; var y1Function = attrToProjector["y"]; @@ -3013,9 +2675,9 @@ var Plottable; }; Area.AREA_CLASS = "area"; return Area; - })(_Drawer.Line); - _Drawer.Area = Area; - })(_Drawer = Plottable._Drawer || (Plottable._Drawer = {})); + })(Drawers.Line); + Drawers.Area = Area; + })(Drawers = Plottable.Drawers || (Plottable.Drawers = {})); })(Plottable || (Plottable = {})); /// @@ -3027,8 +2689,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var _Drawer; - (function (_Drawer) { + var Drawers; + (function (Drawers) { var Element = (function (_super) { __extends(Element, _super); function Element() { @@ -3083,9 +2745,9 @@ var Plottable; return this._svgElement; }; return Element; - })(_Drawer.AbstractDrawer); - _Drawer.Element = Element; - })(_Drawer = Plottable._Drawer || (Plottable._Drawer = {})); + })(Drawers.AbstractDrawer); + Drawers.Element = Element; + })(Drawers = Plottable.Drawers || (Plottable.Drawers = {})); })(Plottable || (Plottable = {})); /// @@ -3097,8 +2759,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var _Drawer; - (function (_Drawer) { + var Drawers; + (function (Drawers) { var LABEL_VERTICAL_PADDING = 5; var LABEL_HORIZONTAL_PADDING = 5; var Rect = (function (_super) { @@ -3133,7 +2795,7 @@ var Plottable; var positive = attrToProjector["positive"](d, i, userMetadata, plotMetadata); var measurement = _this._measurer.measure(text); var color = attrToProjector["fill"](d, i, userMetadata, plotMetadata); - var dark = Plottable._Util.Color.contrast("white", color) * 1.6 < Plottable._Util.Color.contrast("black", color); + var dark = Plottable.Utils.Colors.contrast("white", color) * 1.6 < Plottable.Utils.Colors.contrast("black", color); var primary = _this._isVertical ? h : w; var primarySpace = _this._isVertical ? measurement.height : measurement.width; var secondaryAttrTextSpace = _this._isVertical ? measurement.width : measurement.height; @@ -3186,16 +2848,16 @@ var Plottable; }; Rect.prototype.draw = function (data, drawSteps, userMetadata, plotMetadata) { var attrToProjector = drawSteps[0].attrToProjector; - var isValidNumber = Plottable._Util.Methods.isValidNumber; + var isValidNumber = Plottable.Utils.Methods.isValidNumber; data = data.filter(function (e, i) { return isValidNumber(attrToProjector["x"](e, null, userMetadata, plotMetadata)) && isValidNumber(attrToProjector["y"](e, null, userMetadata, plotMetadata)) && isValidNumber(attrToProjector["width"](e, null, userMetadata, plotMetadata)) && isValidNumber(attrToProjector["height"](e, null, userMetadata, plotMetadata)); }); return _super.prototype.draw.call(this, data, drawSteps, userMetadata, plotMetadata); }; return Rect; - })(_Drawer.Element); - _Drawer.Rect = Rect; - })(_Drawer = Plottable._Drawer || (Plottable._Drawer = {})); + })(Drawers.Element); + Drawers.Rect = Rect; + })(Drawers = Plottable.Drawers || (Plottable.Drawers = {})); })(Plottable || (Plottable = {})); /// @@ -3207,8 +2869,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var _Drawer; - (function (_Drawer) { + var Drawers; + (function (Drawers) { var Arc = (function (_super) { __extends(Arc, _super); function Arc(key) { @@ -3226,7 +2888,7 @@ var Plottable; return retargetedAttrToProjector; }; Arc.prototype._drawStep = function (step) { - var attrToProjector = Plottable._Util.Methods.copyMap(step.attrToProjector); + var attrToProjector = Plottable.Utils.Methods.copyMap(step.attrToProjector); attrToProjector = this.retargetProjectors(attrToProjector); this._attrToProjector = this.retargetProjectors(this._attrToProjector); var innerRadiusAccessor = attrToProjector["inner-radius"]; @@ -3236,18 +2898,18 @@ var Plottable; attrToProjector["d"] = this._createArc(innerRadiusAccessor, outerRadiusAccessor); return _super.prototype._drawStep.call(this, { attrToProjector: attrToProjector, animator: step.animator }); }; - Arc.prototype.draw = function (data, drawSteps, userMetadata, plotMetadata) { + Arc.prototype.draw = function (data, drawSteps, dataset, plotMetadata) { // HACKHACK Applying metadata should be done in base class - var valueAccessor = function (d, i) { return drawSteps[0].attrToProjector["value"](d, i, userMetadata, plotMetadata); }; - data = data.filter(function (e) { return Plottable._Util.Methods.isValidNumber(+valueAccessor(e, null)); }); + var valueAccessor = function (d, i) { return drawSteps[0].attrToProjector["sector-value"](d, i, dataset, plotMetadata); }; + data = data.filter(function (e) { return Plottable.Utils.Methods.isValidNumber(+valueAccessor(e, null)); }); var pie = d3.layout.pie().sort(null).value(valueAccessor)(data); - drawSteps.forEach(function (s) { return delete s.attrToProjector["value"]; }); + drawSteps.forEach(function (s) { return delete s.attrToProjector["sector-value"]; }); pie.forEach(function (slice) { if (slice.value < 0) { - Plottable._Util.Methods.warn("Negative values will not render correctly in a pie chart."); + Plottable.Utils.Methods.warn("Negative values will not render correctly in a pie chart."); } }); - return _super.prototype.draw.call(this, pie, drawSteps, userMetadata, plotMetadata); + return _super.prototype.draw.call(this, pie, drawSteps, dataset, plotMetadata); }; Arc.prototype._getPixelPoint = function (datum, index) { var innerRadiusAccessor = this._attrToProjector["inner-radius"]; @@ -3259,9 +2921,9 @@ var Plottable; return { x: avgRadius * Math.sin(avgAngle), y: -avgRadius * Math.cos(avgAngle) }; }; return Arc; - })(_Drawer.Element); - _Drawer.Arc = Arc; - })(_Drawer = Plottable._Drawer || (Plottable._Drawer = {})); + })(Drawers.Element); + Drawers.Arc = Arc; + })(Drawers = Plottable.Drawers || (Plottable.Drawers = {})); })(Plottable || (Plottable = {})); /// @@ -3273,8 +2935,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var _Drawer; - (function (_Drawer) { + var Drawers; + (function (Drawers) { var Symbol = (function (_super) { __extends(Symbol, _super); function Symbol(key) { @@ -3284,7 +2946,7 @@ var Plottable; } Symbol.prototype._drawStep = function (step) { var attrToProjector = step.attrToProjector; - this._attrToProjector = Plottable._Util.Methods.copyMap(step.attrToProjector); + this._attrToProjector = Plottable.Utils.Methods.copyMap(step.attrToProjector); var xProjector = attrToProjector["x"]; var yProjector = attrToProjector["y"]; delete attrToProjector["x"]; @@ -3301,576 +2963,502 @@ var Plottable; return { x: this._attrToProjector["x"](datum, index), y: this._attrToProjector["y"](datum, index) }; }; return Symbol; - })(_Drawer.Element); - _Drawer.Symbol = Symbol; - })(_Drawer = Plottable._Drawer || (Plottable._Drawer = {})); + })(Drawers.Element); + Drawers.Symbol = Symbol; + })(Drawers = Plottable.Drawers || (Plottable.Drawers = {})); })(Plottable || (Plottable = {})); /// -var __extends = this.__extends || function (d, b) { - for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; - function __() { this.constructor = d; } - __.prototype = b.prototype; - d.prototype = new __(); -}; var Plottable; (function (Plottable) { - var Component; - (function (Component) { - var AbstractComponent = (function (_super) { - __extends(AbstractComponent, _super); - function AbstractComponent() { - _super.apply(this, arguments); - this.clipPathEnabled = false; - this._xAlignProportion = 0; // What % along the free space do we want to position (0 = left, .5 = center, 1 = right) - this._yAlignProportion = 0; - this._fixedHeightFlag = false; - this._fixedWidthFlag = false; - this._isSetup = false; - this._isAnchored = false; - this._interactionsToRegister = []; - this._boxes = []; - this._isTopLevelComponent = false; - this._xOffset = 0; // Offset from Origin, used for alignment and floating positioning - this._yOffset = 0; - this._cssClasses = ["component"]; - this._removed = false; - this._usedLastLayout = false; - } - /** - * Attaches the Component as a child of a given a DOM element. Usually only directly invoked on root-level Components. - * - * @param {D3.Selection} element A D3 selection consisting of the element to anchor under. - */ - AbstractComponent.prototype._anchor = function (element) { - if (this._removed) { - throw new Error("Can't reuse remove()-ed components!"); - } - if (element.node().nodeName.toLowerCase() === "svg") { - // svg node gets the "plottable" CSS class - this._rootSVG = element; - this._rootSVG.classed("plottable", true); - // visible overflow for firefox https://stackoverflow.com/questions/5926986/why-does-firefox-appear-to-truncate-embedded-svgs - this._rootSVG.style("overflow", "visible"); - this._isTopLevelComponent = true; - } - if (this._element != null) { - // reattach existing element - element.node().appendChild(this._element.node()); - } - else { - this._element = element.append("g"); - this._setup(); - } - this._isAnchored = true; + var Components; + (function (Components) { + var Alignment = (function () { + function Alignment() { + } + Alignment.TOP = "top"; + Alignment.BOTTOM = "bottom"; + Alignment.LEFT = "left"; + Alignment.RIGHT = "right"; + Alignment.CENTER = "center"; + return Alignment; + })(); + Components.Alignment = Alignment; + })(Components = Plottable.Components || (Plottable.Components = {})); + var Component = (function () { + function Component() { + this._clipPathEnabled = false; + this._origin = { x: 0, y: 0 }; // Origin of the coordinate space for the Component. + this._xAlignment = "left"; + this._yAlignment = "top"; + this._isSetup = false; + this._isAnchored = false; + this._boxes = []; + this._isTopLevelComponent = false; + this._cssClasses = ["component"]; + this._destroyed = false; + this._onAnchorCallbacks = new Plottable.Utils.CallbackSet(); + this._onDetachCallbacks = new Plottable.Utils.CallbackSet(); + } + /** + * Attaches the Component as a child of a given D3 Selection. + * + * @param {D3.Selection} selection The Selection containing the Element to anchor under. + * @returns {Component} The calling Component. + */ + Component.prototype.anchor = function (selection) { + if (this._destroyed) { + throw new Error("Can't reuse destroy()-ed components!"); + } + if (selection.node().nodeName.toLowerCase() === "svg") { + // svg node gets the "plottable" CSS class + this._rootSVG = selection; + this._rootSVG.classed("plottable", true); + // visible overflow for firefox https://stackoverflow.com/questions/5926986/why-does-firefox-appear-to-truncate-embedded-svgs + this._rootSVG.style("overflow", "visible"); + this._isTopLevelComponent = true; + } + if (this._element != null) { + // reattach existing element + selection.node().appendChild(this._element.node()); + } + else { + this._element = selection.append("g"); + this._setup(); + } + this._isAnchored = true; + this._onAnchorCallbacks.callCallbacks(this); + return this; + }; + /** + * Adds a callback to be called on anchoring the Component to the DOM. + * If the component is already anchored, the callback is called immediately. + * + * @param {ComponentCallback} callback The callback to be added. + * + * @return {Component} + */ + Component.prototype.onAnchor = function (callback) { + if (this._isAnchored) { + callback(this); + } + this._onAnchorCallbacks.add(callback); + return this; + }; + /** + * Removes a callback to be called on anchoring the Component to the DOM. + * The callback is identified by reference equality. + * + * @param {ComponentCallback} callback The callback to be removed. + * + * @return {Component} + */ + Component.prototype.offAnchor = function (callback) { + this._onAnchorCallbacks.delete(callback); + return this; + }; + /** + * Creates additional elements as necessary for the Component to function. + * Called during anchor() if the Component's element has not been created yet. + * Override in subclasses to provide additional functionality. + */ + Component.prototype._setup = function () { + var _this = this; + if (this._isSetup) { + return; + } + this._cssClasses.forEach(function (cssClass) { + _this._element.classed(cssClass, true); + }); + this._cssClasses = null; + this._backgroundContainer = this._element.append("g").classed("background-container", true); + this._addBox("background-fill", this._backgroundContainer); + this._content = this._element.append("g").classed("content", true); + this._foregroundContainer = this._element.append("g").classed("foreground-container", true); + this._boxContainer = this._element.append("g").classed("box-container", true); + if (this._clipPathEnabled) { + this._generateClipPath(); + } + ; + this._boundingBox = this._addBox("bounding-box"); + this._isSetup = true; + }; + Component.prototype.requestedSpace = function (availableWidth, availableHeight) { + return { + minWidth: 0, + minHeight: 0 }; - /** - * Creates additional elements as necessary for the Component to function. - * Called during _anchor() if the Component's element has not been created yet. - * Override in subclasses to provide additional functionality. - */ - AbstractComponent.prototype._setup = function () { - var _this = this; - if (this._isSetup) { - return; - } - this._cssClasses.forEach(function (cssClass) { - _this._element.classed(cssClass, true); - }); - this._cssClasses = null; - this._backgroundContainer = this._element.append("g").classed("background-container", true); - this._addBox("background-fill", this._backgroundContainer); - this._content = this._element.append("g").classed("content", true); - this._foregroundContainer = this._element.append("g").classed("foreground-container", true); - this._boxContainer = this._element.append("g").classed("box-container", true); - if (this.clipPathEnabled) { - this._generateClipPath(); + }; + /** + * Computes the size, position, and alignment from the specified values. + * If no parameters are supplied and the Component is a root node, + * they are inferred from the size of the Component's element. + * + * @param {Point} origin Origin of the space offered to the Component. + * @param {number} availableWidth + * @param {number} availableHeight + * @returns {Component} The calling Component. + */ + Component.prototype.computeLayout = function (origin, availableWidth, availableHeight) { + var _this = this; + if (origin == null || availableWidth == null || availableHeight == null) { + if (this._element == null) { + throw new Error("anchor() must be called before computeLayout()"); } - ; - this._boundingBox = this._addBox("bounding-box"); - this._interactionsToRegister.forEach(function (r) { return _this.registerInteraction(r); }); - this._interactionsToRegister = null; - this._isSetup = true; - }; - AbstractComponent.prototype._requestedSpace = function (availableWidth, availableHeight) { - return { width: 0, height: 0, wantsWidth: false, wantsHeight: false }; - }; - /** - * Computes the size, position, and alignment from the specified values. - * If no parameters are supplied and the Component is a root node, - * they are inferred from the size of the Component's element. - * - * @param {number} offeredXOrigin x-coordinate of the origin of the space offered the Component - * @param {number} offeredYOrigin y-coordinate of the origin of the space offered the Component - * @param {number} availableWidth available width for the Component to render in - * @param {number} availableHeight available height for the Component to render in - */ - AbstractComponent.prototype._computeLayout = function (offeredXOrigin, offeredYOrigin, availableWidth, availableHeight) { - var _this = this; - if (offeredXOrigin == null || offeredYOrigin == null || availableWidth == null || availableHeight == null) { - if (this._element == null) { - throw new Error("anchor must be called before computeLayout"); - } - else if (this._isTopLevelComponent) { - // we are the root node, retrieve height/width from root SVG - offeredXOrigin = 0; - offeredYOrigin = 0; - // Set width/height to 100% if not specified, to allow accurate size calculation - // see http://www.w3.org/TR/CSS21/visudet.html#block-replaced-width - // and http://www.w3.org/TR/CSS21/visudet.html#inline-replaced-height - if (this._rootSVG.attr("width") == null) { - this._rootSVG.attr("width", "100%"); - } - if (this._rootSVG.attr("height") == null) { - this._rootSVG.attr("height", "100%"); - } - var elem = this._rootSVG.node(); - availableWidth = Plottable._Util.DOM.getElementWidth(elem); - availableHeight = Plottable._Util.DOM.getElementHeight(elem); + else if (this._isTopLevelComponent) { + // we are the root node, retrieve height/width from root SVG + origin = { x: 0, y: 0 }; + // Set width/height to 100% if not specified, to allow accurate size calculation + // see http://www.w3.org/TR/CSS21/visudet.html#block-replaced-width + // and http://www.w3.org/TR/CSS21/visudet.html#inline-replaced-height + if (this._rootSVG.attr("width") == null) { + this._rootSVG.attr("width", "100%"); } - else { - throw new Error("null arguments cannot be passed to _computeLayout() on a non-root node"); + if (this._rootSVG.attr("height") == null) { + this._rootSVG.attr("height", "100%"); } - } - var size = this._getSize(availableWidth, availableHeight); - this._width = size.width; - this._height = size.height; - this._xOrigin = offeredXOrigin + this._xOffset + (availableWidth - this.width()) * this._xAlignProportion; - this._yOrigin = offeredYOrigin + this._yOffset + (availableHeight - this.height()) * this._yAlignProportion; - this._element.attr("transform", "translate(" + this._xOrigin + "," + this._yOrigin + ")"); - this._boxes.forEach(function (b) { return b.attr("width", _this.width()).attr("height", _this.height()); }); - }; - AbstractComponent.prototype._getSize = function (availableWidth, availableHeight) { - var requestedSpace = this._requestedSpace(availableWidth, availableHeight); - return { - width: this._isFixedWidth() ? Math.min(availableWidth, requestedSpace.width) : availableWidth, - height: this._isFixedHeight() ? Math.min(availableHeight, requestedSpace.height) : availableHeight - }; - }; - AbstractComponent.prototype._render = function () { - if (this._isAnchored && this._isSetup && this.width() >= 0 && this.height() >= 0) { - Plottable.Core.RenderController.registerToRender(this); - } - }; - AbstractComponent.prototype._scheduleComputeLayout = function () { - if (this._isAnchored && this._isSetup) { - Plottable.Core.RenderController.registerToComputeLayout(this); - } - }; - AbstractComponent.prototype._doRender = function () { - }; - AbstractComponent.prototype._useLastCalculatedLayout = function (useLast) { - if (useLast == null) { - return this._usedLastLayout; + var elem = this._rootSVG.node(); + availableWidth = Plottable.Utils.DOM.getElementWidth(elem); + availableHeight = Plottable.Utils.DOM.getElementHeight(elem); } else { - this._usedLastLayout = useLast; - return this; - } - }; - AbstractComponent.prototype._invalidateLayout = function () { - this._useLastCalculatedLayout(false); - if (this._isAnchored && this._isSetup) { - if (this._isTopLevelComponent) { - this._scheduleComputeLayout(); - } - else { - this._parent()._invalidateLayout(); - } - } - }; - /** - * Renders the Component into a given DOM element. The element must be as . - * - * @param {String|D3.Selection} element A D3 selection or a selector for getting the element to render into. - * @returns {Component} The calling component. - */ - AbstractComponent.prototype.renderTo = function (element) { - this.detach(); - if (element != null) { - var selection; - if (typeof (element) === "string") { - selection = d3.select(element); - } - else { - selection = element; - } - if (!selection.node() || selection.node().nodeName.toLowerCase() !== "svg") { - throw new Error("Plottable requires a valid SVG to renderTo"); - } - this._anchor(selection); + throw new Error("null arguments cannot be passed to computeLayout() on a non-root node"); } - if (this._element == null) { - throw new Error("If a component has never been rendered before, then renderTo must be given a node to render to, \ - or a D3.Selection, or a selector string"); - } - this._computeLayout(); - this._render(); - // flush so that consumers can immediately attach to stuff we create in the DOM - Plottable.Core.RenderController.flush(); - return this; + } + var size = this._getSize(availableWidth, availableHeight); + this._width = size.width; + this._height = size.height; + var xAlignProportion = Component._xAlignToProportion[this._xAlignment]; + var yAlignProportion = Component._yAlignToProportion[this._yAlignment]; + this._origin = { + x: origin.x + (availableWidth - this.width()) * xAlignProportion, + y: origin.y + (availableHeight - this.height()) * yAlignProportion }; - /** - * Causes the Component to recompute layout and redraw. - * - * This function should be called when CSS changes could influence the size - * of the components, e.g. changing the font size. - * - * @returns {Component} The calling component. - */ - AbstractComponent.prototype.redraw = function () { - this._invalidateLayout(); - return this; + this._element.attr("transform", "translate(" + this._origin.x + "," + this._origin.y + ")"); + this._boxes.forEach(function (b) { return b.attr("width", _this.width()).attr("height", _this.height()); }); + return this; + }; + Component.prototype._getSize = function (availableWidth, availableHeight) { + var requestedSpace = this.requestedSpace(availableWidth, availableHeight); + return { + width: this.fixedWidth() ? Math.min(availableWidth, requestedSpace.minWidth) : availableWidth, + height: this.fixedHeight() ? Math.min(availableHeight, requestedSpace.minHeight) : availableHeight }; - /** - * Sets the x alignment of the Component. This will be used if the - * Component is given more space than it needs. - * - * For example, you may want to make a Legend postition itself it the top - * right, so you would call `legend.xAlign("right")` and - * `legend.yAlign("top")`. - * - * @param {string} alignment The x alignment of the Component (one of ["left", "center", "right"]). - * @returns {Component} The calling Component. - */ - AbstractComponent.prototype.xAlign = function (alignment) { - alignment = alignment.toLowerCase(); - if (alignment === "left") { - this._xAlignProportion = 0; - } - else if (alignment === "center") { - this._xAlignProportion = 0.5; - } - else if (alignment === "right") { - this._xAlignProportion = 1; + }; + /** + * Queues the Component for rendering. Set immediately to true if the Component should be rendered + * immediately as opposed to queued to the RenderController. + * + * @returns {Component} The calling Component + */ + Component.prototype.render = function () { + if (this._isAnchored && this._isSetup && this.width() >= 0 && this.height() >= 0) { + Plottable.RenderController.registerToRender(this); + } + return this; + }; + Component.prototype._scheduleComputeLayout = function () { + if (this._isAnchored && this._isSetup) { + Plottable.RenderController.registerToComputeLayout(this); + } + }; + Component.prototype.renderImmediately = function () { + return this; + }; + /** + * Causes the Component to recompute layout and redraw. + * + * This function should be called when CSS changes could influence the size + * of the components, e.g. changing the font size. + * + * @returns {Component} The calling Component. + */ + Component.prototype.redraw = function () { + if (this._isAnchored && this._isSetup) { + if (this._isTopLevelComponent) { + this._scheduleComputeLayout(); } else { - throw new Error("Unsupported alignment"); - } - this._invalidateLayout(); - return this; - }; - /** - * Sets the y alignment of the Component. This will be used if the - * Component is given more space than it needs. - * - * For example, you may want to make a Legend postition itself it the top - * right, so you would call `legend.xAlign("right")` and - * `legend.yAlign("top")`. - * - * @param {string} alignment The x alignment of the Component (one of ["top", "center", "bottom"]). - * @returns {Component} The calling Component. - */ - AbstractComponent.prototype.yAlign = function (alignment) { - alignment = alignment.toLowerCase(); - if (alignment === "top") { - this._yAlignProportion = 0; + this.parent().redraw(); } - else if (alignment === "center") { - this._yAlignProportion = 0.5; - } - else if (alignment === "bottom") { - this._yAlignProportion = 1; + } + return this; + }; + /** + * Renders the Component into a given DOM element. The element must be as . + * + * @param {String|D3.Selection} element A D3 selection or a selector for getting the element to render into. + * @returns {Component} The calling component. + */ + Component.prototype.renderTo = function (element) { + this.detach(); + if (element != null) { + var selection; + if (typeof (element) === "string") { + selection = d3.select(element); } else { - throw new Error("Unsupported alignment"); - } - this._invalidateLayout(); - return this; - }; - /** - * Sets the x offset of the Component. This will be used if the Component - * is given more space than it needs. - * - * @param {number} offset The desired x offset, in pixels, from the left - * side of the container. - * @returns {Component} The calling Component. - */ - AbstractComponent.prototype.xOffset = function (offset) { - this._xOffset = offset; - this._invalidateLayout(); - return this; - }; - /** - * Sets the y offset of the Component. This will be used if the Component - * is given more space than it needs. - * - * @param {number} offset The desired y offset, in pixels, from the top - * side of the container. - * @returns {Component} The calling Component. - */ - AbstractComponent.prototype.yOffset = function (offset) { - this._yOffset = offset; - this._invalidateLayout(); - return this; - }; - AbstractComponent.prototype._addBox = function (className, parentElement) { - if (this._element == null) { - throw new Error("Adding boxes before anchoring is currently disallowed"); + selection = element; } - parentElement = parentElement == null ? this._boxContainer : parentElement; - var box = parentElement.append("rect"); - if (className != null) { - box.classed(className, true); + if (!selection.node() || selection.node().nodeName.toLowerCase() !== "svg") { + throw new Error("Plottable requires a valid SVG to renderTo"); } - this._boxes.push(box); - if (this.width() != null && this.height() != null) { - box.attr("width", this.width()).attr("height", this.height()); + this.anchor(selection); + } + if (this._element == null) { + throw new Error("If a component has never been rendered before, then renderTo must be given a node to render to, \ + or a D3.Selection, or a selector string"); + } + this.computeLayout(); + this.render(); + // flush so that consumers can immediately attach to stuff we create in the DOM + Plottable.RenderController.flush(); + return this; + }; + Component.prototype.xAlignment = function (xAlignment) { + if (xAlignment == null) { + return this._xAlignment; + } + xAlignment = xAlignment.toLowerCase(); + if (Component._xAlignToProportion[xAlignment] == null) { + throw new Error("Unsupported alignment: " + xAlignment); + } + this._xAlignment = xAlignment; + this.redraw(); + return this; + }; + Component.prototype.yAlignment = function (yAlignment) { + if (yAlignment == null) { + return this._yAlignment; + } + yAlignment = yAlignment.toLowerCase(); + if (Component._yAlignToProportion[yAlignment] == null) { + throw new Error("Unsupported alignment: " + yAlignment); + } + this._yAlignment = yAlignment; + this.redraw(); + return this; + }; + Component.prototype._addBox = function (className, parentElement) { + if (this._element == null) { + throw new Error("Adding boxes before anchoring is currently disallowed"); + } + parentElement = parentElement == null ? this._boxContainer : parentElement; + var box = parentElement.append("rect"); + if (className != null) { + box.classed(className, true); + } + this._boxes.push(box); + if (this.width() != null && this.height() != null) { + box.attr("width", this.width()).attr("height", this.height()); + } + return box; + }; + Component.prototype._generateClipPath = function () { + // The clip path will prevent content from overflowing its component space. + // HACKHACK: IE <=9 does not respect the HTML base element in SVG. + // They don't need the current URL in the clip path reference. + var prefix = /MSIE [5-9]/.test(navigator.userAgent) ? "" : document.location.href; + prefix = prefix.split("#")[0]; // To fix cases where an anchor tag was used + var clipPathId = Plottable.Utils.DOM.getUniqueClipPathId(); + this._element.attr("clip-path", "url(\"" + prefix + "#" + clipPathId + "\")"); + var clipPathParent = this._boxContainer.append("clipPath").attr("id", clipPathId); + this._addBox("clip-rect", clipPathParent); + }; + Component.prototype.classed = function (cssClass, addClass) { + if (addClass == null) { + if (cssClass == null) { + return false; } - return box; - }; - AbstractComponent.prototype._generateClipPath = function () { - // The clip path will prevent content from overflowing its component space. - // HACKHACK: IE <=9 does not respect the HTML base element in SVG. - // They don't need the current URL in the clip path reference. - var prefix = /MSIE [5-9]/.test(navigator.userAgent) ? "" : document.location.href; - prefix = prefix.split("#")[0]; // To fix cases where an anchor tag was used - this._element.attr("clip-path", "url(\"" + prefix + "#clipPath" + this.getID() + "\")"); - var clipPathParent = this._boxContainer.append("clipPath").attr("id", "clipPath" + this.getID()); - this._addBox("clip-rect", clipPathParent); - }; - /** - * Attaches an Interaction to the Component, so that the Interaction will listen for events on the Component. - * - * @param {Interaction} interaction The Interaction to attach to the Component. - * @returns {Component} The calling Component. - */ - AbstractComponent.prototype.registerInteraction = function (interaction) { - // Interactions can be registered before or after anchoring. If registered before, they are - // pushed to this._interactionsToRegister and registered during anchoring. If after, they are - // registered immediately - if (this._element) { - if (!this._hitBox && interaction._requiresHitbox()) { - this._hitBox = this._addBox("hit-box"); - this._hitBox.style("fill", "#ffffff").style("opacity", 0); // We need to set these so Chrome will register events - } - interaction._anchor(this, this._hitBox); + else if (this._element == null) { + return (this._cssClasses.indexOf(cssClass) !== -1); } else { - this._interactionsToRegister.push(interaction); + return this._element.classed(cssClass); } - return this; - }; - AbstractComponent.prototype.classed = function (cssClass, addClass) { - if (addClass == null) { - if (cssClass == null) { - return false; - } - else if (this._element == null) { - return (this._cssClasses.indexOf(cssClass) !== -1); - } - else { - return this._element.classed(cssClass); - } + } + else { + if (cssClass == null) { + return this; } - else { - if (cssClass == null) { - return this; - } - if (this._element == null) { - var classIndex = this._cssClasses.indexOf(cssClass); - if (addClass && classIndex === -1) { - this._cssClasses.push(cssClass); - } - else if (!addClass && classIndex !== -1) { - this._cssClasses.splice(classIndex, 1); - } + if (this._element == null) { + var classIndex = this._cssClasses.indexOf(cssClass); + if (addClass && classIndex === -1) { + this._cssClasses.push(cssClass); } - else { - this._element.classed(cssClass, addClass); + else if (!addClass && classIndex !== -1) { + this._cssClasses.splice(classIndex, 1); } - return this; - } - }; - /** - * Checks if the Component has a fixed width or false if it grows to fill available space. - * Returns false by default on the base Component class. - * - * @returns {boolean} Whether the component has a fixed width. - */ - AbstractComponent.prototype._isFixedWidth = function () { - return this._fixedWidthFlag; - }; - /** - * Checks if the Component has a fixed height or false if it grows to fill available space. - * Returns false by default on the base Component class. - * - * @returns {boolean} Whether the component has a fixed height. - */ - AbstractComponent.prototype._isFixedHeight = function () { - return this._fixedHeightFlag; - }; - AbstractComponent.prototype._merge = function (c, below) { - var cg; - if (Plottable.Component.Group.prototype.isPrototypeOf(c)) { - cg = c; - cg._addComponent(this, below); - return cg; } else { - var mergedComponents = below ? [this, c] : [c, this]; - cg = new Plottable.Component.Group(mergedComponents); - return cg; - } - }; - /** - * Merges this Component above another Component, returning a - * ComponentGroup. This is used to layer Components on top of each other. - * - * There are four cases: - * Component + Component: Returns a ComponentGroup with the first component after the second component. - * ComponentGroup + Component: Returns the ComponentGroup with the Component prepended. - * Component + ComponentGroup: Returns the ComponentGroup with the Component appended. - * ComponentGroup + ComponentGroup: Returns a new ComponentGroup with the first group after the second group. - * - * @param {Component} c The component to merge in. - * @returns {ComponentGroup} The relevant ComponentGroup out of the above four cases. - */ - AbstractComponent.prototype.above = function (c) { - return this._merge(c, false); - }; - /** - * Merges this Component below another Component, returning a - * ComponentGroup. This is used to layer Components on top of each other. - * - * There are four cases: - * Component + Component: Returns a ComponentGroup with the first component before the second component. - * ComponentGroup + Component: Returns the ComponentGroup with the Component appended. - * Component + ComponentGroup: Returns the ComponentGroup with the Component prepended. - * ComponentGroup + ComponentGroup: Returns a new ComponentGroup with the first group before the second group. - * - * @param {Component} c The component to merge in. - * @returns {ComponentGroup} The relevant ComponentGroup out of the above four cases. - */ - AbstractComponent.prototype.below = function (c) { - return this._merge(c, true); - }; - /** - * Detaches a Component from the DOM. The component can be reused. - * - * This should only be used if you plan on reusing the calling - * Components. Otherwise, use remove(). - * - * @returns The calling Component. - */ - AbstractComponent.prototype.detach = function () { - if (this._isAnchored) { - this._element.remove(); - } - var parent = this._parent(); - if (parent != null) { - parent._removeComponent(this); + this._element.classed(cssClass, addClass); } - this._isAnchored = false; - this._parentElement = null; return this; + } + }; + /** + * Checks if the Component has a fixed width or false if it grows to fill available space. + * Returns false by default on the base Component class. + * + * @returns {boolean} Whether the component has a fixed width. + */ + Component.prototype.fixedWidth = function () { + return false; + }; + /** + * Checks if the Component has a fixed height or false if it grows to fill available space. + * Returns false by default on the base Component class. + * + * @returns {boolean} Whether the component has a fixed height. + */ + Component.prototype.fixedHeight = function () { + return false; + }; + /** + * Detaches a Component from the DOM. The component can be reused. + * + * This should only be used if you plan on reusing the calling + * Components. Otherwise, use remove(). + * + * @returns The calling Component. + */ + Component.prototype.detach = function () { + this.parent(null); + if (this._isAnchored) { + this._element.remove(); + } + this._isAnchored = false; + this._onDetachCallbacks.callCallbacks(this); + return this; + }; + /** + * Adds a callback to be called when th Component is detach()-ed. + * + * @param {ComponentCallback} callback The callback to be added. + * @return {Component} The calling Component. + */ + Component.prototype.onDetach = function (callback) { + this._onDetachCallbacks.add(callback); + return this; + }; + /** + * Removes a callback to be called when th Component is detach()-ed. + * The callback is identified by reference equality. + * + * @param {ComponentCallback} callback The callback to be removed. + * @return {Component} The calling Component. + */ + Component.prototype.offDetach = function (callback) { + this._onDetachCallbacks.delete(callback); + return this; + }; + Component.prototype.parent = function (parent) { + if (parent === undefined) { + return this._parent; + } + if (parent !== null && !parent.has(this)) { + throw new Error("Passed invalid parent"); + } + this._parent = parent; + return this; + }; + /** + * Removes a Component from the DOM and disconnects it from everything it's + * listening to (effectively destroying it). + */ + Component.prototype.destroy = function () { + this._destroyed = true; + this.detach(); + }; + /** + * Return the width of the component + * + * @return {number} width of the component + */ + Component.prototype.width = function () { + return this._width; + }; + /** + * Return the height of the component + * + * @return {number} height of the component + */ + Component.prototype.height = function () { + return this._height; + }; + /** + * Gets the origin of the Component relative to its parent. + * + * @return {Point} The x-y position of the Component relative to its parent. + */ + Component.prototype.origin = function () { + return { + x: this._origin.x, + y: this._origin.y }; - AbstractComponent.prototype._parent = function (parentElement) { - if (parentElement === undefined) { - return this._parentElement; - } - this.detach(); - this._parentElement = parentElement; - }; - /** - * Removes a Component from the DOM and disconnects it from everything it's - * listening to (effectively destroying it). - */ - AbstractComponent.prototype.remove = function () { - this._removed = true; - this.detach(); - }; - /** - * Return the width of the component - * - * @return {number} width of the component - */ - AbstractComponent.prototype.width = function () { - return this._width; - }; - /** - * Return the height of the component - * - * @return {number} height of the component - */ - AbstractComponent.prototype.height = function () { - return this._height; - }; - /** - * Gets the origin of the Component relative to its parent. - * - * @return {Point} The x-y position of the Component relative to its parent. - */ - AbstractComponent.prototype.origin = function () { - return { - x: this._xOrigin, - y: this._yOrigin - }; - }; - /** - * Gets the origin of the Component relative to the root . - * - * @return {Point} The x-y position of the Component relative to the root - */ - AbstractComponent.prototype.originToSVG = function () { - var origin = this.origin(); - var ancestor = this._parent(); - while (ancestor != null) { - var ancestorOrigin = ancestor.origin(); - origin.x += ancestorOrigin.x; - origin.y += ancestorOrigin.y; - ancestor = ancestor._parent(); - } - return origin; - }; - /** - * Returns the foreground selection for the Component - * (A selection covering the front of the Component) - * - * Will return undefined if the Component has not been anchored. - * - * @return {D3.Selection} foreground selection for the Component - */ - AbstractComponent.prototype.foreground = function () { - return this._foregroundContainer; - }; - /** - * Returns the content selection for the Component - * (A selection containing the visual elements of the Component) - * - * Will return undefined if the Component has not been anchored. - * - * @return {D3.Selection} content selection for the Component - */ - AbstractComponent.prototype.content = function () { - return this._content; - }; - /** - * Returns the background selection for the Component - * (A selection appearing behind of the Component) - * - * Will return undefined if the Component has not been anchored. - * - * @return {D3.Selection} background selection for the Component - */ - AbstractComponent.prototype.background = function () { - return this._backgroundContainer; - }; - /** - * Returns the hitbox selection for the component - * (A selection in front of the foreground used mainly for interactions) - * - * Will return undefined if the component has not been anchored - * - * @return {D3.Selection} hitbox selection for the component - */ - AbstractComponent.prototype.hitBox = function () { - return this._hitBox; - }; - return AbstractComponent; - })(Plottable.Core.PlottableObject); - Component.AbstractComponent = AbstractComponent; - })(Component = Plottable.Component || (Plottable.Component = {})); + }; + /** + * Gets the origin of the Component relative to the root . + * + * @return {Point} The x-y position of the Component relative to the root + */ + Component.prototype.originToSVG = function () { + var origin = this.origin(); + var ancestor = this.parent(); + while (ancestor != null) { + var ancestorOrigin = ancestor.origin(); + origin.x += ancestorOrigin.x; + origin.y += ancestorOrigin.y; + ancestor = ancestor.parent(); + } + return origin; + }; + /** + * Returns the foreground selection for the Component + * (A selection covering the front of the Component) + * + * Will return undefined if the Component has not been anchored. + * + * @return {D3.Selection} foreground selection for the Component + */ + Component.prototype.foreground = function () { + return this._foregroundContainer; + }; + /** + * Returns the content selection for the Component + * (A selection containing the visual elements of the Component) + * + * Will return undefined if the Component has not been anchored. + * + * @return {D3.Selection} content selection for the Component + */ + Component.prototype.content = function () { + return this._content; + }; + /** + * Returns the background selection for the Component + * (A selection appearing behind of the Component) + * + * Will return undefined if the Component has not been anchored. + * + * @return {D3.Selection} background selection for the Component + */ + Component.prototype.background = function () { + return this._backgroundContainer; + }; + Component._xAlignToProportion = { + "left": 0, + "center": 0.5, + "right": 1 + }; + Component._yAlignToProportion = { + "top": 0, + "center": 0.5, + "bottom": 1 + }; + return Component; + })(); + Plottable.Component = Component; })(Plottable || (Plottable = {})); /// @@ -3882,93 +3470,77 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Component; - (function (Component) { - /* - * An abstract ComponentContainer class to encapsulate Table and ComponentGroup's shared functionality. - * It will not do anything if instantiated directly. + /* + * ComponentContainer class encapsulates Table and ComponentGroup's shared functionality. + * It will not do anything if instantiated directly. + */ + var ComponentContainer = (function (_super) { + __extends(ComponentContainer, _super); + function ComponentContainer() { + var _this = this; + _super.call(this); + this._detachCallback = function (component) { return _this.remove(component); }; + } + ComponentContainer.prototype.anchor = function (selection) { + var _this = this; + _super.prototype.anchor.call(this, selection); + this._forEach(function (c) { return c.anchor(_this._content); }); + return this; + }; + ComponentContainer.prototype.render = function () { + this._forEach(function (c) { return c.render(); }); + return this; + }; + /** + * Checks whether the specified Component is in the ComponentContainer. */ - var AbstractComponentContainer = (function (_super) { - __extends(AbstractComponentContainer, _super); - function AbstractComponentContainer() { - _super.apply(this, arguments); - this._components = []; + ComponentContainer.prototype.has = function (component) { + throw new Error("has() is not implemented on ComponentContainer"); + }; + ComponentContainer.prototype._adoptAndAnchor = function (component) { + component.parent(this); + component.onDetach(this._detachCallback); + if (this._isAnchored) { + component.anchor(this._content); } - AbstractComponentContainer.prototype._anchor = function (element) { - var _this = this; - _super.prototype._anchor.call(this, element); - this.components().forEach(function (c) { return c._anchor(_this._content); }); - }; - AbstractComponentContainer.prototype._render = function () { - this._components.forEach(function (c) { return c._render(); }); - }; - AbstractComponentContainer.prototype._removeComponent = function (c) { - var removeIndex = this._components.indexOf(c); - if (removeIndex >= 0) { - this.components().splice(removeIndex, 1); - this._invalidateLayout(); - } - }; - AbstractComponentContainer.prototype._addComponent = function (c, prepend) { - if (prepend === void 0) { prepend = false; } - if (!c || this._components.indexOf(c) >= 0) { - return false; - } - if (prepend) { - this.components().unshift(c); - } - else { - this.components().push(c); - } - c._parent(this); - if (this._isAnchored) { - c._anchor(this._content); - } - this._invalidateLayout(); - return true; - }; - /** - * Returns a list of components in the ComponentContainer. - * - * @returns {Component[]} the contained Components - */ - AbstractComponentContainer.prototype.components = function () { - return this._components; - }; - /** - * Returns true iff the ComponentContainer is empty. - * - * @returns {boolean} Whether the calling ComponentContainer is empty. - */ - AbstractComponentContainer.prototype.empty = function () { - return this._components.length === 0; - }; - /** - * Detaches all components contained in the ComponentContainer, and - * empties the ComponentContainer. - * - * @returns {ComponentContainer} The calling ComponentContainer - */ - AbstractComponentContainer.prototype.detachAll = function () { - // Calling c.remove() will mutate this._components because the component will call this._parent._removeComponent(this) - // Since mutating an array while iterating over it is dangerous, we instead iterate over a copy generated by Arr.slice() - this.components().slice().forEach(function (c) { return c.detach(); }); - return this; - }; - AbstractComponentContainer.prototype.remove = function () { - _super.prototype.remove.call(this); - this.components().slice().forEach(function (c) { return c.remove(); }); - }; - AbstractComponentContainer.prototype._useLastCalculatedLayout = function (calculated) { - if (calculated != null) { - this.components().slice().forEach(function (c) { return c._useLastCalculatedLayout(calculated); }); - } - return _super.prototype._useLastCalculatedLayout.call(this, calculated); - }; - return AbstractComponentContainer; - })(Component.AbstractComponent); - Component.AbstractComponentContainer = AbstractComponentContainer; - })(Component = Plottable.Component || (Plottable.Component = {})); + }; + /** + * Removes the specified Component from the ComponentContainer. + */ + ComponentContainer.prototype.remove = function (component) { + if (this.has(component)) { + component.offDetach(this._detachCallback); + this._remove(component); + component.detach(); + this.redraw(); + } + return this; + }; + /** + * Carry out the actual removal of a Component. + * Implementation dependent on the type of container. + * + * @return {boolean} true if the Component was successfully removed, false otherwise. + */ + ComponentContainer.prototype._remove = function (component) { + return false; + }; + /** + * Invokes a callback on each Component in the ComponentContainer. + */ + ComponentContainer.prototype._forEach = function (callback) { + throw new Error("_forEach() is not implemented on ComponentContainer"); + }; + /** + * Destroys the ComponentContainer and all Components within it. + */ + ComponentContainer.prototype.destroy = function () { + _super.prototype.destroy.call(this); + this._forEach(function (c) { return c.destroy(); }); + }; + return ComponentContainer; + })(Plottable.Component); + Plottable.ComponentContainer = ComponentContainer; })(Plottable || (Plottable = {})); /// @@ -3980,19 +3552,15 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Component; - (function (Component) { + var Components; + (function (Components) { var Group = (function (_super) { __extends(Group, _super); /** * Constructs a Component.Group. * * A Component.Group is a set of Components that will be rendered on top of - * each other. When you call Component.above(Component) or Component.below(Component), - * it creates and returns a Component.Group. - * - * Note that the order of the components will determine placement on the z-axis, - * with the previous items rendered below the later items. + * each other. Components added later will be rendered on top of existing Components. * * @constructor * @param {Component[]} components The Components in the resultant Component.Group (default = []). @@ -4001,27 +3569,31 @@ var Plottable; var _this = this; if (components === void 0) { components = []; } _super.call(this); + this._components = []; this.classed("component-group", true); - components.forEach(function (c) { return _this._addComponent(c); }); + components.forEach(function (c) { return _this.append(c); }); } - Group.prototype._requestedSpace = function (offeredWidth, offeredHeight) { - var requests = this.components().map(function (c) { return c._requestedSpace(offeredWidth, offeredHeight); }); + Group.prototype._forEach = function (callback) { + this._components.forEach(callback); + }; + /** + * Checks whether the specified Component is in the Group. + */ + Group.prototype.has = function (component) { + return this._components.indexOf(component) >= 0; + }; + Group.prototype.requestedSpace = function (offeredWidth, offeredHeight) { + var requests = this._components.map(function (c) { return c.requestedSpace(offeredWidth, offeredHeight); }); return { - width: Plottable._Util.Methods.max(requests, function (request) { return request.width; }, 0), - height: Plottable._Util.Methods.max(requests, function (request) { return request.height; }, 0), - wantsWidth: requests.map(function (r) { return r.wantsWidth; }).some(function (x) { return x; }), - wantsHeight: requests.map(function (r) { return r.wantsHeight; }).some(function (x) { return x; }) + minWidth: Plottable.Utils.Methods.max(requests, function (request) { return request.minWidth; }, 0), + minHeight: Plottable.Utils.Methods.max(requests, function (request) { return request.minHeight; }, 0) }; }; - Group.prototype._merge = function (c, below) { - this._addComponent(c, !below); - return this; - }; - Group.prototype._computeLayout = function (offeredXOrigin, offeredYOrigin, availableWidth, availableHeight) { + Group.prototype.computeLayout = function (origin, availableWidth, availableHeight) { var _this = this; - _super.prototype._computeLayout.call(this, offeredXOrigin, offeredYOrigin, availableWidth, availableHeight); - this.components().forEach(function (c) { - c._computeLayout(0, 0, _this.width(), _this.height()); + _super.prototype.computeLayout.call(this, origin, availableWidth, availableHeight); + this._forEach(function (component) { + component.computeLayout({ x: 0, y: 0 }, _this.width(), _this.height()); }); return this; }; @@ -4031,16 +3603,39 @@ var Plottable; height: availableHeight }; }; - Group.prototype._isFixedWidth = function () { - return this.components().every(function (c) { return c._isFixedWidth(); }); + Group.prototype.fixedWidth = function () { + return this._components.every(function (c) { return c.fixedWidth(); }); + }; + Group.prototype.fixedHeight = function () { + return this._components.every(function (c) { return c.fixedHeight(); }); + }; + /** + * @return {Component[]} The Components in this Group. + */ + Group.prototype.components = function () { + return this._components.slice(); + }; + Group.prototype.append = function (component) { + if (component != null && !this.has(component)) { + component.detach(); + this._components.push(component); + this._adoptAndAnchor(component); + this.redraw(); + } + return this; }; - Group.prototype._isFixedHeight = function () { - return this.components().every(function (c) { return c._isFixedHeight(); }); + Group.prototype._remove = function (component) { + var removeIndex = this._components.indexOf(component); + if (removeIndex >= 0) { + this._components.splice(removeIndex, 1); + return true; + } + return false; }; return Group; - })(Component.AbstractComponentContainer); - Component.Group = Group; - })(Component = Plottable.Component || (Plottable.Component = {})); + })(Plottable.ComponentContainer); + Components.Group = Group; + })(Components = Plottable.Components || (Plottable.Components = {})); })(Plottable || (Plottable = {})); /// @@ -4052,318 +3647,316 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Axis; - (function (Axis) { - var AbstractAxis = (function (_super) { - __extends(AbstractAxis, _super); - /** - * Constructs an axis. An axis is a wrapper around a scale for rendering. - * - * @constructor - * @param {Scale} scale The scale for this axis to render. - * @param {string} orientation One of ["top", "left", "bottom", "right"]; - * on which side the axis will appear. On most axes, this is either "left" - * or "bottom". - * @param {Formatter} Data is passed through this formatter before being - * displayed. - */ - function AbstractAxis(scale, orientation, formatter) { - var _this = this; - if (formatter === void 0) { formatter = Plottable.Formatters.identity(); } - _super.call(this); - this._endTickLength = 5; - this._tickLength = 5; - this._tickLabelPadding = 10; - this._gutter = 15; - this._showEndTickLabels = false; - if (scale == null || orientation == null) { - throw new Error("Axis requires a scale and orientation"); - } - this._scale = scale; - this.orient(orientation); - this._setDefaultAlignment(); - this.classed("axis", true); - if (this._isHorizontal()) { - this.classed("x-axis", true); + var Axis = (function (_super) { + __extends(Axis, _super); + /** + * Constructs an axis. An axis is a wrapper around a scale for rendering. + * + * @constructor + * @param {Scale} scale The scale for this axis to render. + * @param {string} orientation One of ["top", "left", "bottom", "right"]; + * on which side the axis will appear. On most axes, this is either "left" + * or "bottom". + * @param {Formatter} Data is passed through this formatter before being + * displayed. + */ + function Axis(scale, orientation, formatter) { + var _this = this; + if (formatter === void 0) { formatter = Plottable.Formatters.identity(); } + _super.call(this); + this._endTickLength = 5; + this._tickLength = 5; + this._tickLabelPadding = 10; + this._gutter = 15; + this._showEndTickLabels = false; + if (scale == null || orientation == null) { + throw new Error("Axis requires a scale and orientation"); + } + this._scale = scale; + this.orientation(orientation); + this._setDefaultAlignment(); + this.classed("axis", true); + if (this._isHorizontal()) { + this.classed("x-axis", true); + } + else { + this.classed("y-axis", true); + } + this.formatter(formatter); + this._rescaleCallback = function (scale) { return _this._rescale(); }; + this._scale.onUpdate(this._rescaleCallback); + } + Axis.prototype.destroy = function () { + _super.prototype.destroy.call(this); + this._scale.offUpdate(this._rescaleCallback); + }; + Axis.prototype._isHorizontal = function () { + return this._orientation === "top" || this._orientation === "bottom"; + }; + Axis.prototype._computeWidth = function () { + // to be overridden by subclass logic + this._computedWidth = this._maxLabelTickLength(); + return this._computedWidth; + }; + Axis.prototype._computeHeight = function () { + // to be overridden by subclass logic + this._computedHeight = this._maxLabelTickLength(); + return this._computedHeight; + }; + Axis.prototype.requestedSpace = function (offeredWidth, offeredHeight) { + var requestedWidth = 0; + var requestedHeight = 0; + if (this._isHorizontal()) { + if (this._computedHeight == null) { + this._computeHeight(); } - else { - this.classed("y-axis", true); + requestedHeight = this._computedHeight + this._gutter; + } + else { + if (this._computedWidth == null) { + this._computeWidth(); } - this.formatter(formatter); - this._scale.broadcaster.registerListener(this, function () { return _this._rescale(); }); + requestedWidth = this._computedWidth + this._gutter; } - AbstractAxis.prototype.remove = function () { - _super.prototype.remove.call(this); - this._scale.broadcaster.deregisterListener(this); + return { + minWidth: requestedWidth, + minHeight: requestedHeight }; - AbstractAxis.prototype._isHorizontal = function () { - return this._orientation === "top" || this._orientation === "bottom"; - }; - AbstractAxis.prototype._computeWidth = function () { - // to be overridden by subclass logic - this._computedWidth = this._maxLabelTickLength(); - return this._computedWidth; - }; - AbstractAxis.prototype._computeHeight = function () { - // to be overridden by subclass logic - this._computedHeight = this._maxLabelTickLength(); - return this._computedHeight; - }; - AbstractAxis.prototype._requestedSpace = function (offeredWidth, offeredHeight) { - var requestedWidth = 0; - var requestedHeight = 0; - if (this._isHorizontal()) { - if (this._computedHeight == null) { - this._computeHeight(); - } - requestedHeight = this._computedHeight + this._gutter; + }; + Axis.prototype.fixedHeight = function () { + return this._isHorizontal(); + }; + Axis.prototype.fixedWidth = function () { + return !this._isHorizontal(); + }; + Axis.prototype._rescale = function () { + // default implementation; subclasses may call redraw() here + this.render(); + }; + Axis.prototype.computeLayout = function (origin, availableWidth, availableHeight) { + _super.prototype.computeLayout.call(this, origin, availableWidth, availableHeight); + if (this._isHorizontal()) { + this._scale.range([0, this.width()]); + } + else { + this._scale.range([this.height(), 0]); + } + return this; + }; + Axis.prototype._setup = function () { + _super.prototype._setup.call(this); + this._tickMarkContainer = this._content.append("g").classed(Axis.TICK_MARK_CLASS + "-container", true); + this._tickLabelContainer = this._content.append("g").classed(Axis.TICK_LABEL_CLASS + "-container", true); + this._baseline = this._content.append("line").classed("baseline", true); + }; + /* + * Function for generating tick values in data-space (as opposed to pixel values). + * To be implemented by subclasses. + */ + Axis.prototype._getTickValues = function () { + return []; + }; + Axis.prototype.renderImmediately = function () { + var tickMarkValues = this._getTickValues(); + var tickMarks = this._tickMarkContainer.selectAll("." + Axis.TICK_MARK_CLASS).data(tickMarkValues); + tickMarks.enter().append("line").classed(Axis.TICK_MARK_CLASS, true); + tickMarks.attr(this._generateTickMarkAttrHash()); + d3.select(tickMarks[0][0]).classed(Axis.END_TICK_MARK_CLASS, true).attr(this._generateTickMarkAttrHash(true)); + d3.select(tickMarks[0][tickMarkValues.length - 1]).classed(Axis.END_TICK_MARK_CLASS, true).attr(this._generateTickMarkAttrHash(true)); + tickMarks.exit().remove(); + this._baseline.attr(this._generateBaselineAttrHash()); + return this; + }; + Axis.prototype._generateBaselineAttrHash = function () { + var baselineAttrHash = { + x1: 0, + y1: 0, + x2: 0, + y2: 0 + }; + switch (this._orientation) { + case "bottom": + baselineAttrHash.x2 = this.width(); + break; + case "top": + baselineAttrHash.x2 = this.width(); + baselineAttrHash.y1 = this.height(); + baselineAttrHash.y2 = this.height(); + break; + case "left": + baselineAttrHash.x1 = this.width(); + baselineAttrHash.x2 = this.width(); + baselineAttrHash.y2 = this.height(); + break; + case "right": + baselineAttrHash.y2 = this.height(); + break; + } + return baselineAttrHash; + }; + Axis.prototype._generateTickMarkAttrHash = function (isEndTickMark) { + var _this = this; + if (isEndTickMark === void 0) { isEndTickMark = false; } + var tickMarkAttrHash = { + x1: 0, + y1: 0, + x2: 0, + y2: 0 + }; + var scalingFunction = function (d) { return _this._scale.scale(d); }; + if (this._isHorizontal()) { + tickMarkAttrHash["x1"] = scalingFunction; + tickMarkAttrHash["x2"] = scalingFunction; + } + else { + tickMarkAttrHash["y1"] = scalingFunction; + tickMarkAttrHash["y2"] = scalingFunction; + } + var tickLength = isEndTickMark ? this._endTickLength : this._tickLength; + switch (this._orientation) { + case "bottom": + tickMarkAttrHash["y2"] = tickLength; + break; + case "top": + tickMarkAttrHash["y1"] = this.height(); + tickMarkAttrHash["y2"] = this.height() - tickLength; + break; + case "left": + tickMarkAttrHash["x1"] = this.width(); + tickMarkAttrHash["x2"] = this.width() - tickLength; + break; + case "right": + tickMarkAttrHash["x2"] = tickLength; + break; + } + return tickMarkAttrHash; + }; + Axis.prototype.redraw = function () { + this._computedWidth = null; + this._computedHeight = null; + return _super.prototype.redraw.call(this); + }; + Axis.prototype._setDefaultAlignment = function () { + switch (this._orientation) { + case "bottom": + this.yAlignment("top"); + break; + case "top": + this.yAlignment("bottom"); + break; + case "left": + this.xAlignment("right"); + break; + case "right": + this.xAlignment("left"); + break; + } + }; + Axis.prototype.formatter = function (formatter) { + if (formatter === undefined) { + return this._formatter; + } + this._formatter = formatter; + this.redraw(); + return this; + }; + Axis.prototype.tickLength = function (length) { + if (length == null) { + return this._tickLength; + } + else { + if (length < 0) { + throw new Error("tick length must be positive"); } - else { - if (this._computedWidth == null) { - this._computeWidth(); - } - requestedWidth = this._computedWidth + this._gutter; + this._tickLength = length; + this.redraw(); + return this; + } + }; + Axis.prototype.endTickLength = function (length) { + if (length == null) { + return this._endTickLength; + } + else { + if (length < 0) { + throw new Error("end tick length must be positive"); } - return { - width: requestedWidth, - height: requestedHeight, - wantsWidth: !this._isHorizontal() && offeredWidth < requestedWidth, - wantsHeight: this._isHorizontal() && offeredHeight < requestedHeight - }; - }; - AbstractAxis.prototype._isFixedHeight = function () { - return this._isHorizontal(); - }; - AbstractAxis.prototype._isFixedWidth = function () { - return !this._isHorizontal(); - }; - AbstractAxis.prototype._rescale = function () { - // default implementation; subclasses may call _invalidateLayout() here - this._render(); - }; - AbstractAxis.prototype._computeLayout = function (offeredXOrigin, offeredYOrigin, availableWidth, availableHeight) { - _super.prototype._computeLayout.call(this, offeredXOrigin, offeredYOrigin, availableWidth, availableHeight); - if (this._isHorizontal()) { - this._scale.range([0, this.width()]); + this._endTickLength = length; + this.redraw(); + return this; + } + }; + Axis.prototype._maxLabelTickLength = function () { + if (this.showEndTickLabels()) { + return Math.max(this.tickLength(), this.endTickLength()); + } + else { + return this.tickLength(); + } + }; + Axis.prototype.tickLabelPadding = function (padding) { + if (padding == null) { + return this._tickLabelPadding; + } + else { + if (padding < 0) { + throw new Error("tick label padding must be positive"); } - else { - this._scale.range([this.height(), 0]); + this._tickLabelPadding = padding; + this.redraw(); + return this; + } + }; + Axis.prototype.gutter = function (size) { + if (size == null) { + return this._gutter; + } + else { + if (size < 0) { + throw new Error("gutter size must be positive"); } - }; - AbstractAxis.prototype._setup = function () { - _super.prototype._setup.call(this); - this._tickMarkContainer = this._content.append("g").classed(AbstractAxis.TICK_MARK_CLASS + "-container", true); - this._tickLabelContainer = this._content.append("g").classed(AbstractAxis.TICK_LABEL_CLASS + "-container", true); - this._baseline = this._content.append("line").classed("baseline", true); - }; - /* - * Function for generating tick values in data-space (as opposed to pixel values). - * To be implemented by subclasses. - */ - AbstractAxis.prototype._getTickValues = function () { - return []; - }; - AbstractAxis.prototype._doRender = function () { - var tickMarkValues = this._getTickValues(); - var tickMarks = this._tickMarkContainer.selectAll("." + AbstractAxis.TICK_MARK_CLASS).data(tickMarkValues); - tickMarks.enter().append("line").classed(AbstractAxis.TICK_MARK_CLASS, true); - tickMarks.attr(this._generateTickMarkAttrHash()); - d3.select(tickMarks[0][0]).classed(AbstractAxis.END_TICK_MARK_CLASS, true).attr(this._generateTickMarkAttrHash(true)); - d3.select(tickMarks[0][tickMarkValues.length - 1]).classed(AbstractAxis.END_TICK_MARK_CLASS, true).attr(this._generateTickMarkAttrHash(true)); - tickMarks.exit().remove(); - this._baseline.attr(this._generateBaselineAttrHash()); - }; - AbstractAxis.prototype._generateBaselineAttrHash = function () { - var baselineAttrHash = { - x1: 0, - y1: 0, - x2: 0, - y2: 0 - }; - switch (this._orientation) { - case "bottom": - baselineAttrHash.x2 = this.width(); - break; - case "top": - baselineAttrHash.x2 = this.width(); - baselineAttrHash.y1 = this.height(); - baselineAttrHash.y2 = this.height(); - break; - case "left": - baselineAttrHash.x1 = this.width(); - baselineAttrHash.x2 = this.width(); - baselineAttrHash.y2 = this.height(); - break; - case "right": - baselineAttrHash.y2 = this.height(); - break; - } - return baselineAttrHash; - }; - AbstractAxis.prototype._generateTickMarkAttrHash = function (isEndTickMark) { - var _this = this; - if (isEndTickMark === void 0) { isEndTickMark = false; } - var tickMarkAttrHash = { - x1: 0, - y1: 0, - x2: 0, - y2: 0 - }; - var scalingFunction = function (d) { return _this._scale.scale(d); }; - if (this._isHorizontal()) { - tickMarkAttrHash["x1"] = scalingFunction; - tickMarkAttrHash["x2"] = scalingFunction; - } - else { - tickMarkAttrHash["y1"] = scalingFunction; - tickMarkAttrHash["y2"] = scalingFunction; - } - var tickLength = isEndTickMark ? this._endTickLength : this._tickLength; - switch (this._orientation) { - case "bottom": - tickMarkAttrHash["y2"] = tickLength; - break; - case "top": - tickMarkAttrHash["y1"] = this.height(); - tickMarkAttrHash["y2"] = this.height() - tickLength; - break; - case "left": - tickMarkAttrHash["x1"] = this.width(); - tickMarkAttrHash["x2"] = this.width() - tickLength; - break; - case "right": - tickMarkAttrHash["x2"] = tickLength; - break; - } - return tickMarkAttrHash; - }; - AbstractAxis.prototype._invalidateLayout = function () { - this._computedWidth = null; - this._computedHeight = null; - _super.prototype._invalidateLayout.call(this); - }; - AbstractAxis.prototype._setDefaultAlignment = function () { - switch (this._orientation) { - case "bottom": - this.yAlign("top"); - break; - case "top": - this.yAlign("bottom"); - break; - case "left": - this.xAlign("right"); - break; - case "right": - this.xAlign("left"); - break; - } - }; - AbstractAxis.prototype.formatter = function (formatter) { - if (formatter === undefined) { - return this._formatter; - } - this._formatter = formatter; - this._invalidateLayout(); + this._gutter = size; + this.redraw(); return this; - }; - AbstractAxis.prototype.tickLength = function (length) { - if (length == null) { - return this._tickLength; - } - else { - if (length < 0) { - throw new Error("tick length must be positive"); - } - this._tickLength = length; - this._invalidateLayout(); - return this; - } - }; - AbstractAxis.prototype.endTickLength = function (length) { - if (length == null) { - return this._endTickLength; - } - else { - if (length < 0) { - throw new Error("end tick length must be positive"); - } - this._endTickLength = length; - this._invalidateLayout(); - return this; - } - }; - AbstractAxis.prototype._maxLabelTickLength = function () { - if (this.showEndTickLabels()) { - return Math.max(this.tickLength(), this.endTickLength()); - } - else { - return this.tickLength(); - } - }; - AbstractAxis.prototype.tickLabelPadding = function (padding) { - if (padding == null) { - return this._tickLabelPadding; - } - else { - if (padding < 0) { - throw new Error("tick label padding must be positive"); - } - this._tickLabelPadding = padding; - this._invalidateLayout(); - return this; - } - }; - AbstractAxis.prototype.gutter = function (size) { - if (size == null) { - return this._gutter; - } - else { - if (size < 0) { - throw new Error("gutter size must be positive"); - } - this._gutter = size; - this._invalidateLayout(); - return this; - } - }; - AbstractAxis.prototype.orient = function (newOrientation) { - if (newOrientation == null) { - return this._orientation; - } - else { - var newOrientationLC = newOrientation.toLowerCase(); - if (newOrientationLC !== "top" && newOrientationLC !== "bottom" && newOrientationLC !== "left" && newOrientationLC !== "right") { - throw new Error("unsupported orientation"); - } - this._orientation = newOrientationLC; - this._invalidateLayout(); - return this; - } - }; - AbstractAxis.prototype.showEndTickLabels = function (show) { - if (show == null) { - return this._showEndTickLabels; + } + }; + Axis.prototype.orientation = function (orientation) { + if (orientation == null) { + return this._orientation; + } + else { + var newOrientationLC = orientation.toLowerCase(); + if (newOrientationLC !== "top" && newOrientationLC !== "bottom" && newOrientationLC !== "left" && newOrientationLC !== "right") { + throw new Error("unsupported orientation"); } - this._showEndTickLabels = show; - this._render(); + this._orientation = newOrientationLC; + this.redraw(); return this; - }; - /** - * The css class applied to each end tick mark (the line on the end tick). - */ - AbstractAxis.END_TICK_MARK_CLASS = "end-tick-mark"; - /** - * The css class applied to each tick mark (the line on the tick). - */ - AbstractAxis.TICK_MARK_CLASS = "tick-mark"; - /** - * The css class applied to each tick label (the text associated with the tick). - */ - AbstractAxis.TICK_LABEL_CLASS = "tick-label"; - return AbstractAxis; - })(Plottable.Component.AbstractComponent); - Axis.AbstractAxis = AbstractAxis; - })(Axis = Plottable.Axis || (Plottable.Axis = {})); + } + }; + Axis.prototype.showEndTickLabels = function (show) { + if (show == null) { + return this._showEndTickLabels; + } + this._showEndTickLabels = show; + this.render(); + return this; + }; + /** + * The css class applied to each end tick mark (the line on the end tick). + */ + Axis.END_TICK_MARK_CLASS = "end-tick-mark"; + /** + * The css class applied to each tick mark (the line on the tick). + */ + Axis.TICK_MARK_CLASS = "tick-mark"; + /** + * The css class applied to each tick label (the text associated with the tick). + */ + Axis.TICK_LABEL_CLASS = "tick-label"; + return Axis; + })(Plottable.Component); + Plottable.Axis = Axis; })(Plottable || (Plottable = {})); /// @@ -4375,8 +3968,19 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Axis; - (function (Axis) { + var TimeInterval; + (function (TimeInterval) { + TimeInterval.second = "second"; + TimeInterval.minute = "minute"; + TimeInterval.hour = "hour"; + TimeInterval.day = "day"; + TimeInterval.week = "week"; + TimeInterval.month = "month"; + TimeInterval.year = "year"; + })(TimeInterval = Plottable.TimeInterval || (Plottable.TimeInterval = {})); + ; + var Axes; + (function (Axes) { var Time = (function (_super) { __extends(Time, _super); /** @@ -4404,7 +4008,7 @@ var Plottable; throw new Error("Unsupported position for tier labels"); } this._tierLabelPositions = newPositions; - this._invalidateLayout(); + this.redraw(); return this; } }; @@ -4413,7 +4017,7 @@ var Plottable; return this._possibleTimeAxisConfigurations; } this._possibleTimeAxisConfigurations = configurations; - this._numTiers = Plottable._Util.Methods.max(this._possibleTimeAxisConfigurations.map(function (config) { return config.length; }), 0); + this._numTiers = Plottable.Utils.Methods.max(this._possibleTimeAxisConfigurations.map(function (config) { return config.length; }), 0); if (this._isAnchored) { this._setupDomElements(); } @@ -4423,7 +4027,7 @@ var Plottable; newLabelPositions.push(oldLabelPositions[i] || "between"); } this.tierLabelPositions(newLabelPositions); - this._invalidateLayout(); + this.redraw(); return this; }; /** @@ -4438,16 +4042,16 @@ var Plottable; } }); if (mostPreciseIndex === this._possibleTimeAxisConfigurations.length) { - Plottable._Util.Methods.warn("zoomed out too far: could not find suitable interval to display labels"); + Plottable.Utils.Methods.warn("zoomed out too far: could not find suitable interval to display labels"); --mostPreciseIndex; } return mostPreciseIndex; }; - Time.prototype.orient = function (orientation) { + Time.prototype.orientation = function (orientation) { if (orientation && (orientation.toLowerCase() === "right" || orientation.toLowerCase() === "left")) { throw new Error(orientation + " is not a supported orientation for TimeAxis - only horizontal orientations are supported"); } - return _super.prototype.orient.call(this, orientation); // maintains getter-setter functionality + return _super.prototype.orientation.call(this, orientation); // maintains getter-setter functionality }; Time.prototype._computeHeight = function () { var textHeight = this._measurer.measure().height; @@ -4460,7 +4064,8 @@ var Plottable; }; Time.prototype._getIntervalLength = function (config) { var startDate = this._scale.domain()[0]; - var endDate = config.interval.offset(startDate, config.step); + var d3Interval = Plottable.Formatters.timeIntervalToD3Time(config.interval); + var endDate = d3Interval.offset(startDate, config.step); if (endDate > this._scale.domain()[1]) { // this offset is too large, so just return available width return this.width(); @@ -4501,8 +4106,8 @@ var Plottable; this._baseline.remove(); for (var i = 0; i < this._numTiers; ++i) { var tierContainer = this._content.append("g").classed(Time.TIME_AXIS_TIER_CLASS, true); - this._tierLabelContainers.push(tierContainer.append("g").classed(Axis.AbstractAxis.TICK_LABEL_CLASS + "-container", true)); - this._tierMarkContainers.push(tierContainer.append("g").classed(Axis.AbstractAxis.TICK_MARK_CLASS + "-container", true)); + this._tierLabelContainers.push(tierContainer.append("g").classed(Plottable.Axis.TICK_LABEL_CLASS + "-container", true)); + this._tierMarkContainers.push(tierContainer.append("g").classed(Plottable.Axis.TICK_MARK_CLASS + "-container", true)); this._tierBaselines.push(tierContainer.append("line").classed("baseline", true)); } this._measurer = new SVGTypewriter.Measurers.Measurer(this._tierLabelContainers[0]); @@ -4516,8 +4121,8 @@ var Plottable; }; Time.prototype._cleanTiers = function () { for (var index = 0; index < this._tierLabelContainers.length; index++) { - this._tierLabelContainers[index].selectAll("." + Axis.AbstractAxis.TICK_LABEL_CLASS).remove(); - this._tierMarkContainers[index].selectAll("." + Axis.AbstractAxis.TICK_MARK_CLASS).remove(); + this._tierLabelContainers[index].selectAll("." + Plottable.Axis.TICK_LABEL_CLASS).remove(); + this._tierMarkContainers[index].selectAll("." + Plottable.Axis.TICK_MARK_CLASS).remove(); this._tierBaselines[index].style("visibility", "hidden"); } }; @@ -4548,15 +4153,14 @@ var Plottable; else { labelPos = tickPos; } - var tickLabels = container.selectAll("." + Axis.AbstractAxis.TICK_LABEL_CLASS).data(labelPos, function (d) { return d.valueOf(); }); - var tickLabelsEnter = tickLabels.enter().append("g").classed(Axis.AbstractAxis.TICK_LABEL_CLASS, true); + var tickLabels = container.selectAll("." + Plottable.Axis.TICK_LABEL_CLASS).data(labelPos, function (d) { return d.valueOf(); }); + var tickLabelsEnter = tickLabels.enter().append("g").classed(Plottable.Axis.TICK_LABEL_CLASS, true); tickLabelsEnter.append("text"); var xTranslate = (this._tierLabelPositions[index] === "center" || config.step === 1) ? 0 : this.tickLabelPadding(); - var markLength = this._measurer.measure().height; - var yTranslate = this.orient() === "bottom" ? d3.sum(this._tierHeights.slice(0, index + 1)) - this.tickLabelPadding() : this.height() - d3.sum(this._tierHeights.slice(0, index)) - this.tickLabelPadding(); + var yTranslate = this.orientation() === "bottom" ? d3.sum(this._tierHeights.slice(0, index + 1)) - this.tickLabelPadding() : this.height() - d3.sum(this._tierHeights.slice(0, index)) - this.tickLabelPadding(); var textSelection = tickLabels.selectAll("text"); if (textSelection.size() > 0) { - Plottable._Util.DOM.translate(textSelection, xTranslate, yTranslate); + Plottable.Utils.DOM.translate(textSelection, xTranslate, yTranslate); } tickLabels.exit().remove(); tickLabels.attr("transform", function (d) { return "translate(" + _this._scale.scale(d) + ",0)"; }); @@ -4564,11 +4168,11 @@ var Plottable; tickLabels.selectAll("text").text(config.formatter).style("text-anchor", anchor); }; Time.prototype._renderTickMarks = function (tickValues, index) { - var tickMarks = this._tierMarkContainers[index].selectAll("." + Axis.AbstractAxis.TICK_MARK_CLASS).data(tickValues); - tickMarks.enter().append("line").classed(Axis.AbstractAxis.TICK_MARK_CLASS, true); + var tickMarks = this._tierMarkContainers[index].selectAll("." + Plottable.Axis.TICK_MARK_CLASS).data(tickValues); + tickMarks.enter().append("line").classed(Plottable.Axis.TICK_MARK_CLASS, true); var attr = this._generateTickMarkAttrHash(); var offset = this._tierHeights.slice(0, index).reduce(function (translate, height) { return translate + height; }, 0); - if (this.orient() === "bottom") { + if (this.orientation() === "bottom") { attr["y1"] = offset; attr["y2"] = offset + (this._tierLabelPositions[index] === "center" ? this.tickLength() : this._tierHeights[index]); } @@ -4577,7 +4181,7 @@ var Plottable; attr["y2"] = this.height() - (offset + (this._tierLabelPositions[index] === "center" ? this.tickLength() : this._tierHeights[index])); } tickMarks.attr(attr); - if (this.orient() === "bottom") { + if (this.orientation() === "bottom") { attr["y1"] = offset; attr["y2"] = offset + this._tierHeights[index]; } @@ -4587,15 +4191,15 @@ var Plottable; } d3.select(tickMarks[0][0]).attr(attr); // Add end-tick classes to first and last tick for CSS customization purposes - d3.select(tickMarks[0][0]).classed(Axis.AbstractAxis.END_TICK_MARK_CLASS, true); - d3.select(tickMarks[0][tickMarks.size() - 1]).classed(Axis.AbstractAxis.END_TICK_MARK_CLASS, true); + d3.select(tickMarks[0][0]).classed(Plottable.Axis.END_TICK_MARK_CLASS, true); + d3.select(tickMarks[0][tickMarks.size() - 1]).classed(Plottable.Axis.END_TICK_MARK_CLASS, true); tickMarks.exit().remove(); }; Time.prototype._renderLabellessTickMarks = function (tickValues) { - var tickMarks = this._tickMarkContainer.selectAll("." + Axis.AbstractAxis.TICK_MARK_CLASS).data(tickValues); - tickMarks.enter().append("line").classed(Axis.AbstractAxis.TICK_MARK_CLASS, true); + var tickMarks = this._tickMarkContainer.selectAll("." + Plottable.Axis.TICK_MARK_CLASS).data(tickValues); + tickMarks.enter().append("line").classed(Plottable.Axis.TICK_MARK_CLASS, true); var attr = this._generateTickMarkAttrHash(); - attr["y2"] = (this.orient() === "bottom") ? this.tickLabelPadding() : this.height() - this.tickLabelPadding(); + attr["y2"] = (this.orientation() === "bottom") ? this.tickLabelPadding() : this.height() - this.tickLabelPadding(); tickMarks.attr(attr); tickMarks.exit().remove(); }; @@ -4605,7 +4209,7 @@ var Plottable; } return this._getTickIntervalValues(this._possibleTimeAxisConfigurations[this._mostPreciseConfigIndex - 1][0]); }; - Time.prototype._doRender = function () { + Time.prototype.renderImmediately = function () { var _this = this; this._mostPreciseConfigIndex = this._getMostPreciseConfigurationIndex(); var tierConfigs = this._possibleTimeAxisConfigurations[this._mostPreciseConfigIndex]; @@ -4615,7 +4219,7 @@ var Plottable; var baselineOffset = 0; for (var i = 0; i < Math.max(tierConfigs.length, 1); ++i) { var attr = this._generateBaselineAttrHash(); - attr["y1"] += (this.orient() === "bottom") ? baselineOffset : -baselineOffset; + attr["y1"] += (this.orientation() === "bottom") ? baselineOffset : -baselineOffset; attr["y2"] = attr["y1"]; this._tierBaselines[i].attr(attr).style("visibility", "inherit"); baselineOffset += this._tierHeights[i]; @@ -4649,13 +4253,13 @@ var Plottable; var isInsideBBox = function (tickBox) { return (Math.floor(boundingBox.left) <= Math.ceil(tickBox.left) && Math.floor(boundingBox.top) <= Math.ceil(tickBox.top) && Math.floor(tickBox.right) <= Math.ceil(boundingBox.left + _this.width()) && Math.floor(tickBox.bottom) <= Math.ceil(boundingBox.top + _this.height())); }; - var visibleTickMarks = this._tierMarkContainers[index].selectAll("." + Axis.AbstractAxis.TICK_MARK_CLASS).filter(function (d, i) { + var visibleTickMarks = this._tierMarkContainers[index].selectAll("." + Plottable.Axis.TICK_MARK_CLASS).filter(function (d, i) { var visibility = d3.select(this).style("visibility"); return visibility === "visible" || visibility === "inherit"; }); // We use the ClientRects because x1/x2 attributes are not comparable to ClientRects of labels var visibleTickMarkRects = visibleTickMarks[0].map(function (mark) { return mark.getBoundingClientRect(); }); - var visibleTickLabels = this._tierLabelContainers[index].selectAll("." + Axis.AbstractAxis.TICK_LABEL_CLASS).filter(function (d, i) { + var visibleTickLabels = this._tierLabelContainers[index].selectAll("." + Plottable.Axis.TICK_LABEL_CLASS).filter(function (d, i) { var visibility = d3.select(this).style("visibility"); return visibility === "visible" || visibility === "inherit"; }); @@ -4665,7 +4269,7 @@ var Plottable; var tickLabel = d3.select(this); var leadingTickMark = visibleTickMarkRects[i]; var trailingTickMark = visibleTickMarkRects[i + 1]; - if (!isInsideBBox(clientRect) || (lastLabelClientRect != null && Plottable._Util.DOM.boxesOverlap(clientRect, lastLabelClientRect)) || (leadingTickMark.right > clientRect.left || trailingTickMark.left < clientRect.right)) { + if (!isInsideBBox(clientRect) || (lastLabelClientRect != null && Plottable.Utils.DOM.boxesOverlap(clientRect, lastLabelClientRect)) || (leadingTickMark.right > clientRect.left || trailingTickMark.left < clientRect.right)) { tickLabel.style("visibility", "hidden"); } else { @@ -4683,121 +4287,121 @@ var Plottable; */ Time._DEFAULT_TIME_AXIS_CONFIGURATIONS = [ [ - { interval: d3.time.second, step: 1, formatter: Plottable.Formatters.time("%I:%M:%S %p") }, - { interval: d3.time.day, step: 1, formatter: Plottable.Formatters.time("%B %e, %Y") } + { interval: TimeInterval.second, step: 1, formatter: Plottable.Formatters.time("%I:%M:%S %p") }, + { interval: TimeInterval.day, step: 1, formatter: Plottable.Formatters.time("%B %e, %Y") } ], [ - { interval: d3.time.second, step: 5, formatter: Plottable.Formatters.time("%I:%M:%S %p") }, - { interval: d3.time.day, step: 1, formatter: Plottable.Formatters.time("%B %e, %Y") } + { interval: TimeInterval.second, step: 5, formatter: Plottable.Formatters.time("%I:%M:%S %p") }, + { interval: TimeInterval.day, step: 1, formatter: Plottable.Formatters.time("%B %e, %Y") } ], [ - { interval: d3.time.second, step: 10, formatter: Plottable.Formatters.time("%I:%M:%S %p") }, - { interval: d3.time.day, step: 1, formatter: Plottable.Formatters.time("%B %e, %Y") } + { interval: TimeInterval.second, step: 10, formatter: Plottable.Formatters.time("%I:%M:%S %p") }, + { interval: TimeInterval.day, step: 1, formatter: Plottable.Formatters.time("%B %e, %Y") } ], [ - { interval: d3.time.second, step: 15, formatter: Plottable.Formatters.time("%I:%M:%S %p") }, - { interval: d3.time.day, step: 1, formatter: Plottable.Formatters.time("%B %e, %Y") } + { interval: TimeInterval.second, step: 15, formatter: Plottable.Formatters.time("%I:%M:%S %p") }, + { interval: TimeInterval.day, step: 1, formatter: Plottable.Formatters.time("%B %e, %Y") } ], [ - { interval: d3.time.second, step: 30, formatter: Plottable.Formatters.time("%I:%M:%S %p") }, - { interval: d3.time.day, step: 1, formatter: Plottable.Formatters.time("%B %e, %Y") } + { interval: TimeInterval.second, step: 30, formatter: Plottable.Formatters.time("%I:%M:%S %p") }, + { interval: TimeInterval.day, step: 1, formatter: Plottable.Formatters.time("%B %e, %Y") } ], [ - { interval: d3.time.minute, step: 1, formatter: Plottable.Formatters.time("%I:%M %p") }, - { interval: d3.time.day, step: 1, formatter: Plottable.Formatters.time("%B %e, %Y") } + { interval: TimeInterval.minute, step: 1, formatter: Plottable.Formatters.time("%I:%M %p") }, + { interval: TimeInterval.day, step: 1, formatter: Plottable.Formatters.time("%B %e, %Y") } ], [ - { interval: d3.time.minute, step: 5, formatter: Plottable.Formatters.time("%I:%M %p") }, - { interval: d3.time.day, step: 1, formatter: Plottable.Formatters.time("%B %e, %Y") } + { interval: TimeInterval.minute, step: 5, formatter: Plottable.Formatters.time("%I:%M %p") }, + { interval: TimeInterval.day, step: 1, formatter: Plottable.Formatters.time("%B %e, %Y") } ], [ - { interval: d3.time.minute, step: 10, formatter: Plottable.Formatters.time("%I:%M %p") }, - { interval: d3.time.day, step: 1, formatter: Plottable.Formatters.time("%B %e, %Y") } + { interval: TimeInterval.minute, step: 10, formatter: Plottable.Formatters.time("%I:%M %p") }, + { interval: TimeInterval.day, step: 1, formatter: Plottable.Formatters.time("%B %e, %Y") } ], [ - { interval: d3.time.minute, step: 15, formatter: Plottable.Formatters.time("%I:%M %p") }, - { interval: d3.time.day, step: 1, formatter: Plottable.Formatters.time("%B %e, %Y") } + { interval: TimeInterval.minute, step: 15, formatter: Plottable.Formatters.time("%I:%M %p") }, + { interval: TimeInterval.day, step: 1, formatter: Plottable.Formatters.time("%B %e, %Y") } ], [ - { interval: d3.time.minute, step: 30, formatter: Plottable.Formatters.time("%I:%M %p") }, - { interval: d3.time.day, step: 1, formatter: Plottable.Formatters.time("%B %e, %Y") } + { interval: TimeInterval.minute, step: 30, formatter: Plottable.Formatters.time("%I:%M %p") }, + { interval: TimeInterval.day, step: 1, formatter: Plottable.Formatters.time("%B %e, %Y") } ], [ - { interval: d3.time.hour, step: 1, formatter: Plottable.Formatters.time("%I %p") }, - { interval: d3.time.day, step: 1, formatter: Plottable.Formatters.time("%B %e, %Y") } + { interval: TimeInterval.hour, step: 1, formatter: Plottable.Formatters.time("%I %p") }, + { interval: TimeInterval.day, step: 1, formatter: Plottable.Formatters.time("%B %e, %Y") } ], [ - { interval: d3.time.hour, step: 3, formatter: Plottable.Formatters.time("%I %p") }, - { interval: d3.time.day, step: 1, formatter: Plottable.Formatters.time("%B %e, %Y") } + { interval: TimeInterval.hour, step: 3, formatter: Plottable.Formatters.time("%I %p") }, + { interval: TimeInterval.day, step: 1, formatter: Plottable.Formatters.time("%B %e, %Y") } ], [ - { interval: d3.time.hour, step: 6, formatter: Plottable.Formatters.time("%I %p") }, - { interval: d3.time.day, step: 1, formatter: Plottable.Formatters.time("%B %e, %Y") } + { interval: TimeInterval.hour, step: 6, formatter: Plottable.Formatters.time("%I %p") }, + { interval: TimeInterval.day, step: 1, formatter: Plottable.Formatters.time("%B %e, %Y") } ], [ - { interval: d3.time.hour, step: 12, formatter: Plottable.Formatters.time("%I %p") }, - { interval: d3.time.day, step: 1, formatter: Plottable.Formatters.time("%B %e, %Y") } + { interval: TimeInterval.hour, step: 12, formatter: Plottable.Formatters.time("%I %p") }, + { interval: TimeInterval.day, step: 1, formatter: Plottable.Formatters.time("%B %e, %Y") } ], [ - { interval: d3.time.day, step: 1, formatter: Plottable.Formatters.time("%a %e") }, - { interval: d3.time.month, step: 1, formatter: Plottable.Formatters.time("%B %Y") } + { interval: TimeInterval.day, step: 1, formatter: Plottable.Formatters.time("%a %e") }, + { interval: TimeInterval.month, step: 1, formatter: Plottable.Formatters.time("%B %Y") } ], [ - { interval: d3.time.day, step: 1, formatter: Plottable.Formatters.time("%e") }, - { interval: d3.time.month, step: 1, formatter: Plottable.Formatters.time("%B %Y") } + { interval: TimeInterval.day, step: 1, formatter: Plottable.Formatters.time("%e") }, + { interval: TimeInterval.month, step: 1, formatter: Plottable.Formatters.time("%B %Y") } ], [ - { interval: d3.time.month, step: 1, formatter: Plottable.Formatters.time("%B") }, - { interval: d3.time.year, step: 1, formatter: Plottable.Formatters.time("%Y") } + { interval: TimeInterval.month, step: 1, formatter: Plottable.Formatters.time("%B") }, + { interval: TimeInterval.year, step: 1, formatter: Plottable.Formatters.time("%Y") } ], [ - { interval: d3.time.month, step: 1, formatter: Plottable.Formatters.time("%b") }, - { interval: d3.time.year, step: 1, formatter: Plottable.Formatters.time("%Y") } + { interval: TimeInterval.month, step: 1, formatter: Plottable.Formatters.time("%b") }, + { interval: TimeInterval.year, step: 1, formatter: Plottable.Formatters.time("%Y") } ], [ - { interval: d3.time.month, step: 3, formatter: Plottable.Formatters.time("%b") }, - { interval: d3.time.year, step: 1, formatter: Plottable.Formatters.time("%Y") } + { interval: TimeInterval.month, step: 3, formatter: Plottable.Formatters.time("%b") }, + { interval: TimeInterval.year, step: 1, formatter: Plottable.Formatters.time("%Y") } ], [ - { interval: d3.time.month, step: 6, formatter: Plottable.Formatters.time("%b") }, - { interval: d3.time.year, step: 1, formatter: Plottable.Formatters.time("%Y") } + { interval: TimeInterval.month, step: 6, formatter: Plottable.Formatters.time("%b") }, + { interval: TimeInterval.year, step: 1, formatter: Plottable.Formatters.time("%Y") } ], [ - { interval: d3.time.year, step: 1, formatter: Plottable.Formatters.time("%Y") } + { interval: TimeInterval.year, step: 1, formatter: Plottable.Formatters.time("%Y") } ], [ - { interval: d3.time.year, step: 1, formatter: Plottable.Formatters.time("%y") } + { interval: TimeInterval.year, step: 1, formatter: Plottable.Formatters.time("%y") } ], [ - { interval: d3.time.year, step: 5, formatter: Plottable.Formatters.time("%Y") } + { interval: TimeInterval.year, step: 5, formatter: Plottable.Formatters.time("%Y") } ], [ - { interval: d3.time.year, step: 25, formatter: Plottable.Formatters.time("%Y") } + { interval: TimeInterval.year, step: 25, formatter: Plottable.Formatters.time("%Y") } ], [ - { interval: d3.time.year, step: 50, formatter: Plottable.Formatters.time("%Y") } + { interval: TimeInterval.year, step: 50, formatter: Plottable.Formatters.time("%Y") } ], [ - { interval: d3.time.year, step: 100, formatter: Plottable.Formatters.time("%Y") } + { interval: TimeInterval.year, step: 100, formatter: Plottable.Formatters.time("%Y") } ], [ - { interval: d3.time.year, step: 200, formatter: Plottable.Formatters.time("%Y") } + { interval: TimeInterval.year, step: 200, formatter: Plottable.Formatters.time("%Y") } ], [ - { interval: d3.time.year, step: 500, formatter: Plottable.Formatters.time("%Y") } + { interval: TimeInterval.year, step: 500, formatter: Plottable.Formatters.time("%Y") } ], [ - { interval: d3.time.year, step: 1000, formatter: Plottable.Formatters.time("%Y") } + { interval: TimeInterval.year, step: 1000, formatter: Plottable.Formatters.time("%Y") } ] ]; Time._LONG_DATE = new Date(9999, 8, 29, 12, 59, 9999); return Time; - })(Axis.AbstractAxis); - Axis.Time = Time; - })(Axis = Plottable.Axis || (Plottable.Axis = {})); + })(Plottable.Axis); + Axes.Time = Time; + })(Axes = Plottable.Axes || (Plottable.Axes = {})); })(Plottable || (Plottable = {})); -// +/// var __extends = this.__extends || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; function __() { this.constructor = d; } @@ -4806,8 +4410,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Axis; - (function (Axis) { + var Axes; + (function (Axes) { var Numeric = (function (_super) { __extends(Numeric, _super); /** @@ -4832,7 +4436,7 @@ var Plottable; } Numeric.prototype._setup = function () { _super.prototype._setup.call(this); - this._measurer = new SVGTypewriter.Measurers.Measurer(this._tickLabelContainer, Axis.AbstractAxis.TICK_LABEL_CLASS); + this._measurer = new SVGTypewriter.Measurers.Measurer(this._tickLabelContainer, Plottable.Axis.TICK_LABEL_CLASS); this._wrapper = new SVGTypewriter.Wrappers.Wrapper().maxLines(1); }; Numeric.prototype._computeWidth = function () { @@ -4842,7 +4446,7 @@ var Plottable; var formattedValue = _this.formatter()(v); return _this._measurer.measure(formattedValue).width; }); - var maxTextLength = Plottable._Util.Methods.max(textLengths, 0); + var maxTextLength = Plottable.Utils.Methods.max(textLengths, 0); if (this._tickLabelPositioning === "center") { this._computedWidth = this._maxLabelTickLength() + this.tickLabelPadding() + maxTextLength; } @@ -4880,15 +4484,15 @@ var Plottable; if (!this._isHorizontal()) { var reComputedWidth = this._computeWidth(); if (reComputedWidth > this.width() || reComputedWidth < (this.width() - this.gutter())) { - this._invalidateLayout(); + this.redraw(); return; } } - this._render(); + this.render(); }; - Numeric.prototype._doRender = function () { + Numeric.prototype.renderImmediately = function () { var _this = this; - _super.prototype._doRender.call(this); + _super.prototype.renderImmediately.call(this); var tickLabelAttrHash = { x: 0, y: 0, @@ -4937,7 +4541,7 @@ var Plottable; } } var tickMarkAttrHash = this._generateTickMarkAttrHash(); - switch (this.orient()) { + switch (this.orientation()) { case "bottom": tickLabelAttrHash["x"] = tickMarkAttrHash["x1"]; tickLabelAttrHash["dy"] = "0.95em"; @@ -4960,8 +4564,8 @@ var Plottable; break; } var tickLabelValues = this._getTickValues(); - var tickLabels = this._tickLabelContainer.selectAll("." + Axis.AbstractAxis.TICK_LABEL_CLASS).data(tickLabelValues); - tickLabels.enter().append("text").classed(Axis.AbstractAxis.TICK_LABEL_CLASS, true); + var tickLabels = this._tickLabelContainer.selectAll("." + Plottable.Axis.TICK_LABEL_CLASS).data(tickLabelValues); + tickLabels.enter().append("text").classed(Plottable.Axis.TICK_LABEL_CLASS, true); tickLabels.exit().remove(); tickLabels.style("text-anchor", tickLabelTextAnchor).style("visibility", "inherit").attr(tickLabelAttrHash).text(function (s) { var formattedText = _this.formatter()(s); @@ -4983,9 +4587,10 @@ var Plottable; if (this._tickLabelPositioning === "bottom" || this._tickLabelPositioning === "top" || this._tickLabelPositioning === "left" || this._tickLabelPositioning === "right") { this._hideTickMarksWithoutLabel(); } + return this; }; Numeric.prototype._showAllTickMarks = function () { - var visibleTickMarks = this._tickMarkContainer.selectAll("." + Axis.AbstractAxis.TICK_MARK_CLASS).each(function () { + this._tickMarkContainer.selectAll("." + Plottable.Axis.TICK_MARK_CLASS).each(function () { d3.select(this).style("visibility", "inherit"); }); }; @@ -4993,8 +4598,8 @@ var Plottable; * Hides the Tick Marks which have no corresponding Tick Labels */ Numeric.prototype._hideTickMarksWithoutLabel = function () { - var visibleTickMarks = this._tickMarkContainer.selectAll("." + Axis.AbstractAxis.TICK_MARK_CLASS); - var visibleTickLabels = this._tickLabelContainer.selectAll("." + Axis.AbstractAxis.TICK_LABEL_CLASS).filter(function (d, i) { + var visibleTickMarks = this._tickMarkContainer.selectAll("." + Plottable.Axis.TICK_MARK_CLASS); + var visibleTickLabels = this._tickLabelContainer.selectAll("." + Plottable.Axis.TICK_LABEL_CLASS).filter(function (d, i) { var visibility = d3.select(this).style("visibility"); return (visibility === "inherit") || (visibility === "visible"); }); @@ -5023,7 +4628,7 @@ var Plottable; } } this._tickLabelPositioning = positionLC; - this._invalidateLayout(); + this.redraw(); return this; } }; @@ -5034,7 +4639,7 @@ var Plottable; } else { this._showFirstTickLabel = show; - this._render(); + this.render(); return this; } } @@ -5044,7 +4649,7 @@ var Plottable; } else { this._showLastTickLabel = show; - this._render(); + this.render(); return this; } } @@ -5054,38 +4659,37 @@ var Plottable; }; Numeric.prototype._hideEndTickLabels = function () { var boundingBox = this._boundingBox.node().getBoundingClientRect(); - var tickLabels = this._tickLabelContainer.selectAll("." + Axis.AbstractAxis.TICK_LABEL_CLASS); + var tickLabels = this._tickLabelContainer.selectAll("." + Plottable.Axis.TICK_LABEL_CLASS); if (tickLabels[0].length === 0) { return; } var firstTickLabel = tickLabels[0][0]; - if (!Plottable._Util.DOM.boxIsInside(firstTickLabel.getBoundingClientRect(), boundingBox)) { + if (!Plottable.Utils.DOM.boxIsInside(firstTickLabel.getBoundingClientRect(), boundingBox)) { d3.select(firstTickLabel).style("visibility", "hidden"); } var lastTickLabel = tickLabels[0][tickLabels[0].length - 1]; - if (!Plottable._Util.DOM.boxIsInside(lastTickLabel.getBoundingClientRect(), boundingBox)) { + if (!Plottable.Utils.DOM.boxIsInside(lastTickLabel.getBoundingClientRect(), boundingBox)) { d3.select(lastTickLabel).style("visibility", "hidden"); } }; // Responsible for hiding any tick labels that break out of the bounding container Numeric.prototype._hideOverflowingTickLabels = function () { var boundingBox = this._boundingBox.node().getBoundingClientRect(); - var tickLabels = this._tickLabelContainer.selectAll("." + Axis.AbstractAxis.TICK_LABEL_CLASS); + var tickLabels = this._tickLabelContainer.selectAll("." + Plottable.Axis.TICK_LABEL_CLASS); if (tickLabels.empty()) { return; } tickLabels.each(function (d, i) { - if (!Plottable._Util.DOM.boxIsInside(this.getBoundingClientRect(), boundingBox)) { + if (!Plottable.Utils.DOM.boxIsInside(this.getBoundingClientRect(), boundingBox)) { d3.select(this).style("visibility", "hidden"); } }); }; Numeric.prototype._hideOverlappingTickLabels = function () { - var visibleTickLabels = this._tickLabelContainer.selectAll("." + Axis.AbstractAxis.TICK_LABEL_CLASS).filter(function (d, i) { + var visibleTickLabels = this._tickLabelContainer.selectAll("." + Plottable.Axis.TICK_LABEL_CLASS).filter(function (d, i) { var visibility = d3.select(this).style("visibility"); return (visibility === "inherit") || (visibility === "visible"); }); - var lastLabelClientRect; var visibleTickLabelRects = visibleTickLabels[0].map(function (label) { return label.getBoundingClientRect(); }); var interval = 1; while (!this._hasOverlapWithInterval(interval, visibleTickLabelRects) && interval < visibleTickLabelRects.length) { @@ -5130,9 +4734,9 @@ var Plottable; return true; }; return Numeric; - })(Axis.AbstractAxis); - Axis.Numeric = Numeric; - })(Axis = Plottable.Axis || (Plottable.Axis = {})); + })(Plottable.Axis); + Axes.Numeric = Numeric; + })(Axes = Plottable.Axes || (Plottable.Axes = {})); })(Plottable || (Plottable = {})); /// @@ -5144,8 +4748,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Axis; - (function (Axis) { + var Axes; + (function (Axes) { var Category = (function (_super) { __extends(Category, _super); /** @@ -5174,28 +4778,22 @@ var Plottable; this._writer = new SVGTypewriter.Writers.Writer(this._measurer, this._wrapper); }; Category.prototype._rescale = function () { - return this._invalidateLayout(); + return this.redraw(); }; - Category.prototype._requestedSpace = function (offeredWidth, offeredHeight) { + Category.prototype.requestedSpace = function (offeredWidth, offeredHeight) { var widthRequiredByTicks = this._isHorizontal() ? 0 : this._maxLabelTickLength() + this.tickLabelPadding() + this.gutter(); var heightRequiredByTicks = this._isHorizontal() ? this._maxLabelTickLength() + this.tickLabelPadding() + this.gutter() : 0; if (this._scale.domain().length === 0) { - return { width: 0, height: 0, wantsWidth: false, wantsHeight: false }; + return { + minWidth: 0, + minHeight: 0 + }; } var categoryScale = this._scale; - var fakeScale = categoryScale.copy(); - if (this._isHorizontal()) { - fakeScale.range([0, offeredWidth]); - } - else { - fakeScale.range([offeredHeight, 0]); - } - var textResult = this._measureTicks(offeredWidth, offeredHeight, fakeScale, categoryScale.domain()); + var measureResult = this._measureTicks(offeredWidth, offeredHeight, categoryScale, categoryScale.domain()); return { - width: textResult.usedWidth + widthRequiredByTicks, - height: textResult.usedHeight + heightRequiredByTicks, - wantsWidth: !textResult.textFits, - wantsHeight: !textResult.textFits + minWidth: measureResult.usedWidth + widthRequiredByTicks, + minHeight: measureResult.usedHeight + heightRequiredByTicks }; }; Category.prototype._getTickValues = function () { @@ -5209,7 +4807,7 @@ var Plottable; throw new Error("Angle " + angle + " not supported; only 0, 90, and -90 are valid values"); } this._tickLabelAngle = angle; - this._invalidateLayout(); + this.redraw(); return this; }; /** @@ -5240,8 +4838,8 @@ var Plottable; var height = self._isHorizontal() ? axisHeight - self._maxLabelTickLength() - self.tickLabelPadding() : bandWidth; var writeOptions = { selection: d3.select(this), - xAlign: xAlign[self.orient()], - yAlign: yAlign[self.orient()], + xAlign: xAlign[self.orientation()], + yAlign: yAlign[self.orientation()], textRotation: self.tickLabelAngle() }; self._writer.write(self.formatter()(d), width, height, writeOptions); @@ -5255,12 +4853,16 @@ var Plottable; */ Category.prototype._measureTicks = function (axisWidth, axisHeight, scale, ticks) { var _this = this; + var axisSpace = this._isHorizontal() ? axisWidth : axisHeight; + var totalOuterPaddingRatio = 2 * scale.outerPadding(); + var totalInnerPaddingRatio = (ticks.length - 1) * scale.innerPadding(); + var expectedRangeBand = axisSpace / (totalOuterPaddingRatio + totalInnerPaddingRatio + ticks.length); + var stepWidth = expectedRangeBand * (1 + scale.innerPadding()); var wrappingResults = ticks.map(function (s) { - var bandWidth = scale.stepWidth(); // HACKHACK: https://github.com/palantir/svg-typewriter/issues/25 var width = axisWidth - _this._maxLabelTickLength() - _this.tickLabelPadding(); // default for left/right if (_this._isHorizontal()) { - width = bandWidth; // defaults to the band width + width = stepWidth; // defaults to the band width if (_this._tickLabelAngle !== 0) { width = axisHeight - _this._maxLabelTickLength() - _this.tickLabelPadding(); // use the axis height } @@ -5268,7 +4870,7 @@ var Plottable; width = Math.max(width, 0); } // HACKHACK: https://github.com/palantir/svg-typewriter/issues/25 - var height = bandWidth; // default for left/right + var height = stepWidth; // default for left/right if (_this._isHorizontal()) { height = axisHeight - _this._maxLabelTickLength() - _this.tickLabelPadding(); if (_this._tickLabelAngle !== 0) { @@ -5280,8 +4882,8 @@ var Plottable; return _this._wrapper.wrap(_this.formatter()(s), _this._measurer, width, height); }); // HACKHACK: https://github.com/palantir/svg-typewriter/issues/25 - var widthFn = (this._isHorizontal() && this._tickLabelAngle === 0) ? d3.sum : Plottable._Util.Methods.max; - var heightFn = (this._isHorizontal() && this._tickLabelAngle === 0) ? Plottable._Util.Methods.max : d3.sum; + var widthFn = (this._isHorizontal() && this._tickLabelAngle === 0) ? d3.sum : Plottable.Utils.Methods.max; + var heightFn = (this._isHorizontal() && this._tickLabelAngle === 0) ? Plottable.Utils.Methods.max : d3.sum; var textFits = wrappingResults.every(function (t) { return !SVGTypewriter.Utils.StringMethods.isNotEmptyString(t.truncatedText) && t.noLines === 1; }); var usedWidth = widthFn(wrappingResults, function (t) { return _this._measurer.measure(t.wrappedText).width; }, 0); var usedHeight = heightFn(wrappingResults, function (t) { return _this._measurer.measure(t.wrappedText).height; }, 0); @@ -5298,11 +4900,11 @@ var Plottable; usedHeight: usedHeight }; }; - Category.prototype._doRender = function () { + Category.prototype.renderImmediately = function () { var _this = this; - _super.prototype._doRender.call(this); + _super.prototype.renderImmediately.call(this); var catScale = this._scale; - var tickLabels = this._tickLabelContainer.selectAll("." + Axis.AbstractAxis.TICK_LABEL_CLASS).data(this._scale.domain(), function (d) { return d; }); + var tickLabels = this._tickLabelContainer.selectAll("." + Plottable.Axis.TICK_LABEL_CLASS).data(this._scale.domain(), function (d) { return d; }); var getTickLabelTransform = function (d, i) { var innerPaddingWidth = catScale.stepWidth() - catScale.rangeBand(); var scaledValue = catScale.scale(d) - catScale.rangeBand() / 2 - innerPaddingWidth / 2; @@ -5310,29 +4912,28 @@ var Plottable; var y = _this._isHorizontal() ? 0 : scaledValue; return "translate(" + x + "," + y + ")"; }; - tickLabels.enter().append("g").classed(Axis.AbstractAxis.TICK_LABEL_CLASS, true); + tickLabels.enter().append("g").classed(Plottable.Axis.TICK_LABEL_CLASS, true); tickLabels.exit().remove(); tickLabels.attr("transform", getTickLabelTransform); // erase all text first, then rewrite tickLabels.text(""); this._drawTicks(this.width(), this.height(), catScale, tickLabels); - var translate = this._isHorizontal() ? [catScale.rangeBand() / 2, 0] : [0, catScale.rangeBand() / 2]; - var xTranslate = this.orient() === "right" ? this._maxLabelTickLength() + this.tickLabelPadding() : 0; - var yTranslate = this.orient() === "bottom" ? this._maxLabelTickLength() + this.tickLabelPadding() : 0; - Plottable._Util.DOM.translate(this._tickLabelContainer, xTranslate, yTranslate); + var xTranslate = this.orientation() === "right" ? this._maxLabelTickLength() + this.tickLabelPadding() : 0; + var yTranslate = this.orientation() === "bottom" ? this._maxLabelTickLength() + this.tickLabelPadding() : 0; + Plottable.Utils.DOM.translate(this._tickLabelContainer, xTranslate, yTranslate); return this; }; - Category.prototype._computeLayout = function (offeredXOrigin, offeredYOrigin, availableWidth, availableHeight) { - // When anyone calls _invalidateLayout, _computeLayout will be called + Category.prototype.computeLayout = function (origin, availableWidth, availableHeight) { + // When anyone calls redraw(), computeLayout() will be called // on everyone, including this. Since CSS or something might have // affected the size of the characters, clear the cache. this._measurer.reset(); - return _super.prototype._computeLayout.call(this, offeredXOrigin, offeredYOrigin, availableWidth, availableHeight); + return _super.prototype.computeLayout.call(this, origin, availableWidth, availableHeight); }; return Category; - })(Axis.AbstractAxis); - Axis.Category = Category; - })(Axis = Plottable.Axis || (Plottable.Axis = {})); + })(Plottable.Axis); + Axes.Category = Category; + })(Axes = Plottable.Axes || (Plottable.Axes = {})); })(Plottable || (Plottable = {})); /// @@ -5344,8 +4945,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Component; - (function (Component) { + var Components; + (function (Components) { var Label = (function (_super) { __extends(Label, _super); /** @@ -5364,47 +4965,17 @@ var Plottable; _super.call(this); this.classed("label", true); this.text(displayText); - this.orient(orientation); - this.xAlign("center").yAlign("center"); - this._fixedHeightFlag = true; - this._fixedWidthFlag = true; + this.orientation(orientation); + this.xAlignment("center").yAlignment("center"); this._padding = 0; } - /** - * Sets the horizontal side the label will go to given the label is given more space that it needs - * - * @param {string} alignment The new setting, one of `["left", "center", - * "right"]`. Defaults to `"center"`. - * @returns {Label} The calling Label. - */ - Label.prototype.xAlign = function (alignment) { - var alignmentLC = alignment.toLowerCase(); - _super.prototype.xAlign.call(this, alignmentLC); - this._xAlignment = alignmentLC; - return this; - }; - /** - * Sets the vertical side the label will go to given the label is given more space that it needs - * - * @param {string} alignment The new setting, one of `["top", "center", - * "bottom"]`. Defaults to `"center"`. - * @returns {Label} The calling Label. - */ - Label.prototype.yAlign = function (alignment) { - var alignmentLC = alignment.toLowerCase(); - _super.prototype.yAlign.call(this, alignmentLC); - this._yAlignment = alignmentLC; - return this; - }; - Label.prototype._requestedSpace = function (offeredWidth, offeredHeight) { + Label.prototype.requestedSpace = function (offeredWidth, offeredHeight) { var desiredWH = this._measurer.measure(this._text); - var desiredWidth = (this.orient() === "horizontal" ? desiredWH.width : desiredWH.height) + 2 * this.padding(); - var desiredHeight = (this.orient() === "horizontal" ? desiredWH.height : desiredWH.width) + 2 * this.padding(); + var desiredWidth = (this.orientation() === "horizontal" ? desiredWH.width : desiredWH.height) + 2 * this.padding(); + var desiredHeight = (this.orientation() === "horizontal" ? desiredWH.height : desiredWH.width) + 2 * this.padding(); return { - width: desiredWidth, - height: desiredHeight, - wantsWidth: desiredWidth > offeredWidth, - wantsHeight: desiredHeight > offeredHeight + minWidth: desiredWidth, + minHeight: desiredHeight }; }; Label.prototype._setup = function () { @@ -5421,23 +4992,23 @@ var Plottable; } else { this._text = displayText; - this._invalidateLayout(); + this.redraw(); return this; } }; - Label.prototype.orient = function (newOrientation) { - if (newOrientation == null) { + Label.prototype.orientation = function (orientation) { + if (orientation == null) { return this._orientation; } else { - newOrientation = newOrientation.toLowerCase(); - if (newOrientation === "horizontal" || newOrientation === "left" || newOrientation === "right") { - this._orientation = newOrientation; + orientation = orientation.toLowerCase(); + if (orientation === "horizontal" || orientation === "left" || orientation === "right") { + this._orientation = orientation; } else { - throw new Error(newOrientation + " is not a valid orientation for LabelComponent"); + throw new Error(orientation + " is not a valid orientation for LabelComponent"); } - this._invalidateLayout(); + this.redraw(); return this; } }; @@ -5451,12 +5022,18 @@ var Plottable; throw new Error(padAmount + " is not a valid padding value. Cannot be less than 0."); } this._padding = padAmount; - this._invalidateLayout(); + this.redraw(); return this; } }; - Label.prototype._doRender = function () { - _super.prototype._doRender.call(this); + Label.prototype.fixedWidth = function () { + return true; + }; + Label.prototype.fixedHeight = function () { + return true; + }; + Label.prototype.renderImmediately = function () { + _super.prototype.renderImmediately.call(this); // HACKHACK SVGTypewriter should remove existing content - #21 on SVGTypewriter. this._textContainer.selectAll("g").remove(); var textMeasurement = this._measurer.measure(this._text); @@ -5468,44 +5045,21 @@ var Plottable; var textRotation = { horizontal: 0, right: 90, left: -90 }; var writeOptions = { selection: this._textContainer, - xAlign: this._xAlignment, - yAlign: this._yAlignment, - textRotation: textRotation[this.orient()] + xAlign: this.xAlignment(), + yAlign: this.yAlignment(), + textRotation: textRotation[this.orientation()] }; this._writer.write(this._text, writeWidth, writeHeight, writeOptions); + return this; }; + // Css class for labels that are made for rendering titles. + Label.TITLE_LABEL_CLASS = "title-label"; + // Css class for labels that are made for rendering axis titles. + Label.AXIS_LABEL_CLASS = "axis-label"; return Label; - })(Component.AbstractComponent); - Component.Label = Label; - var TitleLabel = (function (_super) { - __extends(TitleLabel, _super); - /** - * Creates a TitleLabel, a type of label made for rendering titles. - * - * @constructor - */ - function TitleLabel(text, orientation) { - _super.call(this, text, orientation); - this.classed("title-label", true); - } - return TitleLabel; - })(Label); - Component.TitleLabel = TitleLabel; - var AxisLabel = (function (_super) { - __extends(AxisLabel, _super); - /** - * Creates a AxisLabel, a type of label made for rendering axis labels. - * - * @constructor - */ - function AxisLabel(text, orientation) { - _super.call(this, text, orientation); - this.classed("axis-label", true); - } - return AxisLabel; - })(Label); - Component.AxisLabel = AxisLabel; - })(Component = Plottable.Component || (Plottable.Component = {})); + })(Plottable.Component); + Components.Label = Label; + })(Components = Plottable.Components || (Plottable.Components = {})); })(Plottable || (Plottable = {})); /// @@ -5517,8 +5071,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Component; - (function (Component) { + var Components; + (function (Components) { var Legend = (function (_super) { __extends(Legend, _super); /** @@ -5540,10 +5094,9 @@ var Plottable; throw new Error("Legend requires a colorScale"); } this._scale = colorScale; - this._scale.broadcaster.registerListener(this, function () { return _this._invalidateLayout(); }); - this.xAlign("right").yAlign("top"); - this._fixedWidthFlag = true; - this._fixedHeightFlag = true; + this._redrawCallback = function (scale) { return _this.redraw(); }; + this._scale.onUpdate(this._redrawCallback); + this.xAlignment("right").yAlignment("top"); this._sortFn = function (a, b) { return _this._scale.domain().indexOf(a) - _this._scale.domain().indexOf(b); }; this._symbolFactoryAccessor = function () { return Plottable.SymbolFactories.circle(); }; } @@ -5562,7 +5115,7 @@ var Plottable; } else { this._maxEntriesPerRow = numEntries; - this._invalidateLayout(); + this.redraw(); return this; } }; @@ -5572,39 +5125,41 @@ var Plottable; } else { this._sortFn = newFn; - this._invalidateLayout(); + this.redraw(); return this; } }; Legend.prototype.scale = function (scale) { - var _this = this; if (scale != null) { - this._scale.broadcaster.deregisterListener(this); + this._scale.offUpdate(this._redrawCallback); this._scale = scale; - this._scale.broadcaster.registerListener(this, function () { return _this._invalidateLayout(); }); - this._invalidateLayout(); + this._scale.onUpdate(this._redrawCallback); + this.redraw(); return this; } else { return this._scale; } }; - Legend.prototype.remove = function () { - _super.prototype.remove.call(this); - this._scale.broadcaster.deregisterListener(this); + Legend.prototype.destroy = function () { + _super.prototype.destroy.call(this); + this._scale.offUpdate(this._redrawCallback); }; Legend.prototype._calculateLayoutInfo = function (availableWidth, availableHeight) { var _this = this; var textHeight = this._measurer.measure().height; var availableWidthForEntries = Math.max(0, (availableWidth - this._padding)); - var measureEntry = function (entryText) { - var originalEntryLength = (textHeight + _this._measurer.measure(entryText).width + _this._padding); - return Math.min(originalEntryLength, availableWidthForEntries); - }; - var entries = this._scale.domain().slice(); - entries.sort(this.sortFunction()); - var entryLengths = Plottable._Util.Methods.populateMap(entries, measureEntry); - var rows = this._packRows(availableWidthForEntries, entries, entryLengths); + var entryNames = this._scale.domain().slice(); + entryNames.sort(this.sortFunction()); + var entryLengths = d3.map(); + var untruncatedEntryLengths = d3.map(); + entryNames.forEach(function (entryName) { + var untruncatedEntryLength = textHeight + _this._measurer.measure(entryName).width + _this._padding; + var entryLength = Math.min(untruncatedEntryLength, availableWidthForEntries); + entryLengths.set(entryName, entryLength); + untruncatedEntryLengths.set(entryName, untruncatedEntryLength); + }); + var rows = this._packRows(availableWidthForEntries, entryNames, entryLengths); var rowsAvailable = Math.floor((availableHeight - 2 * this._padding) / textHeight); if (rowsAvailable !== rowsAvailable) { rowsAvailable = 0; @@ -5612,29 +5167,20 @@ var Plottable; return { textHeight: textHeight, entryLengths: entryLengths, + untruncatedEntryLengths: untruncatedEntryLengths, rows: rows, numRowsToDraw: Math.max(Math.min(rowsAvailable, rows.length), 0) }; }; - Legend.prototype._requestedSpace = function (offeredWidth, offeredHeight) { - var _this = this; + Legend.prototype.requestedSpace = function (offeredWidth, offeredHeight) { var estimatedLayout = this._calculateLayoutInfo(offeredWidth, offeredHeight); - var rowLengths = estimatedLayout.rows.map(function (row) { - return d3.sum(row, function (entry) { return estimatedLayout.entryLengths.get(entry); }); + var untruncatedRowLengths = estimatedLayout.rows.map(function (row) { + return d3.sum(row, function (entry) { return estimatedLayout.untruncatedEntryLengths.get(entry); }); }); - var longestRowLength = Plottable._Util.Methods.max(rowLengths, 0); - var longestUntruncatedEntryLength = Plottable._Util.Methods.max(this._scale.domain(), function (d) { return _this._measurer.measure(d).width; }, 0); - longestUntruncatedEntryLength += estimatedLayout.textHeight + this._padding; - var desiredWidth = this._padding + Math.max(longestRowLength, longestUntruncatedEntryLength); - var acceptableHeight = estimatedLayout.numRowsToDraw * estimatedLayout.textHeight + 2 * this._padding; - var desiredHeight = estimatedLayout.rows.length * estimatedLayout.textHeight + 2 * this._padding; - var desiredNumRows = Math.max(Math.ceil(this._scale.domain().length / this._maxEntriesPerRow), 1); - var wantsFitMoreEntriesInRow = estimatedLayout.rows.length > desiredNumRows; + var longestUntruncatedRowLength = Plottable.Utils.Methods.max(untruncatedRowLengths, 0); return { - width: this._padding + longestRowLength, - height: acceptableHeight, - wantsWidth: offeredWidth < desiredWidth || wantsFitMoreEntriesInRow, - wantsHeight: offeredHeight < desiredHeight + minWidth: this._padding + longestUntruncatedRowLength, + minHeight: estimatedLayout.rows.length * estimatedLayout.textHeight + 2 * this._padding }; }; Legend.prototype._packRows = function (availableWidth, entries, entryLengths) { @@ -5685,9 +5231,9 @@ var Plottable; }); return entry; }; - Legend.prototype._doRender = function () { + Legend.prototype.renderImmediately = function () { var _this = this; - _super.prototype._doRender.call(this); + _super.prototype.renderImmediately.call(this); var layout = this._calculateLayoutInfo(this.width(), this.height()); var rowsToDraw = layout.rows.slice(0, layout.numRowsToDraw); var rows = this._content.selectAll("g." + Legend.LEGEND_ROW_CLASS).data(rowsToDraw); @@ -5726,6 +5272,7 @@ var Plottable; }; self._writer.write(value, maxTextLength, self.height(), writeOptions); }); + return this; }; Legend.prototype.symbolFactoryAccessor = function (symbolFactoryAccessor) { if (symbolFactoryAccessor == null) { @@ -5733,10 +5280,16 @@ var Plottable; } else { this._symbolFactoryAccessor = symbolFactoryAccessor; - this._render(); + this.render(); return this; } }; + Legend.prototype.fixedWidth = function () { + return true; + }; + Legend.prototype.fixedHeight = function () { + return true; + }; /** * The css class applied to each legend row */ @@ -5750,9 +5303,9 @@ var Plottable; */ Legend.LEGEND_SYMBOL_CLASS = "legend-symbol"; return Legend; - })(Component.AbstractComponent); - Component.Legend = Legend; - })(Component = Plottable.Component || (Plottable.Component = {})); + })(Plottable.Component); + Components.Legend = Legend; + })(Components = Plottable.Components || (Plottable.Components = {})); })(Plottable || (Plottable = {})); /// @@ -5764,8 +5317,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Component; - (function (Component) { + var Components; + (function (Components) { var InterpolatedColorLegend = (function (_super) { __extends(InterpolatedColorLegend, _super); /** @@ -5791,23 +5344,22 @@ var Plottable; throw new Error("InterpolatedColorLegend requires a interpolatedColorScale"); } this._scale = interpolatedColorScale; - this._scale.broadcaster.registerListener(this, function () { return _this._invalidateLayout(); }); + this._redrawCallback = function (scale) { return _this.redraw(); }; + this._scale.onUpdate(this._redrawCallback); this._formatter = formatter; this._orientation = InterpolatedColorLegend._ensureOrientation(orientation); - this._fixedWidthFlag = true; - this._fixedHeightFlag = true; this.classed("legend", true).classed("interpolated-color-legend", true); } - InterpolatedColorLegend.prototype.remove = function () { - _super.prototype.remove.call(this); - this._scale.broadcaster.deregisterListener(this); + InterpolatedColorLegend.prototype.destroy = function () { + _super.prototype.destroy.call(this); + this._scale.offUpdate(this._redrawCallback); }; InterpolatedColorLegend.prototype.formatter = function (formatter) { if (formatter === undefined) { return this._formatter; } this._formatter = formatter; - this._invalidateLayout(); + this.redraw(); return this; }; InterpolatedColorLegend._ensureOrientation = function (orientation) { @@ -5819,16 +5371,22 @@ var Plottable; throw new Error("\"" + orientation + "\" is not a valid orientation for InterpolatedColorLegend"); } }; - InterpolatedColorLegend.prototype.orient = function (newOrientation) { - if (newOrientation == null) { + InterpolatedColorLegend.prototype.orientation = function (orientation) { + if (orientation == null) { return this._orientation; } else { - this._orientation = InterpolatedColorLegend._ensureOrientation(newOrientation); - this._invalidateLayout(); + this._orientation = InterpolatedColorLegend._ensureOrientation(orientation); + this.redraw(); return this; } }; + InterpolatedColorLegend.prototype.fixedWidth = function () { + return true; + }; + InterpolatedColorLegend.prototype.fixedHeight = function () { + return true; + }; InterpolatedColorLegend.prototype._generateTicks = function () { var domain = this._scale.domain(); var slope = (domain[1] - domain[0]) / this._numSwatches; @@ -5848,7 +5406,7 @@ var Plottable; this._wrapper = new SVGTypewriter.Wrappers.Wrapper(); this._writer = new SVGTypewriter.Writers.Writer(this._measurer, this._wrapper); }; - InterpolatedColorLegend.prototype._requestedSpace = function (offeredWidth, offeredHeight) { + InterpolatedColorLegend.prototype.requestedSpace = function (offeredWidth, offeredHeight) { var _this = this; var textHeight = this._measurer.measure().height; var ticks = this._generateTicks(); @@ -5858,7 +5416,7 @@ var Plottable; var desiredHeight; var desiredWidth; if (this._isVertical()) { - var longestWidth = Plottable._Util.Methods.max(labelWidths, 0); + var longestWidth = Plottable.Utils.Methods.max(labelWidths, 0); desiredWidth = this._padding + textHeight + this._padding + longestWidth + this._padding; desiredHeight = this._padding + numSwatches * textHeight + this._padding; } @@ -5867,20 +5425,17 @@ var Plottable; desiredWidth = this._padding + labelWidths[0] + this._padding + numSwatches * textHeight + this._padding + labelWidths[1] + this._padding; } return { - width: desiredWidth, - height: desiredHeight, - wantsWidth: offeredWidth < desiredWidth, - wantsHeight: offeredHeight < desiredHeight + minWidth: desiredWidth, + minHeight: desiredHeight }; }; InterpolatedColorLegend.prototype._isVertical = function () { return this._orientation !== "horizontal"; }; - InterpolatedColorLegend.prototype._doRender = function () { + InterpolatedColorLegend.prototype.renderImmediately = function () { var _this = this; - _super.prototype._doRender.call(this); + _super.prototype.renderImmediately.call(this); var domain = this._scale.domain(); - var textHeight = this._measurer.measure().height; var text0 = this._formatter(domain[0]); var text0Width = this._measurer.measure(text0).width; var text1 = this._formatter(domain[1]); @@ -5970,15 +5525,16 @@ var Plottable; "x": swatchX, "y": swatchY }); + return this; }; /** * The css class applied to the legend labels. */ InterpolatedColorLegend.LEGEND_LABEL_CLASS = "legend-label"; return InterpolatedColorLegend; - })(Component.AbstractComponent); - Component.InterpolatedColorLegend = InterpolatedColorLegend; - })(Component = Plottable.Component || (Plottable.Component = {})); + })(Plottable.Component); + Components.InterpolatedColorLegend = InterpolatedColorLegend; + })(Components = Plottable.Components || (Plottable.Components = {})); })(Plottable || (Plottable = {})); /// @@ -5990,8 +5546,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Component; - (function (Component) { + var Components; + (function (Components) { var Gridlines = (function (_super) { __extends(Gridlines, _super); /** @@ -6003,30 +5559,31 @@ var Plottable; */ function Gridlines(xScale, yScale) { var _this = this; - if (xScale != null && !(Plottable.Scale.AbstractQuantitative.prototype.isPrototypeOf(xScale))) { - throw new Error("xScale needs to inherit from Scale.AbstractQuantitative"); + if (xScale != null && !(Plottable.QuantitativeScale.prototype.isPrototypeOf(xScale))) { + throw new Error("xScale needs to inherit from Scale.QuantitativeScale"); } - if (yScale != null && !(Plottable.Scale.AbstractQuantitative.prototype.isPrototypeOf(yScale))) { - throw new Error("yScale needs to inherit from Scale.AbstractQuantitative"); + if (yScale != null && !(Plottable.QuantitativeScale.prototype.isPrototypeOf(yScale))) { + throw new Error("yScale needs to inherit from Scale.QuantitativeScale"); } _super.call(this); this.classed("gridlines", true); this._xScale = xScale; this._yScale = yScale; + this._renderCallback = function (scale) { return _this.render(); }; if (this._xScale) { - this._xScale.broadcaster.registerListener(this, function () { return _this._render(); }); + this._xScale.onUpdate(this._renderCallback); } if (this._yScale) { - this._yScale.broadcaster.registerListener(this, function () { return _this._render(); }); + this._yScale.onUpdate(this._renderCallback); } } - Gridlines.prototype.remove = function () { - _super.prototype.remove.call(this); + Gridlines.prototype.destroy = function () { + _super.prototype.destroy.call(this); if (this._xScale) { - this._xScale.broadcaster.deregisterListener(this); + this._xScale.offUpdate(this._renderCallback); } if (this._yScale) { - this._yScale.broadcaster.deregisterListener(this); + this._yScale.offUpdate(this._renderCallback); } return this; }; @@ -6035,10 +5592,11 @@ var Plottable; this._xLinesContainer = this._content.append("g").classed("x-gridlines", true); this._yLinesContainer = this._content.append("g").classed("y-gridlines", true); }; - Gridlines.prototype._doRender = function () { - _super.prototype._doRender.call(this); + Gridlines.prototype.renderImmediately = function () { + _super.prototype.renderImmediately.call(this); this._redrawXLines(); this._redrawYLines(); + return this; }; Gridlines.prototype._redrawXLines = function () { var _this = this; @@ -6063,9 +5621,9 @@ var Plottable; } }; return Gridlines; - })(Component.AbstractComponent); - Component.Gridlines = Gridlines; - })(Component = Plottable.Component || (Plottable.Component = {})); + })(Plottable.Component); + Components.Gridlines = Gridlines; + })(Components = Plottable.Components || (Plottable.Components = {})); })(Plottable || (Plottable = {})); /// @@ -6077,8 +5635,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Component; - (function (Component) { + var Components; + (function (Components) { var Table = (function (_super) { __extends(Table, _super); /** @@ -6100,10 +5658,10 @@ var Plottable; if (rows === void 0) { rows = []; } _super.call(this); this._rowPadding = 0; - this._colPadding = 0; + this._columnPadding = 0; this._rows = []; this._rowWeights = []; - this._colWeights = []; + this._columnWeights = []; this._nRows = 0; this._nCols = 0; this._calculatedLayout = null; @@ -6111,67 +5669,82 @@ var Plottable; rows.forEach(function (row, rowIndex) { row.forEach(function (component, colIndex) { if (component != null) { - _this.addComponent(rowIndex, colIndex, component); + _this.add(component, rowIndex, colIndex); } }); }); } + Table.prototype._forEach = function (callback) { + for (var r = 0; r < this._nRows; r++) { + for (var c = 0; c < this._nCols; c++) { + if (this._rows[r][c] != null) { + callback(this._rows[r][c]); + } + } + } + }; /** - * Adds a Component in the specified cell. - * - * If the cell is already occupied, there are 3 cases - * - Component + Component => Group containing both components - * - Component + Group => Component is added to the group - * - Group + Component => Component is added to the group + * Checks whether the specified Component is in the Table. + */ + Table.prototype.has = function (component) { + for (var r = 0; r < this._nRows; r++) { + for (var c = 0; c < this._nCols; c++) { + if (this._rows[r][c] === component) { + return true; + } + } + } + return false; + }; + /** + * Adds a Component in the specified row and column position. * * For example, instead of calling `new Table([[a, b], [null, c]])`, you * could call * ```typescript * var table = new Table(); - * table.addComponent(0, 0, a); - * table.addComponent(0, 1, b); - * table.addComponent(1, 1, c); + * table.add(a, 0, 0); + * table.add(b, 0, 1); + * table.add(c, 1, 1); * ``` * + * @param {Component} component The Component to be added. * @param {number} row The row in which to add the Component. * @param {number} col The column in which to add the Component. - * @param {Component} component The Component to be added. * @returns {Table} The calling Table. */ - Table.prototype.addComponent = function (row, col, component) { + Table.prototype.add = function (component, row, col) { if (component == null) { throw Error("Cannot add null to a table cell"); } - var currentComponent = this._rows[row] && this._rows[row][col]; - if (currentComponent) { - component = component.above(currentComponent); - } - if (this._addComponent(component)) { + if (!this.has(component)) { + var currentComponent = this._rows[row] && this._rows[row][col]; + if (currentComponent != null) { + throw new Error("cell is occupied"); + } + component.detach(); this._nRows = Math.max(row + 1, this._nRows); this._nCols = Math.max(col + 1, this._nCols); this._padTableToSize(this._nRows, this._nCols); this._rows[row][col] = component; + this._adoptAndAnchor(component); + this.redraw(); } return this; }; - Table.prototype._removeComponent = function (component) { - _super.prototype._removeComponent.call(this, component); - var rowpos; - var colpos; - outer: for (var i = 0; i < this._nRows; i++) { - for (var j = 0; j < this._nCols; j++) { - if (this._rows[i][j] === component) { - rowpos = i; - colpos = j; - break outer; + Table.prototype._remove = function (component) { + for (var r = 0; r < this._nRows; r++) { + for (var c = 0; c < this._nCols; c++) { + if (this._rows[r][c] === component) { + this._rows[r][c] = null; + return true; } } } - if (rowpos !== undefined) { - this._rows[rowpos][colpos] = null; - } + return false; }; - Table.prototype._iterateLayout = function (availableWidth, availableHeight) { + Table.prototype._iterateLayout = function (availableWidth, availableHeight, isFinalOffer) { + if (isFinalOffer === void 0) { isFinalOffer = false; } /* * Given availableWidth and availableHeight, figure out how to allocate it between rows and columns using an iterative algorithm. * @@ -6195,25 +5768,25 @@ var Plottable; */ var rows = this._rows; var cols = d3.transpose(this._rows); - var availableWidthAfterPadding = availableWidth - this._colPadding * (this._nCols - 1); + var availableWidthAfterPadding = availableWidth - this._columnPadding * (this._nCols - 1); var availableHeightAfterPadding = availableHeight - this._rowPadding * (this._nRows - 1); - var rowWeights = Table._calcComponentWeights(this._rowWeights, rows, function (c) { return (c == null) || c._isFixedHeight(); }); - var colWeights = Table._calcComponentWeights(this._colWeights, cols, function (c) { return (c == null) || c._isFixedWidth(); }); + var rowWeights = Table._calcComponentWeights(this._rowWeights, rows, function (c) { return (c == null) || c.fixedHeight(); }); + var colWeights = Table._calcComponentWeights(this._columnWeights, cols, function (c) { return (c == null) || c.fixedWidth(); }); // To give the table a good starting position to iterate from, we give the fixed-width components half-weight // so that they will get some initial space allocated to work with var heuristicColWeights = colWeights.map(function (c) { return c === 0 ? 0.5 : c; }); var heuristicRowWeights = rowWeights.map(function (c) { return c === 0 ? 0.5 : c; }); var colProportionalSpace = Table._calcProportionalSpace(heuristicColWeights, availableWidthAfterPadding); var rowProportionalSpace = Table._calcProportionalSpace(heuristicRowWeights, availableHeightAfterPadding); - var guaranteedWidths = Plottable._Util.Methods.createFilledArray(0, this._nCols); - var guaranteedHeights = Plottable._Util.Methods.createFilledArray(0, this._nRows); + var guaranteedWidths = Plottable.Utils.Methods.createFilledArray(0, this._nCols); + var guaranteedHeights = Plottable.Utils.Methods.createFilledArray(0, this._nRows); var freeWidth; var freeHeight; var nIterations = 0; while (true) { - var offeredHeights = Plottable._Util.Methods.addArrays(guaranteedHeights, rowProportionalSpace); - var offeredWidths = Plottable._Util.Methods.addArrays(guaranteedWidths, colProportionalSpace); - var guarantees = this._determineGuarantees(offeredWidths, offeredHeights); + var offeredHeights = Plottable.Utils.Methods.addArrays(guaranteedHeights, rowProportionalSpace); + var offeredWidths = Plottable.Utils.Methods.addArrays(guaranteedWidths, colProportionalSpace); + var guarantees = this._determineGuarantees(offeredWidths, offeredHeights, isFinalOffer); guaranteedWidths = guarantees.guaranteedWidths; guaranteedHeights = guarantees.guaranteedHeights; var wantsWidth = guarantees.wantsWidthArr.some(function (x) { return x; }); @@ -6225,7 +5798,7 @@ var Plottable; var xWeights; if (wantsWidth) { xWeights = guarantees.wantsWidthArr.map(function (x) { return x ? 0.1 : 0; }); - xWeights = Plottable._Util.Methods.addArrays(xWeights, colWeights); + xWeights = Plottable.Utils.Methods.addArrays(xWeights, colWeights); } else { xWeights = colWeights; @@ -6233,7 +5806,7 @@ var Plottable; var yWeights; if (wantsHeight) { yWeights = guarantees.wantsHeightArr.map(function (x) { return x ? 0.1 : 0; }); - yWeights = Plottable._Util.Methods.addArrays(yWeights, rowWeights); + yWeights = Plottable.Utils.Methods.addArrays(yWeights, rowWeights); } else { yWeights = rowWeights; @@ -6257,118 +5830,111 @@ var Plottable; rowProportionalSpace = Table._calcProportionalSpace(rowWeights, freeHeight); return { colProportionalSpace: colProportionalSpace, rowProportionalSpace: rowProportionalSpace, guaranteedWidths: guarantees.guaranteedWidths, guaranteedHeights: guarantees.guaranteedHeights, wantsWidth: wantsWidth, wantsHeight: wantsHeight }; }; - Table.prototype._determineGuarantees = function (offeredWidths, offeredHeights) { - var requestedWidths = Plottable._Util.Methods.createFilledArray(0, this._nCols); - var requestedHeights = Plottable._Util.Methods.createFilledArray(0, this._nRows); - var layoutWantsWidth = Plottable._Util.Methods.createFilledArray(false, this._nCols); - var layoutWantsHeight = Plottable._Util.Methods.createFilledArray(false, this._nRows); + Table.prototype._determineGuarantees = function (offeredWidths, offeredHeights, isFinalOffer) { + if (isFinalOffer === void 0) { isFinalOffer = false; } + var requestedWidths = Plottable.Utils.Methods.createFilledArray(0, this._nCols); + var requestedHeights = Plottable.Utils.Methods.createFilledArray(0, this._nRows); + var columnNeedsWidth = Plottable.Utils.Methods.createFilledArray(false, this._nCols); + var rowNeedsHeight = Plottable.Utils.Methods.createFilledArray(false, this._nRows); this._rows.forEach(function (row, rowIndex) { row.forEach(function (component, colIndex) { var spaceRequest; if (component != null) { - spaceRequest = component._requestedSpace(offeredWidths[colIndex], offeredHeights[rowIndex]); + spaceRequest = component.requestedSpace(offeredWidths[colIndex], offeredHeights[rowIndex]); } else { - spaceRequest = { width: 0, height: 0, wantsWidth: false, wantsHeight: false }; + spaceRequest = { + minWidth: 0, + minHeight: 0 + }; } - var allocatedWidth = Math.min(spaceRequest.width, offeredWidths[colIndex]); - var allocatedHeight = Math.min(spaceRequest.height, offeredHeights[rowIndex]); - requestedWidths[colIndex] = Math.max(requestedWidths[colIndex], allocatedWidth); - requestedHeights[rowIndex] = Math.max(requestedHeights[rowIndex], allocatedHeight); - layoutWantsWidth[colIndex] = layoutWantsWidth[colIndex] || spaceRequest.wantsWidth; - layoutWantsHeight[rowIndex] = layoutWantsHeight[rowIndex] || spaceRequest.wantsHeight; + var columnWidth = isFinalOffer ? Math.min(spaceRequest.minWidth, offeredWidths[colIndex]) : spaceRequest.minWidth; + requestedWidths[colIndex] = Math.max(requestedWidths[colIndex], columnWidth); + var rowHeight = isFinalOffer ? Math.min(spaceRequest.minHeight, offeredHeights[rowIndex]) : spaceRequest.minHeight; + requestedHeights[rowIndex] = Math.max(requestedHeights[rowIndex], rowHeight); + var componentNeedsWidth = spaceRequest.minWidth > offeredWidths[colIndex]; + columnNeedsWidth[colIndex] = columnNeedsWidth[colIndex] || componentNeedsWidth; + var componentNeedsHeight = spaceRequest.minHeight > offeredHeights[rowIndex]; + rowNeedsHeight[rowIndex] = rowNeedsHeight[rowIndex] || componentNeedsHeight; }); }); - return { guaranteedWidths: requestedWidths, guaranteedHeights: requestedHeights, wantsWidthArr: layoutWantsWidth, wantsHeightArr: layoutWantsHeight }; + return { + guaranteedWidths: requestedWidths, + guaranteedHeights: requestedHeights, + wantsWidthArr: columnNeedsWidth, + wantsHeightArr: rowNeedsHeight + }; }; - Table.prototype._requestedSpace = function (offeredWidth, offeredHeight) { + Table.prototype.requestedSpace = function (offeredWidth, offeredHeight) { this._calculatedLayout = this._iterateLayout(offeredWidth, offeredHeight); - return { width: d3.sum(this._calculatedLayout.guaranteedWidths), height: d3.sum(this._calculatedLayout.guaranteedHeights), wantsWidth: this._calculatedLayout.wantsWidth, wantsHeight: this._calculatedLayout.wantsHeight }; + return { + minWidth: d3.sum(this._calculatedLayout.guaranteedWidths), + minHeight: d3.sum(this._calculatedLayout.guaranteedHeights) + }; }; - Table.prototype._computeLayout = function (offeredXOrigin, offeredYOrigin, availableWidth, availableHeight) { + Table.prototype.computeLayout = function (origin, availableWidth, availableHeight) { var _this = this; - _super.prototype._computeLayout.call(this, offeredXOrigin, offeredYOrigin, availableWidth, availableHeight); - var layout = this._useLastCalculatedLayout() ? this._calculatedLayout : this._iterateLayout(this.width(), this.height()); - this._useLastCalculatedLayout(true); + _super.prototype.computeLayout.call(this, origin, availableWidth, availableHeight); + var lastLayoutWidth = d3.sum(this._calculatedLayout.guaranteedWidths); + var lastLayoutHeight = d3.sum(this._calculatedLayout.guaranteedHeights); + var layout = this._calculatedLayout; + if (lastLayoutWidth > this.width() || lastLayoutHeight > this.height()) { + layout = this._iterateLayout(this.width(), this.height(), true); + } var childYOrigin = 0; - var rowHeights = Plottable._Util.Methods.addArrays(layout.rowProportionalSpace, layout.guaranteedHeights); - var colWidths = Plottable._Util.Methods.addArrays(layout.colProportionalSpace, layout.guaranteedWidths); + var rowHeights = Plottable.Utils.Methods.addArrays(layout.rowProportionalSpace, layout.guaranteedHeights); + var colWidths = Plottable.Utils.Methods.addArrays(layout.colProportionalSpace, layout.guaranteedWidths); this._rows.forEach(function (row, rowIndex) { var childXOrigin = 0; row.forEach(function (component, colIndex) { // recursively compute layout if (component != null) { - component._computeLayout(childXOrigin, childYOrigin, colWidths[colIndex], rowHeights[rowIndex]); + component.computeLayout({ x: childXOrigin, y: childYOrigin }, colWidths[colIndex], rowHeights[rowIndex]); } - childXOrigin += colWidths[colIndex] + _this._colPadding; + childXOrigin += colWidths[colIndex] + _this._columnPadding; }); childYOrigin += rowHeights[rowIndex] + _this._rowPadding; }); + return this; }; - /** - * Sets the row and column padding on the Table. - * - * @param {number} rowPadding The padding above and below each row, in pixels. - * @param {number} colPadding the padding to the left and right of each column, in pixels. - * @returns {Table} The calling Table. - */ - Table.prototype.padding = function (rowPadding, colPadding) { + Table.prototype.rowPadding = function (rowPadding) { + if (rowPadding == null) { + return this._rowPadding; + } this._rowPadding = rowPadding; - this._colPadding = colPadding; - this._invalidateLayout(); + this.redraw(); + return this; + }; + Table.prototype.columnPadding = function (columnPadding) { + if (columnPadding == null) { + return this._columnPadding; + } + this._columnPadding = columnPadding; + this.redraw(); return this; }; - /** - * Sets the layout weight of a particular row. - * Space is allocated to rows based on their weight. Rows with higher weights receive proportionally more space. - * - * A common case would be to have one row take up 2/3rds of the space, - * and the other row take up 1/3rd. - * - * Example: - * - * ```JavaScript - * plot = new Plottable.Component.Table([ - * [row1], - * [row2] - * ]); - * - * // assign twice as much space to the first row - * plot - * .rowWeight(0, 2) - * .rowWeight(1, 1) - * ``` - * - * @param {number} index The index of the row. - * @param {number} weight The weight to be set on the row. - * @returns {Table} The calling Table. - */ Table.prototype.rowWeight = function (index, weight) { + if (weight == null) { + return this._rowWeights[index]; + } this._rowWeights[index] = weight; - this._invalidateLayout(); + this.redraw(); return this; }; - /** - * Sets the layout weight of a particular column. - * Space is allocated to columns based on their weight. Columns with higher weights receive proportionally more space. - * - * Please see `rowWeight` docs for an example. - * - * @param {number} index The index of the column. - * @param {number} weight The weight to be set on the column. - * @returns {Table} The calling Table. - */ - Table.prototype.colWeight = function (index, weight) { - this._colWeights[index] = weight; - this._invalidateLayout(); + Table.prototype.columnWeight = function (index, weight) { + if (weight == null) { + return this._columnWeights[index]; + } + this._columnWeights[index] = weight; + this.redraw(); return this; }; - Table.prototype._isFixedWidth = function () { + Table.prototype.fixedWidth = function () { var cols = d3.transpose(this._rows); - return Table._fixedSpace(cols, function (c) { return (c == null) || c._isFixedWidth(); }); + return Table._fixedSpace(cols, function (c) { return (c == null) || c.fixedWidth(); }); }; - Table.prototype._isFixedHeight = function () { - return Table._fixedSpace(this._rows, function (c) { return (c == null) || c._isFixedHeight(); }); + Table.prototype.fixedHeight = function () { + return Table._fixedSpace(this._rows, function (c) { return (c == null) || c.fixedHeight(); }); }; Table.prototype._padTableToSize = function (nRows, nCols) { for (var i = 0; i < nRows; i++) { @@ -6383,8 +5949,8 @@ var Plottable; } } for (j = 0; j < nCols; j++) { - if (this._colWeights[j] === undefined) { - this._colWeights[j] = null; + if (this._columnWeights[j] === undefined) { + this._columnWeights[j] = null; } } }; @@ -6404,7 +5970,7 @@ var Plottable; Table._calcProportionalSpace = function (weights, freeSpace) { var weightSum = d3.sum(weights); if (weightSum === 0) { - return Plottable._Util.Methods.createFilledArray(0, weights.length); + return Plottable.Utils.Methods.createFilledArray(0, weights.length); } else { return weights.map(function (w) { return freeSpace * w / weightSum; }); @@ -6416,9 +5982,9 @@ var Plottable; return all(componentGroup.map(group_isFixed)); }; return Table; - })(Component.AbstractComponentContainer); - Component.Table = Table; - })(Component = Plottable.Component || (Plottable.Component = {})); + })(Plottable.ComponentContainer); + Components.Table = Table; + })(Components = Plottable.Components || (Plottable.Components = {})); })(Plottable || (Plottable = {})); /// @@ -6430,8 +5996,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Component; - (function (Component) { + var Components; + (function (Components) { var SelectionBoxLayer = (function (_super) { __extends(SelectionBoxLayer, _super); function SelectionBoxLayer() { @@ -6442,8 +6008,6 @@ var Plottable; bottomRight: { x: 0, y: 0 } }; this.classed("selection-box-layer", true); - this._fixedWidthFlag = true; - this._fixedHeightFlag = true; } SelectionBoxLayer.prototype._setup = function () { _super.prototype._setup.call(this); @@ -6461,7 +6025,7 @@ var Plottable; return this._boxBounds; } this._setBounds(newBounds); - this._render(); + this.render(); return this; }; SelectionBoxLayer.prototype._setBounds = function (newBounds) { @@ -6478,7 +6042,7 @@ var Plottable; bottomRight: bottomRight }; }; - SelectionBoxLayer.prototype._doRender = function () { + SelectionBoxLayer.prototype.renderImmediately = function () { if (this._boxVisible) { var t = this._boxBounds.topLeft.y; var b = this._boxBounds.bottomRight.y; @@ -6495,19 +6059,26 @@ var Plottable; else { this._box.remove(); } + return this; }; SelectionBoxLayer.prototype.boxVisible = function (show) { if (show == null) { return this._boxVisible; } this._boxVisible = show; - this._render(); + this.render(); return this; }; + SelectionBoxLayer.prototype.fixedWidth = function () { + return true; + }; + SelectionBoxLayer.prototype.fixedHeight = function () { + return true; + }; return SelectionBoxLayer; - })(Component.AbstractComponent); - Component.SelectionBoxLayer = SelectionBoxLayer; - })(Component = Plottable.Component || (Plottable.Component = {})); + })(Plottable.Component); + Components.SelectionBoxLayer = SelectionBoxLayer; + })(Components = Plottable.Components || (Plottable.Components = {})); })(Plottable || (Plottable = {})); /// @@ -6519,469 +6090,496 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Plot; - (function (Plot) { - var AbstractPlot = (function (_super) { - __extends(AbstractPlot, _super); - /** - * Constructs a Plot. - * - * Plots render data. Common example include Plot.Scatter, Plot.Bar, and Plot.Line. - * - * A bare Plot has a DataSource and any number of projectors, which take - * data and "project" it onto the Plot, such as "x", "y", "fill", "r". - * - * @constructor - * @param {any[]|Dataset} [dataset] If provided, the data or Dataset to be associated with this Plot. - */ - function AbstractPlot() { - _super.call(this); - this._dataChanged = false; - this._projections = {}; - this._animate = false; - this._animators = {}; - this._animateOnNextRender = true; - this.clipPathEnabled = true; - this.classed("plot", true); - this._key2PlotDatasetKey = d3.map(); - this._datasetKeysInOrder = []; - this._nextSeriesIndex = 0; - } - AbstractPlot.prototype._anchor = function (element) { - _super.prototype._anchor.call(this, element); - this._animateOnNextRender = true; - this._dataChanged = true; - this._updateScaleExtents(); - }; - AbstractPlot.prototype._setup = function () { - var _this = this; - _super.prototype._setup.call(this); - this._renderArea = this._content.append("g").classed("render-area", true); - // HACKHACK on 591 - this._getDrawersInOrder().forEach(function (d) { return d.setup(_this._renderArea.append("g")); }); - }; - AbstractPlot.prototype.remove = function () { - var _this = this; - _super.prototype.remove.call(this); - this._datasetKeysInOrder.forEach(function (k) { return _this.removeDataset(k); }); - // deregister from all scales - var properties = Object.keys(this._projections); - properties.forEach(function (property) { - var projector = _this._projections[property]; - if (projector.scale) { - projector.scale.broadcaster.deregisterListener(_this); - } - }); - }; - AbstractPlot.prototype.addDataset = function (keyOrDataset, dataset) { - if (typeof (keyOrDataset) !== "string" && dataset !== undefined) { - throw new Error("invalid input to addDataset"); - } - if (typeof (keyOrDataset) === "string" && keyOrDataset[0] === "_") { - Plottable._Util.Methods.warn("Warning: Using _named series keys may produce collisions with unlabeled data sources"); - } - var key = typeof (keyOrDataset) === "string" ? keyOrDataset : "_" + this._nextSeriesIndex++; - var data = typeof (keyOrDataset) !== "string" ? keyOrDataset : dataset; - dataset = (data instanceof Plottable.Dataset) ? data : new Plottable.Dataset(data); - this._addDataset(key, dataset); - return this; - }; - AbstractPlot.prototype._addDataset = function (key, dataset) { - var _this = this; - if (this._key2PlotDatasetKey.has(key)) { - this.removeDataset(key); - } - ; - var drawer = this._getDrawer(key); - var metadata = this._getPlotMetadataForDataset(key); - var pdk = { drawer: drawer, dataset: dataset, key: key, plotMetadata: metadata }; - this._datasetKeysInOrder.push(key); - this._key2PlotDatasetKey.set(key, pdk); - if (this._isSetup) { - drawer.setup(this._renderArea.append("g")); - } - dataset.broadcaster.registerListener(this, function () { return _this._onDatasetUpdate(); }); - this._onDatasetUpdate(); - }; - AbstractPlot.prototype._getDrawer = function (key) { - return new Plottable._Drawer.AbstractDrawer(key); - }; - AbstractPlot.prototype._getAnimator = function (key) { - if (this._animate && this._animateOnNextRender) { - return this._animators[key] || new Plottable.Animator.Null(); - } - else { - return new Plottable.Animator.Null(); - } - }; - AbstractPlot.prototype._onDatasetUpdate = function () { - this._updateScaleExtents(); - this._animateOnNextRender = true; - this._dataChanged = true; - this._render(); - }; - /** - * Sets an attribute of every data point. - * - * Here's a common use case: - * ```typescript - * plot.attr("x", function(d) { return d.foo; }, xScale); - * ``` - * This will set the x accessor of each datum `d` to be `d.foo`, - * scaled in accordance with `xScale` - * - * @param {string} attrToSet The attribute to set across each data - * point. Popular examples include "x", "y". - * - * @param {Function|string|any} accessor Function to apply to each element - * of the dataSource. If a Function, use `accessor(d, i)`. If a string, - * `d[accessor]` is used. If anything else, use `accessor` as a constant - * across all data points. - * - * @param {Scale.AbstractScale} scale If provided, the result of the accessor - * is passed through the scale, such as `scale.scale(accessor(d, i))`. - * - * @returns {Plot} The calling Plot. - */ - AbstractPlot.prototype.attr = function (attrToSet, accessor, scale) { - return this.project(attrToSet, accessor, scale); - }; - /** - * Identical to plot.attr - */ - AbstractPlot.prototype.project = function (attrToSet, accessor, scale) { - var _this = this; - attrToSet = attrToSet.toLowerCase(); - var currentProjection = this._projections[attrToSet]; - var existingScale = currentProjection && currentProjection.scale; - if (existingScale) { - this._datasetKeysInOrder.forEach(function (key) { - existingScale._removeExtent(_this.getID().toString() + "_" + key, attrToSet); - existingScale.broadcaster.deregisterListener(_this); - }); - } - if (scale) { - scale.broadcaster.registerListener(this, function () { return _this._render(); }); - } - accessor = Plottable._Util.Methods.accessorize(accessor); - this._projections[attrToSet] = { accessor: accessor, scale: scale, attribute: attrToSet }; - this._updateScaleExtent(attrToSet); - this._render(); // queue a re-render upon changing projector - return this; - }; - AbstractPlot.prototype._generateAttrToProjector = function () { - var _this = this; - var h = {}; - d3.keys(this._projections).forEach(function (a) { - var projection = _this._projections[a]; - var accessor = projection.accessor; - var scale = projection.scale; - var fn = scale ? function (d, i, u, m) { return scale.scale(accessor(d, i, u, m)); } : accessor; - h[a] = fn; - }); - return h; - }; - /** - * Generates a dictionary mapping an attribute to a function that calculate that attribute's value - * in accordance with the given datasetKey. - * - * Note that this will return all of the data attributes, which may not perfectly align to svg attributes - * - * @param {datasetKey} the key of the dataset to generate the dictionary for - * @returns {AttributeToAppliedProjector} A dictionary mapping attributes to functions - */ - AbstractPlot.prototype.generateProjectors = function (datasetKey) { + var Plots; + (function (Plots) { + })(Plots = Plottable.Plots || (Plottable.Plots = {})); + var Plot = (function (_super) { + __extends(Plot, _super); + /** + * Constructs a Plot. + * + * Plots render data. Common example include Plot.Scatter, Plot.Bar, and Plot.Line. + * + * A bare Plot has a DataSource and any number of projectors, which take + * data and "project" it onto the Plot, such as "x", "y", "fill", "r". + * + * @constructor + * @param {any[]|Dataset} [dataset] If provided, the data or Dataset to be associated with this Plot. + */ + function Plot() { + var _this = this; + _super.call(this); + this._dataChanged = false; + this._animate = false; + this._animators = {}; + this._animateOnNextRender = true; + this._clipPathEnabled = true; + this.classed("plot", true); + this._key2PlotDatasetKey = d3.map(); + this._attrBindings = d3.map(); + this._attrExtents = d3.map(); + this._extentsProvider = function (scale) { return _this._extentsForScale(scale); }; + this._datasetKeysInOrder = []; + this._nextSeriesIndex = 0; + this._renderCallback = function (scale) { return _this.render(); }; + this._onDatasetUpdateCallback = function () { return _this._onDatasetUpdate(); }; + this._propertyBindings = d3.map(); + this._propertyExtents = d3.map(); + } + Plot.prototype.anchor = function (selection) { + _super.prototype.anchor.call(this, selection); + this._animateOnNextRender = true; + this._dataChanged = true; + this._updateExtents(); + return this; + }; + Plot.prototype._setup = function () { + var _this = this; + _super.prototype._setup.call(this); + this._renderArea = this._content.append("g").classed("render-area", true); + // HACKHACK on 591 + this._getDrawersInOrder().forEach(function (d) { return d.setup(_this._renderArea.append("g")); }); + }; + Plot.prototype.destroy = function () { + var _this = this; + _super.prototype.destroy.call(this); + this._scales().forEach(function (scale) { return scale.offUpdate(_this._renderCallback); }); + this.datasets().forEach(function (dataset) { return _this.removeDataset(dataset); }); + }; + /** + * @param {Dataset} dataset + * @returns {Plot} The calling Plot. + */ + Plot.prototype.addDataset = function (dataset) { + var key = "_" + this._nextSeriesIndex++; + if (this._key2PlotDatasetKey.has(key)) { + this.removeDataset(dataset); + } + ; + var drawer = this._getDrawer(key); + var metadata = this._getPlotMetadataForDataset(key); + var pdk = { drawer: drawer, dataset: dataset, key: key, plotMetadata: metadata }; + this._datasetKeysInOrder.push(key); + this._key2PlotDatasetKey.set(key, pdk); + if (this._isSetup) { + drawer.setup(this._renderArea.append("g")); + } + dataset.onUpdate(this._onDatasetUpdateCallback); + this._onDatasetUpdate(); + return this; + }; + Plot.prototype._getDrawer = function (key) { + return new Plottable.Drawers.AbstractDrawer(key); + }; + Plot.prototype._getAnimator = function (key) { + if (this._animate && this._animateOnNextRender) { + return this._animators[key] || new Plottable.Animators.Null(); + } + else { + return new Plottable.Animators.Null(); + } + }; + Plot.prototype._onDatasetUpdate = function () { + this._updateExtents(); + this._animateOnNextRender = true; + this._dataChanged = true; + this.render(); + }; + Plot.prototype.attr = function (attr, attrValue, scale) { + if (attrValue == null) { + return this._attrBindings.get(attr); + } + this._bindAttr(attr, attrValue, scale); + this.render(); // queue a re-render upon changing projector + return this; + }; + Plot.prototype._bindProperty = function (property, value, scale) { + this._bind(property, value, scale, this._propertyBindings, this._propertyExtents); + this._updateExtentsForProperty(property); + }; + Plot.prototype._bindAttr = function (attr, value, scale) { + this._bind(attr, value, scale, this._attrBindings, this._attrExtents); + this._updateExtentsForAttr(attr); + }; + Plot.prototype._bind = function (key, value, scale, bindings, extents) { + var binding = bindings.get(key); + var oldScale = binding != null ? binding.scale : null; + if (oldScale != null) { + this._uninstallScaleForKey(oldScale, key); + } + if (scale != null) { + this._installScaleForKey(scale, key); + } + bindings.set(key, { accessor: d3.functor(value), scale: scale }); + }; + Plot.prototype._generateAttrToProjector = function () { + var h = {}; + this._attrBindings.forEach(function (attr, binding) { + var accessor = binding.accessor; + var scale = binding.scale; + var fn = scale ? function (d, i, dataset, m) { return scale.scale(accessor(d, i, dataset, m)); } : accessor; + h[attr] = fn; + }); + var propertyProjectors = this._generatePropertyToProjectors(); + Object.keys(propertyProjectors).forEach(function (key) { + if (h[key] == null) { + h[key] = propertyProjectors[key]; + } + }); + return h; + }; + /** + * Generates a dictionary mapping an attribute to a function that calculate that attribute's value + * in accordance with the given datasetKey. + * + * Note that this will return all of the data attributes, which may not perfectly align to svg attributes + * + * @param {Dataset} dataset The dataset to generate the dictionary for + * @returns {AttributeToAppliedProjector} A dictionary mapping attributes to functions + */ + Plot.prototype.generateProjectors = function (dataset) { + var attrToAppliedProjector = {}; + var datasetKey = this._keyForDataset(dataset); + if (datasetKey != null) { var attrToProjector = this._generateAttrToProjector(); var plotDatasetKey = this._key2PlotDatasetKey.get(datasetKey); var plotMetadata = plotDatasetKey.plotMetadata; - var userMetadata = plotDatasetKey.dataset.metadata(); - var attrToAppliedProjector = {}; d3.entries(attrToProjector).forEach(function (keyValue) { - attrToAppliedProjector[keyValue.key] = function (datum, index) { return keyValue.value(datum, index, userMetadata, plotMetadata); }; + attrToAppliedProjector[keyValue.key] = function (datum, index) { + return keyValue.value(datum, index, plotDatasetKey.dataset, plotMetadata); + }; }); - return attrToAppliedProjector; - }; - AbstractPlot.prototype._doRender = function () { - if (this._isAnchored) { - this._paint(); - this._dataChanged = false; - this._animateOnNextRender = false; - } - }; - /** - * Enables or disables animation. - * - * @param {boolean} enabled Whether or not to animate. - */ - AbstractPlot.prototype.animate = function (enabled) { - this._animate = enabled; - return this; - }; - AbstractPlot.prototype.detach = function () { - _super.prototype.detach.call(this); - // make the domain resize - this._updateScaleExtents(); - return this; - }; - /** - * This function makes sure that all of the scales in this._projections - * have an extent that includes all the data that is projected onto them. - */ - AbstractPlot.prototype._updateScaleExtents = function () { - var _this = this; - d3.keys(this._projections).forEach(function (attr) { return _this._updateScaleExtent(attr); }); - }; - AbstractPlot.prototype._updateScaleExtent = function (attr) { - var _this = this; - var projector = this._projections[attr]; - if (projector.scale) { - this._datasetKeysInOrder.forEach(function (key) { - var plotDatasetKey = _this._key2PlotDatasetKey.get(key); - var dataset = plotDatasetKey.dataset; - var plotMetadata = plotDatasetKey.plotMetadata; - var extent = dataset._getExtent(projector.accessor, projector.scale._typeCoercer, plotMetadata); - var scaleKey = _this.getID().toString() + "_" + key; - if (extent.length === 0 || !_this._isAnchored) { - projector.scale._removeExtent(scaleKey, attr); - } - else { - projector.scale._updateExtent(scaleKey, attr, extent); - } - }); - } - }; - AbstractPlot.prototype.animator = function (animatorKey, animator) { - if (animator === undefined) { - return this._animators[animatorKey]; - } - else { - this._animators[animatorKey] = animator; - return this; - } - }; - AbstractPlot.prototype.datasetOrder = function (order) { - if (order === undefined) { - return this._datasetKeysInOrder; - } - function isPermutation(l1, l2) { - var intersection = Plottable._Util.Methods.intersection(d3.set(l1), d3.set(l2)); - var size = intersection.size(); // HACKHACK pending on borisyankov/definitelytyped/ pr #2653 - return size === l1.length && size === l2.length; - } - if (isPermutation(order, this._datasetKeysInOrder)) { - this._datasetKeysInOrder = order; - this._onDatasetUpdate(); + } + return attrToAppliedProjector; + }; + Plot.prototype.renderImmediately = function () { + if (this._isAnchored) { + this._paint(); + this._dataChanged = false; + this._animateOnNextRender = false; + } + return this; + }; + /** + * Enables or disables animation. + * + * @param {boolean} enabled Whether or not to animate. + */ + Plot.prototype.animate = function (enabled) { + this._animate = enabled; + return this; + }; + Plot.prototype.detach = function () { + _super.prototype.detach.call(this); + // make the domain resize + this._updateExtents(); + return this; + }; + /** + * @returns {Scale[]} A unique array of all scales currently used by the Plot. + */ + Plot.prototype._scales = function () { + var scales = []; + this._attrBindings.forEach(function (attr, binding) { + var scale = binding.scale; + if (scale != null && scales.indexOf(scale) === -1) { + scales.push(scale); + } + }); + this._propertyBindings.forEach(function (property, binding) { + var scale = binding.scale; + if (scale != null && scales.indexOf(scale) === -1) { + scales.push(scale); + } + }); + return scales; + }; + /** + * Updates the extents associated with each attribute, then autodomains all scales the Plot uses. + */ + Plot.prototype._updateExtents = function () { + var _this = this; + this._attrBindings.forEach(function (attr) { return _this._updateExtentsForAttr(attr); }); + this._propertyExtents.forEach(function (property) { return _this._updateExtentsForProperty(property); }); + this._scales().forEach(function (scale) { return scale._autoDomainIfAutomaticMode(); }); + }; + Plot.prototype._updateExtentsForAttr = function (attr) { + // Filters should never be applied to attributes + this._updateExtentsForKey(attr, this._attrBindings, this._attrExtents, null); + }; + Plot.prototype._updateExtentsForProperty = function (property) { + this._updateExtentsForKey(property, this._propertyBindings, this._propertyExtents, this._filterForProperty(property)); + }; + Plot.prototype._filterForProperty = function (property) { + return null; + }; + Plot.prototype._updateExtentsForKey = function (key, bindings, extents, filter) { + var _this = this; + var accScaleBinding = bindings.get(key); + if (accScaleBinding.accessor == null) { + return; + } + extents.set(key, this._datasetKeysInOrder.map(function (key) { + var plotDatasetKey = _this._key2PlotDatasetKey.get(key); + var dataset = plotDatasetKey.dataset; + var plotMetadata = plotDatasetKey.plotMetadata; + return _this._computeExtent(dataset, accScaleBinding.accessor, plotMetadata, filter); + })); + }; + Plot.prototype._computeExtent = function (dataset, accessor, plotMetadata, filter) { + var data = dataset.data(); + if (filter != null) { + data = data.filter(function (d, i) { return filter(d, i, dataset, plotMetadata); }); + } + var appliedAccessor = function (d, i) { return accessor(d, i, dataset, plotMetadata); }; + var mappedData = data.map(appliedAccessor); + if (mappedData.length === 0) { + return []; + } + else if (typeof (mappedData[0]) === "string") { + return Plottable.Utils.Methods.uniq(mappedData); + } + else { + var extent = d3.extent(mappedData); + if (extent[0] == null || extent[1] == null) { + return []; } else { - Plottable._Util.Methods.warn("Attempted to change datasetOrder, but new order is not permutation of old. Ignoring."); - } - return this; - }; - /** - * Removes a dataset by the given identifier - * - * @param {string | Dataset | any[]} datasetIdentifer The identifier as the key of the Dataset to remove - * If string is inputted, it is interpreted as the dataset key to remove. - * If Dataset is inputted, the first Dataset in the plot that is the same will be removed. - * If any[] is inputted, the first data array in the plot that is the same will be removed. - * @returns {AbstractPlot} The calling AbstractPlot. - */ - AbstractPlot.prototype.removeDataset = function (datasetIdentifier) { - var key; - if (typeof datasetIdentifier === "string") { - key = datasetIdentifier; + return extent; } - else if (typeof datasetIdentifier === "object") { - var index = -1; - if (datasetIdentifier instanceof Plottable.Dataset) { - var datasetArray = this.datasets(); - index = datasetArray.indexOf(datasetIdentifier); - } - else if (datasetIdentifier instanceof Array) { - var dataArray = this.datasets().map(function (d) { return d.data(); }); - index = dataArray.indexOf(datasetIdentifier); - } - if (index !== -1) { - key = this._datasetKeysInOrder[index]; + } + }; + /** + * Override in subclass to add special extents, such as included values + */ + Plot.prototype._extentsForProperty = function (property) { + return this._propertyExtents.get(property); + }; + Plot.prototype._extentsForScale = function (scale) { + var _this = this; + if (!this._isAnchored) { + return []; + } + var allSetsOfExtents = []; + this._attrBindings.forEach(function (attr, binding) { + if (binding.scale === scale) { + var extents = _this._attrExtents.get(attr); + if (extents != null) { + allSetsOfExtents.push(extents); } } - return this._removeDataset(key); - }; - AbstractPlot.prototype._removeDataset = function (key) { - if (key != null && this._key2PlotDatasetKey.has(key)) { - var pdk = this._key2PlotDatasetKey.get(key); - pdk.drawer.remove(); - var projectors = d3.values(this._projections); - var scaleKey = this.getID().toString() + "_" + key; - projectors.forEach(function (p) { - if (p.scale != null) { - p.scale._removeExtent(scaleKey, p.attribute); - } - }); - pdk.dataset.broadcaster.deregisterListener(this); - this._datasetKeysInOrder.splice(this._datasetKeysInOrder.indexOf(key), 1); - this._key2PlotDatasetKey.remove(key); - this._onDatasetUpdate(); + }); + this._propertyBindings.forEach(function (property, binding) { + if (binding.scale === scale) { + var extents = _this._extentsForProperty(property); + if (extents != null) { + allSetsOfExtents.push(extents); + } } + }); + return d3.merge(allSetsOfExtents); + }; + Plot.prototype.animator = function (animatorKey, animator) { + if (animator === undefined) { + return this._animators[animatorKey]; + } + else { + this._animators[animatorKey] = animator; return this; + } + }; + /** + * @param {Dataset} dataset + * @returns {Plot} The calling Plot. + */ + Plot.prototype.removeDataset = function (dataset) { + var key = this._keyForDataset(dataset); + if (key != null && this._key2PlotDatasetKey.has(key)) { + var pdk = this._key2PlotDatasetKey.get(key); + pdk.drawer.remove(); + pdk.dataset.offUpdate(this._onDatasetUpdateCallback); + this._datasetKeysInOrder.splice(this._datasetKeysInOrder.indexOf(key), 1); + this._key2PlotDatasetKey.remove(key); + this._onDatasetUpdate(); + } + return this; + }; + /** + * Returns the internal key for the Dataset, or undefined if not found + */ + Plot.prototype._keyForDataset = function (dataset) { + return this._datasetKeysInOrder[this.datasets().indexOf(dataset)]; + }; + /** + * Returns an array of internal keys corresponding to those Datasets actually on the plot + */ + Plot.prototype._keysForDatasets = function (datasets) { + var _this = this; + return datasets.map(function (dataset) { return _this._keyForDataset(dataset); }).filter(function (key) { return key != null; }); + }; + Plot.prototype.datasets = function (datasets) { + var _this = this; + var currentDatasets = this._datasetKeysInOrder.map(function (k) { return _this._key2PlotDatasetKey.get(k).dataset; }); + if (datasets == null) { + return currentDatasets; + } + currentDatasets.forEach(function (dataset) { return _this.removeDataset(dataset); }); + datasets.forEach(function (dataset) { return _this.addDataset(dataset); }); + return this; + }; + Plot.prototype._getDrawersInOrder = function () { + var _this = this; + return this._datasetKeysInOrder.map(function (k) { return _this._key2PlotDatasetKey.get(k).drawer; }); + }; + Plot.prototype._generateDrawSteps = function () { + return [{ attrToProjector: this._generateAttrToProjector(), animator: new Plottable.Animators.Null() }]; + }; + Plot.prototype._additionalPaint = function (time) { + // no-op + }; + Plot.prototype._getDataToDraw = function () { + var _this = this; + var datasets = d3.map(); + this._datasetKeysInOrder.forEach(function (key) { + datasets.set(key, _this._key2PlotDatasetKey.get(key).dataset.data()); + }); + return datasets; + }; + /** + * Gets the new plot metadata for new dataset with provided key + * + * @param {string} key The key of new dataset + */ + Plot.prototype._getPlotMetadataForDataset = function (key) { + return { + datasetKey: key }; - AbstractPlot.prototype.datasets = function () { - var _this = this; - return this._datasetKeysInOrder.map(function (k) { return _this._key2PlotDatasetKey.get(k).dataset; }); - }; - AbstractPlot.prototype._getDrawersInOrder = function () { - var _this = this; - return this._datasetKeysInOrder.map(function (k) { return _this._key2PlotDatasetKey.get(k).drawer; }); - }; - AbstractPlot.prototype._generateDrawSteps = function () { - return [{ attrToProjector: this._generateAttrToProjector(), animator: new Plottable.Animator.Null() }]; - }; - AbstractPlot.prototype._additionalPaint = function (time) { - // no-op - }; - AbstractPlot.prototype._getDataToDraw = function () { - var _this = this; - var datasets = d3.map(); - this._datasetKeysInOrder.forEach(function (key) { - datasets.set(key, _this._key2PlotDatasetKey.get(key).dataset.data()); - }); - return datasets; - }; - /** - * Gets the new plot metadata for new dataset with provided key - * - * @param {string} key The key of new dataset - */ - AbstractPlot.prototype._getPlotMetadataForDataset = function (key) { - return { - datasetKey: key - }; - }; - AbstractPlot.prototype._paint = function () { - var _this = this; - var drawSteps = this._generateDrawSteps(); - var dataToDraw = this._getDataToDraw(); - var drawers = this._getDrawersInOrder(); - // TODO: Use metadata instead of dataToDraw #1297. - var times = this._datasetKeysInOrder.map(function (k, i) { return drawers[i].draw(dataToDraw.get(k), drawSteps, _this._key2PlotDatasetKey.get(k).dataset.metadata(), _this._key2PlotDatasetKey.get(k).plotMetadata); }); - var maxTime = Plottable._Util.Methods.max(times, 0); - this._additionalPaint(maxTime); - }; - /** - * Retrieves all of the selections of this plot for the specified dataset(s) - * - * @param {string | string[]} datasetKeys The dataset(s) to retrieve the selections from. - * If not provided, all selections will be retrieved. - * @param {boolean} exclude If set to true, all datasets will be queried excluding the keys referenced - * in the previous datasetKeys argument (default = false). - * @returns {D3.Selection} The retrieved selections. - */ - AbstractPlot.prototype.getAllSelections = function (datasetKeys, exclude) { - var _this = this; - if (datasetKeys === void 0) { datasetKeys = this.datasetOrder(); } - if (exclude === void 0) { exclude = false; } - var datasetKeyArray = []; - if (typeof (datasetKeys) === "string") { - datasetKeyArray = [datasetKeys]; - } - else { - datasetKeyArray = datasetKeys; - } - if (exclude) { - var excludedDatasetKeys = d3.set(datasetKeyArray); - datasetKeyArray = this.datasetOrder().filter(function (datasetKey) { return !excludedDatasetKeys.has(datasetKey); }); + }; + Plot.prototype._paint = function () { + var _this = this; + var drawSteps = this._generateDrawSteps(); + var dataToDraw = this._getDataToDraw(); + var drawers = this._getDrawersInOrder(); + var times = this._datasetKeysInOrder.map(function (k, i) { return drawers[i].draw(dataToDraw.get(k), drawSteps, _this._key2PlotDatasetKey.get(k).dataset, _this._key2PlotDatasetKey.get(k).plotMetadata); }); + var maxTime = Plottable.Utils.Methods.max(times, 0); + this._additionalPaint(maxTime); + }; + /** + * Retrieves all of the Selections of this Plot for the specified Datasets. + * + * @param {Dataset[]} datasets The Datasets to retrieve the selections from. + * If not provided, all selections will be retrieved. + * @param {boolean} exclude If set to true, all Datasets will be queried excluding the keys referenced + * in the previous datasetKeys argument (default = false). + * @returns {D3.Selection} The retrieved Selections. + */ + Plot.prototype.getAllSelections = function (datasets, exclude) { + var _this = this; + if (datasets === void 0) { datasets = this.datasets(); } + if (exclude === void 0) { exclude = false; } + var datasetKeyArray = this._keysForDatasets(datasets); + if (exclude) { + var excludedDatasetKeys = d3.set(datasetKeyArray); + datasetKeyArray = this._datasetKeysInOrder.filter(function (datasetKey) { return !excludedDatasetKeys.has(datasetKey); }); + } + var allSelections = []; + datasetKeyArray.forEach(function (datasetKey) { + var plotDatasetKey = _this._key2PlotDatasetKey.get(datasetKey); + if (plotDatasetKey == null) { + return; } - var allSelections = []; - datasetKeyArray.forEach(function (datasetKey) { - var plotDatasetKey = _this._key2PlotDatasetKey.get(datasetKey); - if (plotDatasetKey == null) { - return; - } - var drawer = plotDatasetKey.drawer; - drawer._getRenderArea().selectAll(drawer._getSelector()).each(function () { - allSelections.push(this); - }); + var drawer = plotDatasetKey.drawer; + drawer._getRenderArea().selectAll(drawer._getSelector()).each(function () { + allSelections.push(this); }); - return d3.selectAll(allSelections); - }; - /** - * Retrieves all of the PlotData of this plot for the specified dataset(s) - * - * @param {string | string[]} datasetKeys The dataset(s) to retrieve the selections from. - * If not provided, all selections will be retrieved. - * @returns {PlotData} The retrieved PlotData. - */ - AbstractPlot.prototype.getAllPlotData = function (datasetKeys) { - if (datasetKeys === void 0) { datasetKeys = this.datasetOrder(); } - var datasetKeyArray = []; - if (typeof (datasetKeys) === "string") { - datasetKeyArray = [datasetKeys]; - } - else { - datasetKeyArray = datasetKeys; + }); + return d3.selectAll(allSelections); + }; + /** + * Retrieves all of the PlotData of this plot for the specified dataset(s) + * + * @param {Dataset[]} datasets The Datasets to retrieve the PlotData from. + * If not provided, all PlotData will be retrieved. + * @returns {PlotData} The retrieved PlotData. + */ + Plot.prototype.getAllPlotData = function (datasets) { + var _this = this; + if (datasets === void 0) { datasets = this.datasets(); } + var data = []; + var pixelPoints = []; + var allElements = []; + this._keysForDatasets(datasets).forEach(function (datasetKey) { + var plotDatasetKey = _this._key2PlotDatasetKey.get(datasetKey); + if (plotDatasetKey == null) { + return; } - return this._getAllPlotData(datasetKeyArray); - }; - AbstractPlot.prototype._getAllPlotData = function (datasetKeys) { - var _this = this; - var data = []; - var pixelPoints = []; - var allElements = []; - datasetKeys.forEach(function (datasetKey) { - var plotDatasetKey = _this._key2PlotDatasetKey.get(datasetKey); - if (plotDatasetKey == null) { + var drawer = plotDatasetKey.drawer; + plotDatasetKey.dataset.data().forEach(function (datum, index) { + var pixelPoint = drawer._getPixelPoint(datum, index); + if (pixelPoint.x !== pixelPoint.x || pixelPoint.y !== pixelPoint.y) { return; } - var drawer = plotDatasetKey.drawer; - plotDatasetKey.dataset.data().forEach(function (datum, index) { - var pixelPoint = drawer._getPixelPoint(datum, index); - if (pixelPoint.x !== pixelPoint.x || pixelPoint.y !== pixelPoint.y) { - return; - } - data.push(datum); - pixelPoints.push(pixelPoint); - allElements.push(drawer._getSelection(index).node()); - }); + data.push(datum); + pixelPoints.push(pixelPoint); + allElements.push(drawer._getSelection(index).node()); }); - return { data: data, pixelPoints: pixelPoints, selection: d3.selectAll(allElements) }; - }; - /** - * Retrieves PlotData with the lowest distance, where distance is defined - * to be the Euclidiean norm. - * - * @param {Point} queryPoint The point to which plot data should be compared - * - * @returns {PlotData} The PlotData closest to queryPoint - */ - AbstractPlot.prototype.getClosestPlotData = function (queryPoint) { - var _this = this; - var closestDistanceSquared = Infinity; - var closestIndex; - var plotData = this.getAllPlotData(); - plotData.pixelPoints.forEach(function (pixelPoint, index) { - var datum = plotData.data[index]; - var selection = d3.select(plotData.selection[0][index]); - if (!_this._isVisibleOnPlot(datum, pixelPoint, selection)) { - return; - } - var distance = Plottable._Util.Methods.distanceSquared(pixelPoint, queryPoint); - if (distance < closestDistanceSquared) { - closestDistanceSquared = distance; - closestIndex = index; - } - }); - if (closestIndex == null) { - return { data: [], pixelPoints: [], selection: d3.select() }; + }); + return { data: data, pixelPoints: pixelPoints, selection: d3.selectAll(allElements) }; + }; + /** + * Retrieves PlotData with the lowest distance, where distance is defined + * to be the Euclidiean norm. + * + * @param {Point} queryPoint The point to which plot data should be compared + * + * @returns {PlotData} The PlotData closest to queryPoint + */ + Plot.prototype.getClosestPlotData = function (queryPoint) { + var _this = this; + var closestDistanceSquared = Infinity; + var closestIndex; + var plotData = this.getAllPlotData(); + plotData.pixelPoints.forEach(function (pixelPoint, index) { + var datum = plotData.data[index]; + var selection = d3.select(plotData.selection[0][index]); + if (!_this._isVisibleOnPlot(datum, pixelPoint, selection)) { + return; } - return { data: [plotData.data[closestIndex]], pixelPoints: [plotData.pixelPoints[closestIndex]], selection: d3.select(plotData.selection[0][closestIndex]) }; - }; - AbstractPlot.prototype._isVisibleOnPlot = function (datum, pixelPoint, selection) { - return !(pixelPoint.x < 0 || pixelPoint.y < 0 || pixelPoint.x > this.width() || pixelPoint.y > this.height()); - }; - return AbstractPlot; - })(Plottable.Component.AbstractComponent); - Plot.AbstractPlot = AbstractPlot; - })(Plot = Plottable.Plot || (Plottable.Plot = {})); + var distance = Plottable.Utils.Methods.distanceSquared(pixelPoint, queryPoint); + if (distance < closestDistanceSquared) { + closestDistanceSquared = distance; + closestIndex = index; + } + }); + if (closestIndex == null) { + return { data: [], pixelPoints: [], selection: d3.select() }; + } + return { data: [plotData.data[closestIndex]], pixelPoints: [plotData.pixelPoints[closestIndex]], selection: d3.select(plotData.selection[0][closestIndex]) }; + }; + Plot.prototype._isVisibleOnPlot = function (datum, pixelPoint, selection) { + return !(pixelPoint.x < 0 || pixelPoint.y < 0 || pixelPoint.x > this.width() || pixelPoint.y > this.height()); + }; + Plot.prototype._uninstallScaleForKey = function (scale, key) { + scale.offUpdate(this._renderCallback); + scale.removeExtentsProvider(this._extentsProvider); + scale._autoDomainIfAutomaticMode(); + }; + Plot.prototype._installScaleForKey = function (scale, key) { + scale.onUpdate(this._renderCallback); + scale.addExtentsProvider(this._extentsProvider); + scale._autoDomainIfAutomaticMode(); + }; + Plot.prototype._generatePropertyToProjectors = function () { + var attrToProjector = {}; + this._propertyBindings.forEach(function (key, binding) { + var scaledAccessor = function (d, i, dataset, m) { return binding.scale.scale(binding.accessor(d, i, dataset, m)); }; + attrToProjector[key] = binding.scale == null ? binding.accessor : scaledAccessor; + }); + return attrToProjector; + }; + return Plot; + })(Plottable.Component); + Plottable.Plot = Plot; })(Plottable || (Plottable = {})); /// @@ -6993,17 +6591,11 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Plot; - (function (Plot) { + var Plots; + (function (Plots) { /* * A PiePlot is a plot meant to show how much out of a total an attribute's value is. * One usecase is to show how much funding departments are given out of a total budget. - * - * Primary projection attributes: - * "fill" - Accessor determining the color of each sector - * "inner-radius" - Accessor determining the distance from the center to the inner edge of the sector - * "outer-radius" - Accessor determining the distance from the center to the outer edge of the sector - * "value" - Accessor to extract the value determining the proportion of each slice to the total */ var Pie = (function (_super) { __extends(Pie, _super); @@ -7013,47 +6605,77 @@ var Plottable; * @constructor */ function Pie() { + var _this = this; _super.call(this); - this._colorScale = new Plottable.Scale.Color(); + this.innerRadius(0); + this.outerRadius(function () { return Math.min(_this.width(), _this.height()) / 2; }); this.classed("pie-plot", true); + this.attr("fill", function (d, i) { return String(i); }, new Plottable.Scales.Color()); } - Pie.prototype._computeLayout = function (offeredXOrigin, offeredYOrigin, availableWidth, availableHeight) { - _super.prototype._computeLayout.call(this, offeredXOrigin, offeredYOrigin, availableWidth, availableHeight); + Pie.prototype.computeLayout = function (origin, availableWidth, availableHeight) { + _super.prototype.computeLayout.call(this, origin, availableWidth, availableHeight); this._renderArea.attr("transform", "translate(" + this.width() / 2 + "," + this.height() / 2 + ")"); + var radiusLimit = Math.min(this.width(), this.height()) / 2; + if (this.innerRadius().scale != null) { + this.innerRadius().scale.range([0, radiusLimit]); + } + if (this.outerRadius().scale != null) { + this.outerRadius().scale.range([0, radiusLimit]); + } + return this; }; - Pie.prototype.addDataset = function (keyOrDataset, dataset) { + Pie.prototype.addDataset = function (dataset) { if (this._datasetKeysInOrder.length === 1) { - Plottable._Util.Methods.warn("Only one dataset is supported in Pie plots"); + Plottable.Utils.Methods.warn("Only one dataset is supported in Pie plots"); return this; } - _super.prototype.addDataset.call(this, keyOrDataset, dataset); + _super.prototype.addDataset.call(this, dataset); return this; }; - Pie.prototype._generateAttrToProjector = function () { - var _this = this; - var attrToProjector = _super.prototype._generateAttrToProjector.call(this); - attrToProjector["inner-radius"] = attrToProjector["inner-radius"] || d3.functor(0); - attrToProjector["outer-radius"] = attrToProjector["outer-radius"] || d3.functor(Math.min(this.width(), this.height()) / 2); - var defaultFillFunction = function (d, i) { return _this._colorScale.scale(String(i)); }; - attrToProjector["fill"] = attrToProjector["fill"] || defaultFillFunction; - return attrToProjector; - }; Pie.prototype._getDrawer = function (key) { - return new Plottable._Drawer.Arc(key).setClass("arc"); + return new Plottable.Drawers.Arc(key).setClass("arc"); }; - Pie.prototype.getAllPlotData = function (datasetKeys) { + Pie.prototype.getAllPlotData = function (datasets) { var _this = this; - var allPlotData = _super.prototype.getAllPlotData.call(this, datasetKeys); + if (datasets === void 0) { datasets = this.datasets(); } + var allPlotData = _super.prototype.getAllPlotData.call(this, datasets); allPlotData.pixelPoints.forEach(function (pixelPoint) { pixelPoint.x = pixelPoint.x + _this.width() / 2; pixelPoint.y = pixelPoint.y + _this.height() / 2; }); return allPlotData; }; + Pie.prototype.sectorValue = function (sectorValue, scale) { + if (sectorValue == null) { + return this._propertyBindings.get(Pie._SECTOR_VALUE_KEY); + } + this._bindProperty(Pie._SECTOR_VALUE_KEY, sectorValue, scale); + this.renderImmediately(); + return this; + }; + Pie.prototype.innerRadius = function (innerRadius, scale) { + if (innerRadius == null) { + return this._propertyBindings.get(Pie._INNER_RADIUS_KEY); + } + this._bindProperty(Pie._INNER_RADIUS_KEY, innerRadius, scale); + this.renderImmediately(); + return this; + }; + Pie.prototype.outerRadius = function (outerRadius, scale) { + if (outerRadius == null) { + return this._propertyBindings.get(Pie._OUTER_RADIUS_KEY); + } + this._bindProperty(Pie._OUTER_RADIUS_KEY, outerRadius, scale); + this.renderImmediately(); + return this; + }; + Pie._INNER_RADIUS_KEY = "inner-radius"; + Pie._OUTER_RADIUS_KEY = "outer-radius"; + Pie._SECTOR_VALUE_KEY = "sector-value"; return Pie; - })(Plot.AbstractPlot); - Plot.Pie = Pie; - })(Plot = Plottable.Plot || (Plottable.Plot = {})); + })(Plottable.Plot); + Plots.Pie = Pie; + })(Plots = Plottable.Plots || (Plottable.Plots = {})); })(Plottable || (Plottable = {})); /// @@ -7065,212 +6687,222 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Plot; - (function (Plot) { - var AbstractXYPlot = (function (_super) { - __extends(AbstractXYPlot, _super); - /** - * Constructs an XYPlot. - * - * An XYPlot is a plot from drawing 2-dimensional data. Common examples - * include Scale.Line and Scale.Bar. - * - * @constructor - * @param {any[]|Dataset} [dataset] The data or Dataset to be associated with this Renderer. - * @param {Scale} xScale The x scale to use. - * @param {Scale} yScale The y scale to use. - */ - function AbstractXYPlot(xScale, yScale) { - var _this = this; - _super.call(this); - this._autoAdjustXScaleDomain = false; - this._autoAdjustYScaleDomain = false; - if (xScale == null || yScale == null) { - throw new Error("XYPlots require an xScale and yScale"); - } - this.classed("xy-plot", true); - this._xScale = xScale; - this._yScale = yScale; - this._updateXDomainer(); - xScale.broadcaster.registerListener("yDomainAdjustment" + this.getID(), function () { return _this._adjustYDomainOnChangeFromX(); }); - this._updateYDomainer(); - yScale.broadcaster.registerListener("xDomainAdjustment" + this.getID(), function () { return _this._adjustXDomainOnChangeFromY(); }); + var XYPlot = (function (_super) { + __extends(XYPlot, _super); + /** + * Constructs an XYPlot. + * + * An XYPlot is a plot from drawing 2-dimensional data. Common examples + * include Scale.Line and Scale.Bar. + * + * @constructor + * @param {any[]|Dataset} [dataset] The data or Dataset to be associated with this Renderer. + * @param {Scale} xScale The x scale to use. + * @param {Scale} yScale The y scale to use. + */ + function XYPlot(xScale, yScale) { + var _this = this; + _super.call(this); + this._autoAdjustXScaleDomain = false; + this._autoAdjustYScaleDomain = false; + if (xScale == null || yScale == null) { + throw new Error("XYPlots require an xScale and yScale"); + } + this.classed("xy-plot", true); + this._propertyBindings.set(XYPlot._X_KEY, { accessor: null, scale: xScale }); + this._propertyBindings.set(XYPlot._Y_KEY, { accessor: null, scale: yScale }); + this._adjustYDomainOnChangeFromXCallback = function (scale) { return _this._adjustYDomainOnChangeFromX(); }; + this._adjustXDomainOnChangeFromYCallback = function (scale) { return _this._adjustXDomainOnChangeFromY(); }; + this._updateXDomainer(); + xScale.onUpdate(this._adjustYDomainOnChangeFromXCallback); + this._updateYDomainer(); + yScale.onUpdate(this._adjustXDomainOnChangeFromYCallback); + } + XYPlot.prototype.x = function (x, xScale) { + if (x == null) { + return this._propertyBindings.get(XYPlot._X_KEY); } - /** - * @param {string} attrToSet One of ["x", "y"] which determines the point's - * x and y position in the Plot. - */ - AbstractXYPlot.prototype.project = function (attrToSet, accessor, scale) { - var _this = this; - // We only want padding and nice-ing on scales that will correspond to axes / pixel layout. - // So when we get an "x" or "y" scale, enable autoNiceing and autoPadding. - if (attrToSet === "x" && scale) { - if (this._xScale) { - this._xScale.broadcaster.deregisterListener("yDomainAdjustment" + this.getID()); - } - this._xScale = scale; - this._updateXDomainer(); - scale.broadcaster.registerListener("yDomainAdjustment" + this.getID(), function () { return _this._adjustYDomainOnChangeFromX(); }); - } - if (attrToSet === "y" && scale) { - if (this._yScale) { - this._yScale.broadcaster.deregisterListener("xDomainAdjustment" + this.getID()); - } - this._yScale = scale; - this._updateYDomainer(); - scale.broadcaster.registerListener("xDomainAdjustment" + this.getID(), function () { return _this._adjustXDomainOnChangeFromY(); }); - } - _super.prototype.project.call(this, attrToSet, accessor, scale); - return this; - }; - AbstractXYPlot.prototype.remove = function () { - _super.prototype.remove.call(this); - if (this._xScale) { - this._xScale.broadcaster.deregisterListener("yDomainAdjustment" + this.getID()); - } - if (this._yScale) { - this._yScale.broadcaster.deregisterListener("xDomainAdjustment" + this.getID()); - } - return this; - }; - /** - * Sets the automatic domain adjustment over visible points for y scale. - * - * If autoAdjustment is true adjustment is immediately performend. - * - * @param {boolean} autoAdjustment The new value for the automatic adjustment domain for y scale. - * @returns {AbstractXYPlot} The calling AbstractXYPlot. - */ - AbstractXYPlot.prototype.automaticallyAdjustYScaleOverVisiblePoints = function (autoAdjustment) { - this._autoAdjustYScaleDomain = autoAdjustment; - this._adjustYDomainOnChangeFromX(); - return this; - }; - /** - * Sets the automatic domain adjustment over visible points for x scale. - * - * If autoAdjustment is true adjustment is immediately performend. - * - * @param {boolean} autoAdjustment The new value for the automatic adjustment domain for x scale. - * @returns {AbstractXYPlot} The calling AbstractXYPlot. - */ - AbstractXYPlot.prototype.automaticallyAdjustXScaleOverVisiblePoints = function (autoAdjustment) { - this._autoAdjustXScaleDomain = autoAdjustment; - this._adjustXDomainOnChangeFromY(); - return this; - }; - AbstractXYPlot.prototype._generateAttrToProjector = function () { - var attrToProjector = _super.prototype._generateAttrToProjector.call(this); - var positionXFn = attrToProjector["x"]; - var positionYFn = attrToProjector["y"]; - attrToProjector["defined"] = function (d, i, u, m) { - var positionX = positionXFn(d, i, u, m); - var positionY = positionYFn(d, i, u, m); - return positionX != null && positionX === positionX && positionY != null && positionY === positionY; - }; - return attrToProjector; - }; - AbstractXYPlot.prototype._computeLayout = function (offeredXOrigin, offeredYOffset, availableWidth, availableHeight) { - _super.prototype._computeLayout.call(this, offeredXOrigin, offeredYOffset, availableWidth, availableHeight); - this._xScale.range([0, this.width()]); - if (this._yScale instanceof Plottable.Scale.Category) { - this._yScale.range([0, this.height()]); - } - else { - this._yScale.range([this.height(), 0]); - } - }; - AbstractXYPlot.prototype._updateXDomainer = function () { - if (this._xScale instanceof Plottable.Scale.AbstractQuantitative) { - var scale = this._xScale; - if (!scale._userSetDomainer) { - scale.domainer().pad().nice(); - } - } - }; - AbstractXYPlot.prototype._updateYDomainer = function () { - if (this._yScale instanceof Plottable.Scale.AbstractQuantitative) { - var scale = this._yScale; - if (!scale._userSetDomainer) { - scale.domainer().pad().nice(); - } - } - }; - /** - * Adjusts both domains' extents to show all datasets. - * - * This call does not override auto domain adjustment behavior over visible points. - */ - AbstractXYPlot.prototype.showAllData = function () { - this._xScale.autoDomain(); - if (!this._autoAdjustYScaleDomain) { - this._yScale.autoDomain(); - } - }; - AbstractXYPlot.prototype._adjustYDomainOnChangeFromX = function () { - if (!this._projectorsReady()) { - return; - } - if (this._autoAdjustYScaleDomain) { - this._adjustDomainToVisiblePoints(this._xScale, this._yScale, true); + this._bindProperty(XYPlot._X_KEY, x, xScale); + if (this._autoAdjustYScaleDomain) { + this._updateYExtentsAndAutodomain(); + } + this._updateXDomainer(); + this.renderImmediately(); + return this; + }; + XYPlot.prototype.y = function (y, yScale) { + if (y == null) { + return this._propertyBindings.get(XYPlot._Y_KEY); + } + this._bindProperty(XYPlot._Y_KEY, y, yScale); + if (this._autoAdjustXScaleDomain) { + this._updateXExtentsAndAutodomain(); + } + this._updateYDomainer(); + this.renderImmediately(); + return this; + }; + XYPlot.prototype._filterForProperty = function (property) { + if (property === "x" && this._autoAdjustXScaleDomain) { + return this._makeFilterByProperty("y"); + } + else if (property === "y" && this._autoAdjustYScaleDomain) { + return this._makeFilterByProperty("x"); + } + return null; + }; + XYPlot.prototype._makeFilterByProperty = function (property) { + var binding = this._propertyBindings.get(property); + if (binding != null) { + var accessor = binding.accessor; + var scale = binding.scale; + if (scale != null) { + return function (datum, index, dataset, plotMetadata) { + var range = scale.range(); + return Plottable.Utils.Methods.inRange(scale.scale(accessor(datum, index, dataset, plotMetadata)), range[0], range[1]); + }; } - }; - AbstractXYPlot.prototype._adjustXDomainOnChangeFromY = function () { - if (!this._projectorsReady()) { - return; + } + return null; + }; + XYPlot.prototype._uninstallScaleForKey = function (scale, key) { + _super.prototype._uninstallScaleForKey.call(this, scale, key); + var adjustCallback = key === XYPlot._X_KEY ? this._adjustYDomainOnChangeFromXCallback : this._adjustXDomainOnChangeFromYCallback; + scale.offUpdate(adjustCallback); + }; + XYPlot.prototype._installScaleForKey = function (scale, key) { + _super.prototype._installScaleForKey.call(this, scale, key); + var adjustCallback = key === XYPlot._X_KEY ? this._adjustYDomainOnChangeFromXCallback : this._adjustXDomainOnChangeFromYCallback; + scale.onUpdate(adjustCallback); + }; + XYPlot.prototype.destroy = function () { + _super.prototype.destroy.call(this); + if (this.x().scale) { + this.x().scale.offUpdate(this._adjustYDomainOnChangeFromXCallback); + } + if (this.y().scale) { + this.y().scale.offUpdate(this._adjustXDomainOnChangeFromYCallback); + } + return this; + }; + /** + * Sets the automatic domain adjustment over visible points for y scale. + * + * If autoAdjustment is true adjustment is immediately performend. + * + * @param {boolean} autoAdjustment The new value for the automatic adjustment domain for y scale. + * @returns {XYPlot} The calling XYPlot. + */ + XYPlot.prototype.automaticallyAdjustYScaleOverVisiblePoints = function (autoAdjustment) { + this._autoAdjustYScaleDomain = autoAdjustment; + this._adjustYDomainOnChangeFromX(); + return this; + }; + /** + * Sets the automatic domain adjustment over visible points for x scale. + * + * If autoAdjustment is true adjustment is immediately performend. + * + * @param {boolean} autoAdjustment The new value for the automatic adjustment domain for x scale. + * @returns {XYPlot} The calling XYPlot. + */ + XYPlot.prototype.automaticallyAdjustXScaleOverVisiblePoints = function (autoAdjustment) { + this._autoAdjustXScaleDomain = autoAdjustment; + this._adjustXDomainOnChangeFromY(); + return this; + }; + XYPlot.prototype._generatePropertyToProjectors = function () { + var attrToProjector = _super.prototype._generatePropertyToProjectors.call(this); + var positionXFn = attrToProjector["x"]; + var positionYFn = attrToProjector["y"]; + attrToProjector["defined"] = function (d, i, dataset, m) { + var positionX = positionXFn(d, i, dataset, m); + var positionY = positionYFn(d, i, dataset, m); + return positionX != null && positionX === positionX && positionY != null && positionY === positionY; + }; + return attrToProjector; + }; + XYPlot.prototype.computeLayout = function (origin, availableWidth, availableHeight) { + _super.prototype.computeLayout.call(this, origin, availableWidth, availableHeight); + var xScale = this.x().scale; + if (xScale != null) { + xScale.range([0, this.width()]); + } + var yScale = this.y().scale; + if (yScale != null) { + if (this.y().scale instanceof Plottable.Scales.Category) { + this.y().scale.range([0, this.height()]); } - if (this._autoAdjustXScaleDomain) { - this._adjustDomainToVisiblePoints(this._yScale, this._xScale, false); + else { + this.y().scale.range([this.height(), 0]); } - }; - AbstractXYPlot.prototype._adjustDomainToVisiblePoints = function (fromScale, toScale, fromX) { - if (toScale instanceof Plottable.Scale.AbstractQuantitative) { - var toScaleQ = toScale; - var normalizedData = this._normalizeDatasets(fromX); - var filterFn; - if (fromScale instanceof Plottable.Scale.AbstractQuantitative) { - var fromDomain = fromScale.domain(); - filterFn = function (a) { return fromDomain[0] <= a && fromDomain[1] >= a; }; - } - else { - var fromDomainSet = d3.set(fromScale.domain()); - filterFn = function (a) { return fromDomainSet.has(a); }; - } - var adjustedDomain = this._adjustDomainOverVisiblePoints(normalizedData, filterFn); - if (adjustedDomain.length === 0) { - return; - } - adjustedDomain = toScaleQ.domainer().computeDomain([adjustedDomain], toScaleQ); - toScaleQ.domain(adjustedDomain); + } + return this; + }; + XYPlot.prototype._updateXDomainer = function () { + if (this.x().scale instanceof Plottable.QuantitativeScale) { + var scale = this.x().scale; + if (!scale._userSetDomainer) { + scale.domainer().pad().nice(); } - }; - AbstractXYPlot.prototype._normalizeDatasets = function (fromX) { - var _this = this; - var aAccessor = this._projections[fromX ? "x" : "y"].accessor; - var bAccessor = this._projections[fromX ? "y" : "x"].accessor; - return Plottable._Util.Methods.flatten(this._datasetKeysInOrder.map(function (key) { - var dataset = _this._key2PlotDatasetKey.get(key).dataset; - var plotMetadata = _this._key2PlotDatasetKey.get(key).plotMetadata; - return dataset.data().map(function (d, i) { - return { a: aAccessor(d, i, dataset.metadata(), plotMetadata), b: bAccessor(d, i, dataset.metadata(), plotMetadata) }; - }); - })); - }; - AbstractXYPlot.prototype._adjustDomainOverVisiblePoints = function (values, filterFn) { - var bVals = values.filter(function (v) { return filterFn(v.a); }).map(function (v) { return v.b; }); - var retVal = []; - if (bVals.length !== 0) { - retVal = [Plottable._Util.Methods.min(bVals, null), Plottable._Util.Methods.max(bVals, null)]; + } + }; + XYPlot.prototype._updateYDomainer = function () { + if (this.y().scale instanceof Plottable.QuantitativeScale) { + var scale = this.y().scale; + if (!scale._userSetDomainer) { + scale.domainer().pad().nice(); } - return retVal; - }; - AbstractXYPlot.prototype._projectorsReady = function () { - return this._projections["x"] && this._projections["y"]; - }; - return AbstractXYPlot; - })(Plot.AbstractPlot); - Plot.AbstractXYPlot = AbstractXYPlot; - })(Plot = Plottable.Plot || (Plottable.Plot = {})); + } + }; + XYPlot.prototype._updateXExtentsAndAutodomain = function () { + this._updateExtentsForProperty("x"); + var xScale = this.x().scale; + if (xScale != null) { + xScale.autoDomain(); + } + }; + XYPlot.prototype._updateYExtentsAndAutodomain = function () { + this._updateExtentsForProperty("y"); + var yScale = this.y().scale; + if (yScale != null) { + yScale.autoDomain(); + } + }; + /** + * Adjusts both domains' extents to show all datasets. + * + * This call does not override auto domain adjustment behavior over visible points. + */ + XYPlot.prototype.showAllData = function () { + this._updateXExtentsAndAutodomain(); + this._updateYExtentsAndAutodomain(); + return this; + }; + XYPlot.prototype._adjustYDomainOnChangeFromX = function () { + if (!this._projectorsReady()) { + return; + } + if (this._autoAdjustYScaleDomain) { + this._updateYExtentsAndAutodomain(); + } + }; + XYPlot.prototype._adjustXDomainOnChangeFromY = function () { + if (!this._projectorsReady()) { + return; + } + if (this._autoAdjustXScaleDomain) { + this._updateXExtentsAndAutodomain(); + } + }; + XYPlot.prototype._projectorsReady = function () { + return this.x().accessor != null && this.y().accessor != null; + }; + XYPlot._X_KEY = "x"; + XYPlot._Y_KEY = "y"; + return XYPlot; + })(Plottable.Plot); + Plottable.XYPlot = XYPlot; })(Plottable || (Plottable = {})); /// @@ -7282,8 +6914,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Plot; - (function (Plot) { + var Plots; + (function (Plots) { var Rectangle = (function (_super) { __extends(Rectangle, _super); /** @@ -7294,16 +6926,16 @@ var Plottable; * as well as the bottom and top bounds (y1 and y2 respectively) * * @constructor - * @param {Scale.AbstractScale} xScale The x scale to use. - * @param {Scale.AbstractScale} yScale The y scale to use. + * @param {Scale.Scale} xScale The x scale to use. + * @param {Scale.Scale} yScale The y scale to use. */ function Rectangle(xScale, yScale) { _super.call(this, xScale, yScale); - this._defaultFillColor = new Plottable.Scale.Color().range()[0]; this.classed("rectangle-plot", true); + this.attr("fill", new Plottable.Scales.Color().range()[0]); } Rectangle.prototype._getDrawer = function (key) { - return new Plottable._Drawer.Rect(key, true); + return new Plottable.Drawers.Rect(key, true); }; Rectangle.prototype._generateAttrToProjector = function () { var attrToProjector = _super.prototype._generateAttrToProjector.call(this); @@ -7313,26 +6945,63 @@ var Plottable; var x2Attr = attrToProjector["x2"]; var y2Attr = attrToProjector["y2"]; // Generate width based on difference, then adjust for the correct x origin - attrToProjector["width"] = function (d, i, u, m) { return Math.abs(x2Attr(d, i, u, m) - x1Attr(d, i, u, m)); }; - attrToProjector["x"] = function (d, i, u, m) { return Math.min(x1Attr(d, i, u, m), x2Attr(d, i, u, m)); }; + attrToProjector["width"] = function (d, i, dataset, m) { return Math.abs(x2Attr(d, i, dataset, m) - x1Attr(d, i, dataset, m)); }; + attrToProjector["x"] = function (d, i, dataset, m) { return Math.min(x1Attr(d, i, dataset, m), x2Attr(d, i, dataset, m)); }; // Generate height based on difference, then adjust for the correct y origin - attrToProjector["height"] = function (d, i, u, m) { return Math.abs(y2Attr(d, i, u, m) - y1Attr(d, i, u, m)); }; - attrToProjector["y"] = function (d, i, u, m) { return Math.max(y1Attr(d, i, u, m), y2Attr(d, i, u, m)) - attrToProjector["height"](d, i, u, m); }; + attrToProjector["height"] = function (d, i, dataset, m) { return Math.abs(y2Attr(d, i, dataset, m) - y1Attr(d, i, dataset, m)); }; + attrToProjector["y"] = function (d, i, dataset, m) { + return Math.max(y1Attr(d, i, dataset, m), y2Attr(d, i, dataset, m)) - attrToProjector["height"](d, i, dataset, m); + }; // Clean up the attributes projected onto the SVG elements delete attrToProjector["x1"]; delete attrToProjector["y1"]; delete attrToProjector["x2"]; delete attrToProjector["y2"]; - attrToProjector["fill"] = attrToProjector["fill"] || d3.functor(this._defaultFillColor); return attrToProjector; }; Rectangle.prototype._generateDrawSteps = function () { return [{ attrToProjector: this._generateAttrToProjector(), animator: this._getAnimator("rectangles") }]; }; + Rectangle.prototype.x1 = function (x1, scale) { + if (x1 == null) { + return this._propertyBindings.get(Rectangle._X1_KEY); + } + this._bindProperty(Rectangle._X1_KEY, x1, scale); + this.renderImmediately(); + return this; + }; + Rectangle.prototype.x2 = function (x2, scale) { + if (x2 == null) { + return this._propertyBindings.get(Rectangle._X2_KEY); + } + this._bindProperty(Rectangle._X2_KEY, x2, scale); + this.renderImmediately(); + return this; + }; + Rectangle.prototype.y1 = function (y1, scale) { + if (y1 == null) { + return this._propertyBindings.get(Rectangle._Y1_KEY); + } + this._bindProperty(Rectangle._Y1_KEY, y1, scale); + this.renderImmediately(); + return this; + }; + Rectangle.prototype.y2 = function (y2, scale) { + if (y2 == null) { + return this._propertyBindings.get(Rectangle._Y2_KEY); + } + this._bindProperty(Rectangle._Y2_KEY, y2, scale); + this.renderImmediately(); + return this; + }; + Rectangle._X1_KEY = "x1"; + Rectangle._X2_KEY = "x2"; + Rectangle._Y1_KEY = "y1"; + Rectangle._Y2_KEY = "y2"; return Rectangle; - })(Plot.AbstractXYPlot); - Plot.Rectangle = Rectangle; - })(Plot = Plottable.Plot || (Plottable.Plot = {})); + })(Plottable.XYPlot); + Plots.Rectangle = Rectangle; + })(Plots = Plottable.Plots || (Plottable.Plots = {})); })(Plottable || (Plottable = {})); /// @@ -7344,8 +7013,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Plot; - (function (Plot) { + var Plots; + (function (Plots) { var Scatter = (function (_super) { __extends(Scatter, _super); /** @@ -7357,23 +7026,37 @@ var Plottable; */ function Scatter(xScale, yScale) { _super.call(this, xScale, yScale); - this._closeDetectionRadius = 5; this.classed("scatter-plot", true); - this._defaultFillColor = new Plottable.Scale.Color().range()[0]; - this.animator("symbols-reset", new Plottable.Animator.Null()); - this.animator("symbols", new Plottable.Animator.Base().duration(250).delay(5)); + this.animator("symbols-reset", new Plottable.Animators.Null()); + this.animator("symbols", new Plottable.Animators.Base().duration(250).delay(5)); + this.attr("opacity", 0.6); + this.attr("fill", new Plottable.Scales.Color().range()[0]); } Scatter.prototype._getDrawer = function (key) { - return new Plottable._Drawer.Symbol(key); + return new Plottable.Drawers.Symbol(key); }; Scatter.prototype._generateAttrToProjector = function () { var attrToProjector = _super.prototype._generateAttrToProjector.call(this); attrToProjector["size"] = attrToProjector["size"] || d3.functor(6); - attrToProjector["opacity"] = attrToProjector["opacity"] || d3.functor(0.6); - attrToProjector["fill"] = attrToProjector["fill"] || d3.functor(this._defaultFillColor); attrToProjector["symbol"] = attrToProjector["symbol"] || (function () { return Plottable.SymbolFactories.circle(); }); return attrToProjector; }; + Scatter.prototype.size = function (size, scale) { + if (size == null) { + return this._propertyBindings.get(Scatter._SIZE_KEY); + } + this._bindProperty(Scatter._SIZE_KEY, size, scale); + this.renderImmediately(); + return this; + }; + Scatter.prototype.symbol = function (symbol) { + if (symbol == null) { + return this._propertyBindings.get(Scatter._SYMBOL_KEY); + } + this._propertyBindings.set(Scatter._SYMBOL_KEY, { accessor: symbol }); + this.renderImmediately(); + return this; + }; Scatter.prototype._generateDrawSteps = function () { var drawSteps = []; if (this._dataChanged && this._animate) { @@ -7384,67 +7067,6 @@ var Plottable; drawSteps.push({ attrToProjector: this._generateAttrToProjector(), animator: this._getAnimator("symbols") }); return drawSteps; }; - Scatter.prototype._getClosestStruckPoint = function (p, range) { - var _this = this; - var attrToProjector = this._generateAttrToProjector(); - var xProjector = attrToProjector["x"]; - var yProjector = attrToProjector["y"]; - var getDistSq = function (d, i, userMetdata, plotMetadata) { - var dx = attrToProjector["x"](d, i, userMetdata, plotMetadata) - p.x; - var dy = attrToProjector["y"](d, i, userMetdata, plotMetadata) - p.y; - return (dx * dx + dy * dy); - }; - var overAPoint = false; - var closestElement; - var closestElementUserMetadata; - var closestElementPlotMetadata; - var closestIndex; - var minDistSq = range * range; - this._datasetKeysInOrder.forEach(function (key) { - var dataset = _this._key2PlotDatasetKey.get(key).dataset; - var plotMetadata = _this._key2PlotDatasetKey.get(key).plotMetadata; - var drawer = _this._key2PlotDatasetKey.get(key).drawer; - drawer._getRenderArea().selectAll("path").each(function (d, i) { - var distSq = getDistSq(d, i, dataset.metadata(), plotMetadata); - var r = attrToProjector["size"](d, i, dataset.metadata(), plotMetadata) / 2; - if (distSq < r * r) { - if (!overAPoint || distSq < minDistSq) { - closestElement = this; - closestIndex = i; - minDistSq = distSq; - closestElementUserMetadata = dataset.metadata(); - closestElementPlotMetadata = plotMetadata; - } - overAPoint = true; - } - else if (!overAPoint && distSq < minDistSq) { - closestElement = this; - closestIndex = i; - minDistSq = distSq; - closestElementUserMetadata = dataset.metadata(); - closestElementPlotMetadata = plotMetadata; - } - }); - }); - if (!closestElement) { - return { - selection: null, - pixelPositions: null, - data: null - }; - } - var closestSelection = d3.select(closestElement); - var closestData = closestSelection.data(); - var closestPoint = { - x: attrToProjector["x"](closestData[0], closestIndex, closestElementUserMetadata, closestElementPlotMetadata), - y: attrToProjector["y"](closestData[0], closestIndex, closestElementUserMetadata, closestElementPlotMetadata) - }; - return { - selection: closestSelection, - pixelPositions: [closestPoint], - data: closestData - }; - }; Scatter.prototype._isVisibleOnPlot = function (datum, pixelPoint, selection) { var xRange = { min: 0, max: this.width() }; var yRange = { min: 0, max: this.height() }; @@ -7456,22 +7078,14 @@ var Plottable; width: bbox.width, height: bbox.height }; - return Plottable._Util.Methods.intersectsBBox(xRange, yRange, translatedBbox); - }; - //===== Hover logic ===== - Scatter.prototype._hoverOverComponent = function (p) { - // no-op - }; - Scatter.prototype._hoverOutComponent = function (p) { - // no-op - }; - Scatter.prototype._doHover = function (p) { - return this._getClosestStruckPoint(p, this._closeDetectionRadius); + return Plottable.Utils.Methods.intersectsBBox(xRange, yRange, translatedBbox); }; + Scatter._SIZE_KEY = "size"; + Scatter._SYMBOL_KEY = "symbol"; return Scatter; - })(Plot.AbstractXYPlot); - Plot.Scatter = Scatter; - })(Plot = Plottable.Plot || (Plottable.Plot = {})); + })(Plottable.XYPlot); + Plots.Scatter = Scatter; + })(Plots = Plottable.Plots || (Plottable.Plots = {})); })(Plottable || (Plottable = {})); /// @@ -7483,8 +7097,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Plot; - (function (Plot) { + var Plots; + (function (Plots) { var Grid = (function (_super) { __extends(Grid, _super); /** @@ -7494,84 +7108,83 @@ var Plottable; * grid, and the datum can control what color it is. * * @constructor - * @param {Scale.AbstractScale} xScale The x scale to use. - * @param {Scale.AbstractScale} yScale The y scale to use. + * @param {Scale.Scale} xScale The x scale to use. + * @param {Scale.Scale} yScale The y scale to use. * @param {Scale.Color|Scale.InterpolatedColor} colorScale The color scale * to use for each grid cell. */ - function Grid(xScale, yScale, colorScale) { + function Grid(xScale, yScale) { _super.call(this, xScale, yScale); this.classed("grid-plot", true); // The x and y scales should render in bands with no padding for category scales - if (xScale instanceof Plottable.Scale.Category) { + if (xScale instanceof Plottable.Scales.Category) { xScale.innerPadding(0).outerPadding(0); } - if (yScale instanceof Plottable.Scale.Category) { + if (yScale instanceof Plottable.Scales.Category) { yScale.innerPadding(0).outerPadding(0); } - this._colorScale = colorScale; - this.animator("cells", new Plottable.Animator.Null()); + this.animator("cells", new Plottable.Animators.Null()); } - Grid.prototype.addDataset = function (keyOrDataset, dataset) { + Grid.prototype.addDataset = function (dataset) { if (this._datasetKeysInOrder.length === 1) { - Plottable._Util.Methods.warn("Only one dataset is supported in Grid plots"); + Plottable.Utils.Methods.warn("Only one dataset is supported in Grid plots"); return this; } - _super.prototype.addDataset.call(this, keyOrDataset, dataset); + _super.prototype.addDataset.call(this, dataset); return this; }; Grid.prototype._getDrawer = function (key) { - return new Plottable._Drawer.Rect(key, true); + return new Plottable.Drawers.Rect(key, true); }; - /** - * @param {string} attrToSet One of ["x", "y", "x2", "y2", "fill"]. If "fill" is used, - * the data should return a valid CSS color. - */ - Grid.prototype.project = function (attrToSet, accessor, scale) { + Grid.prototype._generateDrawSteps = function () { + return [{ attrToProjector: this._generateAttrToProjector(), animator: this._getAnimator("cells") }]; + }; + Grid.prototype.x = function (x, scale) { var _this = this; - _super.prototype.project.call(this, attrToSet, accessor, scale); - if (attrToSet === "x") { - if (scale instanceof Plottable.Scale.Category) { - this.project("x1", function (d, i, u, m) { - return scale.scale(_this._projections["x"].accessor(d, i, u, m)) - scale.rangeBand() / 2; - }); - this.project("x2", function (d, i, u, m) { - return scale.scale(_this._projections["x"].accessor(d, i, u, m)) + scale.rangeBand() / 2; - }); + if (x == null) { + return _super.prototype.x.call(this); + } + if (scale == null) { + _super.prototype.x.call(this, x); + } + else { + _super.prototype.x.call(this, x, scale); + if (scale instanceof Plottable.Scales.Category) { + var xCatScale = scale; + this.x1(function (d, i, dataset, m) { return scale.scale(_this.x().accessor(d, i, dataset, m)) - xCatScale.rangeBand() / 2; }); + this.x2(function (d, i, dataset, m) { return scale.scale(_this.x().accessor(d, i, dataset, m)) + xCatScale.rangeBand() / 2; }); } - if (scale instanceof Plottable.Scale.AbstractQuantitative) { - this.project("x1", function (d, i, u, m) { - return scale.scale(_this._projections["x"].accessor(d, i, u, m)); - }); + else if (scale instanceof Plottable.QuantitativeScale) { + this.x1(function (d, i, dataset, m) { return scale.scale(_this.x().accessor(d, i, dataset, m)); }); } } - if (attrToSet === "y") { - if (scale instanceof Plottable.Scale.Category) { - this.project("y1", function (d, i, u, m) { - return scale.scale(_this._projections["y"].accessor(d, i, u, m)) - scale.rangeBand() / 2; - }); - this.project("y2", function (d, i, u, m) { - return scale.scale(_this._projections["y"].accessor(d, i, u, m)) + scale.rangeBand() / 2; - }); + return this; + }; + Grid.prototype.y = function (y, scale) { + var _this = this; + if (y == null) { + return _super.prototype.y.call(this); + } + if (scale == null) { + _super.prototype.y.call(this, y); + } + else { + _super.prototype.y.call(this, y, scale); + if (scale instanceof Plottable.Scales.Category) { + var yCatScale = scale; + this.y1(function (d, i, dataset, m) { return scale.scale(_this.y().accessor(d, i, dataset, m)) - yCatScale.rangeBand() / 2; }); + this.y2(function (d, i, dataset, m) { return scale.scale(_this.y().accessor(d, i, dataset, m)) + yCatScale.rangeBand() / 2; }); } - if (scale instanceof Plottable.Scale.AbstractQuantitative) { - this.project("y1", function (d, i, u, m) { - return scale.scale(_this._projections["y"].accessor(d, i, u, m)); - }); + else if (scale instanceof Plottable.QuantitativeScale) { + this.y1(function (d, i, dataset, m) { return scale.scale(_this.y().accessor(d, i, dataset, m)); }); } } - if (attrToSet === "fill") { - this._colorScale = this._projections["fill"].scale; - } return this; }; - Grid.prototype._generateDrawSteps = function () { - return [{ attrToProjector: this._generateAttrToProjector(), animator: this._getAnimator("cells") }]; - }; return Grid; - })(Plot.Rectangle); - Plot.Grid = Grid; - })(Plot = Plottable.Plot || (Plottable.Plot = {})); + })(Plots.Rectangle); + Plots.Grid = Grid; + })(Plots = Plottable.Plots || (Plottable.Plots = {})); })(Plottable || (Plottable = {})); /// @@ -7583,8 +7196,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Plot; - (function (Plot) { + var Plots; + (function (Plots) { var Bar = (function (_super) { __extends(Bar, _super); /** @@ -7596,23 +7209,24 @@ var Plottable; * @param {boolean} isVertical if the plot if vertical. */ function Bar(xScale, yScale, isVertical) { + var _this = this; if (isVertical === void 0) { isVertical = true; } _super.call(this, xScale, yScale); this._barAlignmentFactor = 0.5; - this._barLabelFormatter = Plottable.Formatters.identity(); - this._barLabelsEnabled = false; - this._hoverMode = "point"; + this._labelFormatter = Plottable.Formatters.identity(); + this._labelsEnabled = false; this._hideBarsIfAnyAreTooWide = true; this.classed("bar-plot", true); - this._defaultFillColor = new Plottable.Scale.Color().range()[0]; - this.animator("bars-reset", new Plottable.Animator.Null()); - this.animator("bars", new Plottable.Animator.Base()); - this.animator("baseline", new Plottable.Animator.Null()); + this.animator("bars-reset", new Plottable.Animators.Null()); + this.animator("bars", new Plottable.Animators.Base()); + this.animator("baseline", new Plottable.Animators.Null()); this._isVertical = isVertical; this.baseline(0); + this.attr("fill", new Plottable.Scales.Color().range()[0]); + this.attr("width", function () { return _this._getBarPixelWidth(); }); } Bar.prototype._getDrawer = function (key) { - return new Plottable._Drawer.Rect(key, this._isVertical); + return new Plottable.Drawers.Rect(key, this._isVertical); }; Bar.prototype._setup = function () { _super.prototype._setup.call(this); @@ -7625,7 +7239,7 @@ var Plottable; this._baselineValue = value; this._updateXDomainer(); this._updateYDomainer(); - this._render(); + this.render(); return this; }; /** @@ -7643,26 +7257,26 @@ var Plottable; throw new Error("unsupported bar alignment"); } this._barAlignmentFactor = align2factor[alignmentLC]; - this._render(); + this.render(); return this; }; - Bar.prototype.barLabelsEnabled = function (enabled) { + Bar.prototype.labelsEnabled = function (enabled) { if (enabled === undefined) { - return this._barLabelsEnabled; + return this._labelsEnabled; } else { - this._barLabelsEnabled = enabled; - this._render(); + this._labelsEnabled = enabled; + this.render(); return this; } }; - Bar.prototype.barLabelFormatter = function (formatter) { + Bar.prototype.labelFormatter = function (formatter) { if (formatter == null) { - return this._barLabelFormatter; + return this._labelFormatter; } else { - this._barLabelFormatter = formatter; - this._render(); + this._labelFormatter = formatter; + this.render(); return this; } }; @@ -7679,8 +7293,6 @@ var Plottable; */ Bar.prototype.getClosestPlotData = function (queryPoint) { var _this = this; - var chartXExtent = { min: 0, max: this.width() }; - var chartYExtent = { min: 0, max: this.height() }; var minPrimaryDist = Infinity; var minSecondaryDist = Infinity; var closestData = []; @@ -7692,8 +7304,8 @@ var Plottable; // for the x, y, height & width attributes), but user selections (e.g. via // mouse events) usually have pixel accuracy. We add a tolerance of 0.5 pixels. var tolerance = 0.5; - this.datasetOrder().forEach(function (key) { - var plotData = _this.getAllPlotData(key); + this.datasets().forEach(function (dataset) { + var plotData = _this.getAllPlotData([dataset]); plotData.pixelPoints.forEach(function (plotPt, index) { var datum = plotData.data[index]; var bar = plotData.selection[0][index]; @@ -7704,7 +7316,7 @@ var Plottable; var secondaryDist = 0; // if we're inside a bar, distance in both directions should stay 0 var barBBox = bar.getBBox(); - if (!Plottable._Util.Methods.intersectsBBox(queryPoint.x, queryPoint.y, barBBox, tolerance)) { + if (!Plottable.Utils.Methods.intersectsBBox(queryPoint.x, queryPoint.y, barBBox, tolerance)) { var plotPtPrimary = _this._isVertical ? plotPt.x : plotPt.y; primaryDist = Math.abs(queryPtPrimary - plotPtPrimary); // compute this bar's min and max along the secondary axis @@ -7745,7 +7357,7 @@ var Plottable; var xRange = { min: 0, max: this.width() }; var yRange = { min: 0, max: this.height() }; var barBBox = selection[0][0].getBBox(); - return Plottable._Util.Methods.intersectsBBox(xRange, yRange, barBBox); + return Plottable.Utils.Methods.intersectsBBox(xRange, yRange, barBBox); }; /** * Gets the bar under the given pixel position (if [xValOrExtent] @@ -7770,21 +7382,21 @@ var Plottable; var bars = []; var drawer = this._key2PlotDatasetKey.get(key).drawer; drawer._getRenderArea().selectAll("rect").each(function (d) { - if (Plottable._Util.Methods.intersectsBBox(xValOrExtent, yValOrExtent, this.getBBox())) { + if (Plottable.Utils.Methods.intersectsBBox(xValOrExtent, yValOrExtent, this.getBBox())) { bars.push(this); } }); return bars; }; Bar.prototype._updateDomainer = function (scale) { - if (scale instanceof Plottable.Scale.AbstractQuantitative) { + if (scale instanceof Plottable.QuantitativeScale) { var qscale = scale; if (!qscale._userSetDomainer) { if (this._baselineValue != null) { - qscale.domainer().addPaddingException(this._baselineValue, "BAR_PLOT+" + this.getID()).addIncludedValue(this._baselineValue, "BAR_PLOT+" + this.getID()); + qscale.domainer().addPaddingException(this, this._baselineValue).addIncludedValue(this, this._baselineValue); } else { - qscale.domainer().removePaddingException("BAR_PLOT+" + this.getID()).removeIncludedValue("BAR_PLOT+" + this.getID()); + qscale.domainer().removePaddingException(this).removeIncludedValue(this); } qscale.domainer().pad().nice(); } @@ -7794,7 +7406,7 @@ var Plottable; }; Bar.prototype._updateYDomainer = function () { if (this._isVertical) { - this._updateDomainer(this._yScale); + this._updateDomainer(this.y().scale); } else { _super.prototype._updateYDomainer.call(this); @@ -7802,7 +7414,7 @@ var Plottable; }; Bar.prototype._updateXDomainer = function () { if (!this._isVertical) { - this._updateDomainer(this._xScale); + this._updateDomainer(this.x().scale); } else { _super.prototype._updateXDomainer.call(this); @@ -7810,7 +7422,7 @@ var Plottable; }; Bar.prototype._additionalPaint = function (time) { var _this = this; - var primaryScale = this._isVertical ? this._yScale : this._xScale; + var primaryScale = this._isVertical ? this.y().scale : this.x().scale; var scaledBaseline = primaryScale.scale(this._baselineValue); var baselineAttr = { "x1": this._isVertical ? 0 : scaledBaseline, @@ -7821,8 +7433,8 @@ var Plottable; this._getAnimator("baseline").animate(this._baseline, baselineAttr); var drawers = this._getDrawersInOrder(); drawers.forEach(function (d) { return d.removeLabels(); }); - if (this._barLabelsEnabled) { - Plottable._Util.Methods.setTimeout(function () { return _this._drawLabels(); }, time); + if (this._labelsEnabled) { + Plottable.Utils.Methods.setTimeout(function () { return _this._drawLabels(); }, time); } }; Bar.prototype._drawLabels = function () { @@ -7830,7 +7442,7 @@ var Plottable; var drawers = this._getDrawersInOrder(); var attrToProjector = this._generateAttrToProjector(); var dataToDraw = this._getDataToDraw(); - this._datasetKeysInOrder.forEach(function (k, i) { return drawers[i].drawText(dataToDraw.get(k), attrToProjector, _this._key2PlotDatasetKey.get(k).dataset.metadata(), _this._key2PlotDatasetKey.get(k).plotMetadata); }); + this._datasetKeysInOrder.forEach(function (k, i) { return drawers[i].drawText(dataToDraw.get(k), attrToProjector, _this._key2PlotDatasetKey.get(k).dataset, _this._key2PlotDatasetKey.get(k).plotMetadata); }); if (this._hideBarsIfAnyAreTooWide && drawers.some(function (d) { return d._getIfLabelsTooWide(); })) { drawers.forEach(function (d) { return d.removeLabels(); }); } @@ -7839,7 +7451,7 @@ var Plottable; var drawSteps = []; if (this._dataChanged && this._animate) { var resetAttrToProjector = this._generateAttrToProjector(); - var primaryScale = this._isVertical ? this._yScale : this._xScale; + var primaryScale = this._isVertical ? this.y().scale : this.x().scale; var scaledBaseline = primaryScale.scale(this._baselineValue); var positionAttr = this._isVertical ? "y" : "x"; var dimensionAttr = this._isVertical ? "height" : "width"; @@ -7855,43 +7467,39 @@ var Plottable; // Primary scale/direction: the "length" of the bars // Secondary scale/direction: the "width" of the bars var attrToProjector = _super.prototype._generateAttrToProjector.call(this); - var primaryScale = this._isVertical ? this._yScale : this._xScale; - var secondaryScale = this._isVertical ? this._xScale : this._yScale; + var primaryScale = this._isVertical ? this.y().scale : this.x().scale; + var secondaryScale = this._isVertical ? this.x().scale : this.y().scale; var primaryAttr = this._isVertical ? "y" : "x"; var secondaryAttr = this._isVertical ? "x" : "y"; var scaledBaseline = primaryScale.scale(this._baselineValue); var positionF = attrToProjector[secondaryAttr]; var widthF = attrToProjector["width"]; - if (widthF == null) { - widthF = function () { return _this._getBarPixelWidth(); }; - } var originalPositionFn = attrToProjector[primaryAttr]; - var heightF = function (d, i, u, m) { - return Math.abs(scaledBaseline - originalPositionFn(d, i, u, m)); + var heightF = function (d, i, dataset, m) { + return Math.abs(scaledBaseline - originalPositionFn(d, i, dataset, m)); }; attrToProjector["width"] = this._isVertical ? widthF : heightF; attrToProjector["height"] = this._isVertical ? heightF : widthF; - if (secondaryScale instanceof Plottable.Scale.Category) { - attrToProjector[secondaryAttr] = function (d, i, u, m) { return positionF(d, i, u, m) - widthF(d, i, u, m) / 2; }; + if (secondaryScale instanceof Plottable.Scales.Category) { + attrToProjector[secondaryAttr] = function (d, i, dataset, m) { return positionF(d, i, dataset, m) - widthF(d, i, dataset, m) / 2; }; } else { - attrToProjector[secondaryAttr] = function (d, i, u, m) { return positionF(d, i, u, m) - widthF(d, i, u, m) * _this._barAlignmentFactor; }; + attrToProjector[secondaryAttr] = function (d, i, dataset, m) { return positionF(d, i, dataset, m) - widthF(d, i, dataset, m) * _this._barAlignmentFactor; }; } - attrToProjector[primaryAttr] = function (d, i, u, m) { - var originalPos = originalPositionFn(d, i, u, m); + attrToProjector[primaryAttr] = function (d, i, dataset, m) { + var originalPos = originalPositionFn(d, i, dataset, m); // If it is past the baseline, it should start at the baselin then width/height // carries it over. If it's not past the baseline, leave it at original position and // then width/height carries it to baseline return (originalPos > scaledBaseline) ? scaledBaseline : originalPos; }; - var primaryAccessor = this._projections[primaryAttr].accessor; - if (this.barLabelsEnabled && this.barLabelFormatter) { - attrToProjector["label"] = function (d, i, u, m) { - return _this._barLabelFormatter(primaryAccessor(d, i, u, m)); + var primaryAccessor = this._propertyBindings.get(primaryAttr).accessor; + if (this._labelsEnabled && this._labelFormatter) { + attrToProjector["label"] = function (d, i, dataset, m) { + return _this._labelFormatter(primaryAccessor(d, i, dataset, m)); }; - attrToProjector["positive"] = function (d, i, u, m) { return originalPositionFn(d, i, u, m) <= scaledBaseline; }; + attrToProjector["positive"] = function (d, i, dataset, m) { return originalPositionFn(d, i, dataset, m) <= scaledBaseline; }; } - attrToProjector["fill"] = attrToProjector["fill"] || d3.functor(this._defaultFillColor); return attrToProjector; }; /** @@ -7904,30 +7512,33 @@ var Plottable; */ Bar.prototype._getBarPixelWidth = function () { var _this = this; + if (!this._projectorsReady()) { + return 0; + } var barPixelWidth; - var barScale = this._isVertical ? this._xScale : this._yScale; - if (barScale instanceof Plottable.Scale.Category) { + var barScale = this._isVertical ? this.x().scale : this.y().scale; + if (barScale instanceof Plottable.Scales.Category) { barPixelWidth = barScale.rangeBand(); } else { - var barAccessor = this._isVertical ? this._projections["x"].accessor : this._projections["y"].accessor; - var numberBarAccessorData = d3.set(Plottable._Util.Methods.flatten(this._datasetKeysInOrder.map(function (k) { + var barAccessor = this._isVertical ? this.x().accessor : this.y().accessor; + var numberBarAccessorData = d3.set(Plottable.Utils.Methods.flatten(this._datasetKeysInOrder.map(function (k) { var dataset = _this._key2PlotDatasetKey.get(k).dataset; var plotMetadata = _this._key2PlotDatasetKey.get(k).plotMetadata; - return dataset.data().map(function (d, i) { return barAccessor(d, i, dataset.metadata(), plotMetadata).valueOf(); }); + return dataset.data().map(function (d, i) { return barAccessor(d, i, dataset, plotMetadata).valueOf(); }); }))).values().map(function (value) { return +value; }); numberBarAccessorData.sort(function (a, b) { return a - b; }); var barAccessorDataPairs = d3.pairs(numberBarAccessorData); var barWidthDimension = this._isVertical ? this.width() : this.height(); - barPixelWidth = Plottable._Util.Methods.min(barAccessorDataPairs, function (pair, i) { + barPixelWidth = Plottable.Utils.Methods.min(barAccessorDataPairs, function (pair, i) { return Math.abs(barScale.scale(pair[1]) - barScale.scale(pair[0])); }, barWidthDimension * Bar._SINGLE_BAR_DIMENSION_RATIO); var scaledData = numberBarAccessorData.map(function (datum) { return barScale.scale(datum); }); - var minScaledDatum = Plottable._Util.Methods.min(scaledData, 0); + var minScaledDatum = Plottable.Utils.Methods.min(scaledData, 0); if (this._barAlignmentFactor !== 0 && minScaledDatum > 0) { barPixelWidth = Math.min(barPixelWidth, minScaledDatum / this._barAlignmentFactor); } - var maxScaledDatum = Plottable._Util.Methods.max(scaledData, 0); + var maxScaledDatum = Plottable.Utils.Methods.max(scaledData, 0); if (this._barAlignmentFactor !== 1 && maxScaledDatum < barWidthDimension) { var margin = barWidthDimension - maxScaledDatum; barPixelWidth = Math.min(barPixelWidth, margin / (1 - this._barAlignmentFactor)); @@ -7936,93 +7547,10 @@ var Plottable; } return barPixelWidth; }; - Bar.prototype.hoverMode = function (mode) { - if (mode == null) { - return this._hoverMode; - } - var modeLC = mode.toLowerCase(); - if (modeLC !== "point" && modeLC !== "line") { - throw new Error(mode + " is not a valid hover mode"); - } - this._hoverMode = modeLC; - return this; - }; - Bar.prototype._clearHoverSelection = function () { - this._getDrawersInOrder().forEach(function (d, i) { - d._getRenderArea().selectAll("rect").classed("not-hovered hovered", false); - }); - }; - //===== Hover logic ===== - Bar.prototype._hoverOverComponent = function (p) { - // no-op - }; - Bar.prototype._hoverOutComponent = function (p) { - this._clearHoverSelection(); - }; - Bar.prototype._doHover = function (p) { - var _this = this; - var xPositionOrExtent = p.x; - var yPositionOrExtent = p.y; - if (this._hoverMode === "line") { - var maxExtent = { min: -Infinity, max: Infinity }; - if (this._isVertical) { - yPositionOrExtent = maxExtent; - } - else { - xPositionOrExtent = maxExtent; - } - } - var xExtent = Plottable._Util.Methods.parseExtent(xPositionOrExtent); - var yExtent = Plottable._Util.Methods.parseExtent(yPositionOrExtent); - var bars = []; - var points = []; - var projectors = this._generateAttrToProjector(); - this._datasetKeysInOrder.forEach(function (key) { - var dataset = _this._key2PlotDatasetKey.get(key).dataset; - var plotMetadata = _this._key2PlotDatasetKey.get(key).plotMetadata; - var barsFromDataset = _this._getBarsFromDataset(key, xExtent, yExtent); - d3.selectAll(barsFromDataset).each(function (d, i) { - if (_this._isVertical) { - points.push({ - x: projectors["x"](d, i, dataset.metadata(), plotMetadata) + projectors["width"](d, i, dataset.metadata(), plotMetadata) / 2, - y: projectors["y"](d, i, dataset.metadata(), plotMetadata) + (projectors["positive"](d, i, dataset.metadata(), plotMetadata) ? 0 : projectors["height"](d, i, dataset.metadata(), plotMetadata)) - }); - } - else { - points.push({ - x: projectors["x"](d, i, dataset.metadata(), plotMetadata) + projectors["height"](d, i, dataset.metadata(), plotMetadata) / 2, - y: projectors["y"](d, i, dataset.metadata(), plotMetadata) + (projectors["positive"](d, i, dataset.metadata(), plotMetadata) ? 0 : projectors["width"](d, i, dataset.metadata(), plotMetadata)) - }); - } - }); - bars = bars.concat(barsFromDataset); - }); - var barsSelection = d3.selectAll(bars); - if (!barsSelection.empty()) { - this._getDrawersInOrder().forEach(function (d, i) { - d._getRenderArea().selectAll("rect").classed({ "hovered": false, "not-hovered": true }); - }); - barsSelection.classed({ "hovered": true, "not-hovered": false }); - } - else { - this._clearHoverSelection(); - return { - data: null, - pixelPositions: null, - selection: null - }; - } - return { - data: barsSelection.data(), - pixelPositions: points, - selection: barsSelection - }; - }; - //===== /Hover logic ===== - Bar.prototype._getAllPlotData = function (datasetKeys) { - var plotData = _super.prototype._getAllPlotData.call(this, datasetKeys); - var valueScale = this._isVertical ? this._yScale : this._xScale; - var scaledBaseline = (this._isVertical ? this._yScale : this._xScale).scale(this.baseline()); + Bar.prototype.getAllPlotData = function (datasets) { + if (datasets === void 0) { datasets = this.datasets(); } + var plotData = _super.prototype.getAllPlotData.call(this, datasets); + var scaledBaseline = (this._isVertical ? this.y().scale : this.x().scale).scale(this.baseline()); var isVertical = this._isVertical; var barAlignmentFactor = this._barAlignmentFactor; plotData.selection.each(function (datum, index) { @@ -8048,9 +7576,9 @@ var Plottable; Bar._BAR_WIDTH_RATIO = 0.95; Bar._SINGLE_BAR_DIMENSION_RATIO = 0.4; return Bar; - })(Plot.AbstractXYPlot); - Plot.Bar = Bar; - })(Plot = Plottable.Plot || (Plottable.Plot = {})); + })(Plottable.XYPlot); + Plots.Bar = Bar; + })(Plots = Plottable.Plots || (Plottable.Plots = {})); })(Plottable || (Plottable = {})); /// @@ -8062,8 +7590,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Plot; - (function (Plot) { + var Plots; + (function (Plots) { var Line = (function (_super) { __extends(Line, _super); /** @@ -8075,33 +7603,25 @@ var Plottable; */ function Line(xScale, yScale) { _super.call(this, xScale, yScale); - this._hoverDetectionRadius = 15; this.classed("line-plot", true); - this.animator("reset", new Plottable.Animator.Null()); - this.animator("main", new Plottable.Animator.Base().duration(600).easing("exp-in-out")); - this._defaultStrokeColor = new Plottable.Scale.Color().range()[0]; + this.animator("reset", new Plottable.Animators.Null()); + this.animator("main", new Plottable.Animators.Base().duration(600).easing("exp-in-out")); + this.attr("stroke", new Plottable.Scales.Color().range()[0]); + this.attr("stroke-width", "2px"); } - Line.prototype._setup = function () { - _super.prototype._setup.call(this); - this._hoverTarget = this.foreground().append("circle").classed("hover-target", true).attr("r", this._hoverDetectionRadius).style("visibility", "hidden"); - }; - Line.prototype._rejectNullsAndNaNs = function (d, i, userMetdata, plotMetadata, accessor) { - var value = accessor(d, i, userMetdata, plotMetadata); - return value != null && value === value; - }; Line.prototype._getDrawer = function (key) { - return new Plottable._Drawer.Line(key); + return new Plottable.Drawers.Line(key); }; Line.prototype._getResetYFunction = function () { // gets the y-value generator for the animation start point - var yDomain = this._yScale.domain(); + var yDomain = this.y().scale.domain(); var domainMax = Math.max(yDomain[0], yDomain[1]); var domainMin = Math.min(yDomain[0], yDomain[1]); // start from zero, or the closest domain value to zero // avoids lines zooming on from offscreen. var startValue = (domainMax < 0 && domainMax) || (domainMin > 0 && domainMin) || 0; - var scaledStartValue = this._yScale.scale(startValue); - return function (d, i, u, m) { return scaledStartValue; }; + var scaledStartValue = this.y().scale.scale(startValue); + return function (d, i, dataset, m) { return scaledStartValue; }; }; Line.prototype._generateDrawSteps = function () { var drawSteps = []; @@ -8114,64 +7634,26 @@ var Plottable; return drawSteps; }; Line.prototype._generateAttrToProjector = function () { - var _this = this; var attrToProjector = _super.prototype._generateAttrToProjector.call(this); var wholeDatumAttributes = this._wholeDatumAttributes(); var isSingleDatumAttr = function (attr) { return wholeDatumAttributes.indexOf(attr) === -1; }; var singleDatumAttributes = d3.keys(attrToProjector).filter(isSingleDatumAttr); singleDatumAttributes.forEach(function (attribute) { var projector = attrToProjector[attribute]; - attrToProjector[attribute] = function (data, i, u, m) { return data.length > 0 ? projector(data[0], i, u, m) : null; }; + attrToProjector[attribute] = function (data, i, dataset, m) { return data.length > 0 ? projector(data[0], i, dataset, m) : null; }; }); - var xFunction = attrToProjector["x"]; - var yFunction = attrToProjector["y"]; - attrToProjector["defined"] = function (d, i, u, m) { return _this._rejectNullsAndNaNs(d, i, u, m, xFunction) && _this._rejectNullsAndNaNs(d, i, u, m, yFunction); }; - attrToProjector["stroke"] = attrToProjector["stroke"] || d3.functor(this._defaultStrokeColor); - attrToProjector["stroke-width"] = attrToProjector["stroke-width"] || d3.functor("2px"); return attrToProjector; }; Line.prototype._wholeDatumAttributes = function () { - return ["x", "y"]; - }; - Line.prototype._getClosestWithinRange = function (p, range) { - var _this = this; - var attrToProjector = this._generateAttrToProjector(); - var xProjector = attrToProjector["x"]; - var yProjector = attrToProjector["y"]; - var getDistSq = function (d, i, userMetdata, plotMetadata) { - var dx = +xProjector(d, i, userMetdata, plotMetadata) - p.x; - var dy = +yProjector(d, i, userMetdata, plotMetadata) - p.y; - return (dx * dx + dy * dy); - }; - var closestOverall; - var closestPoint; - var closestDistSq = range * range; - this._datasetKeysInOrder.forEach(function (key) { - var dataset = _this._key2PlotDatasetKey.get(key).dataset; - var plotMetadata = _this._key2PlotDatasetKey.get(key).plotMetadata; - dataset.data().forEach(function (d, i) { - var distSq = getDistSq(d, i, dataset.metadata(), plotMetadata); - if (distSq < closestDistSq) { - closestOverall = d; - closestPoint = { - x: xProjector(d, i, dataset.metadata(), plotMetadata), - y: yProjector(d, i, dataset.metadata(), plotMetadata) - }; - closestDistSq = distSq; - } - }); - }); - return { - closestValue: closestOverall, - closestPoint: closestPoint - }; + return ["x", "y", "defined"]; }; - Line.prototype._getAllPlotData = function (datasetKeys) { + Line.prototype.getAllPlotData = function (datasets) { var _this = this; + if (datasets === void 0) { datasets = this.datasets(); } var data = []; var pixelPoints = []; var allElements = []; - datasetKeys.forEach(function (datasetKey) { + this._keysForDatasets(datasets).forEach(function (datasetKey) { var plotDatasetKey = _this._key2PlotDatasetKey.get(datasetKey); if (plotDatasetKey == null) { return; @@ -8208,8 +7690,8 @@ var Plottable; var closestData = []; var closestPixelPoints = []; var closestElements = []; - this.datasetOrder().forEach(function (key) { - var plotData = _this.getAllPlotData(key); + this.datasets().forEach(function (dataset) { + var plotData = _this.getAllPlotData([dataset]); plotData.pixelPoints.forEach(function (pixelPoint, index) { var datum = plotData.data[index]; var line = plotData.selection[0][0]; @@ -8238,38 +7720,10 @@ var Plottable; selection: d3.selectAll(closestElements) }; }; - //===== Hover logic ===== - Line.prototype._hoverOverComponent = function (p) { - // no-op - }; - Line.prototype._hoverOutComponent = function (p) { - // no-op - }; - Line.prototype._doHover = function (p) { - var closestInfo = this._getClosestWithinRange(p, this._hoverDetectionRadius); - var closestValue = closestInfo.closestValue; - if (closestValue === undefined) { - return { - data: null, - pixelPositions: null, - selection: null - }; - } - var closestPoint = closestInfo.closestPoint; - this._hoverTarget.attr({ - "cx": closestInfo.closestPoint.x, - "cy": closestInfo.closestPoint.y - }); - return { - data: [closestValue], - pixelPositions: [closestPoint], - selection: this._hoverTarget - }; - }; return Line; - })(Plot.AbstractXYPlot); - Plot.Line = Line; - })(Plot = Plottable.Plot || (Plottable.Plot = {})); + })(Plottable.XYPlot); + Plots.Line = Line; + })(Plots = Plottable.Plots || (Plottable.Plots = {})); })(Plottable || (Plottable = {})); /// @@ -8281,8 +7735,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Plot; - (function (Plot) { + var Plots; + (function (Plots) { /** * An AreaPlot draws a filled region (area) between the plot's projected "y" and projected "y0" values. */ @@ -8298,51 +7752,48 @@ var Plottable; function Area(xScale, yScale) { _super.call(this, xScale, yScale); this.classed("area-plot", true); - this.project("y0", 0, yScale); // default - this.animator("reset", new Plottable.Animator.Null()); - this.animator("main", new Plottable.Animator.Base().duration(600).easing("exp-in-out")); - this._defaultFillColor = new Plottable.Scale.Color().range()[0]; - } + this.y0(0, yScale); // default + this.animator("reset", new Plottable.Animators.Null()); + this.animator("main", new Plottable.Animators.Base().duration(600).easing("exp-in-out")); + var defaultColor = new Plottable.Scales.Color().range()[0]; + this.attr("fill-opacity", 0.25); + this.attr("fill", defaultColor); + this.attr("stroke", defaultColor); + } + Area.prototype.y0 = function (y0, y0Scale) { + if (y0 == null) { + return this._propertyBindings.get(Area._Y0_KEY); + } + this._bindProperty(Area._Y0_KEY, y0, y0Scale); + this._updateYDomainer(); + this.renderImmediately(); + return this; + }; Area.prototype._onDatasetUpdate = function () { _super.prototype._onDatasetUpdate.call(this); - if (this._yScale != null) { + if (this.y().scale != null) { this._updateYDomainer(); } }; Area.prototype._getDrawer = function (key) { - return new Plottable._Drawer.Area(key); + return new Plottable.Drawers.Area(key); }; Area.prototype._updateYDomainer = function () { - var _this = this; _super.prototype._updateYDomainer.call(this); - var constantBaseline; - var y0Projector = this._projections["y0"]; - var y0Accessor = y0Projector && y0Projector.accessor; - if (y0Accessor != null) { - var extents = this.datasets().map(function (d) { return d._getExtent(y0Accessor, _this._yScale._typeCoercer); }); - var extent = Plottable._Util.Methods.flatten(extents); - var uniqExtentVals = Plottable._Util.Methods.uniq(extent); - if (uniqExtentVals.length === 1) { - constantBaseline = uniqExtentVals[0]; - } - } - if (!this._yScale._userSetDomainer) { + var extents = this._propertyExtents.get("y0"); + var extent = Plottable.Utils.Methods.flatten(extents); + var uniqExtentVals = Plottable.Utils.Methods.uniq(extent); + var constantBaseline = uniqExtentVals.length === 1 ? uniqExtentVals[0] : null; + var yScale = this.y().scale; + if (!yScale._userSetDomainer) { if (constantBaseline != null) { - this._yScale.domainer().addPaddingException(constantBaseline, "AREA_PLOT+" + this.getID()); + yScale.domainer().addPaddingException(this, constantBaseline); } else { - this._yScale.domainer().removePaddingException("AREA_PLOT+" + this.getID()); + yScale.domainer().removePaddingException(this); } - // prepending "AREA_PLOT" is unnecessary but reduces likely of user accidentally creating collisions - this._yScale._autoDomainIfAutomaticMode(); - } - }; - Area.prototype.project = function (attrToSet, accessor, scale) { - _super.prototype.project.call(this, attrToSet, accessor, scale); - if (attrToSet === "y0") { - this._updateYDomainer(); + yScale._autoDomainIfAutomaticMode(); } - return this; }; Area.prototype._getResetYFunction = function () { return this._generateAttrToProjector()["y0"]; @@ -8352,17 +7803,11 @@ var Plottable; wholeDatumAttributes.push("y0"); return wholeDatumAttributes; }; - Area.prototype._generateAttrToProjector = function () { - var attrToProjector = _super.prototype._generateAttrToProjector.call(this); - attrToProjector["fill-opacity"] = attrToProjector["fill-opacity"] || d3.functor(0.25); - attrToProjector["fill"] = attrToProjector["fill"] || d3.functor(this._defaultFillColor); - attrToProjector["stroke"] = attrToProjector["stroke"] || d3.functor(this._defaultFillColor); - return attrToProjector; - }; + Area._Y0_KEY = "y0"; return Area; - })(Plot.Line); - Plot.Area = Area; - })(Plot = Plottable.Plot || (Plottable.Plot = {})); + })(Plots.Line); + Plots.Area = Area; + })(Plots = Plottable.Plots || (Plottable.Plots = {})); })(Plottable || (Plottable = {})); /// @@ -8374,8 +7819,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Plot; - (function (Plot) { + var Plots; + (function (Plots) { var ClusteredBar = (function (_super) { __extends(ClusteredBar, _super); /** @@ -8404,8 +7849,8 @@ var Plottable; attrToProjector["height"] = !this._isVertical ? innerWidthF : attrToProjector["height"]; var xAttr = attrToProjector["x"]; var yAttr = attrToProjector["y"]; - attrToProjector["x"] = function (d, i, u, m) { return _this._isVertical ? xAttr(d, i, u, m) + m.position : xAttr(d, u, u, m); }; - attrToProjector["y"] = function (d, i, u, m) { return _this._isVertical ? yAttr(d, i, u, m) : yAttr(d, i, u, m) + m.position; }; + attrToProjector["x"] = function (d, i, dataset, m) { return _this._isVertical ? xAttr(d, i, dataset, m) + m.position : xAttr(d, i, dataset, m); }; + attrToProjector["y"] = function (d, i, dataset, m) { return _this._isVertical ? yAttr(d, i, dataset, m) : yAttr(d, i, dataset, m) + m.position; }; return attrToProjector; }; ClusteredBar.prototype._updateClusterPosition = function () { @@ -8417,16 +7862,16 @@ var Plottable; }); }; ClusteredBar.prototype._makeInnerScale = function () { - var innerScale = new Plottable.Scale.Category(); + var innerScale = new Plottable.Scales.Category(); innerScale.domain(this._datasetKeysInOrder); - if (!this._projections["width"]) { + if (!this._attrBindings.get("width")) { innerScale.range([0, this._getBarPixelWidth()]); } else { - var projection = this._projections["width"]; + var projection = this._attrBindings.get("width"); var accessor = projection.accessor; var scale = projection.scale; - var fn = scale ? function (d, i, u, m) { return scale.scale(accessor(d, i, u, m)); } : accessor; + var fn = scale ? function (d, i, dataset, m) { return scale.scale(accessor(d, i, dataset, m)); } : accessor; innerScale.range([0, fn(null, 0, null, null)]); } return innerScale; @@ -8441,9 +7886,9 @@ var Plottable; return metadata; }; return ClusteredBar; - })(Plot.Bar); - Plot.ClusteredBar = ClusteredBar; - })(Plot = Plottable.Plot || (Plottable.Plot = {})); + })(Plots.Bar); + Plots.ClusteredBar = ClusteredBar; + })(Plots = Plottable.Plots || (Plottable.Plots = {})); })(Plottable || (Plottable = {})); /// @@ -8455,197 +7900,204 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Plot; - (function (Plot) { - var AbstractStacked = (function (_super) { - __extends(AbstractStacked, _super); - function AbstractStacked() { - _super.apply(this, arguments); - this._stackedExtent = [0, 0]; + var Plots; + (function (Plots) { + })(Plots = Plottable.Plots || (Plottable.Plots = {})); + var Stacked = (function (_super) { + __extends(Stacked, _super); + function Stacked() { + _super.apply(this, arguments); + this._stackedExtent = [0, 0]; + } + Stacked.prototype._getPlotMetadataForDataset = function (key) { + var metadata = _super.prototype._getPlotMetadataForDataset.call(this, key); + metadata.offsets = d3.map(); + return metadata; + }; + Stacked.prototype.x = function (x, scale) { + if (x == null) { + return _super.prototype.x.call(this); } - AbstractStacked.prototype._getPlotMetadataForDataset = function (key) { - var metadata = _super.prototype._getPlotMetadataForDataset.call(this, key); - metadata.offsets = d3.map(); - return metadata; - }; - AbstractStacked.prototype.project = function (attrToSet, accessor, scale) { - _super.prototype.project.call(this, attrToSet, accessor, scale); - if (this._projections["x"] && this._projections["y"] && (attrToSet === "x" || attrToSet === "y")) { - this._updateStackOffsets(); - } - return this; - }; - AbstractStacked.prototype._onDatasetUpdate = function () { - if (this._projectorsReady()) { - this._updateStackOffsets(); - } - _super.prototype._onDatasetUpdate.call(this); - }; - AbstractStacked.prototype._updateStackOffsets = function () { - var dataMapArray = this._generateDefaultMapArray(); - var domainKeys = this._getDomainKeys(); - var positiveDataMapArray = dataMapArray.map(function (dataMap) { - return Plottable._Util.Methods.populateMap(domainKeys, function (domainKey) { - return { key: domainKey, value: Math.max(0, dataMap.get(domainKey).value) || 0 }; - }); + if (scale == null) { + _super.prototype.x.call(this, x); + } + else { + _super.prototype.x.call(this, x, scale); + } + if (this.x().accessor != null && this.y().accessor != null) { + this._updateStackOffsets(); + } + return this; + }; + Stacked.prototype.y = function (y, scale) { + if (y == null) { + return _super.prototype.y.call(this); + } + if (scale == null) { + _super.prototype.y.call(this, y); + } + else { + _super.prototype.y.call(this, y, scale); + } + if (this.x().accessor != null && this.y().accessor != null) { + this._updateStackOffsets(); + } + return this; + }; + Stacked.prototype._onDatasetUpdate = function () { + if (this._projectorsReady()) { + this._updateStackOffsets(); + } + _super.prototype._onDatasetUpdate.call(this); + }; + Stacked.prototype._updateStackOffsets = function () { + var dataMapArray = this._generateDefaultMapArray(); + var domainKeys = this._getDomainKeys(); + var positiveDataMapArray = dataMapArray.map(function (dataMap) { + return Plottable.Utils.Methods.populateMap(domainKeys, function (domainKey) { + return { key: domainKey, value: Math.max(0, dataMap.get(domainKey).value) || 0 }; }); - var negativeDataMapArray = dataMapArray.map(function (dataMap) { - return Plottable._Util.Methods.populateMap(domainKeys, function (domainKey) { - return { key: domainKey, value: Math.min(dataMap.get(domainKey).value, 0) || 0 }; - }); + }); + var negativeDataMapArray = dataMapArray.map(function (dataMap) { + return Plottable.Utils.Methods.populateMap(domainKeys, function (domainKey) { + return { key: domainKey, value: Math.min(dataMap.get(domainKey).value, 0) || 0 }; }); - this._setDatasetStackOffsets(this._stack(positiveDataMapArray), this._stack(negativeDataMapArray)); - this._updateStackExtents(); - }; - AbstractStacked.prototype._updateStackExtents = function () { - var _this = this; - var datasets = this.datasets(); - var valueAccessor = this._valueAccessor(); - var keyAccessor = this._keyAccessor(); - var maxStackExtent = Plottable._Util.Methods.max(this._datasetKeysInOrder, function (k) { - var dataset = _this._key2PlotDatasetKey.get(k).dataset; - var plotMetadata = _this._key2PlotDatasetKey.get(k).plotMetadata; - return Plottable._Util.Methods.max(dataset.data(), function (datum, i) { - return +valueAccessor(datum, i, dataset.metadata(), plotMetadata) + plotMetadata.offsets.get(keyAccessor(datum, i, dataset.metadata(), plotMetadata)); - }, 0); + }); + this._setDatasetStackOffsets(this._stack(positiveDataMapArray), this._stack(negativeDataMapArray)); + this._updateStackExtents(); + }; + Stacked.prototype._updateStackExtents = function () { + var _this = this; + var valueAccessor = this._valueAccessor(); + var keyAccessor = this._keyAccessor(); + var filter = this._filterForProperty(this._isVertical ? "y" : "x"); + var maxStackExtent = Plottable.Utils.Methods.max(this._datasetKeysInOrder, function (k) { + var dataset = _this._key2PlotDatasetKey.get(k).dataset; + var plotMetadata = _this._key2PlotDatasetKey.get(k).plotMetadata; + var data = dataset.data(); + if (filter != null) { + data = data.filter(function (d, i) { return filter(d, i, dataset, plotMetadata); }); + } + return Plottable.Utils.Methods.max(data, function (datum, i) { + return +valueAccessor(datum, i, dataset, plotMetadata) + plotMetadata.offsets.get(String(keyAccessor(datum, i, dataset, plotMetadata))); }, 0); - var minStackExtent = Plottable._Util.Methods.min(this._datasetKeysInOrder, function (k) { - var dataset = _this._key2PlotDatasetKey.get(k).dataset; - var plotMetadata = _this._key2PlotDatasetKey.get(k).plotMetadata; - return Plottable._Util.Methods.min(dataset.data(), function (datum, i) { - return +valueAccessor(datum, i, dataset.metadata(), plotMetadata) + plotMetadata.offsets.get(keyAccessor(datum, i, dataset.metadata(), plotMetadata)); - }, 0); + }, 0); + var minStackExtent = Plottable.Utils.Methods.min(this._datasetKeysInOrder, function (k) { + var dataset = _this._key2PlotDatasetKey.get(k).dataset; + var plotMetadata = _this._key2PlotDatasetKey.get(k).plotMetadata; + var data = dataset.data(); + if (filter != null) { + data = data.filter(function (d, i) { return filter(d, i, dataset, plotMetadata); }); + } + return Plottable.Utils.Methods.min(data, function (datum, i) { + return +valueAccessor(datum, i, dataset, plotMetadata) + plotMetadata.offsets.get(String(keyAccessor(datum, i, dataset, plotMetadata))); }, 0); - this._stackedExtent = [Math.min(minStackExtent, 0), Math.max(0, maxStackExtent)]; - }; - /** - * Feeds the data through d3's stack layout function which will calculate - * the stack offsets and use the the function declared in .out to set the offsets on the data. - */ - AbstractStacked.prototype._stack = function (dataArray) { - var _this = this; - var outFunction = function (d, y0, y) { - d.offset = y0; - }; - d3.layout.stack().x(function (d) { return d.key; }).y(function (d) { return +d.value; }).values(function (d) { return _this._getDomainKeys().map(function (domainKey) { return d.get(domainKey); }); }).out(outFunction)(dataArray); - return dataArray; + }, 0); + this._stackedExtent = [Math.min(minStackExtent, 0), Math.max(0, maxStackExtent)]; + }; + /** + * Feeds the data through d3's stack layout function which will calculate + * the stack offsets and use the the function declared in .out to set the offsets on the data. + */ + Stacked.prototype._stack = function (dataArray) { + var _this = this; + var outFunction = function (d, y0, y) { + d.offset = y0; }; - /** - * After the stack offsets have been determined on each separate dataset, the offsets need - * to be determined correctly on the overall datasets - */ - AbstractStacked.prototype._setDatasetStackOffsets = function (positiveDataMapArray, negativeDataMapArray) { - var _this = this; - var keyAccessor = this._keyAccessor(); - var valueAccessor = this._valueAccessor(); - this._datasetKeysInOrder.forEach(function (k, index) { - var dataset = _this._key2PlotDatasetKey.get(k).dataset; - var plotMetadata = _this._key2PlotDatasetKey.get(k).plotMetadata; - var positiveDataMap = positiveDataMapArray[index]; - var negativeDataMap = negativeDataMapArray[index]; - var isAllNegativeValues = dataset.data().every(function (datum, i) { return valueAccessor(datum, i, dataset.metadata(), plotMetadata) <= 0; }); - dataset.data().forEach(function (datum, datumIndex) { - var key = keyAccessor(datum, datumIndex, dataset.metadata(), plotMetadata); - var positiveOffset = positiveDataMap.get(key).offset; - var negativeOffset = negativeDataMap.get(key).offset; - var value = valueAccessor(datum, datumIndex, dataset.metadata(), plotMetadata); - var offset; - if (!+value) { - offset = isAllNegativeValues ? negativeOffset : positiveOffset; - } - else { - offset = value > 0 ? positiveOffset : negativeOffset; - } - plotMetadata.offsets.set(key, offset); - }); + d3.layout.stack().x(function (d) { return d.key; }).y(function (d) { return +d.value; }).values(function (d) { return _this._getDomainKeys().map(function (domainKey) { return d.get(domainKey); }); }).out(outFunction)(dataArray); + return dataArray; + }; + /** + * After the stack offsets have been determined on each separate dataset, the offsets need + * to be determined correctly on the overall datasets + */ + Stacked.prototype._setDatasetStackOffsets = function (positiveDataMapArray, negativeDataMapArray) { + var _this = this; + var keyAccessor = this._keyAccessor(); + var valueAccessor = this._valueAccessor(); + this._datasetKeysInOrder.forEach(function (k, index) { + var dataset = _this._key2PlotDatasetKey.get(k).dataset; + var plotMetadata = _this._key2PlotDatasetKey.get(k).plotMetadata; + var positiveDataMap = positiveDataMapArray[index]; + var negativeDataMap = negativeDataMapArray[index]; + var isAllNegativeValues = dataset.data().every(function (datum, i) { return valueAccessor(datum, i, dataset, plotMetadata) <= 0; }); + dataset.data().forEach(function (datum, datumIndex) { + var key = String(keyAccessor(datum, datumIndex, dataset, plotMetadata)); + var positiveOffset = positiveDataMap.get(key).offset; + var negativeOffset = negativeDataMap.get(key).offset; + var value = valueAccessor(datum, datumIndex, dataset, plotMetadata); + var offset; + if (!+value) { + offset = isAllNegativeValues ? negativeOffset : positiveOffset; + } + else { + offset = value > 0 ? positiveOffset : negativeOffset; + } + plotMetadata.offsets.set(key, offset); }); - }; - AbstractStacked.prototype._getDomainKeys = function () { - var _this = this; - var keyAccessor = this._keyAccessor(); - var domainKeys = d3.set(); - this._datasetKeysInOrder.forEach(function (k) { - var dataset = _this._key2PlotDatasetKey.get(k).dataset; - var plotMetadata = _this._key2PlotDatasetKey.get(k).plotMetadata; - dataset.data().forEach(function (datum, index) { - domainKeys.add(keyAccessor(datum, index, dataset.metadata(), plotMetadata)); - }); + }); + }; + Stacked.prototype._getDomainKeys = function () { + var _this = this; + var keyAccessor = this._keyAccessor(); + var domainKeys = d3.set(); + this._datasetKeysInOrder.forEach(function (k) { + var dataset = _this._key2PlotDatasetKey.get(k).dataset; + var plotMetadata = _this._key2PlotDatasetKey.get(k).plotMetadata; + dataset.data().forEach(function (datum, index) { + domainKeys.add(keyAccessor(datum, index, dataset, plotMetadata)); }); - return domainKeys.values(); - }; - AbstractStacked.prototype._generateDefaultMapArray = function () { - var _this = this; - var keyAccessor = this._keyAccessor(); - var valueAccessor = this._valueAccessor(); - var domainKeys = this._getDomainKeys(); - var dataMapArray = this._datasetKeysInOrder.map(function () { - return Plottable._Util.Methods.populateMap(domainKeys, function (domainKey) { - return { key: domainKey, value: 0 }; - }); + }); + return domainKeys.values(); + }; + Stacked.prototype._generateDefaultMapArray = function () { + var _this = this; + var keyAccessor = this._keyAccessor(); + var valueAccessor = this._valueAccessor(); + var domainKeys = this._getDomainKeys(); + var dataMapArray = this._datasetKeysInOrder.map(function () { + return Plottable.Utils.Methods.populateMap(domainKeys, function (domainKey) { + return { key: domainKey, value: 0 }; }); - this._datasetKeysInOrder.forEach(function (k, datasetIndex) { - var dataset = _this._key2PlotDatasetKey.get(k).dataset; - var plotMetadata = _this._key2PlotDatasetKey.get(k).plotMetadata; - dataset.data().forEach(function (datum, index) { - var key = keyAccessor(datum, index, dataset.metadata(), plotMetadata); - var value = valueAccessor(datum, index, dataset.metadata(), plotMetadata); - dataMapArray[datasetIndex].set(key, { key: key, value: value }); - }); + }); + this._datasetKeysInOrder.forEach(function (k, datasetIndex) { + var dataset = _this._key2PlotDatasetKey.get(k).dataset; + var plotMetadata = _this._key2PlotDatasetKey.get(k).plotMetadata; + dataset.data().forEach(function (datum, index) { + var key = String(keyAccessor(datum, index, dataset, plotMetadata)); + var value = valueAccessor(datum, index, dataset, plotMetadata); + dataMapArray[datasetIndex].set(key, { key: key, value: value }); }); - return dataMapArray; - }; - AbstractStacked.prototype._updateScaleExtents = function () { - _super.prototype._updateScaleExtents.call(this); - var primaryScale = this._isVertical ? this._yScale : this._xScale; - if (!primaryScale) { - return; - } - if (this._isAnchored && this._stackedExtent.length > 0) { - primaryScale._updateExtent(this.getID().toString(), "_PLOTTABLE_PROTECTED_FIELD_STACK_EXTENT", this._stackedExtent); - } - else { - primaryScale._removeExtent(this.getID().toString(), "_PLOTTABLE_PROTECTED_FIELD_STACK_EXTENT"); - } - }; - AbstractStacked.prototype._normalizeDatasets = function (fromX) { - var _this = this; - var aAccessor = this._projections[fromX ? "x" : "y"].accessor; - var bAccessor = this._projections[fromX ? "y" : "x"].accessor; - var aStackedAccessor = function (d, i, u, m) { - var value = aAccessor(d, i, u, m); - if (_this._isVertical ? !fromX : fromX) { - value += m.offsets.get(bAccessor(d, i, u, m)); - } - return value; - }; - var bStackedAccessor = function (d, i, u, m) { - var value = bAccessor(d, i, u, m); - if (_this._isVertical ? fromX : !fromX) { - value += m.offsets.get(aAccessor(d, i, u, m)); - } - return value; - }; - return Plottable._Util.Methods.flatten(this._datasetKeysInOrder.map(function (key) { - var dataset = _this._key2PlotDatasetKey.get(key).dataset; - var plotMetadata = _this._key2PlotDatasetKey.get(key).plotMetadata; - return dataset.data().map(function (d, i) { - return { - a: aStackedAccessor(d, i, dataset.metadata(), plotMetadata), - b: bStackedAccessor(d, i, dataset.metadata(), plotMetadata) - }; - }); - })); - }; - AbstractStacked.prototype._keyAccessor = function () { - return this._isVertical ? this._projections["x"].accessor : this._projections["y"].accessor; - }; - AbstractStacked.prototype._valueAccessor = function () { - return this._isVertical ? this._projections["y"].accessor : this._projections["x"].accessor; - }; - return AbstractStacked; - })(Plot.AbstractXYPlot); - Plot.AbstractStacked = AbstractStacked; - })(Plot = Plottable.Plot || (Plottable.Plot = {})); + }); + return dataMapArray; + }; + Stacked.prototype._updateExtentsForProperty = function (property) { + _super.prototype._updateExtentsForProperty.call(this, property); + if ((property === "x" || property === "y") && this._projectorsReady()) { + this._updateStackExtents(); + } + }; + Stacked.prototype._extentsForProperty = function (attr) { + var extents = _super.prototype._extentsForProperty.call(this, attr); + var primaryAttr = this._isVertical ? "y" : "x"; + if (attr === primaryAttr && this._stackedExtent) { + var clonedExtents = extents.slice(); + clonedExtents.push(this._stackedExtent); + return clonedExtents; + } + else { + return extents; + } + }; + Stacked.prototype._keyAccessor = function () { + return this._isVertical ? this.x().accessor : this.y().accessor; + }; + Stacked.prototype._valueAccessor = function () { + return this._isVertical ? this.y().accessor : this.x().accessor; + }; + return Stacked; + })(Plottable.XYPlot); + Plottable.Stacked = Stacked; })(Plottable || (Plottable = {})); /// @@ -8657,8 +8109,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Plot; - (function (Plot) { + var Plots; + (function (Plots) { var StackedArea = (function (_super) { __extends(StackedArea, _super); /** @@ -8673,19 +8125,48 @@ var Plottable; this._baselineValue = 0; this.classed("area-plot", true); this._isVertical = true; + this.attr("fill-opacity", 1); } StackedArea.prototype._getDrawer = function (key) { - return new Plottable._Drawer.Area(key).drawLine(false); + return new Plottable.Drawers.Area(key).drawLine(false); }; StackedArea.prototype._getAnimator = function (key) { - return new Plottable.Animator.Null(); + return new Plottable.Animators.Null(); }; StackedArea.prototype._setup = function () { _super.prototype._setup.call(this); this._baseline = this._renderArea.append("line").classed("baseline", true); }; + StackedArea.prototype.x = function (x, xScale) { + if (x == null) { + return _super.prototype.x.call(this); + } + if (xScale == null) { + _super.prototype.x.call(this, x); + Plottable.Stacked.prototype.x.apply(this, [x]); + } + else { + _super.prototype.x.call(this, x, xScale); + Plottable.Stacked.prototype.x.apply(this, [x, xScale]); + } + return this; + }; + StackedArea.prototype.y = function (y, yScale) { + if (y == null) { + return _super.prototype.y.call(this); + } + if (yScale == null) { + _super.prototype.y.call(this, y); + Plottable.Stacked.prototype.y.apply(this, [y]); + } + else { + _super.prototype.y.call(this, y, yScale); + Plottable.Stacked.prototype.y.apply(this, [y, yScale]); + } + return this; + }; StackedArea.prototype._additionalPaint = function () { - var scaledBaseline = this._yScale.scale(this._baselineValue); + var scaledBaseline = this.y().scale.scale(this._baselineValue); var baselineAttr = { "x1": 0, "y1": scaledBaseline, @@ -8696,90 +8177,82 @@ var Plottable; }; StackedArea.prototype._updateYDomainer = function () { _super.prototype._updateYDomainer.call(this); - var scale = this._yScale; + var scale = this.y().scale; if (!scale._userSetDomainer) { - scale.domainer().addPaddingException(0, "STACKED_AREA_PLOT+" + this.getID()).addIncludedValue(0, "STACKED_AREA_PLOT+" + this.getID()); + scale.domainer().addPaddingException(this, 0).addIncludedValue(this, 0); // prepending "AREA_PLOT" is unnecessary but reduces likely of user accidentally creating collisions scale._autoDomainIfAutomaticMode(); } }; - StackedArea.prototype.project = function (attrToSet, accessor, scale) { - _super.prototype.project.call(this, attrToSet, accessor, scale); - Plot.AbstractStacked.prototype.project.apply(this, [attrToSet, accessor, scale]); - return this; - }; StackedArea.prototype._onDatasetUpdate = function () { _super.prototype._onDatasetUpdate.call(this); - Plot.AbstractStacked.prototype._onDatasetUpdate.apply(this); + Plottable.Stacked.prototype._onDatasetUpdate.apply(this); return this; }; StackedArea.prototype._generateAttrToProjector = function () { var _this = this; var attrToProjector = _super.prototype._generateAttrToProjector.call(this); - if (this._projections["fill-opacity"] == null) { - attrToProjector["fill-opacity"] = d3.functor(1); - } - var yAccessor = this._projections["y"].accessor; - var xAccessor = this._projections["x"].accessor; - attrToProjector["y"] = function (d, i, u, m) { return _this._yScale.scale(+yAccessor(d, i, u, m) + m.offsets.get(xAccessor(d, i, u, m))); }; - attrToProjector["y0"] = function (d, i, u, m) { return _this._yScale.scale(m.offsets.get(xAccessor(d, i, u, m))); }; + var yAccessor = this.y().accessor; + var xAccessor = this.x().accessor; + attrToProjector["y"] = function (d, i, dataset, m) { return _this.y().scale.scale(+yAccessor(d, i, dataset, m) + m.offsets.get(xAccessor(d, i, dataset, m))); }; + attrToProjector["y0"] = function (d, i, dataset, m) { return _this.y().scale.scale(m.offsets.get(xAccessor(d, i, dataset, m))); }; return attrToProjector; }; StackedArea.prototype._wholeDatumAttributes = function () { return ["x", "y", "defined"]; }; - //===== Stack logic from AbstractStackedPlot ===== + // ===== Stack logic from StackedPlot ===== StackedArea.prototype._updateStackOffsets = function () { var _this = this; if (!this._projectorsReady()) { return; } var domainKeys = this._getDomainKeys(); - var keyAccessor = this._isVertical ? this._projections["x"].accessor : this._projections["y"].accessor; + var keyAccessor = this._isVertical ? this.x().accessor : this.y().accessor; var keySets = this._datasetKeysInOrder.map(function (k) { var dataset = _this._key2PlotDatasetKey.get(k).dataset; var plotMetadata = _this._key2PlotDatasetKey.get(k).plotMetadata; - return d3.set(dataset.data().map(function (datum, i) { return keyAccessor(datum, i, dataset.metadata(), plotMetadata).toString(); })).values(); + return d3.set(dataset.data().map(function (datum, i) { return keyAccessor(datum, i, dataset, plotMetadata).toString(); })).values(); }); if (keySets.some(function (keySet) { return keySet.length !== domainKeys.length; })) { - Plottable._Util.Methods.warn("the domains across the datasets are not the same. Plot may produce unintended behavior."); + Plottable.Utils.Methods.warn("the domains across the datasets are not the same. Plot may produce unintended behavior."); } - Plot.AbstractStacked.prototype._updateStackOffsets.call(this); + Plottable.Stacked.prototype._updateStackOffsets.call(this); }; StackedArea.prototype._updateStackExtents = function () { - Plot.AbstractStacked.prototype._updateStackExtents.call(this); + Plottable.Stacked.prototype._updateStackExtents.call(this); }; StackedArea.prototype._stack = function (dataArray) { - return Plot.AbstractStacked.prototype._stack.call(this, dataArray); + return Plottable.Stacked.prototype._stack.call(this, dataArray); }; StackedArea.prototype._setDatasetStackOffsets = function (positiveDataMapArray, negativeDataMapArray) { - Plot.AbstractStacked.prototype._setDatasetStackOffsets.call(this, positiveDataMapArray, negativeDataMapArray); + Plottable.Stacked.prototype._setDatasetStackOffsets.call(this, positiveDataMapArray, negativeDataMapArray); }; StackedArea.prototype._getDomainKeys = function () { - return Plot.AbstractStacked.prototype._getDomainKeys.call(this); + return Plottable.Stacked.prototype._getDomainKeys.call(this); }; StackedArea.prototype._generateDefaultMapArray = function () { - return Plot.AbstractStacked.prototype._generateDefaultMapArray.call(this); + return Plottable.Stacked.prototype._generateDefaultMapArray.call(this); }; - StackedArea.prototype._updateScaleExtents = function () { - Plot.AbstractStacked.prototype._updateScaleExtents.call(this); + StackedArea.prototype._extentsForProperty = function (attr) { + return Plottable.Stacked.prototype._extentsForProperty.call(this, attr); }; StackedArea.prototype._keyAccessor = function () { - return Plot.AbstractStacked.prototype._keyAccessor.call(this); + return Plottable.Stacked.prototype._keyAccessor.call(this); }; StackedArea.prototype._valueAccessor = function () { - return Plot.AbstractStacked.prototype._valueAccessor.call(this); + return Plottable.Stacked.prototype._valueAccessor.call(this); }; StackedArea.prototype._getPlotMetadataForDataset = function (key) { - return Plot.AbstractStacked.prototype._getPlotMetadataForDataset.call(this, key); + return Plottable.Stacked.prototype._getPlotMetadataForDataset.call(this, key); }; - StackedArea.prototype._normalizeDatasets = function (fromX) { - return Plot.AbstractStacked.prototype._normalizeDatasets.call(this, fromX); + StackedArea.prototype._updateExtentsForProperty = function (property) { + Plottable.Stacked.prototype._updateExtentsForProperty.call(this, property); }; return StackedArea; - })(Plot.Area); - Plot.StackedArea = StackedArea; - })(Plot = Plottable.Plot || (Plottable.Plot = {})); + })(Plots.Area); + Plots.StackedArea = StackedArea; + })(Plots = Plottable.Plots || (Plottable.Plots = {})); })(Plottable || (Plottable = {})); /// @@ -8791,8 +8264,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Plot; - (function (Plot) { + var Plots; + (function (Plots) { var StackedBar = (function (_super) { __extends(StackedBar, _super); /** @@ -8814,94 +8287,119 @@ var Plottable; return this.animator(key); } else if (key === "stacked-bar") { - var primaryScale = this._isVertical ? this._yScale : this._xScale; + var primaryScale = this._isVertical ? this.y().scale : this.x().scale; var scaledBaseline = primaryScale.scale(this.baseline()); - return new Plottable.Animator.MovingRect(scaledBaseline, this._isVertical); + return new Plottable.Animators.MovingRect(scaledBaseline, this._isVertical); } } - return new Plottable.Animator.Null(); + return new Plottable.Animators.Null(); + }; + StackedBar.prototype.x = function (x, xScale) { + if (x == null) { + return _super.prototype.x.call(this); + } + if (xScale == null) { + _super.prototype.x.call(this, x); + Plottable.Stacked.prototype.x.apply(this, [x]); + } + else { + _super.prototype.x.call(this, x, xScale); + Plottable.Stacked.prototype.x.apply(this, [x, xScale]); + } + return this; + }; + StackedBar.prototype.y = function (y, yScale) { + if (y == null) { + return _super.prototype.y.call(this); + } + if (yScale == null) { + _super.prototype.y.call(this, y); + Plottable.Stacked.prototype.y.apply(this, [y]); + } + else { + _super.prototype.y.call(this, y, yScale); + Plottable.Stacked.prototype.y.apply(this, [y, yScale]); + } + return this; }; StackedBar.prototype._generateAttrToProjector = function () { var _this = this; var attrToProjector = _super.prototype._generateAttrToProjector.call(this); var valueAttr = this._isVertical ? "y" : "x"; var keyAttr = this._isVertical ? "x" : "y"; - var primaryScale = this._isVertical ? this._yScale : this._xScale; - var primaryAccessor = this._projections[valueAttr].accessor; - var keyAccessor = this._projections[keyAttr].accessor; - var getStart = function (d, i, u, m) { return primaryScale.scale(m.offsets.get(keyAccessor(d, i, u, m))); }; - var getEnd = function (d, i, u, m) { return primaryScale.scale(+primaryAccessor(d, i, u, m) + m.offsets.get(keyAccessor(d, i, u, m))); }; - var heightF = function (d, i, u, m) { return Math.abs(getEnd(d, i, u, m) - getStart(d, i, u, m)); }; - var attrFunction = function (d, i, u, m) { return +primaryAccessor(d, i, u, m) < 0 ? getStart(d, i, u, m) : getEnd(d, i, u, m); }; - attrToProjector[valueAttr] = function (d, i, u, m) { return _this._isVertical ? attrFunction(d, i, u, m) : attrFunction(d, i, u, m) - heightF(d, i, u, m); }; + var primaryScale = this._isVertical ? this.y().scale : this.x().scale; + var primaryAccessor = this._propertyBindings.get(valueAttr).accessor; + var keyAccessor = this._propertyBindings.get(keyAttr).accessor; + var getStart = function (d, i, dataset, m) { return primaryScale.scale(m.offsets.get(keyAccessor(d, i, dataset, m))); }; + var getEnd = function (d, i, dataset, m) { return primaryScale.scale(+primaryAccessor(d, i, dataset, m) + m.offsets.get(keyAccessor(d, i, dataset, m))); }; + var heightF = function (d, i, dataset, m) { + return Math.abs(getEnd(d, i, dataset, m) - getStart(d, i, dataset, m)); + }; + var attrFunction = function (d, i, dataset, m) { return +primaryAccessor(d, i, dataset, m) < 0 ? getStart(d, i, dataset, m) : getEnd(d, i, dataset, m); }; + attrToProjector[valueAttr] = function (d, i, dataset, m) { return _this._isVertical ? attrFunction(d, i, dataset, m) : attrFunction(d, i, dataset, m) - heightF(d, i, dataset, m); }; return attrToProjector; }; StackedBar.prototype._generateDrawSteps = function () { return [{ attrToProjector: this._generateAttrToProjector(), animator: this._getAnimator("stacked-bar") }]; }; - StackedBar.prototype.project = function (attrToSet, accessor, scale) { - _super.prototype.project.call(this, attrToSet, accessor, scale); - Plot.AbstractStacked.prototype.project.apply(this, [attrToSet, accessor, scale]); - return this; - }; StackedBar.prototype._onDatasetUpdate = function () { _super.prototype._onDatasetUpdate.call(this); - Plot.AbstractStacked.prototype._onDatasetUpdate.apply(this); + Plottable.Stacked.prototype._onDatasetUpdate.apply(this); return this; }; StackedBar.prototype._getPlotMetadataForDataset = function (key) { - return Plot.AbstractStacked.prototype._getPlotMetadataForDataset.call(this, key); + return Plottable.Stacked.prototype._getPlotMetadataForDataset.call(this, key); }; - StackedBar.prototype._normalizeDatasets = function (fromX) { - return Plot.AbstractStacked.prototype._normalizeDatasets.call(this, fromX); + StackedBar.prototype._updateExtentsForProperty = function (property) { + Plottable.Stacked.prototype._updateExtentsForProperty.call(this, property); }; - //===== Stack logic from AbstractStackedPlot ===== + // ===== Stack logic from StackedPlot ===== StackedBar.prototype._updateStackOffsets = function () { - Plot.AbstractStacked.prototype._updateStackOffsets.call(this); + Plottable.Stacked.prototype._updateStackOffsets.call(this); }; StackedBar.prototype._updateStackExtents = function () { - Plot.AbstractStacked.prototype._updateStackExtents.call(this); + Plottable.Stacked.prototype._updateStackExtents.call(this); }; StackedBar.prototype._stack = function (dataArray) { - return Plot.AbstractStacked.prototype._stack.call(this, dataArray); + return Plottable.Stacked.prototype._stack.call(this, dataArray); }; StackedBar.prototype._setDatasetStackOffsets = function (positiveDataMapArray, negativeDataMapArray) { - Plot.AbstractStacked.prototype._setDatasetStackOffsets.call(this, positiveDataMapArray, negativeDataMapArray); + Plottable.Stacked.prototype._setDatasetStackOffsets.call(this, positiveDataMapArray, negativeDataMapArray); }; StackedBar.prototype._getDomainKeys = function () { - return Plot.AbstractStacked.prototype._getDomainKeys.call(this); + return Plottable.Stacked.prototype._getDomainKeys.call(this); }; StackedBar.prototype._generateDefaultMapArray = function () { - return Plot.AbstractStacked.prototype._generateDefaultMapArray.call(this); + return Plottable.Stacked.prototype._generateDefaultMapArray.call(this); }; - StackedBar.prototype._updateScaleExtents = function () { - Plot.AbstractStacked.prototype._updateScaleExtents.call(this); + StackedBar.prototype._extentsForProperty = function (attr) { + return Plottable.Stacked.prototype._extentsForProperty.call(this, attr); }; StackedBar.prototype._keyAccessor = function () { - return Plot.AbstractStacked.prototype._keyAccessor.call(this); + return Plottable.Stacked.prototype._keyAccessor.call(this); }; StackedBar.prototype._valueAccessor = function () { - return Plot.AbstractStacked.prototype._valueAccessor.call(this); + return Plottable.Stacked.prototype._valueAccessor.call(this); }; return StackedBar; - })(Plot.Bar); - Plot.StackedBar = StackedBar; - })(Plot = Plottable.Plot || (Plottable.Plot = {})); + })(Plots.Bar); + Plots.StackedBar = StackedBar; + })(Plots = Plottable.Plots || (Plottable.Plots = {})); })(Plottable || (Plottable = {})); /// var Plottable; (function (Plottable) { - var Animator; - (function (Animator) { - })(Animator = Plottable.Animator || (Plottable.Animator = {})); + var Animators; + (function (Animators) { + })(Animators = Plottable.Animators || (Plottable.Animators = {})); })(Plottable || (Plottable = {})); /// var Plottable; (function (Plottable) { - var Animator; - (function (Animator) { + var Animators; + (function (Animators) { /** * An animator implementation with no animation. The attributes are * immediately set on the selection. @@ -8917,15 +8415,15 @@ var Plottable; }; return Null; })(); - Animator.Null = Null; - })(Animator = Plottable.Animator || (Plottable.Animator = {})); + Animators.Null = Null; + })(Animators = Plottable.Animators || (Plottable.Animators = {})); })(Plottable || (Plottable = {})); /// var Plottable; (function (Plottable) { - var Animator; - (function (Animator) { + var Animators; + (function (Animators) { /** * The base animator implementation with easing, duration, and delay. * @@ -9031,8 +8529,8 @@ var Plottable; Base.DEFAULT_EASING = "exp-out"; return Base; })(); - Animator.Base = Base; - })(Animator = Plottable.Animator || (Plottable.Animator = {})); + Animators.Base = Base; + })(Animators = Plottable.Animators || (Plottable.Animators = {})); })(Plottable || (Plottable = {})); /// @@ -9044,8 +8542,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Animator; - (function (Animator) { + var Animators; + (function (Animators) { /** * The default animator implementation with easing, duration, and delay. */ @@ -9072,7 +8570,9 @@ var Plottable; } var movingAttrProjector = attrToProjector[this._getMovingAttr()]; var growingAttrProjector = attrToProjector[this._getGrowingAttr()]; - return function (d, i, u, m) { return movingAttrProjector(d, i, u, m) + growingAttrProjector(d, i, u, m); }; + return function (d, i, dataset, m) { + return movingAttrProjector(d, i, dataset, m) + growingAttrProjector(d, i, dataset, m); + }; }; Rect.prototype._getGrowingAttr = function () { return this.isVertical ? "height" : "width"; @@ -9082,9 +8582,9 @@ var Plottable; }; Rect.ANIMATED_ATTRIBUTES = ["height", "width", "x", "y", "fill"]; return Rect; - })(Animator.Base); - Animator.Rect = Rect; - })(Animator = Plottable.Animator || (Plottable.Animator = {})); + })(Animators.Base); + Animators.Rect = Rect; + })(Animators = Plottable.Animators || (Plottable.Animators = {})); })(Plottable || (Plottable = {})); /// @@ -9096,8 +8596,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Animator; - (function (Animator) { + var Animators; + (function (Animators) { /** * A child class of RectAnimator that will move the rectangle * as well as animate its growth. @@ -9119,73 +8619,55 @@ var Plottable; return d3.functor(this.startPixelValue); }; return MovingRect; - })(Animator.Rect); - Animator.MovingRect = MovingRect; - })(Animator = Plottable.Animator || (Plottable.Animator = {})); + })(Animators.Rect); + Animators.MovingRect = MovingRect; + })(Animators = Plottable.Animators || (Plottable.Animators = {})); })(Plottable || (Plottable = {})); /// -var __extends = this.__extends || function (d, b) { - for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; - function __() { this.constructor = d; } - __.prototype = b.prototype; - d.prototype = new __(); -}; var Plottable; (function (Plottable) { - var Dispatcher; - (function (Dispatcher) { - var AbstractDispatcher = (function (_super) { - __extends(AbstractDispatcher, _super); - function AbstractDispatcher() { - _super.apply(this, arguments); - this._event2Callback = {}; - this._broadcasters = []; + var Dispatcher = (function () { + function Dispatcher() { + this._event2Callback = {}; + this._callbacks = []; + this._connected = false; + } + Dispatcher.prototype._hasNoListeners = function () { + return this._callbacks.every(function (cbs) { return cbs.values().length === 0; }); + }; + Dispatcher.prototype._connect = function () { + var _this = this; + if (this._connected) { + return; + } + Object.keys(this._event2Callback).forEach(function (event) { + var callback = _this._event2Callback[event]; + document.addEventListener(event, callback); + }); + this._connected = true; + }; + Dispatcher.prototype._disconnect = function () { + var _this = this; + if (this._connected && this._hasNoListeners()) { + Object.keys(this._event2Callback).forEach(function (event) { + var callback = _this._event2Callback[event]; + document.removeEventListener(event, callback); + }); this._connected = false; } - AbstractDispatcher.prototype._hasNoListeners = function () { - return this._broadcasters.every(function (b) { return b.getListenerKeys().length === 0; }); - }; - AbstractDispatcher.prototype._connect = function () { - var _this = this; - if (!this._connected) { - Object.keys(this._event2Callback).forEach(function (event) { - var callback = _this._event2Callback[event]; - document.addEventListener(event, callback); - }); - this._connected = true; - } - }; - AbstractDispatcher.prototype._disconnect = function () { - var _this = this; - if (this._connected && this._hasNoListeners()) { - Object.keys(this._event2Callback).forEach(function (event) { - var callback = _this._event2Callback[event]; - document.removeEventListener(event, callback); - }); - this._connected = false; - } - }; - /** - * Creates a wrapped version of the callback that can be registered to a Broadcaster - */ - AbstractDispatcher.prototype._getWrappedCallback = function (callback) { - return function () { return callback(); }; - }; - AbstractDispatcher.prototype._setCallback = function (b, key, callback) { - if (callback === null) { - b.deregisterListener(key); - this._disconnect(); - } - else { - this._connect(); - b.registerListener(key, this._getWrappedCallback(callback)); - } - }; - return AbstractDispatcher; - })(Plottable.Core.PlottableObject); - Dispatcher.AbstractDispatcher = AbstractDispatcher; - })(Dispatcher = Plottable.Dispatcher || (Plottable.Dispatcher = {})); + }; + Dispatcher.prototype.setCallback = function (callbackSet, callback) { + this._connect(); + callbackSet.add(callback); + }; + Dispatcher.prototype.unsetCallback = function (callbackSet, callback) { + callbackSet.delete(callback); + this._disconnect(); + }; + return Dispatcher; + })(); + Plottable.Dispatcher = Dispatcher; })(Plottable || (Plottable = {})); /// @@ -9197,8 +8679,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Dispatcher; - (function (Dispatcher) { + var Dispatchers; + (function (Dispatchers) { var Mouse = (function (_super) { __extends(Mouse, _super); /** @@ -9210,22 +8692,22 @@ var Plottable; function Mouse(svg) { var _this = this; _super.call(this); - this.translator = Plottable._Util.ClientToSVGTranslator.getTranslator(svg); + this.translator = Plottable.Utils.ClientToSVGTranslator.getTranslator(svg); this._lastMousePosition = { x: -1, y: -1 }; - this._moveBroadcaster = new Plottable.Core.Broadcaster(this); - var processMoveCallback = function (e) { return _this._measureAndBroadcast(e, _this._moveBroadcaster); }; + this._moveCallbacks = new Plottable.Utils.CallbackSet(); + this._downCallbacks = new Plottable.Utils.CallbackSet(); + this._upCallbacks = new Plottable.Utils.CallbackSet(); + this._wheelCallbacks = new Plottable.Utils.CallbackSet(); + this._dblClickCallbacks = new Plottable.Utils.CallbackSet(); + this._callbacks = [this._moveCallbacks, this._downCallbacks, this._upCallbacks, this._wheelCallbacks, this._dblClickCallbacks]; + var processMoveCallback = function (e) { return _this._measureAndDispatch(e, _this._moveCallbacks); }; this._event2Callback["mouseover"] = processMoveCallback; this._event2Callback["mousemove"] = processMoveCallback; this._event2Callback["mouseout"] = processMoveCallback; - this._downBroadcaster = new Plottable.Core.Broadcaster(this); - this._event2Callback["mousedown"] = function (e) { return _this._measureAndBroadcast(e, _this._downBroadcaster); }; - this._upBroadcaster = new Plottable.Core.Broadcaster(this); - this._event2Callback["mouseup"] = function (e) { return _this._measureAndBroadcast(e, _this._upBroadcaster); }; - this._wheelBroadcaster = new Plottable.Core.Broadcaster(this); - this._event2Callback["wheel"] = function (e) { return _this._measureAndBroadcast(e, _this._wheelBroadcaster); }; - this._dblClickBroadcaster = new Plottable.Core.Broadcaster(this); - this._event2Callback["dblclick"] = function (e) { return _this._measureAndBroadcast(e, _this._dblClickBroadcaster); }; - this._broadcasters = [this._moveBroadcaster, this._downBroadcaster, this._upBroadcaster, this._wheelBroadcaster, this._dblClickBroadcaster]; + this._event2Callback["mousedown"] = function (e) { return _this._measureAndDispatch(e, _this._downCallbacks); }; + this._event2Callback["mouseup"] = function (e) { return _this._measureAndDispatch(e, _this._upCallbacks); }; + this._event2Callback["wheel"] = function (e) { return _this._measureAndDispatch(e, _this._wheelCallbacks); }; + this._event2Callback["dblclick"] = function (e) { return _this._measureAndDispatch(e, _this._dblClickCallbacks); }; } /** * Get a Dispatcher.Mouse for the containing elem. If one already exists @@ -9235,7 +8717,7 @@ var Plottable; * @return {Dispatcher.Mouse} A Dispatcher.Mouse */ Mouse.getDispatcher = function (elem) { - var svg = Plottable._Util.DOM.getBoundingSVG(elem); + var svg = Plottable.Utils.DOM.getBoundingSVG(elem); var dispatcher = svg[Mouse._DISPATCHER_KEY]; if (dispatcher == null) { dispatcher = new Mouse(svg); @@ -9243,93 +8725,135 @@ var Plottable; } return dispatcher; }; - Mouse.prototype._getWrappedCallback = function (callback) { - return function (md, p, e) { return callback(p, e); }; - }; /** * Registers a callback to be called whenever the mouse position changes, - * or removes the callback if `null` is passed as the callback. * - * @param {any} key The key associated with the callback. - * Key uniqueness is determined by deep equality. * @param {(p: Point) => any} callback A callback that takes the pixel position * in svg-coordinate-space. Pass `null` * to remove a callback. * @return {Dispatcher.Mouse} The calling Dispatcher.Mouse. */ - Mouse.prototype.onMouseMove = function (key, callback) { - this._setCallback(this._moveBroadcaster, key, callback); + Mouse.prototype.onMouseMove = function (callback) { + this.setCallback(this._moveCallbacks, callback); + return this; + }; + /** + * Registers the callback to be called whenever the mouse position changes, + * + * @param {(p: Point) => any} callback A callback that takes the pixel position + * in svg-coordinate-space. Pass `null` + * to remove a callback. + * @return {Dispatcher.Mouse} The calling Dispatcher.Mouse. + */ + Mouse.prototype.offMouseMove = function (callback) { + this.unsetCallback(this._moveCallbacks, callback); + return this; + }; + /** + * Registers a callback to be called whenever a mousedown occurs. + * + * @param {(p: Point) => any} callback A callback that takes the pixel position + * in svg-coordinate-space. Pass `null` + * to remove a callback. + * @return {Dispatcher.Mouse} The calling Dispatcher.Mouse. + */ + Mouse.prototype.onMouseDown = function (callback) { + this.setCallback(this._downCallbacks, callback); + return this; + }; + /** + * Registers the callback to be called whenever a mousedown occurs. + * + * @param {(p: Point) => any} callback A callback that takes the pixel position + * in svg-coordinate-space. Pass `null` + * to remove a callback. + * @return {Dispatcher.Mouse} The calling Dispatcher.Mouse. + */ + Mouse.prototype.offMouseDown = function (callback) { + this.unsetCallback(this._downCallbacks, callback); return this; }; /** - * Registers a callback to be called whenever a mousedown occurs, - * or removes the callback if `null` is passed as the callback. + * Registers a callback to be called whenever a mouseup occurs. * - * @param {any} key The key associated with the callback. - * Key uniqueness is determined by deep equality. * @param {(p: Point) => any} callback A callback that takes the pixel position * in svg-coordinate-space. Pass `null` * to remove a callback. * @return {Dispatcher.Mouse} The calling Dispatcher.Mouse. */ - Mouse.prototype.onMouseDown = function (key, callback) { - this._setCallback(this._downBroadcaster, key, callback); + Mouse.prototype.onMouseUp = function (callback) { + this.setCallback(this._upCallbacks, callback); return this; }; /** - * Registers a callback to be called whenever a mouseup occurs, - * or removes the callback if `null` is passed as the callback. + * Registers the callback to be called whenever a mouseup occurs. * - * @param {any} key The key associated with the callback. - * Key uniqueness is determined by deep equality. * @param {(p: Point) => any} callback A callback that takes the pixel position * in svg-coordinate-space. Pass `null` * to remove a callback. * @return {Dispatcher.Mouse} The calling Dispatcher.Mouse. */ - Mouse.prototype.onMouseUp = function (key, callback) { - this._setCallback(this._upBroadcaster, key, callback); + Mouse.prototype.offMouseUp = function (callback) { + this.unsetCallback(this._upCallbacks, callback); + return this; + }; + /** + * Registers a callback to be called whenever a wheel occurs. + * + * @param {MouseCallback} callback A callback that takes the pixel position + * in svg-coordinate-space. + * Pass `null` to remove a callback. + * @return {Dispatcher.Mouse} The calling Dispatcher.Mouse. + */ + Mouse.prototype.onWheel = function (callback) { + this.setCallback(this._wheelCallbacks, callback); + return this; + }; + /** + * Registers the callback to be called whenever a wheel occurs. + * + * @param {MouseCallback} callback A callback that takes the pixel position + * in svg-coordinate-space. + * Pass `null` to remove a callback. + * @return {Dispatcher.Mouse} The calling Dispatcher.Mouse. + */ + Mouse.prototype.offWheel = function (callback) { + this.unsetCallback(this._wheelCallbacks, callback); return this; }; /** - * Registers a callback to be called whenever a wheel occurs, - * or removes the callback if `null` is passed as the callback. + * Registers a callback to be called whenever a dblClick occurs. * - * @param {any} key The key associated with the callback. - * Key uniqueness is determined by deep equality. * @param {MouseCallback} callback A callback that takes the pixel position * in svg-coordinate-space. * Pass `null` to remove a callback. * @return {Dispatcher.Mouse} The calling Dispatcher.Mouse. */ - Mouse.prototype.onWheel = function (key, callback) { - this._setCallback(this._wheelBroadcaster, key, callback); + Mouse.prototype.onDblClick = function (callback) { + this.setCallback(this._dblClickCallbacks, callback); return this; }; /** - * Registers a callback to be called whenever a dblClick occurs, - * or removes the callback if `null` is passed as the callback. + * Registers the callback to be called whenever a dblClick occurs. * - * @param {any} key The key associated with the callback. - * Key uniqueness is determined by deep equality. * @param {MouseCallback} callback A callback that takes the pixel position * in svg-coordinate-space. * Pass `null` to remove a callback. * @return {Dispatcher.Mouse} The calling Dispatcher.Mouse. */ - Mouse.prototype.onDblClick = function (key, callback) { - this._setCallback(this._dblClickBroadcaster, key, callback); + Mouse.prototype.offDblClick = function (callback) { + this.unsetCallback(this._dblClickCallbacks, callback); return this; }; /** * Computes the mouse position from the given event, and if successful - * calls broadcast() on the supplied Broadcaster. + * calls all the callbacks in the provided callbackSet. */ - Mouse.prototype._measureAndBroadcast = function (e, b) { - var newMousePosition = this.translator.computePosition(e.clientX, e.clientY); + Mouse.prototype._measureAndDispatch = function (event, callbackSet) { + var newMousePosition = this.translator.computePosition(event.clientX, event.clientY); if (newMousePosition != null) { this._lastMousePosition = newMousePosition; - b.broadcast(this.getLastMousePosition(), e); + callbackSet.callCallbacks(this.getLastMousePosition(), event); } }; /** @@ -9342,9 +8866,9 @@ var Plottable; }; Mouse._DISPATCHER_KEY = "__Plottable_Dispatcher_Mouse"; return Mouse; - })(Dispatcher.AbstractDispatcher); - Dispatcher.Mouse = Mouse; - })(Dispatcher = Plottable.Dispatcher || (Plottable.Dispatcher = {})); + })(Plottable.Dispatcher); + Dispatchers.Mouse = Mouse; + })(Dispatchers = Plottable.Dispatchers || (Plottable.Dispatchers = {})); })(Plottable || (Plottable = {})); /// @@ -9356,8 +8880,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Dispatcher; - (function (Dispatcher) { + var Dispatchers; + (function (Dispatchers) { var Touch = (function (_super) { __extends(Touch, _super); /** @@ -9369,14 +8893,16 @@ var Plottable; function Touch(svg) { var _this = this; _super.call(this); - this.translator = Plottable._Util.ClientToSVGTranslator.getTranslator(svg); - this._startBroadcaster = new Plottable.Core.Broadcaster(this); - this._event2Callback["touchstart"] = function (e) { return _this._measureAndBroadcast(e, _this._startBroadcaster); }; - this._moveBroadcaster = new Plottable.Core.Broadcaster(this); - this._event2Callback["touchmove"] = function (e) { return _this._measureAndBroadcast(e, _this._moveBroadcaster); }; - this._endBroadcaster = new Plottable.Core.Broadcaster(this); - this._event2Callback["touchend"] = function (e) { return _this._measureAndBroadcast(e, _this._endBroadcaster); }; - this._broadcasters = [this._moveBroadcaster, this._startBroadcaster, this._endBroadcaster]; + this.translator = Plottable.Utils.ClientToSVGTranslator.getTranslator(svg); + this._startCallbacks = new Plottable.Utils.CallbackSet(); + this._moveCallbacks = new Plottable.Utils.CallbackSet(); + this._endCallbacks = new Plottable.Utils.CallbackSet(); + this._cancelCallbacks = new Plottable.Utils.CallbackSet(); + this._callbacks = [this._moveCallbacks, this._startCallbacks, this._endCallbacks, this._cancelCallbacks]; + this._event2Callback["touchstart"] = function (e) { return _this._measureAndDispatch(e, _this._startCallbacks); }; + this._event2Callback["touchmove"] = function (e) { return _this._measureAndDispatch(e, _this._moveCallbacks); }; + this._event2Callback["touchend"] = function (e) { return _this._measureAndDispatch(e, _this._endCallbacks); }; + this._event2Callback["touchcancel"] = function (e) { return _this._measureAndDispatch(e, _this._cancelCallbacks); }; } /** * Get a Dispatcher.Touch for the containing elem. If one already exists @@ -9386,7 +8912,7 @@ var Plottable; * @return {Dispatcher.Touch} A Dispatcher.Touch */ Touch.getDispatcher = function (elem) { - var svg = Plottable._Util.DOM.getBoundingSVG(elem); + var svg = Plottable.Utils.DOM.getBoundingSVG(elem); var dispatcher = svg[Touch._DISPATCHER_KEY]; if (dispatcher == null) { dispatcher = new Touch(svg); @@ -9394,60 +8920,108 @@ var Plottable; } return dispatcher; }; - Touch.prototype._getWrappedCallback = function (callback) { - return function (td, ids, idToPoint, e) { return callback(ids, idToPoint, e); }; + /** + * Registers a callback to be called whenever a touch starts. + * + * @param {TouchCallback} callback A callback that takes the pixel position + * in svg-coordinate-space. Pass `null` + * to remove a callback. + * @return {Dispatcher.Touch} The calling Dispatcher.Touch. + */ + Touch.prototype.onTouchStart = function (callback) { + this.setCallback(this._startCallbacks, callback); + return this; + }; + /** + * Removes the callback to be called whenever a touch starts. + * + * @param {TouchCallback} callback A callback that takes the pixel position + * in svg-coordinate-space. Pass `null` + * to remove a callback. + * @return {Dispatcher.Touch} The calling Dispatcher.Touch. + */ + Touch.prototype.offTouchStart = function (callback) { + this.unsetCallback(this._startCallbacks, callback); + return this; + }; + /** + * Registers a callback to be called whenever the touch position changes. + * + * @param {TouchCallback} callback A callback that takes the pixel position + * in svg-coordinate-space. Pass `null` + * to remove a callback. + * @return {Dispatcher.Touch} The calling Dispatcher.Touch. + */ + Touch.prototype.onTouchMove = function (callback) { + this.setCallback(this._moveCallbacks, callback); + return this; + }; + /** + * Removes the callback to be called whenever the touch position changes. + * + * @param {TouchCallback} callback A callback that takes the pixel position + * in svg-coordinate-space. Pass `null` + * to remove a callback. + * @return {Dispatcher.Touch} The calling Dispatcher.Touch. + */ + Touch.prototype.offTouchMove = function (callback) { + this.unsetCallback(this._moveCallbacks, callback); + return this; + }; + /** + * Registers a callback to be called whenever a touch ends. + * + * @param {TouchCallback} callback A callback that takes the pixel position + * in svg-coordinate-space. Pass `null` + * to remove a callback. + * @return {Dispatcher.Touch} The calling Dispatcher.Touch. + */ + Touch.prototype.onTouchEnd = function (callback) { + this.setCallback(this._endCallbacks, callback); + return this; }; /** - * Registers a callback to be called whenever a touch starts, - * or removes the callback if `null` is passed as the callback. + * Removes the callback to be called whenever a touch ends. * - * @param {any} key The key associated with the callback. - * Key uniqueness is determined by deep equality. * @param {TouchCallback} callback A callback that takes the pixel position * in svg-coordinate-space. Pass `null` * to remove a callback. * @return {Dispatcher.Touch} The calling Dispatcher.Touch. */ - Touch.prototype.onTouchStart = function (key, callback) { - this._setCallback(this._startBroadcaster, key, callback); + Touch.prototype.offTouchEnd = function (callback) { + this.unsetCallback(this._endCallbacks, callback); return this; }; /** - * Registers a callback to be called whenever the touch position changes, - * or removes the callback if `null` is passed as the callback. + * Registers a callback to be called whenever a touch is cancelled. * - * @param {any} key The key associated with the callback. - * Key uniqueness is determined by deep equality. * @param {TouchCallback} callback A callback that takes the pixel position * in svg-coordinate-space. Pass `null` * to remove a callback. * @return {Dispatcher.Touch} The calling Dispatcher.Touch. */ - Touch.prototype.onTouchMove = function (key, callback) { - this._setCallback(this._moveBroadcaster, key, callback); + Touch.prototype.onTouchCancel = function (callback) { + this.setCallback(this._cancelCallbacks, callback); return this; }; /** - * Registers a callback to be called whenever a touch ends, - * or removes the callback if `null` is passed as the callback. + * Removes the callback to be called whenever a touch is cancelled. * - * @param {any} key The key associated with the callback. - * Key uniqueness is determined by deep equality. * @param {TouchCallback} callback A callback that takes the pixel position * in svg-coordinate-space. Pass `null` * to remove a callback. * @return {Dispatcher.Touch} The calling Dispatcher.Touch. */ - Touch.prototype.onTouchEnd = function (key, callback) { - this._setCallback(this._endBroadcaster, key, callback); + Touch.prototype.offTouchCancel = function (callback) { + this.unsetCallback(this._cancelCallbacks, callback); return this; }; /** * Computes the Touch position from the given event, and if successful - * calls broadcast() on the supplied Broadcaster. + * calls all the callbacks in the provided callbackSet. */ - Touch.prototype._measureAndBroadcast = function (e, b) { - var touches = e.changedTouches; + Touch.prototype._measureAndDispatch = function (event, callbackSet) { + var touches = event.changedTouches; var touchPositions = {}; var touchIdentifiers = []; for (var i = 0; i < touches.length; i++) { @@ -9461,7 +9035,7 @@ var Plottable; } ; if (touchIdentifiers.length > 0) { - b.broadcast(touchIdentifiers, touchPositions, e); + callbackSet.callCallbacks(touchIdentifiers, touchPositions, event); } }; /** @@ -9471,9 +9045,9 @@ var Plottable; */ Touch._DISPATCHER_KEY = "__Plottable_Dispatcher_Touch"; return Touch; - })(Dispatcher.AbstractDispatcher); - Dispatcher.Touch = Touch; - })(Dispatcher = Plottable.Dispatcher || (Plottable.Dispatcher = {})); + })(Plottable.Dispatcher); + Dispatchers.Touch = Touch; + })(Dispatchers = Plottable.Dispatchers || (Plottable.Dispatchers = {})); })(Plottable || (Plottable = {})); /// @@ -9485,8 +9059,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Dispatcher; - (function (Dispatcher) { + var Dispatchers; + (function (Dispatchers) { var Key = (function (_super) { __extends(Key, _super); /** @@ -9499,8 +9073,8 @@ var Plottable; var _this = this; _super.call(this); this._event2Callback["keydown"] = function (e) { return _this._processKeydown(e); }; - this._keydownBroadcaster = new Plottable.Core.Broadcaster(this); - this._broadcasters = [this._keydownBroadcaster]; + this._keydownCallbacks = new Plottable.Utils.CallbackSet(); + this._callbacks = [this._keydownCallbacks]; } /** * Get a Dispatcher.Key. If one already exists it will be returned; @@ -9516,84 +9090,109 @@ var Plottable; } return dispatcher; }; - Key.prototype._getWrappedCallback = function (callback) { - return function (d, e) { return callback(e.keyCode, e); }; + /** + * Registers a callback to be called whenever a key is pressed. + * + * @param {KeyCallback} callback + * @return {Dispatcher.Key} The calling Dispatcher.Key. + */ + Key.prototype.onKeyDown = function (callback) { + this.setCallback(this._keydownCallbacks, callback); + return this; }; /** - * Registers a callback to be called whenever a key is pressed, - * or removes the callback if `null` is passed as the callback. + * Removes the callback to be called whenever a key is pressed. * - * @param {any} key The registration key associated with the callback. - * Registration key uniqueness is determined by deep equality. * @param {KeyCallback} callback * @return {Dispatcher.Key} The calling Dispatcher.Key. */ - Key.prototype.onKeyDown = function (key, callback) { - this._setCallback(this._keydownBroadcaster, key, callback); + Key.prototype.offKeyDown = function (callback) { + this.unsetCallback(this._keydownCallbacks, callback); return this; }; - Key.prototype._processKeydown = function (e) { - this._keydownBroadcaster.broadcast(e); + Key.prototype._processKeydown = function (event) { + this._keydownCallbacks.callCallbacks(event.keyCode, event); }; Key._DISPATCHER_KEY = "__Plottable_Dispatcher_Key"; return Key; - })(Dispatcher.AbstractDispatcher); - Dispatcher.Key = Key; - })(Dispatcher = Plottable.Dispatcher || (Plottable.Dispatcher = {})); + })(Plottable.Dispatcher); + Dispatchers.Key = Key; + })(Dispatchers = Plottable.Dispatchers || (Plottable.Dispatchers = {})); })(Plottable || (Plottable = {})); /// -var __extends = this.__extends || function (d, b) { - for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; - function __() { this.constructor = d; } - __.prototype = b.prototype; - d.prototype = new __(); -}; var Plottable; (function (Plottable) { - var Interaction; - (function (Interaction) { - var AbstractInteraction = (function (_super) { - __extends(AbstractInteraction, _super); - function AbstractInteraction() { - _super.apply(this, arguments); + var Interaction = (function () { + function Interaction() { + var _this = this; + this._anchorCallback = function (component) { return _this._anchor(component); }; + } + Interaction.prototype._anchor = function (component) { + this._isAnchored = true; + }; + Interaction.prototype._unanchor = function () { + this._isAnchored = false; + }; + /** + * Attaches this interaction to a Component. + * If the interaction was already attached to a Component, it first detaches itself from the old Component. + * + * @param {Component} component The component to which to attach the interaction. + * + * @return {Interaction} + */ + Interaction.prototype.attachTo = function (component) { + if (this._componentAttachedTo) { + this.detachFrom(this._componentAttachedTo); + } + this._componentAttachedTo = component; + component.onAnchor(this._anchorCallback); + return this; + }; + /** + * Detaches this interaction from the Component. + * This interaction can be reused. + * + * @param {Component} component The component from which to detach the interaction. + * + * @return {Interaction} + */ + Interaction.prototype.detachFrom = function (component) { + if (this._isAnchored) { + this._unanchor(); } - AbstractInteraction.prototype._anchor = function (component, hitBox) { - this._componentToListenTo = component; - this._hitBox = hitBox; - }; - // HACKHACK: After all Interactions use Dispatchers, we won't need hitboxes at all (#1757) - AbstractInteraction.prototype._requiresHitbox = function () { - return false; - }; - /** - * Translates an -coordinate-space point to Component-space coordinates. - * - * @param {Point} p A Point in -space coordinates. - * - * @return {Point} The same location in Component-space coordinates. - */ - AbstractInteraction.prototype._translateToComponentSpace = function (p) { - var origin = this._componentToListenTo.originToSVG(); - return { - x: p.x - origin.x, - y: p.y - origin.y - }; - }; - /** - * Checks whether a Component-coordinate-space Point is inside the Component. - * - * @param {Point} p A Point in Coordinate-space coordinates. - * - * @return {boolean} Whether or not the point is inside the Component. - */ - AbstractInteraction.prototype._isInsideComponent = function (p) { - return 0 <= p.x && 0 <= p.y && p.x <= this._componentToListenTo.width() && p.y <= this._componentToListenTo.height(); + this._componentAttachedTo = null; + component.offAnchor(this._anchorCallback); + return this; + }; + /** + * Translates an -coordinate-space point to Component-space coordinates. + * + * @param {Point} p A Point in -space coordinates. + * + * @return {Point} The same location in Component-space coordinates. + */ + Interaction.prototype._translateToComponentSpace = function (p) { + var origin = this._componentAttachedTo.originToSVG(); + return { + x: p.x - origin.x, + y: p.y - origin.y }; - return AbstractInteraction; - })(Plottable.Core.PlottableObject); - Interaction.AbstractInteraction = AbstractInteraction; - })(Interaction = Plottable.Interaction || (Plottable.Interaction = {})); + }; + /** + * Checks whether a Component-coordinate-space Point is inside the Component. + * + * @param {Point} p A Point in Coordinate-space coordinates. + * + * @return {boolean} Whether or not the point is inside the Component. + */ + Interaction.prototype._isInsideComponent = function (p) { + return 0 <= p.x && 0 <= p.y && p.x <= this._componentAttachedTo.width() && p.y <= this._componentAttachedTo.height(); + }; + return Interaction; + })(); + Plottable.Interaction = Interaction; })(Plottable || (Plottable = {})); /// @@ -9605,23 +9204,40 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Interaction; - (function (Interaction) { + var Interactions; + (function (Interactions) { var Click = (function (_super) { __extends(Click, _super); function Click() { + var _this = this; _super.apply(this, arguments); this._clickedDown = false; - } - Click.prototype._anchor = function (component, hitBox) { - var _this = this; - _super.prototype._anchor.call(this, component, hitBox); - this._mouseDispatcher = Plottable.Dispatcher.Mouse.getDispatcher(component.content().node()); - this._mouseDispatcher.onMouseDown("Interaction.Click" + this.getID(), function (p) { return _this._handleClickDown(p); }); - this._mouseDispatcher.onMouseUp("Interaction.Click" + this.getID(), function (p) { return _this._handleClickUp(p); }); - this._touchDispatcher = Plottable.Dispatcher.Touch.getDispatcher(component.content().node()); - this._touchDispatcher.onTouchStart("Interaction.Click" + this.getID(), function (ids, idToPoint) { return _this._handleClickDown(idToPoint[ids[0]]); }); - this._touchDispatcher.onTouchEnd("Interaction.Click" + this.getID(), function (ids, idToPoint) { return _this._handleClickUp(idToPoint[ids[0]]); }); + this._onClickCallbacks = new Plottable.Utils.CallbackSet(); + this._mouseDownCallback = function (p) { return _this._handleClickDown(p); }; + this._mouseUpCallback = function (p) { return _this._handleClickUp(p); }; + this._touchStartCallback = function (ids, idToPoint) { return _this._handleClickDown(idToPoint[ids[0]]); }; + this._touchEndCallback = function (ids, idToPoint) { return _this._handleClickUp(idToPoint[ids[0]]); }; + this._touchCancelCallback = function (ids, idToPoint) { return _this._clickedDown = false; }; + } + Click.prototype._anchor = function (component) { + _super.prototype._anchor.call(this, component); + this._mouseDispatcher = Plottable.Dispatchers.Mouse.getDispatcher(component.content().node()); + this._mouseDispatcher.onMouseDown(this._mouseDownCallback); + this._mouseDispatcher.onMouseUp(this._mouseUpCallback); + this._touchDispatcher = Plottable.Dispatchers.Touch.getDispatcher(component.content().node()); + this._touchDispatcher.onTouchStart(this._touchStartCallback); + this._touchDispatcher.onTouchEnd(this._touchEndCallback); + this._touchDispatcher.onTouchCancel(this._touchCancelCallback); + }; + Click.prototype._unanchor = function () { + _super.prototype._unanchor.call(this); + this._mouseDispatcher.offMouseDown(this._mouseDownCallback); + this._mouseDispatcher.offMouseUp(this._mouseUpCallback); + this._mouseDispatcher = null; + this._touchDispatcher.offTouchStart(this._touchStartCallback); + this._touchDispatcher.offTouchEnd(this._touchEndCallback); + this._touchDispatcher.offTouchCancel(this._touchCancelCallback); + this._touchDispatcher = null; }; Click.prototype._handleClickDown = function (p) { var translatedPoint = this._translateToComponentSpace(p); @@ -9631,22 +9247,35 @@ var Plottable; }; Click.prototype._handleClickUp = function (p) { var translatedPoint = this._translateToComponentSpace(p); - if (this._clickedDown && this._isInsideComponent(translatedPoint) && (this._clickCallback != null)) { - this._clickCallback(translatedPoint); + if (this._clickedDown && this._isInsideComponent(translatedPoint)) { + this._onClickCallbacks.callCallbacks(translatedPoint); } this._clickedDown = false; }; + /** + * Sets the callback called when the Component is clicked. + * + * @param {ClickCallback} callback The callback to set. + * @return {Interaction.Click} The calling Interaction.Click. + */ Click.prototype.onClick = function (callback) { - if (callback === undefined) { - return this._clickCallback; - } - this._clickCallback = callback; + this._onClickCallbacks.add(callback); + return this; + }; + /** + * Removes the callback from click. + * + * @param {ClickCallback} callback The callback to remove. + * @return {Interaction.Click} The calling Interaction.Click. + */ + Click.prototype.offClick = function (callback) { + this._onClickCallbacks.delete(callback); return this; }; return Click; - })(Interaction.AbstractInteraction); - Interaction.Click = Click; - })(Interaction = Plottable.Interaction || (Plottable.Interaction = {})); + })(Plottable.Interaction); + Interactions.Click = Click; + })(Interactions = Plottable.Interactions || (Plottable.Interactions = {})); })(Plottable || (Plottable = {})); /// @@ -9658,8 +9287,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Interaction; - (function (Interaction) { + var Interactions; + (function (Interactions) { var ClickState; (function (ClickState) { ClickState[ClickState["NotClicked"] = 0] = "NotClicked"; @@ -9670,20 +9299,39 @@ var Plottable; var DoubleClick = (function (_super) { __extends(DoubleClick, _super); function DoubleClick() { + var _this = this; _super.apply(this, arguments); this._clickState = 0 /* NotClicked */; this._clickedDown = false; - } - DoubleClick.prototype._anchor = function (component, hitBox) { - var _this = this; - _super.prototype._anchor.call(this, component, hitBox); - this._mouseDispatcher = Plottable.Dispatcher.Mouse.getDispatcher(component.content().node()); - this._mouseDispatcher.onMouseDown("Interaction.DoubleClick" + this.getID(), function (p) { return _this._handleClickDown(p); }); - this._mouseDispatcher.onMouseUp("Interaction.DoubleClick" + this.getID(), function (p) { return _this._handleClickUp(p); }); - this._mouseDispatcher.onDblClick("Interaction.DoubleClick" + this.getID(), function (p) { return _this._handleDblClick(); }); - this._touchDispatcher = Plottable.Dispatcher.Touch.getDispatcher(component.content().node()); - this._touchDispatcher.onTouchStart("Interaction.DoubleClick" + this.getID(), function (ids, idToPoint) { return _this._handleClickDown(idToPoint[ids[0]]); }); - this._touchDispatcher.onTouchEnd("Interaction.DoubleClick" + this.getID(), function (ids, idToPoint) { return _this._handleClickUp(idToPoint[ids[0]]); }); + this._onDoubleClickCallbacks = new Plottable.Utils.CallbackSet(); + this._mouseDownCallback = function (p) { return _this._handleClickDown(p); }; + this._mouseUpCallback = function (p) { return _this._handleClickUp(p); }; + this._dblClickCallback = function (p) { return _this._handleDblClick(); }; + this._touchStartCallback = function (ids, idToPoint) { return _this._handleClickDown(idToPoint[ids[0]]); }; + this._touchEndCallback = function (ids, idToPoint) { return _this._handleClickUp(idToPoint[ids[0]]); }; + this._touchCancelCallback = function (ids, idToPoint) { return _this._handleClickCancel(); }; + } + DoubleClick.prototype._anchor = function (component) { + _super.prototype._anchor.call(this, component); + this._mouseDispatcher = Plottable.Dispatchers.Mouse.getDispatcher(component.content().node()); + this._mouseDispatcher.onMouseDown(this._mouseDownCallback); + this._mouseDispatcher.onMouseUp(this._mouseUpCallback); + this._mouseDispatcher.onDblClick(this._dblClickCallback); + this._touchDispatcher = Plottable.Dispatchers.Touch.getDispatcher(component.content().node()); + this._touchDispatcher.onTouchStart(this._touchStartCallback); + this._touchDispatcher.onTouchEnd(this._touchEndCallback); + this._touchDispatcher.onTouchCancel(this._touchCancelCallback); + }; + DoubleClick.prototype._unanchor = function () { + _super.prototype._unanchor.call(this); + this._mouseDispatcher.offMouseDown(this._mouseDownCallback); + this._mouseDispatcher.offMouseUp(this._mouseUpCallback); + this._mouseDispatcher.offDblClick(this._dblClickCallback); + this._mouseDispatcher = null; + this._touchDispatcher.offTouchStart(this._touchStartCallback); + this._touchDispatcher.offTouchEnd(this._touchEndCallback); + this._touchDispatcher.offTouchCancel(this._touchCancelCallback); + this._touchDispatcher = null; }; DoubleClick.prototype._handleClickDown = function (p) { var translatedP = this._translateToComponentSpace(p); @@ -9707,26 +9355,41 @@ var Plottable; }; DoubleClick.prototype._handleDblClick = function () { if (this._clickState === 2 /* DoubleClicked */) { - if (this._doubleClickCallback) { - this._doubleClickCallback(this._clickedPoint); - } + this._onDoubleClickCallbacks.callCallbacks(this._clickedPoint); this._clickState = 0 /* NotClicked */; } }; + DoubleClick.prototype._handleClickCancel = function () { + this._clickState = 0 /* NotClicked */; + this._clickedDown = false; + }; DoubleClick.pointsEqual = function (p1, p2) { return p1.x === p2.x && p1.y === p2.y; }; + /** + * Sets the callback called when the Component is double-clicked. + * + * @param {ClickCallback} callback The callback to set. + * @return {Interaction.DoubleClick} The calling Interaction.DoubleClick. + */ DoubleClick.prototype.onDoubleClick = function (callback) { - if (callback === undefined) { - return this._doubleClickCallback; - } - this._doubleClickCallback = callback; + this._onDoubleClickCallbacks.add(callback); + return this; + }; + /** + * Removes the callback called when the Component is double-clicked. + * + * @param {ClickCallback} callback The callback to remove. + * @return {Interaction.DoubleClick} The calling Interaction.DoubleClick. + */ + DoubleClick.prototype.offDoubleClick = function (callback) { + this._onDoubleClickCallbacks.delete(callback); return this; }; return DoubleClick; - })(Interaction.AbstractInteraction); - Interaction.DoubleClick = DoubleClick; - })(Interaction = Plottable.Interaction || (Plottable.Interaction = {})); + })(Plottable.Interaction); + Interactions.DoubleClick = DoubleClick; + })(Interactions = Plottable.Interactions || (Plottable.Interactions = {})); })(Plottable || (Plottable = {})); /// @@ -9738,26 +9401,35 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Interaction; - (function (Interaction) { + var Interactions; + (function (Interactions) { var Key = (function (_super) { __extends(Key, _super); function Key() { - _super.apply(this, arguments); - this._keyCode2Callback = {}; - } - Key.prototype._anchor = function (component, hitBox) { var _this = this; - _super.prototype._anchor.call(this, component, hitBox); - this._positionDispatcher = Plottable.Dispatcher.Mouse.getDispatcher(this._componentToListenTo._element.node()); - this._positionDispatcher.onMouseMove("Interaction.Key" + this.getID(), function (p) { return null; }); // HACKHACK: registering a listener - this._keyDispatcher = Plottable.Dispatcher.Key.getDispatcher(); - this._keyDispatcher.onKeyDown("Interaction.Key" + this.getID(), function (keyCode) { return _this._handleKeyEvent(keyCode); }); + _super.apply(this, arguments); + this._keyCodeCallbacks = {}; + this._mouseMoveCallback = function (point) { return false; }; // HACKHACK: registering a listener + this._keyDownCallback = function (keyCode) { return _this._handleKeyEvent(keyCode); }; + } + Key.prototype._anchor = function (component) { + _super.prototype._anchor.call(this, component); + this._positionDispatcher = Plottable.Dispatchers.Mouse.getDispatcher(this._componentAttachedTo._element.node()); + this._positionDispatcher.onMouseMove(this._mouseMoveCallback); + this._keyDispatcher = Plottable.Dispatchers.Key.getDispatcher(); + this._keyDispatcher.onKeyDown(this._keyDownCallback); + }; + Key.prototype._unanchor = function () { + _super.prototype._unanchor.call(this); + this._positionDispatcher.offMouseMove(this._mouseMoveCallback); + this._positionDispatcher = null; + this._keyDispatcher.offKeyDown(this._keyDownCallback); + this._keyDispatcher = null; }; Key.prototype._handleKeyEvent = function (keyCode) { var p = this._translateToComponentSpace(this._positionDispatcher.getLastMousePosition()); - if (this._isInsideComponent(p) && this._keyCode2Callback[keyCode]) { - this._keyCode2Callback[keyCode](); + if (this._isInsideComponent(p) && this._keyCodeCallbacks[keyCode]) { + this._keyCodeCallbacks[keyCode].callCallbacks(keyCode); } }; /** @@ -9765,17 +9437,35 @@ var Plottable; * pressed and the user is moused over the Component. * * @param {number} keyCode The key code associated with the key. - * @param {() => void} callback Callback to be called. + * @param {KeyCallback} callback Callback to be set. * @returns The calling Interaction.Key. */ - Key.prototype.on = function (keyCode, callback) { - this._keyCode2Callback[keyCode] = callback; + Key.prototype.onKey = function (keyCode, callback) { + if (!this._keyCodeCallbacks[keyCode]) { + this._keyCodeCallbacks[keyCode] = new Plottable.Utils.CallbackSet(); + } + this._keyCodeCallbacks[keyCode].add(callback); + return this; + }; + /** + * Removes the callback to be called when the key with the given keyCode is + * pressed and the user is moused over the Component. + * + * @param {number} keyCode The key code associated with the key. + * @param {KeyCallback} callback Callback to be removed. + * @returns The calling Interaction.Key. + */ + Key.prototype.offKey = function (keyCode, callback) { + this._keyCodeCallbacks[keyCode].delete(callback); + if (this._keyCodeCallbacks[keyCode].values().length === 0) { + delete this._keyCodeCallbacks[keyCode]; + } return this; }; return Key; - })(Interaction.AbstractInteraction); - Interaction.Key = Key; - })(Interaction = Plottable.Interaction || (Plottable.Interaction = {})); + })(Plottable.Interaction); + Interactions.Key = Key; + })(Interactions = Plottable.Interactions || (Plottable.Interactions = {})); })(Plottable || (Plottable = {})); /// @@ -9787,66 +9477,113 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Interaction; - (function (Interaction) { + var Interactions; + (function (Interactions) { var Pointer = (function (_super) { __extends(Pointer, _super); function Pointer() { + var _this = this; _super.apply(this, arguments); this._overComponent = false; - } - Pointer.prototype._anchor = function (component, hitBox) { - var _this = this; - _super.prototype._anchor.call(this, component, hitBox); - this._mouseDispatcher = Plottable.Dispatcher.Mouse.getDispatcher(this._componentToListenTo.content().node()); - this._mouseDispatcher.onMouseMove("Interaction.Pointer" + this.getID(), function (p) { return _this._handlePointerEvent(p); }); - this._touchDispatcher = Plottable.Dispatcher.Touch.getDispatcher(this._componentToListenTo.content().node()); - this._touchDispatcher.onTouchStart("Interaction.Pointer" + this.getID(), function (ids, idToPoint) { return _this._handlePointerEvent(idToPoint[ids[0]]); }); + this._pointerEnterCallbacks = new Plottable.Utils.CallbackSet(); + this._pointerMoveCallbacks = new Plottable.Utils.CallbackSet(); + this._pointerExitCallbacks = new Plottable.Utils.CallbackSet(); + this._mouseMoveCallback = function (p) { return _this._handlePointerEvent(p); }; + this._touchStartCallback = function (ids, idToPoint) { return _this._handlePointerEvent(idToPoint[ids[0]]); }; + } + Pointer.prototype._anchor = function (component) { + _super.prototype._anchor.call(this, component); + this._mouseDispatcher = Plottable.Dispatchers.Mouse.getDispatcher(this._componentAttachedTo.content().node()); + this._mouseDispatcher.onMouseMove(this._mouseMoveCallback); + this._touchDispatcher = Plottable.Dispatchers.Touch.getDispatcher(this._componentAttachedTo.content().node()); + this._touchDispatcher.onTouchStart(this._touchStartCallback); + }; + Pointer.prototype._unanchor = function () { + _super.prototype._unanchor.call(this); + this._mouseDispatcher.offMouseMove(this._mouseMoveCallback); + this._mouseDispatcher = null; + this._touchDispatcher.offTouchStart(this._touchStartCallback); + this._touchDispatcher = null; }; Pointer.prototype._handlePointerEvent = function (p) { var translatedP = this._translateToComponentSpace(p); if (this._isInsideComponent(translatedP)) { var wasOverComponent = this._overComponent; this._overComponent = true; - if (!wasOverComponent && this._pointerEnterCallback) { - this._pointerEnterCallback(translatedP); - } - if (this._pointerMoveCallback) { - this._pointerMoveCallback(translatedP); + if (!wasOverComponent) { + this._pointerEnterCallbacks.callCallbacks(translatedP); } + this._pointerMoveCallbacks.callCallbacks(translatedP); } else if (this._overComponent) { this._overComponent = false; - if (this._pointerExitCallback) { - this._pointerExitCallback(translatedP); - } + this._pointerExitCallbacks.callCallbacks(translatedP); } }; + /** + * Sets the callback called when the pointer enters the Component. + * + * @param {PointerCallback} callback The callback to set. + * @return {Interaction.Pointer} The calling Interaction.Pointer. + */ Pointer.prototype.onPointerEnter = function (callback) { - if (callback === undefined) { - return this._pointerEnterCallback; - } - this._pointerEnterCallback = callback; + this._pointerEnterCallbacks.add(callback); + return this; + }; + /** + * Removes a callback called when the pointer enters the Component. + * + * @param {PointerCallback} callback The callback to remove. + * @return {Interaction.Pointer} The calling Interaction.Pointer. + */ + Pointer.prototype.offPointerEnter = function (callback) { + this._pointerEnterCallbacks.delete(callback); return this; }; + /** + * Sets the callback called when the pointer moves. + * + * @param {PointerCallback} callback The callback to set. + * @return {Interaction.Pointer} The calling Interaction.Pointer. + */ Pointer.prototype.onPointerMove = function (callback) { - if (callback === undefined) { - return this._pointerMoveCallback; - } - this._pointerMoveCallback = callback; + this._pointerMoveCallbacks.add(callback); + return this; + }; + /** + * Removes a callback called when the pointer moves. + * + * @param {PointerCallback} callback The callback to remove. + * @return {Interaction.Pointer} The calling Interaction.Pointer. + */ + Pointer.prototype.offPointerMove = function (callback) { + this._pointerMoveCallbacks.delete(callback); return this; }; + /** + * Sets the callback called when the pointer exits the Component. + * + * @param {PointerCallback} callback The callback to set. + * @return {Interaction.Pointer} The calling Interaction.Pointer. + */ Pointer.prototype.onPointerExit = function (callback) { - if (callback === undefined) { - return this._pointerExitCallback; - } - this._pointerExitCallback = callback; + this._pointerExitCallbacks.add(callback); + return this; + }; + /** + * Removes a callback called when the pointer exits the Component. + * + * @param {PointerCallback} callback The callback to remove. + * @return {Interaction.Pointer} The calling Interaction.Pointer. + */ + Pointer.prototype.offPointerExit = function (callback) { + this._pointerExitCallbacks.delete(callback); return this; }; return Pointer; - })(Interaction.AbstractInteraction); - Interaction.Pointer = Pointer; - })(Interaction = Plottable.Interaction || (Plottable.Interaction = {})); + })(Plottable.Interaction); + Interactions.Pointer = Pointer; + })(Interactions = Plottable.Interactions || (Plottable.Interactions = {})); })(Plottable || (Plottable = {})); /// @@ -9858,8 +9595,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Interaction; - (function (Interaction) { + var Interactions; + (function (Interactions) { var PanZoom = (function (_super) { __extends(PanZoom, _super); /** @@ -9875,56 +9612,144 @@ var Plottable; function PanZoom(xScale, yScale) { var _this = this; _super.call(this); - if (xScale) { - this._xScale = xScale; - // HACKHACK #1388: self-register for resetZoom() - this._xScale.broadcaster.registerListener("pziX" + this.getID(), function () { return _this.resetZoom(); }); - } - if (yScale) { - this._yScale = yScale; - // HACKHACK #1388: self-register for resetZoom() - this._yScale.broadcaster.registerListener("pziY" + this.getID(), function () { return _this.resetZoom(); }); - } - } - /** - * Sets the scales back to their original domains. - */ - PanZoom.prototype.resetZoom = function () { + this._wheelCallback = function (p, e) { return _this._handleWheelEvent(p, e); }; + this._touchStartCallback = function (ids, idToPoint, e) { return _this._handleTouchStart(ids, idToPoint, e); }; + this._touchMoveCallback = function (ids, idToPoint, e) { return _this._handlePinch(ids, idToPoint, e); }; + this._touchEndCallback = function (ids, idToPoint, e) { return _this._handleTouchEnd(ids, idToPoint, e); }; + this._touchCancelCallback = function (ids, idToPoint, e) { return _this._handleTouchEnd(ids, idToPoint, e); }; + this._xScale = xScale; + this._yScale = yScale; + this._dragInteraction = new Interactions.Drag(); + this._setupDragInteraction(); + this._touchIds = d3.map(); + } + PanZoom.prototype._anchor = function (component) { + _super.prototype._anchor.call(this, component); + this._dragInteraction.attachTo(component); + this._mouseDispatcher = Plottable.Dispatchers.Mouse.getDispatcher(this._componentAttachedTo.content().node()); + this._mouseDispatcher.onWheel(this._wheelCallback); + this._touchDispatcher = Plottable.Dispatchers.Touch.getDispatcher(this._componentAttachedTo.content().node()); + this._touchDispatcher.onTouchStart(this._touchStartCallback); + this._touchDispatcher.onTouchMove(this._touchMoveCallback); + this._touchDispatcher.onTouchEnd(this._touchEndCallback); + this._touchDispatcher.onTouchCancel(this._touchCancelCallback); + }; + PanZoom.prototype._unanchor = function () { + _super.prototype._unanchor.call(this); + this._mouseDispatcher.offWheel(this._wheelCallback); + this._mouseDispatcher = null; + this._touchDispatcher.offTouchStart(this._touchStartCallback); + this._touchDispatcher.offTouchMove(this._touchMoveCallback); + this._touchDispatcher.offTouchEnd(this._touchEndCallback); + this._touchDispatcher.offTouchCancel(this._touchCancelCallback); + this._touchDispatcher = null; + this._dragInteraction.detachFrom(this._componentAttachedTo); + }; + PanZoom.prototype._handleTouchStart = function (ids, idToPoint, e) { + for (var i = 0; i < ids.length && this._touchIds.size() < 2; i++) { + var id = ids[i]; + this._touchIds.set(id.toString(), this._translateToComponentSpace(idToPoint[id])); + } + }; + PanZoom.prototype._handlePinch = function (ids, idToPoint, e) { var _this = this; - // HACKHACK #254 - this._zoom = d3.behavior.zoom(); - if (this._xScale) { - this._zoom.x(this._xScale._d3Scale); - } - if (this._yScale) { - this._zoom.y(this._yScale._d3Scale); + if (this._touchIds.size() < 2) { + return; } - this._zoom.on("zoom", function () { return _this._rerenderZoomed(); }); - this._zoom(this._hitBox); + var oldCenterPoint = this.centerPoint(); + var oldCornerDistance = this.cornerDistance(); + ids.forEach(function (id) { + if (_this._touchIds.has(id.toString())) { + _this._touchIds.set(id.toString(), _this._translateToComponentSpace(idToPoint[id])); + } + }); + var newCenterPoint = this.centerPoint(); + var newCornerDistance = this.cornerDistance(); + if (this._xScale != null && newCornerDistance !== 0 && oldCornerDistance !== 0) { + PanZoom.magnifyScale(this._xScale, oldCornerDistance / newCornerDistance, oldCenterPoint.x); + PanZoom.translateScale(this._xScale, oldCenterPoint.x - newCenterPoint.x); + } + if (this._yScale != null && newCornerDistance !== 0 && oldCornerDistance !== 0) { + PanZoom.magnifyScale(this._yScale, oldCornerDistance / newCornerDistance, oldCenterPoint.y); + PanZoom.translateScale(this._yScale, oldCenterPoint.y - newCenterPoint.y); + } + }; + PanZoom.prototype.centerPoint = function () { + var points = this._touchIds.values(); + var firstTouchPoint = points[0]; + var secondTouchPoint = points[1]; + var leftX = Math.min(firstTouchPoint.x, secondTouchPoint.x); + var rightX = Math.max(firstTouchPoint.x, secondTouchPoint.x); + var topY = Math.min(firstTouchPoint.y, secondTouchPoint.y); + var bottomY = Math.max(firstTouchPoint.y, secondTouchPoint.y); + return { x: (leftX + rightX) / 2, y: (bottomY + topY) / 2 }; + }; + PanZoom.prototype.cornerDistance = function () { + var points = this._touchIds.values(); + var firstTouchPoint = points[0]; + var secondTouchPoint = points[1]; + var leftX = Math.min(firstTouchPoint.x, secondTouchPoint.x); + var rightX = Math.max(firstTouchPoint.x, secondTouchPoint.x); + var topY = Math.min(firstTouchPoint.y, secondTouchPoint.y); + var bottomY = Math.max(firstTouchPoint.y, secondTouchPoint.y); + return Math.sqrt(Math.pow(rightX - leftX, 2) + Math.pow(bottomY - topY, 2)); + }; + PanZoom.prototype._handleTouchEnd = function (ids, idToPoint, e) { + var _this = this; + ids.forEach(function (id) { + _this._touchIds.remove(id.toString()); + }); }; - PanZoom.prototype._anchor = function (component, hitBox) { - _super.prototype._anchor.call(this, component, hitBox); - this.resetZoom(); + PanZoom.magnifyScale = function (scale, magnifyAmount, centerValue) { + var magnifyTransform = function (rangeValue) { return scale.invert(centerValue - (centerValue - rangeValue) * magnifyAmount); }; + scale.domain(scale.range().map(magnifyTransform)); }; - PanZoom.prototype._requiresHitbox = function () { - return true; + PanZoom.translateScale = function (scale, translateAmount) { + var translateTransform = function (rangeValue) { return scale.invert(rangeValue + translateAmount); }; + scale.domain(scale.range().map(translateTransform)); }; - PanZoom.prototype._rerenderZoomed = function () { - // HACKHACK since the d3.zoom.x modifies d3 scales and not our TS scales, and the TS scales have the - // event listener machinery, let's grab the domain out of the d3 scale and pipe it back into the TS scale - if (this._xScale) { - var xDomain = this._xScale._d3Scale.domain(); - this._xScale.domain(xDomain); - } - if (this._yScale) { - var yDomain = this._yScale._d3Scale.domain(); - this._yScale.domain(yDomain); + PanZoom.prototype._handleWheelEvent = function (p, e) { + var translatedP = this._translateToComponentSpace(p); + if (this._isInsideComponent(translatedP)) { + e.preventDefault(); + var deltaPixelAmount = e.deltaY * (e.deltaMode ? PanZoom.PIXELS_PER_LINE : 1); + var zoomAmount = Math.pow(2, deltaPixelAmount * .002); + if (this._xScale != null) { + PanZoom.magnifyScale(this._xScale, zoomAmount, translatedP.x); + } + if (this._yScale != null) { + PanZoom.magnifyScale(this._yScale, zoomAmount, translatedP.y); + } } }; + PanZoom.prototype._setupDragInteraction = function () { + var _this = this; + this._dragInteraction.constrainToComponent(false); + var lastDragPoint; + this._dragInteraction.onDragStart(function () { return lastDragPoint = null; }); + this._dragInteraction.onDrag(function (startPoint, endPoint) { + if (_this._touchIds.size() >= 2) { + return; + } + if (_this._xScale != null) { + var dragAmountX = endPoint.x - (lastDragPoint == null ? startPoint.x : lastDragPoint.x); + PanZoom.translateScale(_this._xScale, -dragAmountX); + } + if (_this._yScale != null) { + var dragAmountY = endPoint.y - (lastDragPoint == null ? startPoint.y : lastDragPoint.y); + PanZoom.translateScale(_this._yScale, -dragAmountY); + } + lastDragPoint = endPoint; + }); + }; + /** + * The number of pixels occupied in a line. + */ + PanZoom.PIXELS_PER_LINE = 120; return PanZoom; - })(Interaction.AbstractInteraction); - Interaction.PanZoom = PanZoom; - })(Interaction = Plottable.Interaction || (Plottable.Interaction = {})); + })(Plottable.Interaction); + Interactions.PanZoom = PanZoom; + })(Interactions = Plottable.Interactions || (Plottable.Interactions = {})); })(Plottable || (Plottable = {})); /// @@ -9936,26 +9761,46 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Interaction; - (function (Interaction) { + var Interactions; + (function (Interactions) { var Drag = (function (_super) { __extends(Drag, _super); function Drag() { + var _this = this; _super.apply(this, arguments); this._dragging = false; this._constrain = true; - } - Drag.prototype._anchor = function (component, hitBox) { - var _this = this; - _super.prototype._anchor.call(this, component, hitBox); - this._mouseDispatcher = Plottable.Dispatcher.Mouse.getDispatcher(this._componentToListenTo.content().node()); - this._mouseDispatcher.onMouseDown("Interaction.Drag" + this.getID(), function (p, e) { return _this._startDrag(p, e); }); - this._mouseDispatcher.onMouseMove("Interaction.Drag" + this.getID(), function (p, e) { return _this._doDrag(p, e); }); - this._mouseDispatcher.onMouseUp("Interaction.Drag" + this.getID(), function (p, e) { return _this._endDrag(p, e); }); - this._touchDispatcher = Plottable.Dispatcher.Touch.getDispatcher(this._componentToListenTo.content().node()); - this._touchDispatcher.onTouchStart("Interaction.Drag" + this.getID(), function (ids, idToPoint, e) { return _this._startDrag(idToPoint[ids[0]], e); }); - this._touchDispatcher.onTouchMove("Interaction.Drag" + this.getID(), function (ids, idToPoint, e) { return _this._doDrag(idToPoint[ids[0]], e); }); - this._touchDispatcher.onTouchEnd("Interaction.Drag" + this.getID(), function (ids, idToPoint, e) { return _this._endDrag(idToPoint[ids[0]], e); }); + this._dragStartCallbacks = new Plottable.Utils.CallbackSet(); + this._dragCallbacks = new Plottable.Utils.CallbackSet(); + this._dragEndCallbacks = new Plottable.Utils.CallbackSet(); + this._mouseDownCallback = function (p, e) { return _this._startDrag(p, e); }; + this._mouseMoveCallback = function (p, e) { return _this._doDrag(p, e); }; + this._mouseUpCallback = function (p, e) { return _this._endDrag(p, e); }; + this._touchStartCallback = function (ids, idToPoint, e) { return _this._startDrag(idToPoint[ids[0]], e); }; + this._touchMoveCallback = function (ids, idToPoint, e) { return _this._doDrag(idToPoint[ids[0]], e); }; + this._touchEndCallback = function (ids, idToPoint, e) { return _this._endDrag(idToPoint[ids[0]], e); }; + } + Drag.prototype._anchor = function (component) { + _super.prototype._anchor.call(this, component); + this._mouseDispatcher = Plottable.Dispatchers.Mouse.getDispatcher(this._componentAttachedTo.content().node()); + this._mouseDispatcher.onMouseDown(this._mouseDownCallback); + this._mouseDispatcher.onMouseMove(this._mouseMoveCallback); + this._mouseDispatcher.onMouseUp(this._mouseUpCallback); + this._touchDispatcher = Plottable.Dispatchers.Touch.getDispatcher(this._componentAttachedTo.content().node()); + this._touchDispatcher.onTouchStart(this._touchStartCallback); + this._touchDispatcher.onTouchMove(this._touchMoveCallback); + this._touchDispatcher.onTouchEnd(this._touchEndCallback); + }; + Drag.prototype._unanchor = function () { + _super.prototype._unanchor.call(this); + this._mouseDispatcher.offMouseDown(this._mouseDownCallback); + this._mouseDispatcher.offMouseMove(this._mouseMoveCallback); + this._mouseDispatcher.offMouseUp(this._mouseUpCallback); + this._mouseDispatcher = null; + this._touchDispatcher.offTouchStart(this._touchStartCallback); + this._touchDispatcher.offTouchMove(this._touchMoveCallback); + this._touchDispatcher.offTouchEnd(this._touchEndCallback); + this._touchDispatcher = null; }; Drag.prototype._translateAndConstrain = function (p) { var translatedP = this._translateToComponentSpace(p); @@ -9963,42 +9808,34 @@ var Plottable; return translatedP; } return { - x: Plottable._Util.Methods.clamp(translatedP.x, 0, this._componentToListenTo.width()), - y: Plottable._Util.Methods.clamp(translatedP.y, 0, this._componentToListenTo.height()) + x: Plottable.Utils.Methods.clamp(translatedP.x, 0, this._componentAttachedTo.width()), + y: Plottable.Utils.Methods.clamp(translatedP.y, 0, this._componentAttachedTo.height()) }; }; - Drag.prototype._startDrag = function (p, e) { - if (e instanceof MouseEvent && e.button !== 0) { + Drag.prototype._startDrag = function (point, event) { + if (event instanceof MouseEvent && event.button !== 0) { return; } - var translatedP = this._translateToComponentSpace(p); + var translatedP = this._translateToComponentSpace(point); if (this._isInsideComponent(translatedP)) { - e.preventDefault(); + event.preventDefault(); this._dragging = true; this._dragOrigin = translatedP; - if (this._dragStartCallback) { - this._dragStartCallback(this._dragOrigin); - } + this._dragStartCallbacks.callCallbacks(this._dragOrigin); } }; - Drag.prototype._doDrag = function (p, e) { + Drag.prototype._doDrag = function (point, event) { if (this._dragging) { - if (this._dragCallback) { - var constrainedP = this._translateAndConstrain(p); - this._dragCallback(this._dragOrigin, constrainedP); - } + this._dragCallbacks.callCallbacks(this._dragOrigin, this._translateAndConstrain(point)); } }; - Drag.prototype._endDrag = function (p, e) { - if (e instanceof MouseEvent && e.button !== 0) { + Drag.prototype._endDrag = function (point, event) { + if (event instanceof MouseEvent && event.button !== 0) { return; } if (this._dragging) { this._dragging = false; - if (this._dragEndCallback) { - var constrainedP = this._translateAndConstrain(p); - this._dragEndCallback(this._dragOrigin, constrainedP); - } + this._dragEndCallbacks.callCallbacks(this._dragOrigin, this._translateAndConstrain(point)); } }; Drag.prototype.constrainToComponent = function (constrain) { @@ -10008,178 +9845,70 @@ var Plottable; this._constrain = constrain; return this; }; - Drag.prototype.onDragStart = function (cb) { - if (cb === undefined) { - return this._dragStartCallback; - } - else { - this._dragStartCallback = cb; - return this; - } - }; - Drag.prototype.onDrag = function (cb) { - if (cb === undefined) { - return this._dragCallback; - } - else { - this._dragCallback = cb; - return this; - } - }; - Drag.prototype.onDragEnd = function (cb) { - if (cb === undefined) { - return this._dragEndCallback; - } - else { - this._dragEndCallback = cb; - return this; - } - }; - return Drag; - })(Interaction.AbstractInteraction); - Interaction.Drag = Drag; - })(Interaction = Plottable.Interaction || (Plottable.Interaction = {})); -})(Plottable || (Plottable = {})); - -/// -var __extends = this.__extends || function (d, b) { - for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; - function __() { this.constructor = d; } - __.prototype = b.prototype; - d.prototype = new __(); -}; -var Plottable; -(function (Plottable) { - var Interaction; - (function (Interaction) { - var Hover = (function (_super) { - __extends(Hover, _super); - function Hover() { - _super.call(this); - this._overComponent = false; - this._currentHoverData = { - data: null, - pixelPositions: null, - selection: null - }; - if (!Hover.warned) { - Hover.warned = true; - Plottable._Util.Methods.warn("Interaction.Hover is deprecated; use Interaction.Pointer in conjunction with getClosestPlotData() instead."); - } - } - Hover.prototype._anchor = function (component, hitBox) { - var _this = this; - _super.prototype._anchor.call(this, component, hitBox); - this._mouseDispatcher = Plottable.Dispatcher.Mouse.getDispatcher(this._componentToListenTo._element.node()); - this._mouseDispatcher.onMouseMove("hover" + this.getID(), function (p) { return _this._handlePointerEvent(p); }); - this._touchDispatcher = Plottable.Dispatcher.Touch.getDispatcher(this._componentToListenTo._element.node()); - this._touchDispatcher.onTouchStart("hover" + this.getID(), function (ids, idToPoint) { return _this._handlePointerEvent(idToPoint[ids[0]]); }); - }; - Hover.prototype._handlePointerEvent = function (p) { - p = this._translateToComponentSpace(p); - if (this._isInsideComponent(p)) { - if (!this._overComponent) { - this._componentToListenTo._hoverOverComponent(p); - } - this.handleHoverOver(p); - this._overComponent = true; - } - else { - this._componentToListenTo._hoverOutComponent(p); - this.safeHoverOut(this._currentHoverData); - this._currentHoverData = { - data: null, - pixelPositions: null, - selection: null - }; - this._overComponent = false; - } - }; /** - * Returns a HoverData consisting of all data and selections in a but not in b. + * Sets the callback to be called when dragging starts. + * + * @param {DragCallback} callback The callback to be called. Takes in a Point in pixels. + * @returns {Drag} The calling Interactions.Drag. */ - Hover.diffHoverData = function (a, b) { - if (a.data == null || b.data == null) { - return a; - } - var diffData = []; - var diffPoints = []; - var diffElements = []; - a.data.forEach(function (d, i) { - if (b.data.indexOf(d) === -1) { - diffData.push(d); - diffPoints.push(a.pixelPositions[i]); - diffElements.push(a.selection[0][i]); - } - }); - if (diffData.length === 0) { - return { - data: null, - pixelPositions: null, - selection: null - }; - } - return { - data: diffData, - pixelPositions: diffPoints, - selection: d3.selectAll(diffElements) - }; - }; - Hover.prototype.handleHoverOver = function (p) { - var lastHoverData = this._currentHoverData; - var newHoverData = this._componentToListenTo._doHover(p); - this._currentHoverData = newHoverData; - var outData = Hover.diffHoverData(lastHoverData, newHoverData); - this.safeHoverOut(outData); - var overData = Hover.diffHoverData(newHoverData, lastHoverData); - this.safeHoverOver(overData); + Drag.prototype.onDragStart = function (callback) { + this._dragStartCallbacks.add(callback); + return this; }; - Hover.prototype.safeHoverOut = function (outData) { - if (this._hoverOutCallback && outData.data) { - this._hoverOutCallback(outData); - } + /** + * Removes the callback to be called when dragging starts. + * + * @param {DragCallback} callback The callback to be removed. + * @returns {Drag} The calling Interactions.Drag. + */ + Drag.prototype.offDragStart = function (callback) { + this._dragStartCallbacks.delete(callback); + return this; }; - Hover.prototype.safeHoverOver = function (overData) { - if (this._hoverOverCallback && overData.data) { - this._hoverOverCallback(overData); - } + /** + * Adds a callback to be called during dragging. + * + * @param {DragCallback} callback The callback to be called. Takes in Points in pixels. + * @returns {Drag} The calling Interactions.Drag. + */ + Drag.prototype.onDrag = function (callback) { + this._dragCallbacks.add(callback); + return this; }; /** - * Attaches an callback to be called when the user mouses over an element. + * Removes a callback to be called during dragging. * - * @param {(hoverData: HoverData) => any} callback The callback to be called. - * The callback will be passed data for newly hovered-over elements. - * @return {Interaction.Hover} The calling Interaction.Hover. + * @param {DragCallback} callback The callback to be removed. + * @returns {Drag} The calling Interactions.Drag. */ - Hover.prototype.onHoverOver = function (callback) { - this._hoverOverCallback = callback; + Drag.prototype.offDrag = function (callback) { + this._dragCallbacks.delete(callback); return this; }; /** - * Attaches a callback to be called when the user mouses off of an element. + * Adds a callback to be called when the dragging ends. * - * @param {(hoverData: HoverData) => any} callback The callback to be called. - * The callback will be passed data from the hovered-out elements. - * @return {Interaction.Hover} The calling Interaction.Hover. + * @param {DragCallback} callback The callback to be called. Takes in Points in pixels. + * @returns {Drag} The calling Interactions.Drag. */ - Hover.prototype.onHoverOut = function (callback) { - this._hoverOutCallback = callback; + Drag.prototype.onDragEnd = function (callback) { + this._dragEndCallbacks.add(callback); return this; }; /** - * Retrieves the HoverData associated with the elements the user is currently hovering over. + * Removes a callback to be called when the dragging ends. * - * @return {HoverData} The data and selection corresponding to the elements - * the user is currently hovering over. + * @param {DragCallback} callback The callback to be removed + * @returns {Drag} The calling Interactions.Drag. */ - Hover.prototype.getCurrentHoverData = function () { - return this._currentHoverData; + Drag.prototype.offDragEnd = function (callback) { + this._dragEndCallbacks.delete(callback); + return this; }; - Hover.warned = false; - return Hover; - })(Interaction.AbstractInteraction); - Interaction.Hover = Hover; - })(Interaction = Plottable.Interaction || (Plottable.Interaction = {})); + return Drag; + })(Plottable.Interaction); + Interactions.Drag = Drag; + })(Interactions = Plottable.Interactions || (Plottable.Interactions = {})); })(Plottable || (Plottable = {})); /// @@ -10191,8 +9920,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Component; - (function (Component) { + var Components; + (function (Components) { var DragBoxLayer = (function (_super) { __extends(DragBoxLayer, _super); function DragBoxLayer() { @@ -10206,11 +9935,14 @@ var Plottable; * user's cursor from changing outside the DragBoxLayer, where they * wouldn't be able to grab the edges or corners for resizing. */ - this.clipPathEnabled = true; + this._clipPathEnabled = true; this.classed("drag-box-layer", true); - this._dragInteraction = new Plottable.Interaction.Drag(); + this._dragInteraction = new Plottable.Interactions.Drag(); this._setUpCallbacks(); - this.registerInteraction(this._dragInteraction); + this._dragInteraction.attachTo(this); + this._dragStartCallbacks = new Plottable.Utils.CallbackSet(); + this._dragCallbacks = new Plottable.Utils.CallbackSet(); + this._dragEndCallbacks = new Plottable.Utils.CallbackSet(); } DragBoxLayer.prototype._setUpCallbacks = function () { var _this = this; @@ -10235,9 +9967,7 @@ var Plottable; // copy points so changes to topLeft and bottomRight don't mutate bounds topLeft = { x: bounds.topLeft.x, y: bounds.topLeft.y }; bottomRight = { x: bounds.bottomRight.x, y: bounds.bottomRight.y }; - if (_this._dragStartCallback) { - _this._dragStartCallback(bounds); - } + _this._dragStartCallbacks.callCallbacks(bounds); }); this._dragInteraction.onDrag(function (s, e) { if (startedNewBox) { @@ -10262,17 +9992,13 @@ var Plottable; topLeft: topLeft, bottomRight: bottomRight }); - if (_this._dragCallback) { - _this._dragCallback(_this.bounds()); - } + _this._dragCallbacks.callCallbacks(_this.bounds()); }); this._dragInteraction.onDragEnd(function (s, e) { if (startedNewBox && s.x === e.x && s.y === e.y) { _this.boxVisible(false); } - if (_this._dragEndCallback) { - _this._dragEndCallback(_this.bounds()); - } + _this._dragEndCallbacks.callCallbacks(_this.bounds()); }); }; DragBoxLayer.prototype._setup = function () { @@ -10323,8 +10049,8 @@ var Plottable; } return edges; }; - DragBoxLayer.prototype._doRender = function () { - _super.prototype._doRender.call(this); + DragBoxLayer.prototype.renderImmediately = function () { + _super.prototype.renderImmediately.call(this); if (this.boxVisible()) { var bounds = this.bounds(); var t = bounds.topLeft.y; @@ -10365,6 +10091,7 @@ var Plottable; this._detectionCornerBL.attr({ cx: l, cy: b, r: this._detectionRadius }); this._detectionCornerBR.attr({ cx: r, cy: b, r: this._detectionRadius }); } + return this; } }; DragBoxLayer.prototype.detectionRadius = function (r) { @@ -10375,7 +10102,7 @@ var Plottable; throw new Error("detection radius cannot be negative."); } this._detectionRadius = r; - this._render(); + this.render(); return this; }; DragBoxLayer.prototype.resizable = function (canResize) { @@ -10391,37 +10118,70 @@ var Plottable; this.classed("x-resizable", canResize); this.classed("y-resizable", canResize); }; - DragBoxLayer.prototype.onDragStart = function (cb) { - if (cb === undefined) { - return this._dragStartCallback; - } - else { - this._dragStartCallback = cb; - return this; - } + /** + * Sets the callback to be called when dragging starts. + * + * @param {DragBoxCallback} callback The callback to be called. Passed the current Bounds in pixels. + * @returns {DragBoxLayer} The calling DragBoxLayer. + */ + DragBoxLayer.prototype.onDragStart = function (callback) { + this._dragStartCallbacks.add(callback); + return this; }; - DragBoxLayer.prototype.onDrag = function (cb) { - if (cb === undefined) { - return this._dragCallback; - } - else { - this._dragCallback = cb; - return this; - } + /** + * Removes a callback to be called when dragging starts. + * + * @param {DragBoxCallback} callback The callback to be removed. + * @returns {DragBoxLayer} The calling DragBoxLayer. + */ + DragBoxLayer.prototype.offDragStart = function (callback) { + this._dragStartCallbacks.delete(callback); + return this; }; - DragBoxLayer.prototype.onDragEnd = function (cb) { - if (cb === undefined) { - return this._dragEndCallback; - } - else { - this._dragEndCallback = cb; - return this; - } + /** + * Sets a callback to be called during dragging. + * + * @param {DragBoxCallback} callback The callback to be called. Passed the current Bounds in pixels. + * @returns {DragBoxLayer} The calling DragBoxLayer. + */ + DragBoxLayer.prototype.onDrag = function (callback) { + this._dragCallbacks.add(callback); + return this; + }; + /** + * Removes a callback to be called during dragging. + * + * @param {DragBoxCallback} callback The callback to be removed. + * @returns {DragBoxLayer} The calling DragBoxLayer. + */ + DragBoxLayer.prototype.offDrag = function (callback) { + this._dragCallbacks.delete(callback); + return this; + }; + /** + * Sets a callback to be called when the dragging ends. + * + * @param {DragBoxCallback} callback The callback to be called. Passed the current Bounds in pixels. + * @returns {DragBoxLayer} The calling DragBoxLayer. + */ + DragBoxLayer.prototype.onDragEnd = function (callback) { + this._dragEndCallbacks.add(callback); + return this; + }; + /** + * Removes a callback to be called when the dragging ends. + * + * @param {DragBoxCallback} callback The callback to be removed. + * @returns {DragBoxLayer} The calling DragBoxLayer. + */ + DragBoxLayer.prototype.offDragEnd = function (callback) { + this._dragEndCallbacks.delete(callback); + return this; }; return DragBoxLayer; - })(Component.SelectionBoxLayer); - Component.DragBoxLayer = DragBoxLayer; - })(Component = Plottable.Component || (Plottable.Component = {})); + })(Components.SelectionBoxLayer); + Components.DragBoxLayer = DragBoxLayer; + })(Components = Plottable.Components || (Plottable.Components = {})); })(Plottable || (Plottable = {})); /// @@ -10433,8 +10193,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Component; - (function (Component) { + var Components; + (function (Components) { var XDragBoxLayer = (function (_super) { __extends(XDragBoxLayer, _super); function XDragBoxLayer() { @@ -10442,9 +10202,10 @@ var Plottable; this.classed("x-drag-box-layer", true); this._hasCorners = false; } - XDragBoxLayer.prototype._computeLayout = function (offeredXOrigin, offeredYOrigin, availableWidth, availableHeight) { - _super.prototype._computeLayout.call(this, offeredXOrigin, offeredYOrigin, availableWidth, availableHeight); + XDragBoxLayer.prototype.computeLayout = function (origin, availableWidth, availableHeight) { + _super.prototype.computeLayout.call(this, origin, availableWidth, availableHeight); this.bounds(this.bounds()); // set correct bounds when width/height changes + return this; }; XDragBoxLayer.prototype._setBounds = function (newBounds) { _super.prototype._setBounds.call(this, { @@ -10456,9 +10217,9 @@ var Plottable; this.classed("x-resizable", canResize); }; return XDragBoxLayer; - })(Component.DragBoxLayer); - Component.XDragBoxLayer = XDragBoxLayer; - })(Component = Plottable.Component || (Plottable.Component = {})); + })(Components.DragBoxLayer); + Components.XDragBoxLayer = XDragBoxLayer; + })(Components = Plottable.Components || (Plottable.Components = {})); })(Plottable || (Plottable = {})); /// @@ -10470,8 +10231,8 @@ var __extends = this.__extends || function (d, b) { }; var Plottable; (function (Plottable) { - var Component; - (function (Component) { + var Components; + (function (Components) { var YDragBoxLayer = (function (_super) { __extends(YDragBoxLayer, _super); function YDragBoxLayer() { @@ -10479,9 +10240,10 @@ var Plottable; this.classed("y-drag-box-layer", true); this._hasCorners = false; } - YDragBoxLayer.prototype._computeLayout = function (offeredXOrigin, offeredYOrigin, availableWidth, availableHeight) { - _super.prototype._computeLayout.call(this, offeredXOrigin, offeredYOrigin, availableWidth, availableHeight); + YDragBoxLayer.prototype.computeLayout = function (origin, availableWidth, availableHeight) { + _super.prototype.computeLayout.call(this, origin, availableWidth, availableHeight); this.bounds(this.bounds()); // set correct bounds when width/height changes + return this; }; YDragBoxLayer.prototype._setBounds = function (newBounds) { _super.prototype._setBounds.call(this, { @@ -10493,9 +10255,9 @@ var Plottable; this.classed("y-resizable", canResize); }; return YDragBoxLayer; - })(Component.DragBoxLayer); - Component.YDragBoxLayer = YDragBoxLayer; - })(Component = Plottable.Component || (Plottable.Component = {})); + })(Components.DragBoxLayer); + Components.YDragBoxLayer = YDragBoxLayer; + })(Components = Plottable.Components || (Plottable.Components = {})); })(Plottable || (Plottable = {})); /*! diff --git a/plottable.min.js b/plottable.min.js index 7b5eea01b0..096158aefb 100644 --- a/plottable.min.js +++ b/plottable.min.js @@ -1,9 +1,7 @@ -var Plottable;!function(a){var b;!function(b){var c;!function(b){function c(a,b,c){return Math.min(b,c)<=a&&a<=Math.max(b,c)}function d(a,b,c){return Math.min(Math.max(b,a),c)}function e(b){a.Config.SHOW_WARNINGS&&null!=window.console&&(null!=window.console.warn?console.warn(b):null!=window.console.log&&console.log(b))}function f(a,b){if(a.length!==b.length)throw new Error("attempted to add arrays of unequal length");return a.map(function(c,d){return a[d]+b[d]})}function g(a,b){var c=d3.set();return a.forEach(function(a){b.has(a)&&c.add(a)}),c}function h(a){return"function"==typeof a?a:"string"==typeof a&&"#"!==a[0]?function(b,c,d){return b[a]}:function(b,c,d){return a}}function i(a,b){var c=d3.set();return a.forEach(function(a){return c.add(a)}),b.forEach(function(a){return c.add(a)}),c}function j(a,b){var c=d3.map();return a.forEach(function(a,d){c.set(a,b(a,d))}),c}function k(a){var b=d3.set(),c=[];return a.forEach(function(a){b.has(a)||(b.add(a),c.push(a))}),c}function l(a,b){for(var c=[],d=0;b>d;d++)c[d]="function"==typeof a?a(d):a;return c}function m(a){return Array.prototype.concat.apply([],a)}function n(a,b){if(null==a||null==b)return a===b;if(a.length!==b.length)return!1;for(var c=0;cf;++f)e[f]=a+c*f;return e}function v(a,b){for(var c=[],d=2;db?"0"+c:c});if(4===d.length&&"00"===d[3])return null;var e="#"+d.join("");return a.classed(b,!1),e}function x(a,b){var c=d3.hsl(a).brighter(b);return c.rgb().toString()}function y(a,b){return Math.pow(b.y-a.y,2)+Math.pow(b.x-a.x,2)}function z(){var a=window.navigator.userAgent;return a.indexOf("MSIE ")>-1||a.indexOf("Trident/")>-1}function A(a,b,c,d){void 0===d&&(d=.5);var e=B(a),f=B(b);return c.x+c.width>=e.min-d&&c.x<=e.max+d&&c.y+c.height>=f.min-d&&c.y<=f.max+d}function B(a){if("number"==typeof a)return{min:a,max:a};if(a instanceof Object&&"min"in a&&"max"in a)return a;throw new Error("input '"+a+"' can't be parsed as an Extent")}b.inRange=c,b.clamp=d,b.warn=e,b.addArrays=f,b.intersection=g,b.accessorize=h,b.union=i,b.populateMap=j,b.uniq=k,b.createFilledArray=l,b.flatten=m,b.arrayEq=n,b.objEq=o,b.max=p,b.min=q,b.isNaN=r,b.isValidNumber=s,b.copyMap=t,b.range=u,b.setTimeout=v,b.colorTest=w,b.lightenColor=x,b.distanceSquared=y,b.isIE=z,b.intersectsBBox=A,b.parseExtent=B}(c=b.Methods||(b.Methods={}))}(b=a._Util||(a._Util={}))}(Plottable||(Plottable={}));var Plottable;!function(a){var b;!function(a){var b;!function(a){function b(a,b,c){for(var d=0,e=b.length;e>d;){var f=d+e>>>1,g=null==c?b[f]:c(b[f]);a>g?d=f+1:e=f}return d}a.sortedIndex=b}(b=a.OpenSource||(a.OpenSource={}))}(b=a._Util||(a._Util={}))}(Plottable||(Plottable={}));var Plottable;!function(a){var b;!function(a){var b=function(){function a(){this._keyValuePairs=[]}return a.prototype.set=function(a,b){if(a!==a)throw new Error("NaN may not be used as a key to the StrictEqualityAssociativeArray");for(var c=0;cb.right?!1:a.bottomb.bottom?!1:!0}function k(a,b){return Math.floor(b.left)<=Math.ceil(a.left)&&Math.floor(b.top)<=Math.ceil(a.top)&&Math.floor(a.right)<=Math.ceil(b.right)&&Math.floor(a.bottom)<=Math.ceil(b.bottom)}function l(a){var b=a.ownerSVGElement;return null!=b?b:"svg"===a.nodeName.toLowerCase()?a:null}a.getBBox=b,a.POLYFILL_TIMEOUT_MSEC=1e3/60,a.requestAnimationFramePolyfill=c,a.isSelectionRemovedFromSVG=e,a.getElementWidth=f,a.getElementHeight=g,a.getSVGPixelWidth=h,a.translate=i,a.boxesOverlap=j,a.boxIsInside=k,a.getBoundingSVG=l}(b=a.DOM||(a.DOM={}))}(b=a._Util||(a._Util={}))}(Plottable||(Plottable={}));var Plottable;!function(a){var b;!function(a){var b;!function(a){function b(a){var b=d3.rgb(a),c=function(a){return a/=255,.03928>=a?a/12.92:Math.pow((a+.055)/1.055,2.4)},d=c(b.r),e=c(b.g),f=c(b.b);return.2126*d+.7152*e+.0722*f}function c(a,c){var d=b(a)+.05,e=b(c)+.05;return d>e?d/e:e/d}a.contrast=c}(b=a.Color||(a.Color={}))}(b=a._Util||(a._Util={}))}(Plottable||(Plottable={}));var Plottable;!function(a){a.MILLISECONDS_IN_ONE_DAY=864e5;var b;!function(b){function c(a,c,d){void 0===a&&(a=2),void 0===c&&(c="$"),void 0===d&&(d=!0);var e=b.fixed(a);return function(a){var b=e(Math.abs(a));return""!==b&&(d?b=c+b:b+=c,0>a&&(b="-"+b)),b}}function d(a){return void 0===a&&(a=3),l(a),function(b){return b.toFixed(a)}}function e(a){return void 0===a&&(a=3),l(a),function(b){if("number"==typeof b){var c=Math.pow(10,a);return String(Math.round(b*c)/c)}return String(b)}}function f(){return function(a){return String(a)}}function g(a){void 0===a&&(a=0);var c=b.fixed(a);return function(a){var b=100*a,d=a.toString(),e=Math.pow(10,d.length-(d.indexOf(".")+1));return b=parseInt((b*e).toString(),10)/e,c(b)+"%"}}function h(a){return void 0===a&&(a=3),l(a),function(b){return d3.format("."+a+"s")(b)}}function i(){var a=8,b={};return b[0]={format:".%L",filter:function(a){return 0!==a.getMilliseconds()}},b[1]={format:":%S",filter:function(a){return 0!==a.getSeconds()}},b[2]={format:"%I:%M",filter:function(a){return 0!==a.getMinutes()}},b[3]={format:"%I %p",filter:function(a){return 0!==a.getHours()}},b[4]={format:"%a %d",filter:function(a){return 0!==a.getDay()&&1!==a.getDate()}},b[5]={format:"%b %d",filter:function(a){return 1!==a.getDate()}},b[6]={format:"%b",filter:function(a){return 0!==a.getMonth()}},b[7]={format:"%Y",filter:function(){return!0}},function(c){for(var d=0;a>d;d++)if(b[d].filter(c))return d3.time.format(b[d].format)(c)}}function j(a){return d3.time.format(a)}function k(b,c,d){return void 0===b&&(b=0),void 0===c&&(c=a.MILLISECONDS_IN_ONE_DAY),void 0===d&&(d=""),function(a){var e=Math.round((a.valueOf()-b)/c);return e.toString()+d}}function l(a){if(0>a||a>20)throw new RangeError("Formatter precision must be between 0 and 20")}b.currency=c,b.fixed=d,b.general=e,b.identity=f,b.percentage=g,b.siSuffix=h,b.multiTime=i,b.time=j,b.relativeDate=k}(b=a.Formatters||(a.Formatters={}))}(Plottable||(Plottable={}));var Plottable;!function(a){var b;!function(a){function b(){return function(a){return d3.svg.symbol().type("circle").size(Math.PI*Math.pow(a/2,2))()}}function c(){return function(a){return d3.svg.symbol().type("square").size(Math.pow(a,2))()}}function d(){return function(a){return d3.svg.symbol().type("cross").size(5/9*Math.pow(a,2))()}}function e(){return function(a){return d3.svg.symbol().type("diamond").size(Math.tan(Math.PI/6)*Math.pow(a,2)/2)()}}function f(){return function(a){return d3.svg.symbol().type("triangle-up").size(Math.sqrt(3)*Math.pow(a/2,2))()}}function g(){return function(a){return d3.svg.symbol().type("triangle-down").size(Math.sqrt(3)*Math.pow(a/2,2))()}}a.circle=b,a.square=c,a.cross=d,a.diamond=e,a.triangleUp=f,a.triangleDown=g}(b=a.SymbolFactories||(a.SymbolFactories={}))}(Plottable||(Plottable={}));var Plottable;!function(a){var b;!function(a){var b=function(){function b(a){this._svg=a,this._measureRect=document.createElementNS(a.namespaceURI,"rect"),this._measureRect.setAttribute("class","measure-rect"),this._measureRect.setAttribute("style","opacity: 0; visibility: hidden;"),this._measureRect.setAttribute("width","1"),this._measureRect.setAttribute("height","1"),this._svg.appendChild(this._measureRect)}return b.getTranslator=function(c){var d=a.DOM.getBoundingSVG(c),e=d[b._TRANSLATOR_KEY];return null==e&&(e=new b(d),d[b._TRANSLATOR_KEY]=e),e},b.prototype.computePosition=function(a,b){this._measureRect.setAttribute("x","0"),this._measureRect.setAttribute("y","0");var c=this._measureRect.getBoundingClientRect(),d={x:c.left,y:c.top},e=100;this._measureRect.setAttribute("x",String(e)),this._measureRect.setAttribute("y",String(e)),c=this._measureRect.getBoundingClientRect();var f={x:c.left,y:c.top};if(d.x===f.x||d.y===f.y)return null;var g=(f.x-d.x)/e,h=(f.y-d.y)/e;this._measureRect.setAttribute("x",String((a-d.x)/g)),this._measureRect.setAttribute("y",String((b-d.y)/h)),c=this._measureRect.getBoundingClientRect();var i={x:c.left,y:c.top},j={x:(i.x-d.x)/g,y:(i.y-d.y)/h};return j},b._TRANSLATOR_KEY="__Plottable_ClientToSVGTranslator",b}();a.ClientToSVGTranslator=b}(b=a._Util||(a._Util={}))}(Plottable||(Plottable={}));var Plottable;!function(a){var b;!function(a){a.SHOW_WARNINGS=!0}(b=a.Config||(a.Config={}))}(Plottable||(Plottable={}));var Plottable;!function(a){a.version="0.54.0"}(Plottable||(Plottable={}));var Plottable;!function(a){var b;!function(a){var b=function(){function a(){}return a.CORAL_RED="#fd373e",a.INDIGO="#5279c7",a.ROBINS_EGG_BLUE="#06cccc",a.FERN="#63c261",a.BURNING_ORANGE="#ff7939",a.ROYAL_HEATH="#962565",a.CONIFER="#99ce50",a.CERISE_RED="#db2e65",a.BRIGHT_SUN="#fad419",a.JACARTA="#2c2b6f",a.PLOTTABLE_COLORS=[a.INDIGO,a.CORAL_RED,a.FERN,a.BRIGHT_SUN,a.JACARTA,a.BURNING_ORANGE,a.CERISE_RED,a.CONIFER,a.ROYAL_HEATH,a.ROBINS_EGG_BLUE],a}();a.Colors=b}(b=a.Core||(a.Core={}))}(Plottable||(Plottable={}));var Plottable;!function(a){var b;!function(a){var b=function(){function a(){this._plottableID=a._nextID++}return a.prototype.getID=function(){return this._plottableID},a._nextID=0,a}();a.PlottableObject=b}(b=a.Core||(a.Core={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(c){b.call(this),this._key2callback=new a._Util.StrictEqualityAssociativeArray,this._listenable=c}return __extends(c,b),c.prototype.registerListener=function(a,b){return this._key2callback.set(a,b),this},c.prototype.broadcast=function(){for(var a=this,b=[],c=0;c0){var f=d.valueOf();return d instanceof Date?[f-b._ONE_DAY,f+b._ONE_DAY]:[f-b._PADDING_FOR_IDENTICAL_DOMAIN,f+b._PADDING_FOR_IDENTICAL_DOMAIN]}var g=a.domain();if(g[0].valueOf()===g[1].valueOf())return c;var h=this._padProportion/2,i=a.invert(a.scale(d)-(a.scale(e)-a.scale(d))*h),j=a.invert(a.scale(e)+(a.scale(e)-a.scale(d))*h),k=this._paddingExceptions.values().concat(this._unregisteredPaddingExceptions.values()),l=d3.set(k);return l.has(d)&&(i=d),l.has(e)&&(j=e),[i,j]},b.prototype._niceDomain=function(a,b){return this._doNice?a._niceDomain(b,this._niceCount):b},b.prototype._includeDomain=function(a){var b=this._includedValues.values().concat(this._unregisteredIncludedValues.values());return b.reduce(function(a,b){return[Math.min(a[0],b),Math.max(a[1],b)]},a)},b._PADDING_FOR_IDENTICAL_DOMAIN=1,b._ONE_DAY=864e5,b}();a.Domainer=b}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(c){b.call(this),this._autoDomainAutomatically=!0,this._rendererAttrID2Extent={},this._typeCoercer=function(a){return a},this._domainModificationInProgress=!1,this._d3Scale=c,this.broadcaster=new a.Core.Broadcaster(this)}return __extends(c,b),c.prototype._getAllExtents=function(){return d3.values(this._rendererAttrID2Extent)},c.prototype._getExtent=function(){return[]},c.prototype.autoDomain=function(){return this._autoDomainAutomatically=!0,this._setDomain(this._getExtent()),this},c.prototype._autoDomainIfAutomaticMode=function(){this._autoDomainAutomatically&&this.autoDomain()},c.prototype.scale=function(a){return this._d3Scale(a)},c.prototype.domain=function(a){return null==a?this._getDomain():(this._autoDomainAutomatically=!1,this._setDomain(a),this)},c.prototype._getDomain=function(){return this._d3Scale.domain()},c.prototype._setDomain=function(a){this._domainModificationInProgress||(this._domainModificationInProgress=!0,this._d3Scale.domain(a),this.broadcaster.broadcast(),this._domainModificationInProgress=!1)},c.prototype.range=function(a){return null==a?this._d3Scale.range():(this._d3Scale.range(a),this)},c.prototype.copy=function(){return new c(this._d3Scale.copy())},c.prototype._updateExtent=function(a,b,c){return this._rendererAttrID2Extent[a+b]=c,this._autoDomainIfAutomaticMode(),this},c.prototype._removeExtent=function(a,b){return delete this._rendererAttrID2Extent[a+b],this._autoDomainIfAutomaticMode(),this},c}(a.Core.PlottableObject);b.AbstractScale=c}(b=a.Scale||(a.Scale={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(c){b.call(this,c),this._numTicks=10,this._PADDING_FOR_IDENTICAL_DOMAIN=1,this._userSetDomainer=!1,this._domainer=new a.Domainer,this._typeCoercer=function(a){return+a},this._tickGenerator=function(a){return a.getDefaultTicks()}}return __extends(c,b),c.prototype._getExtent=function(){return this._domainer.computeDomain(this._getAllExtents(),this)},c.prototype.invert=function(a){return this._d3Scale.invert(a)},c.prototype.copy=function(){return new c(this._d3Scale.copy())},c.prototype.domain=function(a){return b.prototype.domain.call(this,a)},c.prototype._setDomain=function(c){var d=function(a){return a!==a||a===1/0||a===-(1/0)};return d(c[0])||d(c[1])?void a._Util.Methods.warn("Warning: QuantitativeScales cannot take NaN or Infinity as a domain value. Ignoring."):void b.prototype._setDomain.call(this,c)},c.prototype.interpolate=function(a){return null==a?this._d3Scale.interpolate():(this._d3Scale.interpolate(a),this)},c.prototype.rangeRound=function(a){return this._d3Scale.rangeRound(a),this},c.prototype.getDefaultTicks=function(){return this._d3Scale.ticks(this.numTicks())},c.prototype.clamp=function(a){return null==a?this._d3Scale.clamp():(this._d3Scale.clamp(a),this)},c.prototype.ticks=function(){return this._tickGenerator(this)},c.prototype.numTicks=function(a){return null==a?this._numTicks:(this._numTicks=a,this)},c.prototype._niceDomain=function(a,b){return this._d3Scale.copy().domain(a).nice(b).domain()},c.prototype.domainer=function(a){return null==a?this._domainer:(this._domainer=a,this._userSetDomainer=!0,this._autoDomainIfAutomaticMode(),this)},c.prototype._defaultExtent=function(){return[0,1]},c.prototype.tickGenerator=function(a){return null==a?this._tickGenerator:(this._tickGenerator=a,this)},c}(b.AbstractScale);b.AbstractQuantitative=c}(b=a.Scale||(a.Scale={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(a){var b=function(a){function b(b){a.call(this,null==b?d3.scale.linear():b)}return __extends(b,a),b.prototype.copy=function(){return new b(this._d3Scale.copy())},b}(a.AbstractQuantitative);a.Linear=b}(b=a.Scale||(a.Scale={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(d){b.call(this,null==d?d3.scale.log():d),c.warned||(c.warned=!0,a._Util.Methods.warn("Plottable.Scale.Log is deprecated. If possible, use Plottable.Scale.ModifiedLog instead."))}return __extends(c,b),c.prototype.copy=function(){return new c(this._d3Scale.copy())},c.prototype._defaultExtent=function(){return[1,10]},c.warned=!1,c}(b.AbstractQuantitative);b.Log=c}(b=a.Scale||(a.Scale={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(a){if(void 0===a&&(a=10),b.call(this,d3.scale.linear()),this._showIntermediateTicks=!1,this.base=a,this.pivot=this.base,this.untransformedDomain=this._defaultExtent(),this.numTicks(10),1>=a)throw new Error("ModifiedLogScale: The base must be > 1")}return __extends(c,b),c.prototype.adjustedLog=function(a){var b=0>a?-1:1;return a*=b,aa?-1:1;return a*=b,a=Math.pow(this.base,a),a=d&&e>=a}),m=j.concat(l).concat(k);return m.length<=1&&(m=d3.scale.linear().domain([d,e]).ticks(b)),m},c.prototype.logTicks=function(b,c){var d=this,e=this.howManyTicks(b,c);if(0===e)return[];var f=Math.floor(Math.log(b)/Math.log(this.base)),g=Math.ceil(Math.log(c)/Math.log(this.base)),h=d3.range(g,f,-Math.ceil((g-f)/e)),i=this._showIntermediateTicks?Math.floor(e/h.length):1,j=d3.range(this.base,1,-(this.base-1)/i).map(Math.floor),k=a._Util.Methods.uniq(j),l=h.map(function(a){return k.map(function(b){return Math.pow(d.base,a-1)*b})}),m=a._Util.Methods.flatten(l),n=m.filter(function(a){return a>=b&&c>=a}),o=n.sort(function(a,b){return a-b});return o},c.prototype.howManyTicks=function(b,c){var d=this.adjustedLog(a._Util.Methods.min(this.untransformedDomain,0)),e=this.adjustedLog(a._Util.Methods.max(this.untransformedDomain,0)),f=this.adjustedLog(b),g=this.adjustedLog(c),h=(g-f)/(e-d),i=Math.ceil(h*this.numTicks());return i},c.prototype.copy=function(){return new c(this.base)},c.prototype._niceDomain=function(a,b){return a},c.prototype.showIntermediateTicks=function(a){return null==a?this._showIntermediateTicks:void(this._showIntermediateTicks=a)},c}(b.AbstractQuantitative);b.ModifiedLog=c}(b=a.Scale||(a.Scale={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(a){void 0===a&&(a=d3.scale.ordinal()),b.call(this,a),this._range=[0,1],this._typeCoercer=function(a){return null!=a&&a.toString?a.toString():a};var d=.3;this._innerPadding=c._convertToPlottableInnerPadding(d),this._outerPadding=c._convertToPlottableOuterPadding(.5,d)}return __extends(c,b),c.prototype._getExtent=function(){var b=this._getAllExtents();return a._Util.Methods.uniq(a._Util.Methods.flatten(b))},c.prototype.domain=function(a){return b.prototype.domain.call(this,a)},c.prototype._setDomain=function(a){b.prototype._setDomain.call(this,a),this.range(this.range())},c.prototype.range=function(a){if(null==a)return this._range;this._range=a;var b=1-1/(1+this.innerPadding()),c=this.outerPadding()/(1+this.innerPadding());return this._d3Scale.rangeBands(a,b,c),this},c._convertToPlottableInnerPadding=function(a){return 1/(1-a)-1},c._convertToPlottableOuterPadding=function(a,b){return a/(1-b)},c.prototype.rangeBand=function(){return this._d3Scale.rangeBand()},c.prototype.stepWidth=function(){return this.rangeBand()*(1+this.innerPadding())},c.prototype.innerPadding=function(a){return null==a?this._innerPadding:(this._innerPadding=a,this.range(this.range()),this.broadcaster.broadcast(),this)},c.prototype.outerPadding=function(a){return null==a?this._outerPadding:(this._outerPadding=a,this.range(this.range()),this.broadcaster.broadcast(),this)},c.prototype.copy=function(){return new c(this._d3Scale.copy())},c.prototype.scale=function(a){return b.prototype.scale.call(this,a)+this.rangeBand()/2},c}(b.AbstractScale);b.Category=c}(b=a.Scale||(a.Scale={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(a){var d;switch(a){case null:case void 0:d=d3.scale.ordinal().range(c._getPlottableColors());break;case"Category10":case"category10":case"10":d=d3.scale.category10();break;case"Category20":case"category20":case"20":d=d3.scale.category20();break;case"Category20b":case"category20b":case"20b":d=d3.scale.category20b();break;case"Category20c":case"category20c":case"20c":d=d3.scale.category20c();break;default:throw new Error("Unsupported ColorScale type")}b.call(this,d)}return __extends(c,b),c.prototype._getExtent=function(){var b=this._getAllExtents(),c=[];return b.forEach(function(a){c=c.concat(a)}),a._Util.Methods.uniq(c)},c._getPlottableColors=function(){for(var b,c=[],d=d3.select("body").append("plottable-color-tester"),e=a._Util.Methods.colorTest(d,""),f=0;null!==(b=a._Util.Methods.colorTest(d,"plottable-colors-"+f))&&f0&&this._setDomain([a._Util.Methods.min(b,function(a){return a[0]},0),a._Util.Methods.max(b,function(a){return a[1]},0)]),this},c._COLOR_SCALES={reds:["#FFFFFF","#FFF6E1","#FEF4C0","#FED976","#FEB24C","#FD8D3C","#FC4E2A","#E31A1C","#B10026"],blues:["#FFFFFF","#CCFFFF","#A5FFFD","#85F7FB","#6ED3EF","#55A7E0","#417FD0","#2545D3","#0B02E1"],posneg:["#0B02E1","#2545D3","#417FD0","#55A7E0","#6ED3EF","#85F7FB","#A5FFFD","#CCFFFF","#FFFFFF","#FFF6E1","#FEF4C0","#FED976","#FEB24C","#FD8D3C","#FC4E2A","#E31A1C","#B10026"]},c}(b.AbstractScale);b.InterpolatedColor=c}(b=a.Scale||(a.Scale={}))}(Plottable||(Plottable={}));var Plottable;!function(a){var b;!function(a){var b=function(){function a(a){var b=this;if(this._rescaleInProgress=!1,null==a)throw new Error("ScaleDomainCoordinator requires scales to coordinate");this._scales=a,this._scales.forEach(function(a){return a.broadcaster.registerListener(b,function(a){return b.rescale(a)})})}return a.prototype.rescale=function(a){if(!this._rescaleInProgress){this._rescaleInProgress=!0;var b=a.domain();this._scales.forEach(function(a){return a.domain(b)}),this._rescaleInProgress=!1}},a}();a.ScaleDomainCoordinator=b}(b=a._Util||(a._Util={}))}(Plottable||(Plottable={}));var Plottable;!function(a){var b;!function(b){var c;!function(b){function c(b){if(0>=b)throw new Error("interval must be positive number");return function(c){var d=c.domain(),e=Math.min(d[0],d[1]),f=Math.max(d[0],d[1]),g=Math.ceil(e/b)*b,h=Math.floor((f-g)/b)+1,i=e%b===0?[]:[e],j=a._Util.Methods.range(0,h).map(function(a){return g+a*b}),k=f%b===0?[]:[f];return i.concat(j).concat(k)}}function d(){return function(a){var b=a.getDefaultTicks();return b.filter(function(a,c){return a%1===0||0===c||c===b.length-1})}}b.intervalTickGenerator=c,b.integerTickGenerator=d}(c=b.TickGenerators||(b.TickGenerators={}))}(b=a.Scale||(a.Scale={}))}(Plottable||(Plottable={}));var Plottable;!function(a){var b;!function(b){var c=function(){function b(a){this.key=a}return b.prototype.setClass=function(a){return this._className=a,this},b.prototype.setup=function(a){this._renderArea=a},b.prototype.remove=function(){null!=this._getRenderArea()&&this._getRenderArea().remove()},b.prototype._enterData=function(a){},b.prototype._drawStep=function(a){},b.prototype._numberOfAnimationIterations=function(a){return a.length},b.prototype._applyMetadata=function(a,b,c){var d={};return d3.keys(a).forEach(function(e){d[e]=function(d,f){return a[e](d,f,b,c)}}),d},b.prototype._prepareDrawSteps=function(a){},b.prototype._prepareData=function(a,b){return a},b.prototype.draw=function(b,c,d,e){var f=this,g=c.map(function(b){var c=f._applyMetadata(b.attrToProjector,d,e);return f._attrToProjector=a._Util.Methods.copyMap(c),{attrToProjector:c,animator:b.animator}}),h=this._prepareData(b,g);this._prepareDrawSteps(g),this._enterData(h);var i=this._numberOfAnimationIterations(h),j=0;return g.forEach(function(b,c){a._Util.Methods.setTimeout(function(){return f._drawStep(b)},j),j+=b.animator.getTiming(i)}),j},b.prototype._getRenderArea=function(){return this._renderArea},b.prototype._getSelector=function(){return""},b.prototype._getPixelPoint=function(a,b){return null},b.prototype._getSelection=function(a){var b=this._getRenderArea().selectAll(this._getSelector());return d3.select(b[0][a])},b}();b.AbstractDrawer=c}(b=a._Drawer||(a._Drawer={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(){b.apply(this,arguments)}return __extends(c,b),c.prototype._enterData=function(a){b.prototype._enterData.call(this,a),this._pathSelection.datum(a)},c.prototype.setup=function(a){this._pathSelection=a.append("path").classed(c.LINE_CLASS,!0).style({fill:"none","vector-effect":"non-scaling-stroke"}),b.prototype.setup.call(this,a)},c.prototype._createLine=function(a,b,c){return c||(c=function(a,b){return!0}),d3.svg.line().x(a).y(b).defined(c)},c.prototype._numberOfAnimationIterations=function(a){return 1},c.prototype._drawStep=function(d){var e=(b.prototype._drawStep.call(this,d),a._Util.Methods.copyMap(d.attrToProjector)),f=e.defined,g=e.x,h=e.y;delete e.x,delete e.y,e.defined&&delete e.defined,e.d=this._createLine(g,h,f),e.fill&&this._pathSelection.attr("fill",e.fill),e["class"]&&(this._pathSelection.attr("class",e["class"]),this._pathSelection.classed(c.LINE_CLASS,!0),delete e["class"]),d.animator.animate(this._pathSelection,e)},c.prototype._getSelector=function(){return"."+c.LINE_CLASS},c.prototype._getPixelPoint=function(a,b){return{x:this._attrToProjector.x(a,b),y:this._attrToProjector.y(a,b)}},c.prototype._getSelection=function(a){return this._getRenderArea().select(this._getSelector())},c.LINE_CLASS="line",c}(b.AbstractDrawer);b.Line=c}(b=a._Drawer||(a._Drawer={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(c){function d(){c.apply(this,arguments),this._drawLine=!0}return __extends(d,c),d.prototype._enterData=function(a){this._drawLine?c.prototype._enterData.call(this,a):b.AbstractDrawer.prototype._enterData.call(this,a),this._areaSelection.datum(a)},d.prototype.drawLine=function(a){return this._drawLine=a,this},d.prototype.setup=function(a){this._areaSelection=a.append("path").classed(d.AREA_CLASS,!0).style({stroke:"none"}),this._drawLine?c.prototype.setup.call(this,a):b.AbstractDrawer.prototype.setup.call(this,a)},d.prototype._createArea=function(a,b,c,d){return d||(d=function(){return!0}),d3.svg.area().x(a).y0(b).y1(c).defined(d)},d.prototype._drawStep=function(e){this._drawLine?c.prototype._drawStep.call(this,e):b.AbstractDrawer.prototype._drawStep.call(this,e);var f=a._Util.Methods.copyMap(e.attrToProjector),g=f.x,h=f.y0,i=f.y,j=f.defined;delete f.x,delete f.y0,delete f.y,f.defined&&delete f.defined,f.d=this._createArea(g,h,i,j),f.fill&&this._areaSelection.attr("fill",f.fill),f["class"]&&(this._areaSelection.attr("class",f["class"]),this._areaSelection.classed(d.AREA_CLASS,!0),delete f["class"]),e.animator.animate(this._areaSelection,f)},d.prototype._getSelector=function(){return"path"},d.AREA_CLASS="area",d}(b.Line);b.Area=c}(b=a._Drawer||(a._Drawer={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(a){var b=function(a){function b(){a.apply(this,arguments)}return __extends(b,a),b.prototype.svgElement=function(a){return this._svgElement=a,this},b.prototype._getDrawSelection=function(){return this._getRenderArea().selectAll(this._svgElement)},b.prototype._drawStep=function(b){a.prototype._drawStep.call(this,b);var c=this._getDrawSelection();b.attrToProjector.fill&&c.attr("fill",b.attrToProjector.fill),b.animator.animate(c,b.attrToProjector)},b.prototype._enterData=function(b){a.prototype._enterData.call(this,b);var c=this._getDrawSelection().data(b);c.enter().append(this._svgElement),null!=this._className&&c.classed(this._className,!0),c.exit().remove()},b.prototype._filterDefinedData=function(a,b){return b?a.filter(b):a},b.prototype._prepareDrawSteps=function(b){a.prototype._prepareDrawSteps.call(this,b),b.forEach(function(a){a.attrToProjector.defined&&delete a.attrToProjector.defined})},b.prototype._prepareData=function(b,c){var d=this;return c.reduce(function(a,b){return d._filterDefinedData(a,b.attrToProjector.defined)},a.prototype._prepareData.call(this,b,c))},b.prototype._getSelector=function(){return this._svgElement},b}(a.AbstractDrawer);a.Element=b}(b=a._Drawer||(a._Drawer={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=5,d=5,e=function(b){function e(a,c){b.call(this,a),this._labelsTooWide=!1,this.svgElement("rect"),this._isVertical=c}return __extends(e,b),e.prototype.setup=function(a){b.prototype.setup.call(this,a.append("g").classed("bar-area",!0)),this._textArea=a.append("g").classed("bar-label-text-area",!0),this._measurer=new SVGTypewriter.Measurers.CacheCharacterMeasurer(this._textArea),this._writer=new SVGTypewriter.Writers.Writer(this._measurer)},e.prototype.removeLabels=function(){this._textArea.selectAll("g").remove()},e.prototype._getIfLabelsTooWide=function(){return this._labelsTooWide},e.prototype.drawText=function(b,e,f,g){var h=this,i=b.map(function(b,i){var j=e.label(b,i,f,g).toString(),k=e.width(b,i,f,g),l=e.height(b,i,f,g),m=e.x(b,i,f,g),n=e.y(b,i,f,g),o=e.positive(b,i,f,g),p=h._measurer.measure(j),q=e.fill(b,i,f,g),r=1.6*a._Util.Color.contrast("white",q)v;if(p.height<=l&&p.width<=k){var x=Math.min((s-t)/2,c);o||(x=-1*x),h._isVertical?n+=x:m+=x;var y=h._textArea.append("g").attr("transform","translate("+m+","+n+")"),z=r?"dark-label":"light-label";y.classed(z,!0);var A,B;h._isVertical?(A="center",B=o?"top":"bottom"):(A=o?"left":"right",B="center");var C={selection:y,xAlign:A,yAlign:B,textRotation:0};h._writer.write(j,k,l,C)}return w});this._labelsTooWide=i.some(function(a){return a})},e.prototype._getPixelPoint=function(a,b){var c=this._attrToProjector.x(a,b),d=this._attrToProjector.y(a,b),e=this._attrToProjector.width(a,b),f=this._attrToProjector.height(a,b),g=this._isVertical?c+e/2:c+e,h=this._isVertical?d:d+f/2;return{x:g,y:h}},e.prototype.draw=function(c,d,e,f){var g=d[0].attrToProjector,h=a._Util.Methods.isValidNumber;return c=c.filter(function(a,b){return h(g.x(a,null,e,f))&&h(g.y(a,null,e,f))&&h(g.width(a,null,e,f))&&h(g.height(a,null,e,f))}),b.prototype.draw.call(this,c,d,e,f)},e}(b.Element);b.Rect=e}(b=a._Drawer||(a._Drawer={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(a){b.call(this,a),this._svgElement="path"}return __extends(c,b),c.prototype._createArc=function(a,b){return d3.svg.arc().innerRadius(a).outerRadius(b)},c.prototype.retargetProjectors=function(a){var b={};return d3.entries(a).forEach(function(a){b[a.key]=function(b,c){return a.value(b.data,c)}}),b},c.prototype._drawStep=function(c){var d=a._Util.Methods.copyMap(c.attrToProjector);d=this.retargetProjectors(d),this._attrToProjector=this.retargetProjectors(this._attrToProjector);var e=d["inner-radius"],f=d["outer-radius"];return delete d["inner-radius"],delete d["outer-radius"],d.d=this._createArc(e,f),b.prototype._drawStep.call(this,{attrToProjector:d,animator:c.animator})},c.prototype.draw=function(c,d,e,f){var g=function(a,b){return d[0].attrToProjector.value(a,b,e,f)};c=c.filter(function(b){return a._Util.Methods.isValidNumber(+g(b,null))});var h=d3.layout.pie().sort(null).value(g)(c);return d.forEach(function(a){return delete a.attrToProjector.value}),h.forEach(function(b){b.value<0&&a._Util.Methods.warn("Negative values will not render correctly in a pie chart.")}),b.prototype.draw.call(this,h,d,e,f)},c.prototype._getPixelPoint=function(a,b){var c=this._attrToProjector["inner-radius"],d=this._attrToProjector["outer-radius"],e=(c(a,b)+d(a,b))/2,f=+this._getSelection(b).datum().startAngle,g=+this._getSelection(b).datum().endAngle,h=(f+g)/2;return{x:e*Math.sin(h),y:-e*Math.cos(h)}},c}(b.Element);b.Arc=c}(b=a._Drawer||(a._Drawer={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(a){b.call(this,a),this._svgElement="path",this._className="symbol"}return __extends(c,b),c.prototype._drawStep=function(c){var d=c.attrToProjector;this._attrToProjector=a._Util.Methods.copyMap(c.attrToProjector);var e=d.x,f=d.y;delete d.x,delete d.y;var g=d.size;delete d.size,d.transform=function(a,b){return"translate("+e(a,b)+","+f(a,b)+")"};var h=d.symbol;delete d.symbol,d.d=d.d||function(a,b){return h(a,b)(g(a,b))},b.prototype._drawStep.call(this,c)},c.prototype._getPixelPoint=function(a,b){return{x:this._attrToProjector.x(a,b),y:this._attrToProjector.y(a,b)}},c}(b.Element);b.Symbol=c}(b=a._Drawer||(a._Drawer={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(){b.apply(this,arguments),this.clipPathEnabled=!1,this._xAlignProportion=0,this._yAlignProportion=0,this._fixedHeightFlag=!1,this._fixedWidthFlag=!1,this._isSetup=!1,this._isAnchored=!1,this._interactionsToRegister=[],this._boxes=[],this._isTopLevelComponent=!1,this._xOffset=0,this._yOffset=0,this._cssClasses=["component"],this._removed=!1,this._usedLastLayout=!1}return __extends(c,b),c.prototype._anchor=function(a){if(this._removed)throw new Error("Can't reuse remove()-ed components!");"svg"===a.node().nodeName.toLowerCase()&&(this._rootSVG=a,this._rootSVG.classed("plottable",!0),this._rootSVG.style("overflow","visible"),this._isTopLevelComponent=!0),null!=this._element?a.node().appendChild(this._element.node()):(this._element=a.append("g"),this._setup()),this._isAnchored=!0},c.prototype._setup=function(){var a=this;this._isSetup||(this._cssClasses.forEach(function(b){a._element.classed(b,!0)}),this._cssClasses=null,this._backgroundContainer=this._element.append("g").classed("background-container",!0),this._addBox("background-fill",this._backgroundContainer),this._content=this._element.append("g").classed("content",!0),this._foregroundContainer=this._element.append("g").classed("foreground-container",!0),this._boxContainer=this._element.append("g").classed("box-container",!0),this.clipPathEnabled&&this._generateClipPath(),this._boundingBox=this._addBox("bounding-box"),this._interactionsToRegister.forEach(function(b){return a.registerInteraction(b)}),this._interactionsToRegister=null,this._isSetup=!0)},c.prototype._requestedSpace=function(a,b){return{width:0,height:0,wantsWidth:!1,wantsHeight:!1}},c.prototype._computeLayout=function(b,c,d,e){var f=this;if(null==b||null==c||null==d||null==e){if(null==this._element)throw new Error("anchor must be called before computeLayout");if(!this._isTopLevelComponent)throw new Error("null arguments cannot be passed to _computeLayout() on a non-root node");b=0,c=0,null==this._rootSVG.attr("width")&&this._rootSVG.attr("width","100%"),null==this._rootSVG.attr("height")&&this._rootSVG.attr("height","100%");var g=this._rootSVG.node();d=a._Util.DOM.getElementWidth(g),e=a._Util.DOM.getElementHeight(g)}var h=this._getSize(d,e);this._width=h.width,this._height=h.height,this._xOrigin=b+this._xOffset+(d-this.width())*this._xAlignProportion,this._yOrigin=c+this._yOffset+(e-this.height())*this._yAlignProportion,this._element.attr("transform","translate("+this._xOrigin+","+this._yOrigin+")"),this._boxes.forEach(function(a){return a.attr("width",f.width()).attr("height",f.height())})},c.prototype._getSize=function(a,b){var c=this._requestedSpace(a,b);return{width:this._isFixedWidth()?Math.min(a,c.width):a,height:this._isFixedHeight()?Math.min(b,c.height):b}},c.prototype._render=function(){this._isAnchored&&this._isSetup&&this.width()>=0&&this.height()>=0&&a.Core.RenderController.registerToRender(this)},c.prototype._scheduleComputeLayout=function(){this._isAnchored&&this._isSetup&&a.Core.RenderController.registerToComputeLayout(this)},c.prototype._doRender=function(){},c.prototype._useLastCalculatedLayout=function(a){return null==a?this._usedLastLayout:(this._usedLastLayout=a,this)},c.prototype._invalidateLayout=function(){this._useLastCalculatedLayout(!1),this._isAnchored&&this._isSetup&&(this._isTopLevelComponent?this._scheduleComputeLayout():this._parent()._invalidateLayout())},c.prototype.renderTo=function(b){if(this.detach(),null!=b){var c;if(c="string"==typeof b?d3.select(b):b,!c.node()||"svg"!==c.node().nodeName.toLowerCase())throw new Error("Plottable requires a valid SVG to renderTo");this._anchor(c)}if(null==this._element)throw new Error("If a component has never been rendered before, then renderTo must be given a node to render to, or a D3.Selection, or a selector string");return this._computeLayout(),this._render(),a.Core.RenderController.flush(),this},c.prototype.redraw=function(){return this._invalidateLayout(),this},c.prototype.xAlign=function(a){if(a=a.toLowerCase(),"left"===a)this._xAlignProportion=0;else if("center"===a)this._xAlignProportion=.5;else{if("right"!==a)throw new Error("Unsupported alignment");this._xAlignProportion=1}return this._invalidateLayout(),this},c.prototype.yAlign=function(a){if(a=a.toLowerCase(),"top"===a)this._yAlignProportion=0;else if("center"===a)this._yAlignProportion=.5;else{if("bottom"!==a)throw new Error("Unsupported alignment");this._yAlignProportion=1}return this._invalidateLayout(),this},c.prototype.xOffset=function(a){return this._xOffset=a,this._invalidateLayout(),this},c.prototype.yOffset=function(a){return this._yOffset=a,this._invalidateLayout(),this},c.prototype._addBox=function(a,b){if(null==this._element)throw new Error("Adding boxes before anchoring is currently disallowed");b=null==b?this._boxContainer:b;var c=b.append("rect");return null!=a&&c.classed(a,!0),this._boxes.push(c),null!=this.width()&&null!=this.height()&&c.attr("width",this.width()).attr("height",this.height()),c},c.prototype._generateClipPath=function(){var a=/MSIE [5-9]/.test(navigator.userAgent)?"":document.location.href;a=a.split("#")[0],this._element.attr("clip-path",'url("'+a+"#clipPath"+this.getID()+'")');var b=this._boxContainer.append("clipPath").attr("id","clipPath"+this.getID());this._addBox("clip-rect",b)},c.prototype.registerInteraction=function(a){return this._element?(!this._hitBox&&a._requiresHitbox()&&(this._hitBox=this._addBox("hit-box"),this._hitBox.style("fill","#ffffff").style("opacity",0)),a._anchor(this,this._hitBox)):this._interactionsToRegister.push(a),this},c.prototype.classed=function(a,b){if(null==b)return null==a?!1:null==this._element?-1!==this._cssClasses.indexOf(a):this._element.classed(a);if(null==a)return this;if(null==this._element){var c=this._cssClasses.indexOf(a);b&&-1===c?this._cssClasses.push(a):b||-1===c||this._cssClasses.splice(c,1)}else this._element.classed(a,b);return this},c.prototype._isFixedWidth=function(){return this._fixedWidthFlag},c.prototype._isFixedHeight=function(){return this._fixedHeightFlag},c.prototype._merge=function(b,c){var d;if(a.Component.Group.prototype.isPrototypeOf(b))return d=b,d._addComponent(this,c),d;var e=c?[this,b]:[b,this];return d=new a.Component.Group(e)},c.prototype.above=function(a){return this._merge(a,!1)},c.prototype.below=function(a){return this._merge(a,!0)},c.prototype.detach=function(){this._isAnchored&&this._element.remove();var a=this._parent();return null!=a&&a._removeComponent(this),this._isAnchored=!1,this._parentElement=null,this},c.prototype._parent=function(a){return void 0===a?this._parentElement:(this.detach(),void(this._parentElement=a))},c.prototype.remove=function(){this._removed=!0,this.detach()},c.prototype.width=function(){return this._width},c.prototype.height=function(){return this._height},c.prototype.origin=function(){return{x:this._xOrigin,y:this._yOrigin}},c.prototype.originToSVG=function(){for(var a=this.origin(),b=this._parent();null!=b;){var c=b.origin();a.x+=c.x,a.y+=c.y,b=b._parent()}return a},c.prototype.foreground=function(){return this._foregroundContainer},c.prototype.content=function(){return this._content},c.prototype.background=function(){return this._backgroundContainer},c.prototype.hitBox=function(){return this._hitBox},c}(a.Core.PlottableObject);b.AbstractComponent=c}(b=a.Component||(a.Component={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(a){var b=function(a){function b(){a.apply(this,arguments),this._components=[]}return __extends(b,a),b.prototype._anchor=function(b){var c=this;a.prototype._anchor.call(this,b),this.components().forEach(function(a){return a._anchor(c._content)})},b.prototype._render=function(){this._components.forEach(function(a){return a._render()})},b.prototype._removeComponent=function(a){var b=this._components.indexOf(a);b>=0&&(this.components().splice(b,1),this._invalidateLayout())},b.prototype._addComponent=function(a,b){return void 0===b&&(b=!1),!a||this._components.indexOf(a)>=0?!1:(b?this.components().unshift(a):this.components().push(a),a._parent(this),this._isAnchored&&a._anchor(this._content),this._invalidateLayout(),!0)},b.prototype.components=function(){return this._components},b.prototype.empty=function(){return 0===this._components.length},b.prototype.detachAll=function(){return this.components().slice().forEach(function(a){return a.detach()}),this},b.prototype.remove=function(){a.prototype.remove.call(this),this.components().slice().forEach(function(a){return a.remove()})},b.prototype._useLastCalculatedLayout=function(b){return null!=b&&this.components().slice().forEach(function(a){return a._useLastCalculatedLayout(b)}),a.prototype._useLastCalculatedLayout.call(this,b)},b}(a.AbstractComponent);a.AbstractComponentContainer=b}(b=a.Component||(a.Component={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(a){var c=this;void 0===a&&(a=[]),b.call(this),this.classed("component-group",!0),a.forEach(function(a){return c._addComponent(a)})}return __extends(c,b),c.prototype._requestedSpace=function(b,c){var d=this.components().map(function(a){return a._requestedSpace(b,c)});return{width:a._Util.Methods.max(d,function(a){return a.width},0),height:a._Util.Methods.max(d,function(a){return a.height},0),wantsWidth:d.map(function(a){return a.wantsWidth}).some(function(a){return a}),wantsHeight:d.map(function(a){return a.wantsHeight}).some(function(a){return a})}},c.prototype._merge=function(a,b){return this._addComponent(a,!b),this},c.prototype._computeLayout=function(a,c,d,e){var f=this;return b.prototype._computeLayout.call(this,a,c,d,e),this.components().forEach(function(a){a._computeLayout(0,0,f.width(),f.height())}),this},c.prototype._getSize=function(a,b){return{width:a,height:b}},c.prototype._isFixedWidth=function(){return this.components().every(function(a){return a._isFixedWidth()})},c.prototype._isFixedHeight=function(){return this.components().every(function(a){return a._isFixedHeight()})},c}(b.AbstractComponentContainer);b.Group=c}(b=a.Component||(a.Component={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(c,d,e){var f=this;if(void 0===e&&(e=a.Formatters.identity()),b.call(this),this._endTickLength=5,this._tickLength=5,this._tickLabelPadding=10,this._gutter=15,this._showEndTickLabels=!1,null==c||null==d)throw new Error("Axis requires a scale and orientation");this._scale=c,this.orient(d),this._setDefaultAlignment(),this.classed("axis",!0),this._isHorizontal()?this.classed("x-axis",!0):this.classed("y-axis",!0),this.formatter(e),this._scale.broadcaster.registerListener(this,function(){return f._rescale()})}return __extends(c,b),c.prototype.remove=function(){b.prototype.remove.call(this),this._scale.broadcaster.deregisterListener(this)},c.prototype._isHorizontal=function(){return"top"===this._orientation||"bottom"===this._orientation},c.prototype._computeWidth=function(){return this._computedWidth=this._maxLabelTickLength(),this._computedWidth},c.prototype._computeHeight=function(){return this._computedHeight=this._maxLabelTickLength(),this._computedHeight},c.prototype._requestedSpace=function(a,b){var c=0,d=0;return this._isHorizontal()?(null==this._computedHeight&&this._computeHeight(),d=this._computedHeight+this._gutter):(null==this._computedWidth&&this._computeWidth(),c=this._computedWidth+this._gutter),{width:c,height:d,wantsWidth:!this._isHorizontal()&&c>a,wantsHeight:this._isHorizontal()&&d>b}},c.prototype._isFixedHeight=function(){return this._isHorizontal()},c.prototype._isFixedWidth=function(){return!this._isHorizontal()},c.prototype._rescale=function(){this._render()},c.prototype._computeLayout=function(a,c,d,e){b.prototype._computeLayout.call(this,a,c,d,e),this._scale.range(this._isHorizontal()?[0,this.width()]:[this.height(),0])},c.prototype._setup=function(){b.prototype._setup.call(this),this._tickMarkContainer=this._content.append("g").classed(c.TICK_MARK_CLASS+"-container",!0),this._tickLabelContainer=this._content.append("g").classed(c.TICK_LABEL_CLASS+"-container",!0),this._baseline=this._content.append("line").classed("baseline",!0)},c.prototype._getTickValues=function(){return[]},c.prototype._doRender=function(){var a=this._getTickValues(),b=this._tickMarkContainer.selectAll("."+c.TICK_MARK_CLASS).data(a);b.enter().append("line").classed(c.TICK_MARK_CLASS,!0),b.attr(this._generateTickMarkAttrHash()),d3.select(b[0][0]).classed(c.END_TICK_MARK_CLASS,!0).attr(this._generateTickMarkAttrHash(!0)),d3.select(b[0][a.length-1]).classed(c.END_TICK_MARK_CLASS,!0).attr(this._generateTickMarkAttrHash(!0)),b.exit().remove(),this._baseline.attr(this._generateBaselineAttrHash())},c.prototype._generateBaselineAttrHash=function(){var a={x1:0,y1:0,x2:0,y2:0};switch(this._orientation){case"bottom":a.x2=this.width();break;case"top":a.x2=this.width(),a.y1=this.height(),a.y2=this.height();break;case"left":a.x1=this.width(),a.x2=this.width(),a.y2=this.height();break;case"right":a.y2=this.height()}return a},c.prototype._generateTickMarkAttrHash=function(a){var b=this;void 0===a&&(a=!1);var c={x1:0,y1:0,x2:0,y2:0},d=function(a){return b._scale.scale(a)};this._isHorizontal()?(c.x1=d,c.x2=d):(c.y1=d,c.y2=d);var e=a?this._endTickLength:this._tickLength;switch(this._orientation){case"bottom":c.y2=e;break;case"top":c.y1=this.height(),c.y2=this.height()-e;break;case"left":c.x1=this.width(),c.x2=this.width()-e;break;case"right":c.x2=e}return c},c.prototype._invalidateLayout=function(){this._computedWidth=null,this._computedHeight=null,b.prototype._invalidateLayout.call(this)},c.prototype._setDefaultAlignment=function(){switch(this._orientation){case"bottom":this.yAlign("top");break;case"top":this.yAlign("bottom");break;case"left":this.xAlign("right");break;case"right":this.xAlign("left")}},c.prototype.formatter=function(a){return void 0===a?this._formatter:(this._formatter=a,this._invalidateLayout(),this)},c.prototype.tickLength=function(a){if(null==a)return this._tickLength;if(0>a)throw new Error("tick length must be positive");return this._tickLength=a,this._invalidateLayout(),this},c.prototype.endTickLength=function(a){if(null==a)return this._endTickLength;if(0>a)throw new Error("end tick length must be positive");return this._endTickLength=a,this._invalidateLayout(),this},c.prototype._maxLabelTickLength=function(){return this.showEndTickLabels()?Math.max(this.tickLength(),this.endTickLength()):this.tickLength()},c.prototype.tickLabelPadding=function(a){if(null==a)return this._tickLabelPadding;if(0>a)throw new Error("tick label padding must be positive");return this._tickLabelPadding=a,this._invalidateLayout(),this},c.prototype.gutter=function(a){if(null==a)return this._gutter;if(0>a)throw new Error("gutter size must be positive");return this._gutter=a,this._invalidateLayout(),this},c.prototype.orient=function(a){if(null==a)return this._orientation;var b=a.toLowerCase();if("top"!==b&&"bottom"!==b&&"left"!==b&&"right"!==b)throw new Error("unsupported orientation");return this._orientation=b,this._invalidateLayout(),this},c.prototype.showEndTickLabels=function(a){return null==a?this._showEndTickLabels:(this._showEndTickLabels=a,this._render(),this)},c.END_TICK_MARK_CLASS="end-tick-mark",c.TICK_MARK_CLASS="tick-mark",c.TICK_LABEL_CLASS="tick-label",c}(a.Component.AbstractComponent);b.AbstractAxis=c}(b=a.Axis||(a.Axis={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(c){function d(a,b){c.call(this,a,b),this._tierLabelPositions=[],this.classed("time-axis",!0),this.tickLabelPadding(5),this.axisConfigurations(d._DEFAULT_TIME_AXIS_CONFIGURATIONS)}return __extends(d,c),d.prototype.tierLabelPositions=function(a){if(null==a)return this._tierLabelPositions;if(!a.every(function(a){return"between"===a.toLowerCase()||"center"===a.toLowerCase()}))throw new Error("Unsupported position for tier labels");return this._tierLabelPositions=a,this._invalidateLayout(),this},d.prototype.axisConfigurations=function(b){if(null==b)return this._possibleTimeAxisConfigurations;this._possibleTimeAxisConfigurations=b,this._numTiers=a._Util.Methods.max(this._possibleTimeAxisConfigurations.map(function(a){return a.length}),0),this._isAnchored&&this._setupDomElements();for(var c=this.tierLabelPositions(),d=[],e=0;ed&&a.every(function(a){return b._checkTimeAxisTierConfigurationWidth(a)})&&(c=d)}),c===this._possibleTimeAxisConfigurations.length&&(a._Util.Methods.warn("zoomed out too far: could not find suitable interval to display labels"),--c),c},d.prototype.orient=function(a){if(a&&("right"===a.toLowerCase()||"left"===a.toLowerCase()))throw new Error(a+" is not a supported orientation for TimeAxis - only horizontal orientations are supported");return c.prototype.orient.call(this,a)},d.prototype._computeHeight=function(){var a=this._measurer.measure().height;this._tierHeights=[];for(var b=0;bthis._scale.domain()[1])return this.width();var d=Math.abs(this._scale.scale(c)-this._scale.scale(b));return d},d.prototype._maxWidthForInterval=function(a){return this._measurer.measure(a.formatter(d._LONG_DATE)).width},d.prototype._checkTimeAxisTierConfigurationWidth=function(a){var b=this._maxWidthForInterval(a)+2*this.tickLabelPadding();return Math.min(this._getIntervalLength(a),this.width())>=b},d.prototype._getSize=function(a,b){var d=c.prototype._getSize.call(this,a,b);return d.height=this._tierHeights.reduce(function(a,b,c,e){return a+b>d.height?a:a+b}),d},d.prototype._setup=function(){c.prototype._setup.call(this),this._setupDomElements()},d.prototype._setupDomElements=function(){this._element.selectAll("."+d.TIME_AXIS_TIER_CLASS).remove(),this._tierLabelContainers=[],this._tierMarkContainers=[],this._tierBaselines=[],this._tickLabelContainer.remove(),this._baseline.remove();for(var a=0;a=g.length||h.push(new Date((g[b+1].valueOf()-g[b].valueOf())/2+g[b].valueOf()))}):h=g;var i=c.selectAll("."+b.AbstractAxis.TICK_LABEL_CLASS).data(h,function(a){return a.valueOf()}),j=i.enter().append("g").classed(b.AbstractAxis.TICK_LABEL_CLASS,!0);j.append("text");var k="center"===this._tierLabelPositions[e]||1===d.step?0:this.tickLabelPadding(),l=(this._measurer.measure().height,"bottom"===this.orient()?d3.sum(this._tierHeights.slice(0,e+1))-this.tickLabelPadding():this.height()-d3.sum(this._tierHeights.slice(0,e))-this.tickLabelPadding()),m=i.selectAll("text");m.size()>0&&a._Util.DOM.translate(m,k,l),i.exit().remove(),i.attr("transform",function(a){return"translate("+f._scale.scale(a)+",0)"});var n="center"===this._tierLabelPositions[e]||1===d.step?"middle":"start";i.selectAll("text").text(d.formatter).style("text-anchor",n)},d.prototype._renderTickMarks=function(a,c){var d=this._tierMarkContainers[c].selectAll("."+b.AbstractAxis.TICK_MARK_CLASS).data(a);d.enter().append("line").classed(b.AbstractAxis.TICK_MARK_CLASS,!0);var e=this._generateTickMarkAttrHash(),f=this._tierHeights.slice(0,c).reduce(function(a,b){return a+b},0);"bottom"===this.orient()?(e.y1=f,e.y2=f+("center"===this._tierLabelPositions[c]?this.tickLength():this._tierHeights[c])):(e.y1=this.height()-f,e.y2=this.height()-(f+("center"===this._tierLabelPositions[c]?this.tickLength():this._tierHeights[c]))),d.attr(e),"bottom"===this.orient()?(e.y1=f,e.y2=f+this._tierHeights[c]):(e.y1=this.height()-f,e.y2=this.height()-(f+this._tierHeights[c])),d3.select(d[0][0]).attr(e),d3.select(d[0][0]).classed(b.AbstractAxis.END_TICK_MARK_CLASS,!0),d3.select(d[0][d.size()-1]).classed(b.AbstractAxis.END_TICK_MARK_CLASS,!0),d.exit().remove()},d.prototype._renderLabellessTickMarks=function(a){var c=this._tickMarkContainer.selectAll("."+b.AbstractAxis.TICK_MARK_CLASS).data(a);c.enter().append("line").classed(b.AbstractAxis.TICK_MARK_CLASS,!0);var d=this._generateTickMarkAttrHash();d.y2="bottom"===this.orient()?this.tickLabelPadding():this.height()-this.tickLabelPadding(),c.attr(d),c.exit().remove()},d.prototype._generateLabellessTicks=function(){return this._mostPreciseConfigIndex<1?[]:this._getTickIntervalValues(this._possibleTimeAxisConfigurations[this._mostPreciseConfigIndex-1][0])},d.prototype._doRender=function(){var a=this;this._mostPreciseConfigIndex=this._getMostPreciseConfigurationIndex();var b=this._possibleTimeAxisConfigurations[this._mostPreciseConfigIndex];this._cleanTiers(),b.forEach(function(b,c){return a._renderTierLabels(a._tierLabelContainers[c],b,c)});for(var c=b.map(function(b,c){return a._getTickValuesForConfiguration(b)}),d=0,e=0;e=i&&(g=this._generateLabellessTicks()),this._renderLabellessTickMarks(g),this._hideOverflowingTiers(),e=0;e=c?"inherit":"hidden"})},d.prototype._hideOverlappingAndCutOffLabels=function(c){var d,e=this,f=this._element.select(".bounding-box")[0][0].getBoundingClientRect(),g=function(a){return Math.floor(f.left)<=Math.ceil(a.left)&&Math.floor(f.top)<=Math.ceil(a.top)&&Math.floor(a.right)<=Math.ceil(f.left+e.width())&&Math.floor(a.bottom)<=Math.ceil(f.top+e.height())},h=this._tierMarkContainers[c].selectAll("."+b.AbstractAxis.TICK_MARK_CLASS).filter(function(a,b){var c=d3.select(this).style("visibility");return"visible"===c||"inherit"===c}),i=h[0].map(function(a){return a.getBoundingClientRect()}),j=this._tierLabelContainers[c].selectAll("."+b.AbstractAxis.TICK_LABEL_CLASS).filter(function(a,b){var c=d3.select(this).style("visibility");return"visible"===c||"inherit"===c});j.each(function(b,c){var e=this.getBoundingClientRect(),f=d3.select(this),h=i[c],j=i[c+1];!g(e)||null!=d&&a._Util.DOM.boxesOverlap(e,d)||h.right>e.left||j.left=b[1]?b[0]:b[1];return c===b[0]?a.ticks().filter(function(a){return a>=c&&d>=a}):a.ticks().filter(function(a){return a>=c&&d>=a}).reverse()},d.prototype._rescale=function(){if(this._isSetup){if(!this._isHorizontal()){var a=this._computeWidth();if(a>this.width()||a=f.left)return!1}else if(e.top-c<=f.bottom)return!1}return!0},d}(b.AbstractAxis);b.Numeric=c}(b=a.Axis||(a.Axis={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(c){function d(b,d,e){void 0===d&&(d="bottom"),void 0===e&&(e=a.Formatters.identity()),c.call(this,b,d,e),this._tickLabelAngle=0,this.classed("category-axis",!0)}return __extends(d,c),d.prototype._setup=function(){c.prototype._setup.call(this),this._measurer=new SVGTypewriter.Measurers.CacheCharacterMeasurer(this._tickLabelContainer),this._wrapper=new SVGTypewriter.Wrappers.SingleLineWrapper,this._writer=new SVGTypewriter.Writers.Writer(this._measurer,this._wrapper)},d.prototype._rescale=function(){return this._invalidateLayout()},d.prototype._requestedSpace=function(a,b){var c=this._isHorizontal()?0:this._maxLabelTickLength()+this.tickLabelPadding()+this.gutter(),d=this._isHorizontal()?this._maxLabelTickLength()+this.tickLabelPadding()+this.gutter():0;if(0===this._scale.domain().length)return{width:0,height:0,wantsWidth:!1,wantsHeight:!1};var e=this._scale,f=e.copy();f.range(this._isHorizontal()?[0,a]:[b,0]);var g=this._measureTicks(a,b,f,e.domain());return{width:g.usedWidth+c,height:g.usedHeight+d,wantsWidth:!g.textFits,wantsHeight:!g.textFits}},d.prototype._getTickValues=function(){return this._scale.domain()},d.prototype.tickLabelAngle=function(a){if(null==a)return this._tickLabelAngle;if(0!==a&&90!==a&&-90!==a)throw new Error("Angle "+a+" not supported; only 0, 90, and -90 are valid values");return this._tickLabelAngle=a,this._invalidateLayout(),this},d.prototype._drawTicks=function(a,b,c,d){var e,f,g=this;switch(this.tickLabelAngle()){case 0:e={left:"right",right:"left",top:"center",bottom:"center"},f={left:"center",right:"center",top:"bottom",bottom:"top"};break;case 90:e={left:"center",right:"center",top:"right",bottom:"left"},f={left:"top",right:"bottom",top:"center",bottom:"center"};break;case-90:e={left:"center",right:"center",top:"left",bottom:"right"},f={left:"bottom",right:"top",top:"center",bottom:"center"}}d.each(function(d){var h=c.stepWidth(),i=g._isHorizontal()?h:a-g._maxLabelTickLength()-g.tickLabelPadding(),j=g._isHorizontal()?b-g._maxLabelTickLength()-g.tickLabelPadding():h,k={selection:d3.select(this),xAlign:e[g.orient()],yAlign:f[g.orient()],textRotation:g.tickLabelAngle()};g._writer.write(g.formatter()(d),i,j,k)})},d.prototype._measureTicks=function(b,c,d,e){var f=this,g=e.map(function(a){var e=d.stepWidth(),g=b-f._maxLabelTickLength()-f.tickLabelPadding();f._isHorizontal()&&(g=e,0!==f._tickLabelAngle&&(g=c-f._maxLabelTickLength()-f.tickLabelPadding()),g=Math.max(g,0));var h=e;return f._isHorizontal()&&(h=c-f._maxLabelTickLength()-f.tickLabelPadding(),0!==f._tickLabelAngle&&(h=b-f._maxLabelTickLength()-f.tickLabelPadding()),h=Math.max(h,0)),f._wrapper.wrap(f.formatter()(a),f._measurer,g,h)}),h=this._isHorizontal()&&0===this._tickLabelAngle?d3.sum:a._Util.Methods.max,i=this._isHorizontal()&&0===this._tickLabelAngle?a._Util.Methods.max:d3.sum,j=g.every(function(a){return!SVGTypewriter.Utils.StringMethods.isNotEmptyString(a.truncatedText)&&1===a.noLines}),k=h(g,function(a){return f._measurer.measure(a.wrappedText).width},0),l=i(g,function(a){return f._measurer.measure(a.wrappedText).height},0);if(0!==this._tickLabelAngle){var m=l;l=k,k=m}return{textFits:j,usedWidth:k,usedHeight:l}},d.prototype._doRender=function(){var d=this;c.prototype._doRender.call(this);var e=this._scale,f=this._tickLabelContainer.selectAll("."+b.AbstractAxis.TICK_LABEL_CLASS).data(this._scale.domain(),function(a){return a}),g=function(a,b){var c=e.stepWidth()-e.rangeBand(),f=e.scale(a)-e.rangeBand()/2-c/2,g=d._isHorizontal()?f:0,h=d._isHorizontal()?0:f;return"translate("+g+","+h+")"};f.enter().append("g").classed(b.AbstractAxis.TICK_LABEL_CLASS,!0),f.exit().remove(),f.attr("transform",g),f.text(""),this._drawTicks(this.width(),this.height(),e,f);var h=(this._isHorizontal()?[e.rangeBand()/2,0]:[0,e.rangeBand()/2],"right"===this.orient()?this._maxLabelTickLength()+this.tickLabelPadding():0),i="bottom"===this.orient()?this._maxLabelTickLength()+this.tickLabelPadding():0;return a._Util.DOM.translate(this._tickLabelContainer,h,i),this},d.prototype._computeLayout=function(a,b,d,e){return this._measurer.reset(),c.prototype._computeLayout.call(this,a,b,d,e)},d}(b.AbstractAxis);b.Category=c}(b=a.Axis||(a.Axis={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(a){var b=function(a){function b(b,c){void 0===b&&(b=""),void 0===c&&(c="horizontal"),a.call(this),this.classed("label",!0),this.text(b),this.orient(c),this.xAlign("center").yAlign("center"),this._fixedHeightFlag=!0,this._fixedWidthFlag=!0,this._padding=0}return __extends(b,a),b.prototype.xAlign=function(b){var c=b.toLowerCase();return a.prototype.xAlign.call(this,c),this._xAlignment=c,this},b.prototype.yAlign=function(b){var c=b.toLowerCase();return a.prototype.yAlign.call(this,c),this._yAlignment=c,this},b.prototype._requestedSpace=function(a,b){var c=this._measurer.measure(this._text),d=("horizontal"===this.orient()?c.width:c.height)+2*this.padding(),e=("horizontal"===this.orient()?c.height:c.width)+2*this.padding();return{width:d,height:e,wantsWidth:d>a,wantsHeight:e>b}},b.prototype._setup=function(){a.prototype._setup.call(this),this._textContainer=this._content.append("g"),this._measurer=new SVGTypewriter.Measurers.Measurer(this._textContainer),this._wrapper=new SVGTypewriter.Wrappers.Wrapper,this._writer=new SVGTypewriter.Writers.Writer(this._measurer,this._wrapper),this.text(this._text)},b.prototype.text=function(a){return void 0===a?this._text:(this._text=a,this._invalidateLayout(),this)},b.prototype.orient=function(a){if(null==a)return this._orientation;if(a=a.toLowerCase(),"horizontal"!==a&&"left"!==a&&"right"!==a)throw new Error(a+" is not a valid orientation for LabelComponent");return this._orientation=a,this._invalidateLayout(),this},b.prototype.padding=function(a){if(null==a)return this._padding;if(a=+a,0>a)throw new Error(a+" is not a valid padding value. Cannot be less than 0.");return this._padding=a,this._invalidateLayout(),this},b.prototype._doRender=function(){a.prototype._doRender.call(this),this._textContainer.selectAll("g").remove();var b=this._measurer.measure(this._text),c=Math.max(Math.min((this.height()-b.height)/2,this.padding()),0),d=Math.max(Math.min((this.width()-b.width)/2,this.padding()),0);this._textContainer.attr("transform","translate("+d+","+c+")");var e=this.width()-2*d,f=this.height()-2*c,g={horizontal:0,right:90,left:-90},h={selection:this._textContainer,xAlign:this._xAlignment,yAlign:this._yAlignment,textRotation:g[this.orient()]};this._writer.write(this._text,e,f,h)},b}(a.AbstractComponent);a.Label=b;var c=function(a){function b(b,c){a.call(this,b,c),this.classed("title-label",!0)}return __extends(b,a),b}(b);a.TitleLabel=c;var d=function(a){function b(b,c){a.call(this,b,c),this.classed("axis-label",!0)}return __extends(b,a),b}(b);a.AxisLabel=d}(b=a.Component||(a.Component={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(c){var d=this;if(b.call(this),this._padding=5,this.classed("legend",!0),this.maxEntriesPerRow(1),null==c)throw new Error("Legend requires a colorScale");this._scale=c,this._scale.broadcaster.registerListener(this,function(){return d._invalidateLayout()}),this.xAlign("right").yAlign("top"),this._fixedWidthFlag=!0,this._fixedHeightFlag=!0,this._sortFn=function(a,b){return d._scale.domain().indexOf(a)-d._scale.domain().indexOf(b)},this._symbolFactoryAccessor=function(){return a.SymbolFactories.circle()}}return __extends(c,b),c.prototype._setup=function(){b.prototype._setup.call(this);var a=this._content.append("g").classed(c.LEGEND_ROW_CLASS,!0),d=a.append("g").classed(c.LEGEND_ENTRY_CLASS,!0);d.append("text"),this._measurer=new SVGTypewriter.Measurers.Measurer(a),this._wrapper=(new SVGTypewriter.Wrappers.Wrapper).maxLines(1),this._writer=new SVGTypewriter.Writers.Writer(this._measurer,this._wrapper).addTitleElement(!0)},c.prototype.maxEntriesPerRow=function(a){return null==a?this._maxEntriesPerRow:(this._maxEntriesPerRow=a,this._invalidateLayout(),this)},c.prototype.sortFunction=function(a){return null==a?this._sortFn:(this._sortFn=a,this._invalidateLayout(),this)},c.prototype.scale=function(a){var b=this;return null!=a?(this._scale.broadcaster.deregisterListener(this),this._scale=a,this._scale.broadcaster.registerListener(this,function(){return b._invalidateLayout()}),this._invalidateLayout(),this):this._scale},c.prototype.remove=function(){b.prototype.remove.call(this),this._scale.broadcaster.deregisterListener(this)},c.prototype._calculateLayoutInfo=function(b,c){var d=this,e=this._measurer.measure().height,f=Math.max(0,b-this._padding),g=function(a){var b=e+d._measurer.measure(a).width+d._padding;return Math.min(b,f)},h=this._scale.domain().slice();h.sort(this.sortFunction());var i=a._Util.Methods.populateMap(h,g),j=this._packRows(f,h,i),k=Math.floor((c-2*this._padding)/e);return k!==k&&(k=0),{textHeight:e,entryLengths:i,rows:j,numRowsToDraw:Math.max(Math.min(k,j.length),0)}},c.prototype._requestedSpace=function(b,c){var d=this,e=this._calculateLayoutInfo(b,c),f=e.rows.map(function(a){return d3.sum(a,function(a){return e.entryLengths.get(a)})}),g=a._Util.Methods.max(f,0),h=a._Util.Methods.max(this._scale.domain(),function(a){return d._measurer.measure(a).width},0);h+=e.textHeight+this._padding;var i=this._padding+Math.max(g,h),j=e.numRowsToDraw*e.textHeight+2*this._padding,k=e.rows.length*e.textHeight+2*this._padding,l=Math.max(Math.ceil(this._scale.domain().length/this._maxEntriesPerRow),1),m=e.rows.length>l;return{width:this._padding+g,height:j,wantsWidth:i>b||m,wantsHeight:k>c}},c.prototype._packRows=function(a,b,c){var d=this,e=[],f=[],g=a;return b.forEach(function(b){var h=c.get(b);(h>g||f.length===d._maxEntriesPerRow)&&(e.push(f),f=[],g=a),f.push(b),g-=h}),0!==f.length&&e.push(f),e},c.prototype.getEntry=function(a){if(!this._isSetup)return d3.select();var b=d3.select(),d=this._calculateLayoutInfo(this.width(),this.height()),e=this._padding;return this._content.selectAll("g."+c.LEGEND_ROW_CLASS).each(function(f,g){var h=g*d.textHeight+e,i=(g+1)*d.textHeight+e,j=e,k=e;d3.select(this).selectAll("g."+c.LEGEND_ENTRY_CLASS).each(function(c){k+=d.entryLengths.get(c),k>=a.x&&j<=a.x&&i>=a.y&&h<=a.y&&(b=d3.select(this)),j+=d.entryLengths.get(c)})}),b},c.prototype._doRender=function(){var a=this;b.prototype._doRender.call(this);var d=this._calculateLayoutInfo(this.width(),this.height()),e=d.rows.slice(0,d.numRowsToDraw),f=this._content.selectAll("g."+c.LEGEND_ROW_CLASS).data(e);f.enter().append("g").classed(c.LEGEND_ROW_CLASS,!0),f.exit().remove(),f.attr("transform",function(b,c){return"translate(0, "+(c*d.textHeight+a._padding)+")"});var g=f.selectAll("g."+c.LEGEND_ENTRY_CLASS).data(function(a){return a}),h=g.enter().append("g").classed(c.LEGEND_ENTRY_CLASS,!0);h.append("path"),h.append("g").classed("text-container",!0),g.exit().remove();var i=this._padding;f.each(function(a){var b=i,e=d3.select(this).selectAll("g."+c.LEGEND_ENTRY_CLASS);e.attr("transform",function(a,c){var e="translate("+b+", 0)";return b+=d.entryLengths.get(a),e})}),g.select("path").attr("d",function(b,c){return a.symbolFactoryAccessor()(b,c)(.6*d.textHeight)}).attr("transform","translate("+d.textHeight/2+","+d.textHeight/2+")").attr("fill",function(b){return a._scale.scale(b)}).classed(c.LEGEND_SYMBOL_CLASS,!0);var j=this._padding,k=g.select("g.text-container");k.text(""),k.append("title").text(function(a){return a});var l=this;k.attr("transform","translate("+d.textHeight+", 0)").each(function(a){var b=d3.select(this),c=d.entryLengths.get(a)-d.textHeight-j,e={selection:b,xAlign:"left",yAlign:"top",textRotation:0};l._writer.write(a,c,l.height(),e)})},c.prototype.symbolFactoryAccessor=function(a){return null==a?this._symbolFactoryAccessor:(this._symbolFactoryAccessor=a,this._render(),this)},c.LEGEND_ROW_CLASS="legend-row",c.LEGEND_ENTRY_CLASS="legend-entry",c.LEGEND_SYMBOL_CLASS="legend-symbol",c}(b.AbstractComponent);b.Legend=c}(b=a.Component||(a.Component={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(d,e,f){var g=this;if(void 0===e&&(e="horizontal"),void 0===f&&(f=a.Formatters.general()),b.call(this),this._padding=5,this._numSwatches=10,null==d)throw new Error("InterpolatedColorLegend requires a interpolatedColorScale");this._scale=d,this._scale.broadcaster.registerListener(this,function(){return g._invalidateLayout()}),this._formatter=f,this._orientation=c._ensureOrientation(e),this._fixedWidthFlag=!0,this._fixedHeightFlag=!0,this.classed("legend",!0).classed("interpolated-color-legend",!0)}return __extends(c,b),c.prototype.remove=function(){b.prototype.remove.call(this),this._scale.broadcaster.deregisterListener(this)},c.prototype.formatter=function(a){return void 0===a?this._formatter:(this._formatter=a,this._invalidateLayout(),this)},c._ensureOrientation=function(a){if(a=a.toLowerCase(),"horizontal"===a||"left"===a||"right"===a)return a;throw new Error('"'+a+'" is not a valid orientation for InterpolatedColorLegend')},c.prototype.orient=function(a){return null==a?this._orientation:(this._orientation=c._ensureOrientation(a),this._invalidateLayout(),this)},c.prototype._generateTicks=function(){for(var a=this._scale.domain(),b=(a[1]-a[0])/this._numSwatches,c=[],d=0;d<=this._numSwatches;d++)c.push(a[0]+b*d);return c},c.prototype._setup=function(){b.prototype._setup.call(this),this._swatchContainer=this._content.append("g").classed("swatch-container",!0),this._swatchBoundingBox=this._content.append("rect").classed("swatch-bounding-box",!0),this._lowerLabel=this._content.append("g").classed(c.LEGEND_LABEL_CLASS,!0),this._upperLabel=this._content.append("g").classed(c.LEGEND_LABEL_CLASS,!0),this._measurer=new SVGTypewriter.Measurers.Measurer(this._content),this._wrapper=new SVGTypewriter.Wrappers.Wrapper,this._writer=new SVGTypewriter.Writers.Writer(this._measurer,this._wrapper)},c.prototype._requestedSpace=function(b,c){var d,e,f=this,g=this._measurer.measure().height,h=this._generateTicks(),i=h.length,j=this._scale.domain(),k=j.map(function(a){return f._measurer.measure(f._formatter(a)).width});if(this._isVertical()){var l=a._Util.Methods.max(k,0);e=this._padding+g+this._padding+l+this._padding,d=this._padding+i*g+this._padding}else d=this._padding+g+this._padding, -e=this._padding+k[0]+this._padding+i*g+this._padding+k[1]+this._padding;return{width:e,height:d,wantsWidth:e>b,wantsHeight:d>c}},c.prototype._isVertical=function(){return"horizontal"!==this._orientation},c.prototype._doRender=function(){var a=this;b.prototype._doRender.call(this);var c,d,e,f,g=this._scale.domain(),h=(this._measurer.measure().height,this._formatter(g[0])),i=this._measurer.measure(h).width,j=this._formatter(g[1]),k=this._measurer.measure(j).width,l=this._generateTicks(),m=l.length,n=this._padding,o={x:0,y:0},p={x:0,y:0},q={selection:this._lowerLabel,xAlign:"center",yAlign:"center",textRotation:0},r={selection:this._upperLabel,xAlign:"center",yAlign:"center",textRotation:0},s={x:0,y:n,width:0,height:0};if(this._isVertical()){var t=Math.max(i,k);c=Math.max(this.width()-3*n-t,0),d=Math.max((this.height()-2*n)/m,0),f=function(a,b){return n+(m-(b+1))*d},r.yAlign="top",o.y=n,q.yAlign="bottom",p.y=-n,"left"===this._orientation?(e=function(a,b){return n+t+n},r.xAlign="right",o.x=-(n+c+n),q.xAlign="right",p.x=-(n+c+n)):(e=function(a,b){return n},r.xAlign="left",o.x=n+c+n,q.xAlign="left",p.x=n+c+n),s.width=c,s.height=m*d}else c=Math.max((this.width()-4*n-i-k)/m,0),d=Math.max(this.height()-2*n,0),e=function(a,b){return n+i+n+b*c},f=function(a,b){return n},r.xAlign="right",o.x=-n,q.xAlign="left",p.x=n,s.width=m*c,s.height=d;s.x=e(null,0),this._upperLabel.text(""),this._writer.write(j,this.width(),this.height(),r);var u="translate("+o.x+", "+o.y+")";this._upperLabel.attr("transform",u),this._lowerLabel.text(""),this._writer.write(h,this.width(),this.height(),q);var v="translate("+p.x+", "+p.y+")";this._lowerLabel.attr("transform",v),this._swatchBoundingBox.attr(s);var w=this._swatchContainer.selectAll("rect.swatch").data(l);w.enter().append("rect").classed("swatch",!0),w.exit().remove(),w.attr({fill:function(b,c){return a._scale.scale(b)},width:c,height:d,x:e,y:f})},c.LEGEND_LABEL_CLASS="legend-label",c}(b.AbstractComponent);b.InterpolatedColorLegend=c}(b=a.Component||(a.Component={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(c,d){var e=this;if(null!=c&&!a.Scale.AbstractQuantitative.prototype.isPrototypeOf(c))throw new Error("xScale needs to inherit from Scale.AbstractQuantitative");if(null!=d&&!a.Scale.AbstractQuantitative.prototype.isPrototypeOf(d))throw new Error("yScale needs to inherit from Scale.AbstractQuantitative");b.call(this),this.classed("gridlines",!0),this._xScale=c,this._yScale=d,this._xScale&&this._xScale.broadcaster.registerListener(this,function(){return e._render()}),this._yScale&&this._yScale.broadcaster.registerListener(this,function(){return e._render()})}return __extends(c,b),c.prototype.remove=function(){return b.prototype.remove.call(this),this._xScale&&this._xScale.broadcaster.deregisterListener(this),this._yScale&&this._yScale.broadcaster.deregisterListener(this),this},c.prototype._setup=function(){b.prototype._setup.call(this),this._xLinesContainer=this._content.append("g").classed("x-gridlines",!0),this._yLinesContainer=this._content.append("g").classed("y-gridlines",!0)},c.prototype._doRender=function(){b.prototype._doRender.call(this),this._redrawXLines(),this._redrawYLines()},c.prototype._redrawXLines=function(){var a=this;if(this._xScale){var b=this._xScale.ticks(),c=function(b){return a._xScale.scale(b)},d=this._xLinesContainer.selectAll("line").data(b);d.enter().append("line"),d.attr("x1",c).attr("y1",0).attr("x2",c).attr("y2",this.height()).classed("zeroline",function(a){return 0===a}),d.exit().remove()}},c.prototype._redrawYLines=function(){var a=this;if(this._yScale){var b=this._yScale.ticks(),c=function(b){return a._yScale.scale(b)},d=this._yLinesContainer.selectAll("line").data(b);d.enter().append("line"),d.attr("x1",0).attr("y1",c).attr("x2",this.width()).attr("y2",c).classed("zeroline",function(a){return 0===a}),d.exit().remove()}},c}(b.AbstractComponent);b.Gridlines=c}(b=a.Component||(a.Component={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(a){var c=this;void 0===a&&(a=[]),b.call(this),this._rowPadding=0,this._colPadding=0,this._rows=[],this._rowWeights=[],this._colWeights=[],this._nRows=0,this._nCols=0,this._calculatedLayout=null,this.classed("table",!0),a.forEach(function(a,b){a.forEach(function(a,d){null!=a&&c.addComponent(b,d,a)})})}return __extends(c,b),c.prototype.addComponent=function(a,b,c){if(null==c)throw Error("Cannot add null to a table cell");var d=this._rows[a]&&this._rows[a][b];return d&&(c=c.above(d)),this._addComponent(c)&&(this._nRows=Math.max(a+1,this._nRows),this._nCols=Math.max(b+1,this._nCols),this._padTableToSize(this._nRows,this._nCols),this._rows[a][b]=c),this},c.prototype._removeComponent=function(a){b.prototype._removeComponent.call(this,a);var c,d;a:for(var e=0;e0&&e!==y,D=f>0&&f!==z;if(!C&&!D)break;if(s>5)break}return e=i-d3.sum(v.guaranteedWidths),f=j-d3.sum(v.guaranteedHeights),o=c._calcProportionalSpace(l,e),p=c._calcProportionalSpace(k,f),{colProportionalSpace:o,rowProportionalSpace:p,guaranteedWidths:v.guaranteedWidths,guaranteedHeights:v.guaranteedHeights,wantsWidth:w,wantsHeight:x}},c.prototype._determineGuarantees=function(b,c){var d=a._Util.Methods.createFilledArray(0,this._nCols),e=a._Util.Methods.createFilledArray(0,this._nRows),f=a._Util.Methods.createFilledArray(!1,this._nCols),g=a._Util.Methods.createFilledArray(!1,this._nRows);return this._rows.forEach(function(a,h){a.forEach(function(a,i){var j;j=null!=a?a._requestedSpace(b[i],c[h]):{width:0,height:0,wantsWidth:!1,wantsHeight:!1};var k=Math.min(j.width,b[i]),l=Math.min(j.height,c[h]);d[i]=Math.max(d[i],k),e[h]=Math.max(e[h],l),f[i]=f[i]||j.wantsWidth,g[h]=g[h]||j.wantsHeight})}),{guaranteedWidths:d,guaranteedHeights:e,wantsWidthArr:f,wantsHeightArr:g}},c.prototype._requestedSpace=function(a,b){return this._calculatedLayout=this._iterateLayout(a,b),{width:d3.sum(this._calculatedLayout.guaranteedWidths),height:d3.sum(this._calculatedLayout.guaranteedHeights),wantsWidth:this._calculatedLayout.wantsWidth,wantsHeight:this._calculatedLayout.wantsHeight}},c.prototype._computeLayout=function(c,d,e,f){var g=this;b.prototype._computeLayout.call(this,c,d,e,f);var h=this._useLastCalculatedLayout()?this._calculatedLayout:this._iterateLayout(this.width(),this.height());this._useLastCalculatedLayout(!0);var i=0,j=a._Util.Methods.addArrays(h.rowProportionalSpace,h.guaranteedHeights),k=a._Util.Methods.addArrays(h.colProportionalSpace,h.guaranteedWidths);this._rows.forEach(function(a,b){var c=0;a.forEach(function(a,d){null!=a&&a._computeLayout(c,i,k[d],j[b]),c+=k[d]+g._colPadding}),i+=j[b]+g._rowPadding})},c.prototype.padding=function(a,b){return this._rowPadding=a,this._colPadding=b,this._invalidateLayout(),this},c.prototype.rowWeight=function(a,b){return this._rowWeights[a]=b,this._invalidateLayout(),this},c.prototype.colWeight=function(a,b){return this._colWeights[a]=b,this._invalidateLayout(),this},c.prototype._isFixedWidth=function(){var a=d3.transpose(this._rows);return c._fixedSpace(a,function(a){return null==a||a._isFixedWidth()})},c.prototype._isFixedHeight=function(){return c._fixedSpace(this._rows,function(a){return null==a||a._isFixedHeight()})},c.prototype._padTableToSize=function(a,b){for(var c=0;a>c;c++){void 0===this._rows[c]&&(this._rows[c]=[],this._rowWeights[c]=null);for(var d=0;b>d;d++)void 0===this._rows[c][d]&&(this._rows[c][d]=null)}for(d=0;b>d;d++)void 0===this._colWeights[d]&&(this._colWeights[d]=null)},c._calcComponentWeights=function(a,b,c){return a.map(function(a,d){if(null!=a)return a;var e=b[d].map(c),f=e.reduce(function(a,b){return a&&b},!0);return f?0:1})},c._calcProportionalSpace=function(b,c){var d=d3.sum(b);return 0===d?a._Util.Methods.createFilledArray(0,b.length):b.map(function(a){return c*a/d})},c._fixedSpace=function(a,b){var c=function(a){return a.reduce(function(a,b){return a&&b},!0)},d=function(a){return c(a.map(b))};return c(a.map(d))},c}(b.AbstractComponentContainer);b.Table=c}(b=a.Component||(a.Component={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(a){var b=function(a){function b(){a.call(this),this._boxVisible=!1,this._boxBounds={topLeft:{x:0,y:0},bottomRight:{x:0,y:0}},this.classed("selection-box-layer",!0),this._fixedWidthFlag=!0,this._fixedHeightFlag=!0}return __extends(b,a),b.prototype._setup=function(){a.prototype._setup.call(this),this._box=this._content.append("g").classed("selection-box",!0).remove(),this._boxArea=this._box.append("rect").classed("selection-area",!0)},b.prototype._getSize=function(a,b){return{width:a,height:b}},b.prototype.bounds=function(a){return null==a?this._boxBounds:(this._setBounds(a),this._render(),this)},b.prototype._setBounds=function(a){var b={x:Math.min(a.topLeft.x,a.bottomRight.x),y:Math.min(a.topLeft.y,a.bottomRight.y)},c={x:Math.max(a.topLeft.x,a.bottomRight.x),y:Math.max(a.topLeft.y,a.bottomRight.y)};this._boxBounds={topLeft:b,bottomRight:c}},b.prototype._doRender=function(){if(this._boxVisible){var a=this._boxBounds.topLeft.y,b=this._boxBounds.bottomRight.y,c=this._boxBounds.topLeft.x,d=this._boxBounds.bottomRight.x;this._boxArea.attr({x:c,y:a,width:d-c,height:b-a}),this._content.node().appendChild(this._box.node())}else this._box.remove()},b.prototype.boxVisible=function(a){return null==a?this._boxVisible:(this._boxVisible=a,this._render(),this)},b}(a.AbstractComponent);a.SelectionBoxLayer=b}(b=a.Component||(a.Component={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(){b.call(this),this._dataChanged=!1,this._projections={},this._animate=!1,this._animators={},this._animateOnNextRender=!0,this.clipPathEnabled=!0,this.classed("plot",!0),this._key2PlotDatasetKey=d3.map(),this._datasetKeysInOrder=[],this._nextSeriesIndex=0}return __extends(c,b),c.prototype._anchor=function(a){b.prototype._anchor.call(this,a),this._animateOnNextRender=!0,this._dataChanged=!0,this._updateScaleExtents()},c.prototype._setup=function(){var a=this;b.prototype._setup.call(this),this._renderArea=this._content.append("g").classed("render-area",!0),this._getDrawersInOrder().forEach(function(b){return b.setup(a._renderArea.append("g"))})},c.prototype.remove=function(){var a=this;b.prototype.remove.call(this),this._datasetKeysInOrder.forEach(function(b){return a.removeDataset(b)});var c=Object.keys(this._projections);c.forEach(function(b){var c=a._projections[b];c.scale&&c.scale.broadcaster.deregisterListener(a)})},c.prototype.addDataset=function(b,c){if("string"!=typeof b&&void 0!==c)throw new Error("invalid input to addDataset");"string"==typeof b&&"_"===b[0]&&a._Util.Methods.warn("Warning: Using _named series keys may produce collisions with unlabeled data sources");var d="string"==typeof b?b:"_"+this._nextSeriesIndex++,e="string"!=typeof b?b:c;return c=e instanceof a.Dataset?e:new a.Dataset(e),this._addDataset(d,c),this},c.prototype._addDataset=function(a,b){var c=this;this._key2PlotDatasetKey.has(a)&&this.removeDataset(a);var d=this._getDrawer(a),e=this._getPlotMetadataForDataset(a),f={drawer:d,dataset:b,key:a,plotMetadata:e};this._datasetKeysInOrder.push(a),this._key2PlotDatasetKey.set(a,f),this._isSetup&&d.setup(this._renderArea.append("g")),b.broadcaster.registerListener(this,function(){return c._onDatasetUpdate()}),this._onDatasetUpdate()},c.prototype._getDrawer=function(b){return new a._Drawer.AbstractDrawer(b)},c.prototype._getAnimator=function(b){return this._animate&&this._animateOnNextRender?this._animators[b]||new a.Animator.Null:new a.Animator.Null},c.prototype._onDatasetUpdate=function(){this._updateScaleExtents(),this._animateOnNextRender=!0,this._dataChanged=!0,this._render()},c.prototype.attr=function(a,b,c){return this.project(a,b,c)},c.prototype.project=function(b,c,d){var e=this;b=b.toLowerCase();var f=this._projections[b],g=f&&f.scale;return g&&this._datasetKeysInOrder.forEach(function(a){g._removeExtent(e.getID().toString()+"_"+a,b),g.broadcaster.deregisterListener(e)}),d&&d.broadcaster.registerListener(this,function(){return e._render()}),c=a._Util.Methods.accessorize(c),this._projections[b]={accessor:c,scale:d,attribute:b},this._updateScaleExtent(b),this._render(),this},c.prototype._generateAttrToProjector=function(){var a=this,b={};return d3.keys(this._projections).forEach(function(c){var d=a._projections[c],e=d.accessor,f=d.scale,g=f?function(a,b,c,d){return f.scale(e(a,b,c,d))}:e;b[c]=g}),b},c.prototype.generateProjectors=function(a){var b=this._generateAttrToProjector(),c=this._key2PlotDatasetKey.get(a),d=c.plotMetadata,e=c.dataset.metadata(),f={};return d3.entries(b).forEach(function(a){f[a.key]=function(b,c){return a.value(b,c,e,d)}}),f},c.prototype._doRender=function(){this._isAnchored&&(this._paint(),this._dataChanged=!1,this._animateOnNextRender=!1)},c.prototype.animate=function(a){return this._animate=a,this},c.prototype.detach=function(){return b.prototype.detach.call(this),this._updateScaleExtents(),this},c.prototype._updateScaleExtents=function(){var a=this;d3.keys(this._projections).forEach(function(b){return a._updateScaleExtent(b)})},c.prototype._updateScaleExtent=function(a){var b=this,c=this._projections[a];c.scale&&this._datasetKeysInOrder.forEach(function(d){var e=b._key2PlotDatasetKey.get(d),f=e.dataset,g=e.plotMetadata,h=f._getExtent(c.accessor,c.scale._typeCoercer,g),i=b.getID().toString()+"_"+d;0!==h.length&&b._isAnchored?c.scale._updateExtent(i,a,h):c.scale._removeExtent(i,a)})},c.prototype.animator=function(a,b){return void 0===b?this._animators[a]:(this._animators[a]=b,this)},c.prototype.datasetOrder=function(b){function c(b,c){var d=a._Util.Methods.intersection(d3.set(b),d3.set(c)),e=d.size();return e===b.length&&e===c.length}return void 0===b?this._datasetKeysInOrder:(c(b,this._datasetKeysInOrder)?(this._datasetKeysInOrder=b,this._onDatasetUpdate()):a._Util.Methods.warn("Attempted to change datasetOrder, but new order is not permutation of old. Ignoring."),this)},c.prototype.removeDataset=function(b){var c;if("string"==typeof b)c=b;else if("object"==typeof b){var d=-1;if(b instanceof a.Dataset){var e=this.datasets();d=e.indexOf(b)}else if(b instanceof Array){var f=this.datasets().map(function(a){return a.data()});d=f.indexOf(b)}-1!==d&&(c=this._datasetKeysInOrder[d])}return this._removeDataset(c)},c.prototype._removeDataset=function(a){if(null!=a&&this._key2PlotDatasetKey.has(a)){var b=this._key2PlotDatasetKey.get(a);b.drawer.remove();var c=d3.values(this._projections),d=this.getID().toString()+"_"+a;c.forEach(function(a){null!=a.scale&&a.scale._removeExtent(d,a.attribute)}),b.dataset.broadcaster.deregisterListener(this),this._datasetKeysInOrder.splice(this._datasetKeysInOrder.indexOf(a),1),this._key2PlotDatasetKey.remove(a),this._onDatasetUpdate()}return this},c.prototype.datasets=function(){var a=this;return this._datasetKeysInOrder.map(function(b){return a._key2PlotDatasetKey.get(b).dataset})},c.prototype._getDrawersInOrder=function(){var a=this;return this._datasetKeysInOrder.map(function(b){return a._key2PlotDatasetKey.get(b).drawer})},c.prototype._generateDrawSteps=function(){return[{attrToProjector:this._generateAttrToProjector(),animator:new a.Animator.Null}]},c.prototype._additionalPaint=function(a){},c.prototype._getDataToDraw=function(){var a=this,b=d3.map();return this._datasetKeysInOrder.forEach(function(c){b.set(c,a._key2PlotDatasetKey.get(c).dataset.data())}),b},c.prototype._getPlotMetadataForDataset=function(a){return{datasetKey:a}},c.prototype._paint=function(){var b=this,c=this._generateDrawSteps(),d=this._getDataToDraw(),e=this._getDrawersInOrder(),f=this._datasetKeysInOrder.map(function(a,f){return e[f].draw(d.get(a),c,b._key2PlotDatasetKey.get(a).dataset.metadata(),b._key2PlotDatasetKey.get(a).plotMetadata)}),g=a._Util.Methods.max(f,0);this._additionalPaint(g)},c.prototype.getAllSelections=function(a,b){var c=this;void 0===a&&(a=this.datasetOrder()),void 0===b&&(b=!1);var d=[];if(d="string"==typeof a?[a]:a,b){var e=d3.set(d);d=this.datasetOrder().filter(function(a){return!e.has(a)})}var f=[];return d.forEach(function(a){var b=c._key2PlotDatasetKey.get(a);if(null!=b){var d=b.drawer;d._getRenderArea().selectAll(d._getSelector()).each(function(){f.push(this)})}}),d3.selectAll(f)},c.prototype.getAllPlotData=function(a){void 0===a&&(a=this.datasetOrder());var b=[];return b="string"==typeof a?[a]:a,this._getAllPlotData(b)},c.prototype._getAllPlotData=function(a){var b=this,c=[],d=[],e=[];return a.forEach(function(a){var f=b._key2PlotDatasetKey.get(a);if(null!=f){var g=f.drawer;f.dataset.data().forEach(function(a,b){var f=g._getPixelPoint(a,b);f.x===f.x&&f.y===f.y&&(c.push(a),d.push(f),e.push(g._getSelection(b).node()))})}}),{data:c,pixelPoints:d,selection:d3.selectAll(e)}},c.prototype.getClosestPlotData=function(b){var c,d=this,e=1/0,f=this.getAllPlotData();return f.pixelPoints.forEach(function(g,h){var i=f.data[h],j=d3.select(f.selection[0][h]);if(d._isVisibleOnPlot(i,g,j)){var k=a._Util.Methods.distanceSquared(g,b);e>k&&(e=k,c=h)}}),null==c?{data:[],pixelPoints:[],selection:d3.select()}:{data:[f.data[c]],pixelPoints:[f.pixelPoints[c]],selection:d3.select(f.selection[0][c])}},c.prototype._isVisibleOnPlot=function(a,b,c){return!(b.x<0||b.y<0||b.x>this.width()||b.y>this.height())},c}(a.Component.AbstractComponent);b.AbstractPlot=c}(b=a.Plot||(a.Plot={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(){b.call(this),this._colorScale=new a.Scale.Color,this.classed("pie-plot",!0)}return __extends(c,b),c.prototype._computeLayout=function(a,c,d,e){b.prototype._computeLayout.call(this,a,c,d,e),this._renderArea.attr("transform","translate("+this.width()/2+","+this.height()/2+")")},c.prototype.addDataset=function(c,d){return 1===this._datasetKeysInOrder.length?(a._Util.Methods.warn("Only one dataset is supported in Pie plots"),this):(b.prototype.addDataset.call(this,c,d),this)},c.prototype._generateAttrToProjector=function(){var a=this,c=b.prototype._generateAttrToProjector.call(this);c["inner-radius"]=c["inner-radius"]||d3.functor(0),c["outer-radius"]=c["outer-radius"]||d3.functor(Math.min(this.width(),this.height())/2);var d=function(b,c){return a._colorScale.scale(String(c))};return c.fill=c.fill||d,c},c.prototype._getDrawer=function(b){return new a._Drawer.Arc(b).setClass("arc")},c.prototype.getAllPlotData=function(a){var c=this,d=b.prototype.getAllPlotData.call(this,a);return d.pixelPoints.forEach(function(a){a.x=a.x+c.width()/2,a.y=a.y+c.height()/2}),d},c}(b.AbstractPlot);b.Pie=c}(b=a.Plot||(a.Plot={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(a,c){var d=this;if(b.call(this),this._autoAdjustXScaleDomain=!1,this._autoAdjustYScaleDomain=!1,null==a||null==c)throw new Error("XYPlots require an xScale and yScale");this.classed("xy-plot",!0),this._xScale=a,this._yScale=c,this._updateXDomainer(),a.broadcaster.registerListener("yDomainAdjustment"+this.getID(),function(){return d._adjustYDomainOnChangeFromX()}),this._updateYDomainer(),c.broadcaster.registerListener("xDomainAdjustment"+this.getID(),function(){return d._adjustXDomainOnChangeFromY()})}return __extends(c,b),c.prototype.project=function(a,c,d){var e=this;return"x"===a&&d&&(this._xScale&&this._xScale.broadcaster.deregisterListener("yDomainAdjustment"+this.getID()),this._xScale=d,this._updateXDomainer(),d.broadcaster.registerListener("yDomainAdjustment"+this.getID(),function(){return e._adjustYDomainOnChangeFromX()})),"y"===a&&d&&(this._yScale&&this._yScale.broadcaster.deregisterListener("xDomainAdjustment"+this.getID()),this._yScale=d,this._updateYDomainer(),d.broadcaster.registerListener("xDomainAdjustment"+this.getID(),function(){return e._adjustXDomainOnChangeFromY()})),b.prototype.project.call(this,a,c,d),this},c.prototype.remove=function(){return b.prototype.remove.call(this),this._xScale&&this._xScale.broadcaster.deregisterListener("yDomainAdjustment"+this.getID()),this._yScale&&this._yScale.broadcaster.deregisterListener("xDomainAdjustment"+this.getID()),this},c.prototype.automaticallyAdjustYScaleOverVisiblePoints=function(a){return this._autoAdjustYScaleDomain=a,this._adjustYDomainOnChangeFromX(),this},c.prototype.automaticallyAdjustXScaleOverVisiblePoints=function(a){return this._autoAdjustXScaleDomain=a,this._adjustXDomainOnChangeFromY(),this},c.prototype._generateAttrToProjector=function(){var a=b.prototype._generateAttrToProjector.call(this),c=a.x,d=a.y;return a.defined=function(a,b,e,f){var g=c(a,b,e,f),h=d(a,b,e,f);return null!=g&&g===g&&null!=h&&h===h},a},c.prototype._computeLayout=function(c,d,e,f){b.prototype._computeLayout.call(this,c,d,e,f),this._xScale.range([0,this.width()]),this._yScale.range(this._yScale instanceof a.Scale.Category?[0,this.height()]:[this.height(),0])},c.prototype._updateXDomainer=function(){if(this._xScale instanceof a.Scale.AbstractQuantitative){var b=this._xScale;b._userSetDomainer||b.domainer().pad().nice()}},c.prototype._updateYDomainer=function(){if(this._yScale instanceof a.Scale.AbstractQuantitative){var b=this._yScale;b._userSetDomainer||b.domainer().pad().nice()}},c.prototype.showAllData=function(){this._xScale.autoDomain(),this._autoAdjustYScaleDomain||this._yScale.autoDomain()},c.prototype._adjustYDomainOnChangeFromX=function(){this._projectorsReady()&&this._autoAdjustYScaleDomain&&this._adjustDomainToVisiblePoints(this._xScale,this._yScale,!0)},c.prototype._adjustXDomainOnChangeFromY=function(){this._projectorsReady()&&this._autoAdjustXScaleDomain&&this._adjustDomainToVisiblePoints(this._yScale,this._xScale,!1)},c.prototype._adjustDomainToVisiblePoints=function(b,c,d){if(c instanceof a.Scale.AbstractQuantitative){var e,f=c,g=this._normalizeDatasets(d);if(b instanceof a.Scale.AbstractQuantitative){var h=b.domain();e=function(a){return h[0]<=a&&h[1]>=a}}else{var i=d3.set(b.domain());e=function(a){return i.has(a)}}var j=this._adjustDomainOverVisiblePoints(g,e);if(0===j.length)return;j=f.domainer().computeDomain([j],f),f.domain(j)}},c.prototype._normalizeDatasets=function(b){var c=this,d=this._projections[b?"x":"y"].accessor,e=this._projections[b?"y":"x"].accessor;return a._Util.Methods.flatten(this._datasetKeysInOrder.map(function(a){var b=c._key2PlotDatasetKey.get(a).dataset,f=c._key2PlotDatasetKey.get(a).plotMetadata;return b.data().map(function(a,c){return{a:d(a,c,b.metadata(),f),b:e(a,c,b.metadata(),f)}})}))},c.prototype._adjustDomainOverVisiblePoints=function(b,c){var d=b.filter(function(a){return c(a.a)}).map(function(a){return a.b}),e=[];return 0!==d.length&&(e=[a._Util.Methods.min(d,null),a._Util.Methods.max(d,null)]),e},c.prototype._projectorsReady=function(){return this._projections.x&&this._projections.y},c}(b.AbstractPlot);b.AbstractXYPlot=c}(b=a.Plot||(a.Plot={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(c,d){b.call(this,c,d),this._defaultFillColor=(new a.Scale.Color).range()[0],this.classed("rectangle-plot",!0)}return __extends(c,b),c.prototype._getDrawer=function(b){return new a._Drawer.Rect(b,!0)},c.prototype._generateAttrToProjector=function(){var a=b.prototype._generateAttrToProjector.call(this),c=a.x1,d=a.y1,e=a.x2,f=a.y2;return a.width=function(a,b,d,f){return Math.abs(e(a,b,d,f)-c(a,b,d,f))},a.x=function(a,b,d,f){return Math.min(c(a,b,d,f),e(a,b,d,f))},a.height=function(a,b,c,e){return Math.abs(f(a,b,c,e)-d(a,b,c,e))},a.y=function(b,c,e,g){return Math.max(d(b,c,e,g),f(b,c,e,g))-a.height(b,c,e,g)},delete a.x1,delete a.y1,delete a.x2,delete a.y2,a.fill=a.fill||d3.functor(this._defaultFillColor),a},c.prototype._generateDrawSteps=function(){return[{attrToProjector:this._generateAttrToProjector(),animator:this._getAnimator("rectangles")}]},c}(b.AbstractXYPlot);b.Rectangle=c}(b=a.Plot||(a.Plot={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(c,d){b.call(this,c,d),this._closeDetectionRadius=5,this.classed("scatter-plot",!0),this._defaultFillColor=(new a.Scale.Color).range()[0],this.animator("symbols-reset",new a.Animator.Null),this.animator("symbols",(new a.Animator.Base).duration(250).delay(5))}return __extends(c,b),c.prototype._getDrawer=function(b){return new a._Drawer.Symbol(b)},c.prototype._generateAttrToProjector=function(){var c=b.prototype._generateAttrToProjector.call(this);return c.size=c.size||d3.functor(6),c.opacity=c.opacity||d3.functor(.6),c.fill=c.fill||d3.functor(this._defaultFillColor),c.symbol=c.symbol||function(){return a.SymbolFactories.circle()},c},c.prototype._generateDrawSteps=function(){var a=[];if(this._dataChanged&&this._animate){var b=this._generateAttrToProjector();b.size=function(){return 0},a.push({attrToProjector:b,animator:this._getAnimator("symbols-reset")})}return a.push({attrToProjector:this._generateAttrToProjector(),animator:this._getAnimator("symbols")}),a},c.prototype._getClosestStruckPoint=function(a,b){var c,d,e,f,g=this,h=this._generateAttrToProjector(),i=(h.x,h.y,function(b,c,d,e){var f=h.x(b,c,d,e)-a.x,g=h.y(b,c,d,e)-a.y;return f*f+g*g}),j=!1,k=b*b;if(this._datasetKeysInOrder.forEach(function(a){var b=g._key2PlotDatasetKey.get(a).dataset,l=g._key2PlotDatasetKey.get(a).plotMetadata,m=g._key2PlotDatasetKey.get(a).drawer;m._getRenderArea().selectAll("path").each(function(a,g){var m=i(a,g,b.metadata(),l),n=h.size(a,g,b.metadata(),l)/2;n*n>m?((!j||k>m)&&(c=this,f=g,k=m,d=b.metadata(),e=l),j=!0):!j&&k>m&&(c=this,f=g,k=m,d=b.metadata(),e=l)})}),!c)return{selection:null,pixelPositions:null,data:null};var l=d3.select(c),m=l.data(),n={x:h.x(m[0],f,d,e),y:h.y(m[0],f,d,e)};return{selection:l,pixelPositions:[n],data:m}},c.prototype._isVisibleOnPlot=function(b,c,d){var e={min:0,max:this.width()},f={min:0,max:this.height()},g=d3.transform(d.attr("transform")).translate,h=d[0][0].getBBox(),i={x:h.x+g[0],y:h.y+g[1],width:h.width,height:h.height};return a._Util.Methods.intersectsBBox(e,f,i)},c.prototype._hoverOverComponent=function(a){},c.prototype._hoverOutComponent=function(a){},c.prototype._doHover=function(a){return this._getClosestStruckPoint(a,this._closeDetectionRadius)},c}(b.AbstractXYPlot);b.Scatter=c}(b=a.Plot||(a.Plot={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(c,d,e){b.call(this,c,d),this.classed("grid-plot",!0),c instanceof a.Scale.Category&&c.innerPadding(0).outerPadding(0),d instanceof a.Scale.Category&&d.innerPadding(0).outerPadding(0),this._colorScale=e,this.animator("cells",new a.Animator.Null)}return __extends(c,b),c.prototype.addDataset=function(c,d){return 1===this._datasetKeysInOrder.length?(a._Util.Methods.warn("Only one dataset is supported in Grid plots"),this):(b.prototype.addDataset.call(this,c,d),this)},c.prototype._getDrawer=function(b){return new a._Drawer.Rect(b,!0)},c.prototype.project=function(c,d,e){var f=this;return b.prototype.project.call(this,c,d,e),"x"===c&&(e instanceof a.Scale.Category&&(this.project("x1",function(a,b,c,d){return e.scale(f._projections.x.accessor(a,b,c,d))-e.rangeBand()/2}),this.project("x2",function(a,b,c,d){return e.scale(f._projections.x.accessor(a,b,c,d))+e.rangeBand()/2})),e instanceof a.Scale.AbstractQuantitative&&this.project("x1",function(a,b,c,d){return e.scale(f._projections.x.accessor(a,b,c,d))})),"y"===c&&(e instanceof a.Scale.Category&&(this.project("y1",function(a,b,c,d){return e.scale(f._projections.y.accessor(a,b,c,d))-e.rangeBand()/2}),this.project("y2",function(a,b,c,d){return e.scale(f._projections.y.accessor(a,b,c,d))+e.rangeBand()/2})),e instanceof a.Scale.AbstractQuantitative&&this.project("y1",function(a,b,c,d){return e.scale(f._projections.y.accessor(a,b,c,d))})),"fill"===c&&(this._colorScale=this._projections.fill.scale),this},c.prototype._generateDrawSteps=function(){return[{attrToProjector:this._generateAttrToProjector(),animator:this._getAnimator("cells")}]},c}(b.Rectangle);b.Grid=c}(b=a.Plot||(a.Plot={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(c,d,e){void 0===e&&(e=!0),b.call(this,c,d),this._barAlignmentFactor=.5,this._barLabelFormatter=a.Formatters.identity(),this._barLabelsEnabled=!1,this._hoverMode="point",this._hideBarsIfAnyAreTooWide=!0,this.classed("bar-plot",!0),this._defaultFillColor=(new a.Scale.Color).range()[0],this.animator("bars-reset",new a.Animator.Null),this.animator("bars",new a.Animator.Base),this.animator("baseline",new a.Animator.Null),this._isVertical=e,this.baseline(0)}return __extends(c,b),c.prototype._getDrawer=function(b){return new a._Drawer.Rect(b,this._isVertical)},c.prototype._setup=function(){b.prototype._setup.call(this),this._baseline=this._renderArea.append("line").classed("baseline",!0)},c.prototype.baseline=function(a){return null==a?this._baselineValue:(this._baselineValue=a,this._updateXDomainer(),this._updateYDomainer(),this._render(),this)},c.prototype.barAlignment=function(a){var b=a.toLowerCase(),c=this.constructor._BarAlignmentToFactor;if(void 0===c[b])throw new Error("unsupported bar alignment");return this._barAlignmentFactor=c[b], -this._render(),this},c.prototype.barLabelsEnabled=function(a){return void 0===a?this._barLabelsEnabled:(this._barLabelsEnabled=a,this._render(),this)},c.prototype.barLabelFormatter=function(a){return null==a?this._barLabelFormatter:(this._barLabelFormatter=a,this._render(),this)},c.prototype.getClosestPlotData=function(b){var c=this,d=({min:0,max:this.width()},{min:0,max:this.height()},1/0),e=1/0,f=[],g=[],h=[],i=this._isVertical?b.x:b.y,j=this._isVertical?b.y:b.x,k=.5;return this.datasetOrder().forEach(function(l){var m=c.getAllPlotData(l);m.pixelPoints.forEach(function(l,n){var o=m.data[n],p=m.selection[0][n];if(c._isVisibleOnPlot(o,l,d3.select(p))){var q=0,r=0,s=p.getBBox();if(!a._Util.Methods.intersectsBBox(b.x,b.y,s,k)){var t=c._isVertical?l.x:l.y;q=Math.abs(i-t);var u=c._isVertical?s.y:s.x,v=u+(c._isVertical?s.height:s.width);if(j>=u-k&&v+k>=j)r=0;else{var w=c._isVertical?l.y:l.x;r=Math.abs(j-w)}}(d>q||q===d&&e>r)&&(f=[],g=[],h=[],d=q,e=r),q===d&&r===e&&(f.push(o),g.push(l),h.push(p))}})}),{data:f,pixelPoints:g,selection:d3.selectAll(h)}},c.prototype._isVisibleOnPlot=function(b,c,d){var e={min:0,max:this.width()},f={min:0,max:this.height()},g=d[0][0].getBBox();return a._Util.Methods.intersectsBBox(e,f,g)},c.prototype.getBars=function(a,b){var c=this;if(!this._isSetup)return d3.select();var d=this._datasetKeysInOrder.reduce(function(d,e){return d.concat(c._getBarsFromDataset(e,a,b))},[]);return d3.selectAll(d)},c.prototype._getBarsFromDataset=function(b,c,d){var e=[],f=this._key2PlotDatasetKey.get(b).drawer;return f._getRenderArea().selectAll("rect").each(function(b){a._Util.Methods.intersectsBBox(c,d,this.getBBox())&&e.push(this)}),e},c.prototype._updateDomainer=function(b){if(b instanceof a.Scale.AbstractQuantitative){var c=b;c._userSetDomainer||(null!=this._baselineValue?c.domainer().addPaddingException(this._baselineValue,"BAR_PLOT+"+this.getID()).addIncludedValue(this._baselineValue,"BAR_PLOT+"+this.getID()):c.domainer().removePaddingException("BAR_PLOT+"+this.getID()).removeIncludedValue("BAR_PLOT+"+this.getID()),c.domainer().pad().nice()),c._autoDomainIfAutomaticMode()}},c.prototype._updateYDomainer=function(){this._isVertical?this._updateDomainer(this._yScale):b.prototype._updateYDomainer.call(this)},c.prototype._updateXDomainer=function(){this._isVertical?b.prototype._updateXDomainer.call(this):this._updateDomainer(this._xScale)},c.prototype._additionalPaint=function(b){var c=this,d=this._isVertical?this._yScale:this._xScale,e=d.scale(this._baselineValue),f={x1:this._isVertical?0:e,y1:this._isVertical?e:0,x2:this._isVertical?this.width():e,y2:this._isVertical?e:this.height()};this._getAnimator("baseline").animate(this._baseline,f);var g=this._getDrawersInOrder();g.forEach(function(a){return a.removeLabels()}),this._barLabelsEnabled&&a._Util.Methods.setTimeout(function(){return c._drawLabels()},b)},c.prototype._drawLabels=function(){var a=this,b=this._getDrawersInOrder(),c=this._generateAttrToProjector(),d=this._getDataToDraw();this._datasetKeysInOrder.forEach(function(e,f){return b[f].drawText(d.get(e),c,a._key2PlotDatasetKey.get(e).dataset.metadata(),a._key2PlotDatasetKey.get(e).plotMetadata)}),this._hideBarsIfAnyAreTooWide&&b.some(function(a){return a._getIfLabelsTooWide()})&&b.forEach(function(a){return a.removeLabels()})},c.prototype._generateDrawSteps=function(){var a=[];if(this._dataChanged&&this._animate){var b=this._generateAttrToProjector(),c=this._isVertical?this._yScale:this._xScale,d=c.scale(this._baselineValue),e=this._isVertical?"y":"x",f=this._isVertical?"height":"width";b[e]=function(){return d},b[f]=function(){return 0},a.push({attrToProjector:b,animator:this._getAnimator("bars-reset")})}return a.push({attrToProjector:this._generateAttrToProjector(),animator:this._getAnimator("bars")}),a},c.prototype._generateAttrToProjector=function(){var c=this,d=b.prototype._generateAttrToProjector.call(this),e=this._isVertical?this._yScale:this._xScale,f=this._isVertical?this._xScale:this._yScale,g=this._isVertical?"y":"x",h=this._isVertical?"x":"y",i=e.scale(this._baselineValue),j=d[h],k=d.width;null==k&&(k=function(){return c._getBarPixelWidth()});var l=d[g],m=function(a,b,c,d){return Math.abs(i-l(a,b,c,d))};d.width=this._isVertical?k:m,d.height=this._isVertical?m:k,f instanceof a.Scale.Category?d[h]=function(a,b,c,d){return j(a,b,c,d)-k(a,b,c,d)/2}:d[h]=function(a,b,d,e){return j(a,b,d,e)-k(a,b,d,e)*c._barAlignmentFactor},d[g]=function(a,b,c,d){var e=l(a,b,c,d);return e>i?i:e};var n=this._projections[g].accessor;return this.barLabelsEnabled&&this.barLabelFormatter&&(d.label=function(a,b,d,e){return c._barLabelFormatter(n(a,b,d,e))},d.positive=function(a,b,c,d){return l(a,b,c,d)<=i}),d.fill=d.fill||d3.functor(this._defaultFillColor),d},c.prototype._getBarPixelWidth=function(){var b,d=this,e=this._isVertical?this._xScale:this._yScale;if(e instanceof a.Scale.Category)b=e.rangeBand();else{var f=this._isVertical?this._projections.x.accessor:this._projections.y.accessor,g=d3.set(a._Util.Methods.flatten(this._datasetKeysInOrder.map(function(a){var b=d._key2PlotDatasetKey.get(a).dataset,c=d._key2PlotDatasetKey.get(a).plotMetadata;return b.data().map(function(a,d){return f(a,d,b.metadata(),c).valueOf()})}))).values().map(function(a){return+a});g.sort(function(a,b){return a-b});var h=d3.pairs(g),i=this._isVertical?this.width():this.height();b=a._Util.Methods.min(h,function(a,b){return Math.abs(e.scale(a[1])-e.scale(a[0]))},i*c._SINGLE_BAR_DIMENSION_RATIO);var j=g.map(function(a){return e.scale(a)}),k=a._Util.Methods.min(j,0);0!==this._barAlignmentFactor&&k>0&&(b=Math.min(b,k/this._barAlignmentFactor));var l=a._Util.Methods.max(j,0);if(1!==this._barAlignmentFactor&&i>l){var m=i-l;b=Math.min(b,m/(1-this._barAlignmentFactor))}b*=c._BAR_WIDTH_RATIO}return b},c.prototype.hoverMode=function(a){if(null==a)return this._hoverMode;var b=a.toLowerCase();if("point"!==b&&"line"!==b)throw new Error(a+" is not a valid hover mode");return this._hoverMode=b,this},c.prototype._clearHoverSelection=function(){this._getDrawersInOrder().forEach(function(a,b){a._getRenderArea().selectAll("rect").classed("not-hovered hovered",!1)})},c.prototype._hoverOverComponent=function(a){},c.prototype._hoverOutComponent=function(a){this._clearHoverSelection()},c.prototype._doHover=function(b){var c=this,d=b.x,e=b.y;if("line"===this._hoverMode){var f={min:-(1/0),max:1/0};this._isVertical?e=f:d=f}var g=a._Util.Methods.parseExtent(d),h=a._Util.Methods.parseExtent(e),i=[],j=[],k=this._generateAttrToProjector();this._datasetKeysInOrder.forEach(function(a){var b=c._key2PlotDatasetKey.get(a).dataset,d=c._key2PlotDatasetKey.get(a).plotMetadata,e=c._getBarsFromDataset(a,g,h);d3.selectAll(e).each(function(a,e){j.push(c._isVertical?{x:k.x(a,e,b.metadata(),d)+k.width(a,e,b.metadata(),d)/2,y:k.y(a,e,b.metadata(),d)+(k.positive(a,e,b.metadata(),d)?0:k.height(a,e,b.metadata(),d))}:{x:k.x(a,e,b.metadata(),d)+k.height(a,e,b.metadata(),d)/2,y:k.y(a,e,b.metadata(),d)+(k.positive(a,e,b.metadata(),d)?0:k.width(a,e,b.metadata(),d))})}),i=i.concat(e)});var l=d3.selectAll(i);return l.empty()?(this._clearHoverSelection(),{data:null,pixelPositions:null,selection:null}):(this._getDrawersInOrder().forEach(function(a,b){a._getRenderArea().selectAll("rect").classed({hovered:!1,"not-hovered":!0})}),l.classed({hovered:!0,"not-hovered":!1}),{data:l.data(),pixelPositions:j,selection:l})},c.prototype._getAllPlotData=function(a){var c=b.prototype._getAllPlotData.call(this,a),d=(this._isVertical?this._yScale:this._xScale,(this._isVertical?this._yScale:this._xScale).scale(this.baseline())),e=this._isVertical,f=this._barAlignmentFactor;return c.selection.each(function(a,b){var g=d3.select(this);e&&Math.floor(+g.attr("y"))>=Math.floor(d)?c.pixelPoints[b].y+=+g.attr("height"):!e&&Math.floor(+g.attr("x"))b&&b||c>0&&c||0,e=this._yScale.scale(d);return function(a,b,c,d){return e}},c.prototype._generateDrawSteps=function(){var a=[];if(this._dataChanged&&this._animate){var b=this._generateAttrToProjector();b.y=this._getResetYFunction(),a.push({attrToProjector:b,animator:this._getAnimator("reset")})}return a.push({attrToProjector:this._generateAttrToProjector(),animator:this._getAnimator("main")}),a},c.prototype._generateAttrToProjector=function(){var a=this,c=b.prototype._generateAttrToProjector.call(this),d=this._wholeDatumAttributes(),e=function(a){return-1===d.indexOf(a)},f=d3.keys(c).filter(e);f.forEach(function(a){var b=c[a];c[a]=function(a,c,d,e){return a.length>0?b(a[0],c,d,e):null}});var g=c.x,h=c.y;return c.defined=function(b,c,d,e){return a._rejectNullsAndNaNs(b,c,d,e,g)&&a._rejectNullsAndNaNs(b,c,d,e,h)},c.stroke=c.stroke||d3.functor(this._defaultStrokeColor),c["stroke-width"]=c["stroke-width"]||d3.functor("2px"),c},c.prototype._wholeDatumAttributes=function(){return["x","y"]},c.prototype._getClosestWithinRange=function(a,b){var c,d,e=this,f=this._generateAttrToProjector(),g=f.x,h=f.y,i=function(b,c,d,e){var f=+g(b,c,d,e)-a.x,i=+h(b,c,d,e)-a.y;return f*f+i*i},j=b*b;return this._datasetKeysInOrder.forEach(function(a){var b=e._key2PlotDatasetKey.get(a).dataset,f=e._key2PlotDatasetKey.get(a).plotMetadata;b.data().forEach(function(a,e){var k=i(a,e,b.metadata(),f);j>k&&(c=a,d={x:g(a,e,b.metadata(),f),y:h(a,e,b.metadata(),f)},j=k)})}),{closestValue:c,closestPoint:d}},c.prototype._getAllPlotData=function(a){var b=this,c=[],d=[],e=[];return a.forEach(function(a){var f=b._key2PlotDatasetKey.get(a);if(null!=f){var g=f.drawer;f.dataset.data().forEach(function(a,b){var e=g._getPixelPoint(a,b);e.x===e.x&&e.y===e.y&&(c.push(a),d.push(e))}),f.dataset.data().length>0&&e.push(g._getSelection(0).node())}}),{data:c,pixelPoints:d,selection:d3.selectAll(e)}},c.prototype.getClosestPlotData=function(a){var b=this,c=1/0,d=1/0,e=[],f=[],g=[];return this.datasetOrder().forEach(function(h){var i=b.getAllPlotData(h);i.pixelPoints.forEach(function(h,j){var k=i.data[j],l=i.selection[0][0];if(b._isVisibleOnPlot(k,h,d3.select(l))){var m=Math.abs(a.x-h.x),n=Math.abs(a.y-h.y);(c>m||m===c&&d>n)&&(e=[],f=[],g=[],c=m,d=n),m===c&&n===d&&(e.push(k),f.push(h),g.push(l))}})}),{data:e,pixelPoints:f,selection:d3.selectAll(g)}},c.prototype._hoverOverComponent=function(a){},c.prototype._hoverOutComponent=function(a){},c.prototype._doHover=function(a){var b=this._getClosestWithinRange(a,this._hoverDetectionRadius),c=b.closestValue;if(void 0===c)return{data:null,pixelPositions:null,selection:null};var d=b.closestPoint;return this._hoverTarget.attr({cx:b.closestPoint.x,cy:b.closestPoint.y}),{data:[c],pixelPositions:[d],selection:this._hoverTarget}},c}(b.AbstractXYPlot);b.Line=c}(b=a.Plot||(a.Plot={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(c,d){b.call(this,c,d),this.classed("area-plot",!0),this.project("y0",0,d),this.animator("reset",new a.Animator.Null),this.animator("main",(new a.Animator.Base).duration(600).easing("exp-in-out")),this._defaultFillColor=(new a.Scale.Color).range()[0]}return __extends(c,b),c.prototype._onDatasetUpdate=function(){b.prototype._onDatasetUpdate.call(this),null!=this._yScale&&this._updateYDomainer()},c.prototype._getDrawer=function(b){return new a._Drawer.Area(b)},c.prototype._updateYDomainer=function(){var c=this;b.prototype._updateYDomainer.call(this);var d,e=this._projections.y0,f=e&&e.accessor;if(null!=f){var g=this.datasets().map(function(a){return a._getExtent(f,c._yScale._typeCoercer)}),h=a._Util.Methods.flatten(g),i=a._Util.Methods.uniq(h);1===i.length&&(d=i[0])}this._yScale._userSetDomainer||(null!=d?this._yScale.domainer().addPaddingException(d,"AREA_PLOT+"+this.getID()):this._yScale.domainer().removePaddingException("AREA_PLOT+"+this.getID()),this._yScale._autoDomainIfAutomaticMode())},c.prototype.project=function(a,c,d){return b.prototype.project.call(this,a,c,d),"y0"===a&&this._updateYDomainer(),this},c.prototype._getResetYFunction=function(){return this._generateAttrToProjector().y0},c.prototype._wholeDatumAttributes=function(){var a=b.prototype._wholeDatumAttributes.call(this);return a.push("y0"),a},c.prototype._generateAttrToProjector=function(){var a=b.prototype._generateAttrToProjector.call(this);return a["fill-opacity"]=a["fill-opacity"]||d3.functor(.25),a.fill=a.fill||d3.functor(this._defaultFillColor),a.stroke=a.stroke||d3.functor(this._defaultFillColor),a},c}(b.Line);b.Area=c}(b=a.Plot||(a.Plot={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(a,c,d){void 0===d&&(d=!0),b.call(this,a,c,d)}return __extends(c,b),c.prototype._generateAttrToProjector=function(){var a=this,c=b.prototype._generateAttrToProjector.call(this),d=this._makeInnerScale(),e=function(a,b){return d.rangeBand()};c.width=this._isVertical?e:c.width,c.height=this._isVertical?c.height:e;var f=c.x,g=c.y;return c.x=function(b,c,d,e){return a._isVertical?f(b,c,d,e)+e.position:f(b,d,d,e)},c.y=function(b,c,d,e){return a._isVertical?g(b,c,d,e):g(b,c,d,e)+e.position},c},c.prototype._updateClusterPosition=function(){var a=this,b=this._makeInnerScale();this._datasetKeysInOrder.forEach(function(c){var d=a._key2PlotDatasetKey.get(c).plotMetadata;d.position=b.scale(c)-b.rangeBand()/2})},c.prototype._makeInnerScale=function(){var b=new a.Scale.Category;if(b.domain(this._datasetKeysInOrder),this._projections.width){var c=this._projections.width,d=c.accessor,e=c.scale,f=e?function(a,b,c,f){return e.scale(d(a,b,c,f))}:d;b.range([0,f(null,0,null,null)])}else b.range([0,this._getBarPixelWidth()]);return b},c.prototype._getDataToDraw=function(){return this._updateClusterPosition(),b.prototype._getDataToDraw.call(this)},c.prototype._getPlotMetadataForDataset=function(a){var c=b.prototype._getPlotMetadataForDataset.call(this,a);return c.position=0,c},c}(b.Bar);b.ClusteredBar=c}(b=a.Plot||(a.Plot={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(){b.apply(this,arguments),this._stackedExtent=[0,0]}return __extends(c,b),c.prototype._getPlotMetadataForDataset=function(a){var c=b.prototype._getPlotMetadataForDataset.call(this,a);return c.offsets=d3.map(),c},c.prototype.project=function(a,c,d){return b.prototype.project.call(this,a,c,d),this._projections.x&&this._projections.y&&("x"===a||"y"===a)&&this._updateStackOffsets(),this},c.prototype._onDatasetUpdate=function(){this._projectorsReady()&&this._updateStackOffsets(),b.prototype._onDatasetUpdate.call(this)},c.prototype._updateStackOffsets=function(){var b=this._generateDefaultMapArray(),c=this._getDomainKeys(),d=b.map(function(b){return a._Util.Methods.populateMap(c,function(a){return{key:a,value:Math.max(0,b.get(a).value)||0}})}),e=b.map(function(b){return a._Util.Methods.populateMap(c,function(a){return{key:a,value:Math.min(b.get(a).value,0)||0}})});this._setDatasetStackOffsets(this._stack(d),this._stack(e)),this._updateStackExtents()},c.prototype._updateStackExtents=function(){var b=this,c=(this.datasets(),this._valueAccessor()),d=this._keyAccessor(),e=a._Util.Methods.max(this._datasetKeysInOrder,function(e){var f=b._key2PlotDatasetKey.get(e).dataset,g=b._key2PlotDatasetKey.get(e).plotMetadata;return a._Util.Methods.max(f.data(),function(a,b){return+c(a,b,f.metadata(),g)+g.offsets.get(d(a,b,f.metadata(),g))},0)},0),f=a._Util.Methods.min(this._datasetKeysInOrder,function(e){var f=b._key2PlotDatasetKey.get(e).dataset,g=b._key2PlotDatasetKey.get(e).plotMetadata;return a._Util.Methods.min(f.data(),function(a,b){return+c(a,b,f.metadata(),g)+g.offsets.get(d(a,b,f.metadata(),g))},0)},0);this._stackedExtent=[Math.min(f,0),Math.max(0,e)]},c.prototype._stack=function(a){var b=this,c=function(a,b,c){a.offset=b};return d3.layout.stack().x(function(a){return a.key}).y(function(a){return+a.value}).values(function(a){return b._getDomainKeys().map(function(b){return a.get(b)})}).out(c)(a),a},c.prototype._setDatasetStackOffsets=function(a,b){var c=this,d=this._keyAccessor(),e=this._valueAccessor();this._datasetKeysInOrder.forEach(function(f,g){var h=c._key2PlotDatasetKey.get(f).dataset,i=c._key2PlotDatasetKey.get(f).plotMetadata,j=a[g],k=b[g],l=h.data().every(function(a,b){return e(a,b,h.metadata(),i)<=0});h.data().forEach(function(a,b){var c,f=d(a,b,h.metadata(),i),g=j.get(f).offset,m=k.get(f).offset,n=e(a,b,h.metadata(),i);c=+n?n>0?g:m:l?m:g,i.offsets.set(f,c)})})},c.prototype._getDomainKeys=function(){var a=this,b=this._keyAccessor(),c=d3.set();return this._datasetKeysInOrder.forEach(function(d){var e=a._key2PlotDatasetKey.get(d).dataset,f=a._key2PlotDatasetKey.get(d).plotMetadata;e.data().forEach(function(a,d){c.add(b(a,d,e.metadata(),f))})}),c.values()},c.prototype._generateDefaultMapArray=function(){var b=this,c=this._keyAccessor(),d=this._valueAccessor(),e=this._getDomainKeys(),f=this._datasetKeysInOrder.map(function(){return a._Util.Methods.populateMap(e,function(a){return{key:a,value:0}})});return this._datasetKeysInOrder.forEach(function(a,e){var g=b._key2PlotDatasetKey.get(a).dataset,h=b._key2PlotDatasetKey.get(a).plotMetadata;g.data().forEach(function(a,b){var i=c(a,b,g.metadata(),h),j=d(a,b,g.metadata(),h);f[e].set(i,{key:i,value:j})})}),f},c.prototype._updateScaleExtents=function(){b.prototype._updateScaleExtents.call(this);var a=this._isVertical?this._yScale:this._xScale;a&&(this._isAnchored&&this._stackedExtent.length>0?a._updateExtent(this.getID().toString(),"_PLOTTABLE_PROTECTED_FIELD_STACK_EXTENT",this._stackedExtent):a._removeExtent(this.getID().toString(),"_PLOTTABLE_PROTECTED_FIELD_STACK_EXTENT"))},c.prototype._normalizeDatasets=function(b){var c=this,d=this._projections[b?"x":"y"].accessor,e=this._projections[b?"y":"x"].accessor,f=function(a,f,g,h){var i=d(a,f,g,h);return(c._isVertical?!b:b)&&(i+=h.offsets.get(e(a,f,g,h))),i},g=function(a,f,g,h){var i=e(a,f,g,h);return(c._isVertical?b:!b)&&(i+=h.offsets.get(d(a,f,g,h))),i};return a._Util.Methods.flatten(this._datasetKeysInOrder.map(function(a){var b=c._key2PlotDatasetKey.get(a).dataset,d=c._key2PlotDatasetKey.get(a).plotMetadata;return b.data().map(function(a,c){return{a:f(a,c,b.metadata(),d),b:g(a,c,b.metadata(),d)}})}))},c.prototype._keyAccessor=function(){return this._isVertical?this._projections.x.accessor:this._projections.y.accessor},c.prototype._valueAccessor=function(){return this._isVertical?this._projections.y.accessor:this._projections.x.accessor},c}(b.AbstractXYPlot);b.AbstractStacked=c}(b=a.Plot||(a.Plot={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(c){function d(a,b){c.call(this,a,b),this._baselineValue=0,this.classed("area-plot",!0),this._isVertical=!0}return __extends(d,c),d.prototype._getDrawer=function(b){return new a._Drawer.Area(b).drawLine(!1)},d.prototype._getAnimator=function(b){return new a.Animator.Null},d.prototype._setup=function(){c.prototype._setup.call(this),this._baseline=this._renderArea.append("line").classed("baseline",!0)},d.prototype._additionalPaint=function(){var a=this._yScale.scale(this._baselineValue),b={x1:0,y1:a,x2:this.width(),y2:a};this._getAnimator("baseline").animate(this._baseline,b)},d.prototype._updateYDomainer=function(){c.prototype._updateYDomainer.call(this);var a=this._yScale;a._userSetDomainer||(a.domainer().addPaddingException(0,"STACKED_AREA_PLOT+"+this.getID()).addIncludedValue(0,"STACKED_AREA_PLOT+"+this.getID()),a._autoDomainIfAutomaticMode())},d.prototype.project=function(a,d,e){return c.prototype.project.call(this,a,d,e),b.AbstractStacked.prototype.project.apply(this,[a,d,e]),this},d.prototype._onDatasetUpdate=function(){return c.prototype._onDatasetUpdate.call(this),b.AbstractStacked.prototype._onDatasetUpdate.apply(this),this},d.prototype._generateAttrToProjector=function(){var a=this,b=c.prototype._generateAttrToProjector.call(this);null==this._projections["fill-opacity"]&&(b["fill-opacity"]=d3.functor(1));var d=this._projections.y.accessor,e=this._projections.x.accessor;return b.y=function(b,c,f,g){return a._yScale.scale(+d(b,c,f,g)+g.offsets.get(e(b,c,f,g)))},b.y0=function(b,c,d,f){return a._yScale.scale(f.offsets.get(e(b,c,d,f)))},b},d.prototype._wholeDatumAttributes=function(){return["x","y","defined"]},d.prototype._updateStackOffsets=function(){var c=this;if(this._projectorsReady()){var d=this._getDomainKeys(),e=this._isVertical?this._projections.x.accessor:this._projections.y.accessor,f=this._datasetKeysInOrder.map(function(a){var b=c._key2PlotDatasetKey.get(a).dataset,d=c._key2PlotDatasetKey.get(a).plotMetadata;return d3.set(b.data().map(function(a,c){return e(a,c,b.metadata(),d).toString()})).values()});f.some(function(a){return a.length!==d.length})&&a._Util.Methods.warn("the domains across the datasets are not the same. Plot may produce unintended behavior."),b.AbstractStacked.prototype._updateStackOffsets.call(this)}},d.prototype._updateStackExtents=function(){b.AbstractStacked.prototype._updateStackExtents.call(this)},d.prototype._stack=function(a){return b.AbstractStacked.prototype._stack.call(this,a)},d.prototype._setDatasetStackOffsets=function(a,c){b.AbstractStacked.prototype._setDatasetStackOffsets.call(this,a,c)},d.prototype._getDomainKeys=function(){return b.AbstractStacked.prototype._getDomainKeys.call(this)},d.prototype._generateDefaultMapArray=function(){return b.AbstractStacked.prototype._generateDefaultMapArray.call(this)},d.prototype._updateScaleExtents=function(){b.AbstractStacked.prototype._updateScaleExtents.call(this)},d.prototype._keyAccessor=function(){return b.AbstractStacked.prototype._keyAccessor.call(this)},d.prototype._valueAccessor=function(){return b.AbstractStacked.prototype._valueAccessor.call(this)},d.prototype._getPlotMetadataForDataset=function(a){return b.AbstractStacked.prototype._getPlotMetadataForDataset.call(this,a)},d.prototype._normalizeDatasets=function(a){return b.AbstractStacked.prototype._normalizeDatasets.call(this,a)},d}(b.Area);b.StackedArea=c}(b=a.Plot||(a.Plot={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(c){function d(a,b,d){void 0===d&&(d=!0),c.call(this,a,b,d)}return __extends(d,c),d.prototype._getAnimator=function(b){if(this._animate&&this._animateOnNextRender){if(this.animator(b))return this.animator(b);if("stacked-bar"===b){var c=this._isVertical?this._yScale:this._xScale,d=c.scale(this.baseline());return new a.Animator.MovingRect(d,this._isVertical)}}return new a.Animator.Null},d.prototype._generateAttrToProjector=function(){var a=this,b=c.prototype._generateAttrToProjector.call(this),d=this._isVertical?"y":"x",e=this._isVertical?"x":"y",f=this._isVertical?this._yScale:this._xScale,g=this._projections[d].accessor,h=this._projections[e].accessor,i=function(a,b,c,d){return f.scale(d.offsets.get(h(a,b,c,d)))},j=function(a,b,c,d){return f.scale(+g(a,b,c,d)+d.offsets.get(h(a,b,c,d)))},k=function(a,b,c,d){return Math.abs(j(a,b,c,d)-i(a,b,c,d))},l=function(a,b,c,d){return+g(a,b,c,d)<0?i(a,b,c,d):j(a,b,c,d)};return b[d]=function(b,c,d,e){return a._isVertical?l(b,c,d,e):l(b,c,d,e)-k(b,c,d,e)},b},d.prototype._generateDrawSteps=function(){return[{attrToProjector:this._generateAttrToProjector(),animator:this._getAnimator("stacked-bar")}]},d.prototype.project=function(a,d,e){return c.prototype.project.call(this,a,d,e),b.AbstractStacked.prototype.project.apply(this,[a,d,e]),this},d.prototype._onDatasetUpdate=function(){return c.prototype._onDatasetUpdate.call(this),b.AbstractStacked.prototype._onDatasetUpdate.apply(this),this},d.prototype._getPlotMetadataForDataset=function(a){return b.AbstractStacked.prototype._getPlotMetadataForDataset.call(this,a)},d.prototype._normalizeDatasets=function(a){return b.AbstractStacked.prototype._normalizeDatasets.call(this,a)},d.prototype._updateStackOffsets=function(){b.AbstractStacked.prototype._updateStackOffsets.call(this)},d.prototype._updateStackExtents=function(){b.AbstractStacked.prototype._updateStackExtents.call(this)},d.prototype._stack=function(a){return b.AbstractStacked.prototype._stack.call(this,a)},d.prototype._setDatasetStackOffsets=function(a,c){b.AbstractStacked.prototype._setDatasetStackOffsets.call(this,a,c)},d.prototype._getDomainKeys=function(){return b.AbstractStacked.prototype._getDomainKeys.call(this)},d.prototype._generateDefaultMapArray=function(){return b.AbstractStacked.prototype._generateDefaultMapArray.call(this)},d.prototype._updateScaleExtents=function(){b.AbstractStacked.prototype._updateScaleExtents.call(this)},d.prototype._keyAccessor=function(){return b.AbstractStacked.prototype._keyAccessor.call(this)},d.prototype._valueAccessor=function(){return b.AbstractStacked.prototype._valueAccessor.call(this)},d}(b.Bar);b.StackedBar=c}(b=a.Plot||(a.Plot={}))}(Plottable||(Plottable={}));var Plottable;!function(a){var b;!function(a){}(b=a.Animator||(a.Animator={}))}(Plottable||(Plottable={}));var Plottable;!function(a){var b;!function(a){var b=function(){function a(){}return a.prototype.getTiming=function(a){return 0},a.prototype.animate=function(a,b){return a.attr(b)},a}();a.Null=b}(b=a.Animator||(a.Animator={}))}(Plottable||(Plottable={}));var Plottable;!function(a){var b;!function(a){var b=function(){function a(){this._duration=a.DEFAULT_DURATION_MILLISECONDS,this._delay=a.DEFAULT_DELAY_MILLISECONDS,this._easing=a.DEFAULT_EASING,this._maxIterativeDelay=a.DEFAULT_MAX_ITERATIVE_DELAY_MILLISECONDS,this._maxTotalDuration=a.DEFAULT_MAX_TOTAL_DURATION_MILLISECONDS}return a.prototype.getTiming=function(a){var b=Math.max(this.maxTotalDuration()-this.duration(),0),c=Math.min(this.maxIterativeDelay(),b/Math.max(a-1,1)),d=c*a+this.delay()+this.duration();return d},a.prototype.animate=function(a,b){var c=this,d=a[0].length,e=Math.max(this.maxTotalDuration()-this.duration(),0),f=Math.min(this.maxIterativeDelay(),e/Math.max(d-1,1));return a.transition().ease(this.easing()).duration(this.duration()).delay(function(a,b){return c.delay()+f*b}).attr(b)},a.prototype.duration=function(a){return null==a?this._duration:(this._duration=a,this)},a.prototype.delay=function(a){return null==a?this._delay:(this._delay=a,this)},a.prototype.easing=function(a){return null==a?this._easing:(this._easing=a,this)},a.prototype.maxIterativeDelay=function(a){return null==a?this._maxIterativeDelay:(this._maxIterativeDelay=a,this)},a.prototype.maxTotalDuration=function(a){return null==a?this._maxTotalDuration:(this._maxTotalDuration=a,this)},a.DEFAULT_DURATION_MILLISECONDS=300,a.DEFAULT_DELAY_MILLISECONDS=0,a.DEFAULT_MAX_ITERATIVE_DELAY_MILLISECONDS=15,a.DEFAULT_MAX_TOTAL_DURATION_MILLISECONDS=600,a.DEFAULT_EASING="exp-out",a}();a.Base=b}(b=a.Animator||(a.Animator={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(a){var b=function(a){function b(b,c){void 0===b&&(b=!0),void 0===c&&(c=!1),a.call(this),this.isVertical=b,this.isReverse=c}return __extends(b,a),b.prototype.animate=function(c,d){var e={};return b.ANIMATED_ATTRIBUTES.forEach(function(a){return e[a]=d[a]}),e[this._getMovingAttr()]=this._startMovingProjector(d),e[this._getGrowingAttr()]=function(){return 0},c.attr(e),a.prototype.animate.call(this,c,d)},b.prototype._startMovingProjector=function(a){if(this.isVertical===this.isReverse)return a[this._getMovingAttr()];var b=a[this._getMovingAttr()],c=a[this._getGrowingAttr()];return function(a,d,e,f){return b(a,d,e,f)+c(a,d,e,f)}},b.prototype._getGrowingAttr=function(){return this.isVertical?"height":"width"},b.prototype._getMovingAttr=function(){return this.isVertical?"y":"x"},b.ANIMATED_ATTRIBUTES=["height","width","x","y","fill"],b}(a.Base);a.Rect=b}(b=a.Animator||(a.Animator={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(a){var b=function(a){function b(b,c){void 0===c&&(c=!0),a.call(this,c),this.startPixelValue=b}return __extends(b,a),b.prototype._startMovingProjector=function(a){return d3.functor(this.startPixelValue)},b}(a.Rect);a.MovingRect=b}(b=a.Animator||(a.Animator={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(a){function b(){a.apply(this,arguments),this._event2Callback={},this._broadcasters=[],this._connected=!1}return __extends(b,a),b.prototype._hasNoListeners=function(){return this._broadcasters.every(function(a){return 0===a.getListenerKeys().length})},b.prototype._connect=function(){var a=this;this._connected||(Object.keys(this._event2Callback).forEach(function(b){var c=a._event2Callback[b];document.addEventListener(b,c)}),this._connected=!0)},b.prototype._disconnect=function(){var a=this;this._connected&&this._hasNoListeners()&&(Object.keys(this._event2Callback).forEach(function(b){var c=a._event2Callback[b];document.removeEventListener(b,c)}),this._connected=!1)},b.prototype._getWrappedCallback=function(a){return function(){return a()}},b.prototype._setCallback=function(a,b,c){null===c?(a.deregisterListener(b),this._disconnect()):(this._connect(),a.registerListener(b,this._getWrappedCallback(c)))},b}(a.Core.PlottableObject);b.AbstractDispatcher=c}(b=a.Dispatcher||(a.Dispatcher={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(c){var d=this;b.call(this),this.translator=a._Util.ClientToSVGTranslator.getTranslator(c),this._lastMousePosition={x:-1,y:-1},this._moveBroadcaster=new a.Core.Broadcaster(this);var e=function(a){ -return d._measureAndBroadcast(a,d._moveBroadcaster)};this._event2Callback.mouseover=e,this._event2Callback.mousemove=e,this._event2Callback.mouseout=e,this._downBroadcaster=new a.Core.Broadcaster(this),this._event2Callback.mousedown=function(a){return d._measureAndBroadcast(a,d._downBroadcaster)},this._upBroadcaster=new a.Core.Broadcaster(this),this._event2Callback.mouseup=function(a){return d._measureAndBroadcast(a,d._upBroadcaster)},this._wheelBroadcaster=new a.Core.Broadcaster(this),this._event2Callback.wheel=function(a){return d._measureAndBroadcast(a,d._wheelBroadcaster)},this._dblClickBroadcaster=new a.Core.Broadcaster(this),this._event2Callback.dblclick=function(a){return d._measureAndBroadcast(a,d._dblClickBroadcaster)},this._broadcasters=[this._moveBroadcaster,this._downBroadcaster,this._upBroadcaster,this._wheelBroadcaster,this._dblClickBroadcaster]}return __extends(c,b),c.getDispatcher=function(b){var d=a._Util.DOM.getBoundingSVG(b),e=d[c._DISPATCHER_KEY];return null==e&&(e=new c(d),d[c._DISPATCHER_KEY]=e),e},c.prototype._getWrappedCallback=function(a){return function(b,c,d){return a(c,d)}},c.prototype.onMouseMove=function(a,b){return this._setCallback(this._moveBroadcaster,a,b),this},c.prototype.onMouseDown=function(a,b){return this._setCallback(this._downBroadcaster,a,b),this},c.prototype.onMouseUp=function(a,b){return this._setCallback(this._upBroadcaster,a,b),this},c.prototype.onWheel=function(a,b){return this._setCallback(this._wheelBroadcaster,a,b),this},c.prototype.onDblClick=function(a,b){return this._setCallback(this._dblClickBroadcaster,a,b),this},c.prototype._measureAndBroadcast=function(a,b){var c=this.translator.computePosition(a.clientX,a.clientY);null!=c&&(this._lastMousePosition=c,b.broadcast(this.getLastMousePosition(),a))},c.prototype.getLastMousePosition=function(){return this._lastMousePosition},c._DISPATCHER_KEY="__Plottable_Dispatcher_Mouse",c}(b.AbstractDispatcher);b.Mouse=c}(b=a.Dispatcher||(a.Dispatcher={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(c){var d=this;b.call(this),this.translator=a._Util.ClientToSVGTranslator.getTranslator(c),this._startBroadcaster=new a.Core.Broadcaster(this),this._event2Callback.touchstart=function(a){return d._measureAndBroadcast(a,d._startBroadcaster)},this._moveBroadcaster=new a.Core.Broadcaster(this),this._event2Callback.touchmove=function(a){return d._measureAndBroadcast(a,d._moveBroadcaster)},this._endBroadcaster=new a.Core.Broadcaster(this),this._event2Callback.touchend=function(a){return d._measureAndBroadcast(a,d._endBroadcaster)},this._broadcasters=[this._moveBroadcaster,this._startBroadcaster,this._endBroadcaster]}return __extends(c,b),c.getDispatcher=function(b){var d=a._Util.DOM.getBoundingSVG(b),e=d[c._DISPATCHER_KEY];return null==e&&(e=new c(d),d[c._DISPATCHER_KEY]=e),e},c.prototype._getWrappedCallback=function(a){return function(b,c,d,e){return a(c,d,e)}},c.prototype.onTouchStart=function(a,b){return this._setCallback(this._startBroadcaster,a,b),this},c.prototype.onTouchMove=function(a,b){return this._setCallback(this._moveBroadcaster,a,b),this},c.prototype.onTouchEnd=function(a,b){return this._setCallback(this._endBroadcaster,a,b),this},c.prototype._measureAndBroadcast=function(a,b){for(var c=a.changedTouches,d={},e=[],f=0;f0&&b.broadcast(e,d,a)},c._DISPATCHER_KEY="__Plottable_Dispatcher_Touch",c}(b.AbstractDispatcher);b.Touch=c}(b=a.Dispatcher||(a.Dispatcher={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(){var c=this;b.call(this),this._event2Callback.keydown=function(a){return c._processKeydown(a)},this._keydownBroadcaster=new a.Core.Broadcaster(this),this._broadcasters=[this._keydownBroadcaster]}return __extends(c,b),c.getDispatcher=function(){var a=document[c._DISPATCHER_KEY];return null==a&&(a=new c,document[c._DISPATCHER_KEY]=a),a},c.prototype._getWrappedCallback=function(a){return function(b,c){return a(c.keyCode,c)}},c.prototype.onKeyDown=function(a,b){return this._setCallback(this._keydownBroadcaster,a,b),this},c.prototype._processKeydown=function(a){this._keydownBroadcaster.broadcast(a)},c._DISPATCHER_KEY="__Plottable_Dispatcher_Key",c}(b.AbstractDispatcher);b.Key=c}(b=a.Dispatcher||(a.Dispatcher={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(a){function b(){a.apply(this,arguments)}return __extends(b,a),b.prototype._anchor=function(a,b){this._componentToListenTo=a,this._hitBox=b},b.prototype._requiresHitbox=function(){return!1},b.prototype._translateToComponentSpace=function(a){var b=this._componentToListenTo.originToSVG();return{x:a.x-b.x,y:a.y-b.y}},b.prototype._isInsideComponent=function(a){return 0<=a.x&&0<=a.y&&a.x<=this._componentToListenTo.width()&&a.y<=this._componentToListenTo.height()},b}(a.Core.PlottableObject);b.AbstractInteraction=c}(b=a.Interaction||(a.Interaction={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(){b.apply(this,arguments),this._clickedDown=!1}return __extends(c,b),c.prototype._anchor=function(c,d){var e=this;b.prototype._anchor.call(this,c,d),this._mouseDispatcher=a.Dispatcher.Mouse.getDispatcher(c.content().node()),this._mouseDispatcher.onMouseDown("Interaction.Click"+this.getID(),function(a){return e._handleClickDown(a)}),this._mouseDispatcher.onMouseUp("Interaction.Click"+this.getID(),function(a){return e._handleClickUp(a)}),this._touchDispatcher=a.Dispatcher.Touch.getDispatcher(c.content().node()),this._touchDispatcher.onTouchStart("Interaction.Click"+this.getID(),function(a,b){return e._handleClickDown(b[a[0]])}),this._touchDispatcher.onTouchEnd("Interaction.Click"+this.getID(),function(a,b){return e._handleClickUp(b[a[0]])})},c.prototype._handleClickDown=function(a){var b=this._translateToComponentSpace(a);this._isInsideComponent(b)&&(this._clickedDown=!0)},c.prototype._handleClickUp=function(a){var b=this._translateToComponentSpace(a);this._clickedDown&&this._isInsideComponent(b)&&null!=this._clickCallback&&this._clickCallback(b),this._clickedDown=!1},c.prototype.onClick=function(a){return void 0===a?this._clickCallback:(this._clickCallback=a,this)},c}(b.AbstractInteraction);b.Click=c}(b=a.Interaction||(a.Interaction={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c;!function(a){a[a.NotClicked=0]="NotClicked",a[a.SingleClicked=1]="SingleClicked",a[a.DoubleClicked=2]="DoubleClicked"}(c||(c={}));var d=function(b){function c(){b.apply(this,arguments),this._clickState=0,this._clickedDown=!1}return __extends(c,b),c.prototype._anchor=function(c,d){var e=this;b.prototype._anchor.call(this,c,d),this._mouseDispatcher=a.Dispatcher.Mouse.getDispatcher(c.content().node()),this._mouseDispatcher.onMouseDown("Interaction.DoubleClick"+this.getID(),function(a){return e._handleClickDown(a)}),this._mouseDispatcher.onMouseUp("Interaction.DoubleClick"+this.getID(),function(a){return e._handleClickUp(a)}),this._mouseDispatcher.onDblClick("Interaction.DoubleClick"+this.getID(),function(a){return e._handleDblClick()}),this._touchDispatcher=a.Dispatcher.Touch.getDispatcher(c.content().node()),this._touchDispatcher.onTouchStart("Interaction.DoubleClick"+this.getID(),function(a,b){return e._handleClickDown(b[a[0]])}),this._touchDispatcher.onTouchEnd("Interaction.DoubleClick"+this.getID(),function(a,b){return e._handleClickUp(b[a[0]])})},c.prototype._handleClickDown=function(a){var b=this._translateToComponentSpace(a);this._isInsideComponent(b)&&(1===this._clickState&&c.pointsEqual(b,this._clickedPoint)||(this._clickState=0),this._clickedPoint=b,this._clickedDown=!0)},c.prototype._handleClickUp=function(a){var b=this._translateToComponentSpace(a);this._clickedDown&&c.pointsEqual(b,this._clickedPoint)?this._clickState=0===this._clickState?1:2:this._clickState=0,this._clickedDown=!1},c.prototype._handleDblClick=function(){2===this._clickState&&(this._doubleClickCallback&&this._doubleClickCallback(this._clickedPoint),this._clickState=0)},c.pointsEqual=function(a,b){return a.x===b.x&&a.y===b.y},c.prototype.onDoubleClick=function(a){return void 0===a?this._doubleClickCallback:(this._doubleClickCallback=a,this)},c}(b.AbstractInteraction);b.DoubleClick=d}(b=a.Interaction||(a.Interaction={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(){b.apply(this,arguments),this._keyCode2Callback={}}return __extends(c,b),c.prototype._anchor=function(c,d){var e=this;b.prototype._anchor.call(this,c,d),this._positionDispatcher=a.Dispatcher.Mouse.getDispatcher(this._componentToListenTo._element.node()),this._positionDispatcher.onMouseMove("Interaction.Key"+this.getID(),function(a){return null}),this._keyDispatcher=a.Dispatcher.Key.getDispatcher(),this._keyDispatcher.onKeyDown("Interaction.Key"+this.getID(),function(a){return e._handleKeyEvent(a)})},c.prototype._handleKeyEvent=function(a){var b=this._translateToComponentSpace(this._positionDispatcher.getLastMousePosition());this._isInsideComponent(b)&&this._keyCode2Callback[a]&&this._keyCode2Callback[a]()},c.prototype.on=function(a,b){return this._keyCode2Callback[a]=b,this},c}(b.AbstractInteraction);b.Key=c}(b=a.Interaction||(a.Interaction={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(){b.apply(this,arguments),this._overComponent=!1}return __extends(c,b),c.prototype._anchor=function(c,d){var e=this;b.prototype._anchor.call(this,c,d),this._mouseDispatcher=a.Dispatcher.Mouse.getDispatcher(this._componentToListenTo.content().node()),this._mouseDispatcher.onMouseMove("Interaction.Pointer"+this.getID(),function(a){return e._handlePointerEvent(a)}),this._touchDispatcher=a.Dispatcher.Touch.getDispatcher(this._componentToListenTo.content().node()),this._touchDispatcher.onTouchStart("Interaction.Pointer"+this.getID(),function(a,b){return e._handlePointerEvent(b[a[0]])})},c.prototype._handlePointerEvent=function(a){var b=this._translateToComponentSpace(a);if(this._isInsideComponent(b)){var c=this._overComponent;this._overComponent=!0,!c&&this._pointerEnterCallback&&this._pointerEnterCallback(b),this._pointerMoveCallback&&this._pointerMoveCallback(b)}else this._overComponent&&(this._overComponent=!1,this._pointerExitCallback&&this._pointerExitCallback(b))},c.prototype.onPointerEnter=function(a){return void 0===a?this._pointerEnterCallback:(this._pointerEnterCallback=a,this)},c.prototype.onPointerMove=function(a){return void 0===a?this._pointerMoveCallback:(this._pointerMoveCallback=a,this)},c.prototype.onPointerExit=function(a){return void 0===a?this._pointerExitCallback:(this._pointerExitCallback=a,this)},c}(b.AbstractInteraction);b.Pointer=c}(b=a.Interaction||(a.Interaction={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(a){var b=function(a){function b(b,c){var d=this;a.call(this),b&&(this._xScale=b,this._xScale.broadcaster.registerListener("pziX"+this.getID(),function(){return d.resetZoom()})),c&&(this._yScale=c,this._yScale.broadcaster.registerListener("pziY"+this.getID(),function(){return d.resetZoom()}))}return __extends(b,a),b.prototype.resetZoom=function(){var a=this;this._zoom=d3.behavior.zoom(),this._xScale&&this._zoom.x(this._xScale._d3Scale),this._yScale&&this._zoom.y(this._yScale._d3Scale),this._zoom.on("zoom",function(){return a._rerenderZoomed()}),this._zoom(this._hitBox)},b.prototype._anchor=function(b,c){a.prototype._anchor.call(this,b,c),this.resetZoom()},b.prototype._requiresHitbox=function(){return!0},b.prototype._rerenderZoomed=function(){if(this._xScale){var a=this._xScale._d3Scale.domain();this._xScale.domain(a)}if(this._yScale){var b=this._yScale._d3Scale.domain();this._yScale.domain(b)}},b}(a.AbstractInteraction);a.PanZoom=b}(b=a.Interaction||(a.Interaction={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(){b.apply(this,arguments),this._dragging=!1,this._constrain=!0}return __extends(c,b),c.prototype._anchor=function(c,d){var e=this;b.prototype._anchor.call(this,c,d),this._mouseDispatcher=a.Dispatcher.Mouse.getDispatcher(this._componentToListenTo.content().node()),this._mouseDispatcher.onMouseDown("Interaction.Drag"+this.getID(),function(a,b){return e._startDrag(a,b)}),this._mouseDispatcher.onMouseMove("Interaction.Drag"+this.getID(),function(a,b){return e._doDrag(a,b)}),this._mouseDispatcher.onMouseUp("Interaction.Drag"+this.getID(),function(a,b){return e._endDrag(a,b)}),this._touchDispatcher=a.Dispatcher.Touch.getDispatcher(this._componentToListenTo.content().node()),this._touchDispatcher.onTouchStart("Interaction.Drag"+this.getID(),function(a,b,c){return e._startDrag(b[a[0]],c)}),this._touchDispatcher.onTouchMove("Interaction.Drag"+this.getID(),function(a,b,c){return e._doDrag(b[a[0]],c)}),this._touchDispatcher.onTouchEnd("Interaction.Drag"+this.getID(),function(a,b,c){return e._endDrag(b[a[0]],c)})},c.prototype._translateAndConstrain=function(b){var c=this._translateToComponentSpace(b);return this._constrain?{x:a._Util.Methods.clamp(c.x,0,this._componentToListenTo.width()),y:a._Util.Methods.clamp(c.y,0,this._componentToListenTo.height())}:c},c.prototype._startDrag=function(a,b){if(!(b instanceof MouseEvent&&0!==b.button)){var c=this._translateToComponentSpace(a);this._isInsideComponent(c)&&(b.preventDefault(),this._dragging=!0,this._dragOrigin=c,this._dragStartCallback&&this._dragStartCallback(this._dragOrigin))}},c.prototype._doDrag=function(a,b){if(this._dragging&&this._dragCallback){var c=this._translateAndConstrain(a);this._dragCallback(this._dragOrigin,c)}},c.prototype._endDrag=function(a,b){if(!(b instanceof MouseEvent&&0!==b.button)&&this._dragging&&(this._dragging=!1,this._dragEndCallback)){var c=this._translateAndConstrain(a);this._dragEndCallback(this._dragOrigin,c)}},c.prototype.constrainToComponent=function(a){return null==a?this._constrain:(this._constrain=a,this)},c.prototype.onDragStart=function(a){return void 0===a?this._dragStartCallback:(this._dragStartCallback=a,this)},c.prototype.onDrag=function(a){return void 0===a?this._dragCallback:(this._dragCallback=a,this)},c.prototype.onDragEnd=function(a){return void 0===a?this._dragEndCallback:(this._dragEndCallback=a,this)},c}(b.AbstractInteraction);b.Drag=c}(b=a.Interaction||(a.Interaction={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(){b.call(this),this._overComponent=!1,this._currentHoverData={data:null,pixelPositions:null,selection:null},c.warned||(c.warned=!0,a._Util.Methods.warn("Interaction.Hover is deprecated; use Interaction.Pointer in conjunction with getClosestPlotData() instead."))}return __extends(c,b),c.prototype._anchor=function(c,d){var e=this;b.prototype._anchor.call(this,c,d),this._mouseDispatcher=a.Dispatcher.Mouse.getDispatcher(this._componentToListenTo._element.node()),this._mouseDispatcher.onMouseMove("hover"+this.getID(),function(a){return e._handlePointerEvent(a)}),this._touchDispatcher=a.Dispatcher.Touch.getDispatcher(this._componentToListenTo._element.node()),this._touchDispatcher.onTouchStart("hover"+this.getID(),function(a,b){return e._handlePointerEvent(b[a[0]])})},c.prototype._handlePointerEvent=function(a){a=this._translateToComponentSpace(a),this._isInsideComponent(a)?(this._overComponent||this._componentToListenTo._hoverOverComponent(a),this.handleHoverOver(a),this._overComponent=!0):(this._componentToListenTo._hoverOutComponent(a),this.safeHoverOut(this._currentHoverData),this._currentHoverData={data:null,pixelPositions:null,selection:null},this._overComponent=!1)},c.diffHoverData=function(a,b){if(null==a.data||null==b.data)return a;var c=[],d=[],e=[];return a.data.forEach(function(f,g){-1===b.data.indexOf(f)&&(c.push(f),d.push(a.pixelPositions[g]),e.push(a.selection[0][g]))}),0===c.length?{data:null,pixelPositions:null,selection:null}:{data:c,pixelPositions:d,selection:d3.selectAll(e)}},c.prototype.handleHoverOver=function(a){var b=this._currentHoverData,d=this._componentToListenTo._doHover(a);this._currentHoverData=d;var e=c.diffHoverData(b,d);this.safeHoverOut(e);var f=c.diffHoverData(d,b);this.safeHoverOver(f)},c.prototype.safeHoverOut=function(a){this._hoverOutCallback&&a.data&&this._hoverOutCallback(a)},c.prototype.safeHoverOver=function(a){this._hoverOverCallback&&a.data&&this._hoverOverCallback(a)},c.prototype.onHoverOver=function(a){return this._hoverOverCallback=a,this},c.prototype.onHoverOut=function(a){return this._hoverOutCallback=a,this},c.prototype.getCurrentHoverData=function(){return this._currentHoverData},c.warned=!1,c}(b.AbstractInteraction);b.Hover=c}(b=a.Interaction||(a.Interaction={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(){b.call(this),this._detectionRadius=3,this._resizable=!1,this._hasCorners=!0,this.clipPathEnabled=!0,this.classed("drag-box-layer",!0),this._dragInteraction=new a.Interaction.Drag,this._setUpCallbacks(),this.registerInteraction(this._dragInteraction)}return __extends(c,b),c.prototype._setUpCallbacks=function(){var a,b,c,d,e=this;this._dragInteraction.onDragStart(function(f){a=e._getResizingEdges(f),e.boxVisible()&&(a.top||a.bottom||a.left||a.right)?d=!1:(e.bounds({topLeft:f,bottomRight:f}),d=!0),e.boxVisible(!0);var g=e.bounds();b={x:g.topLeft.x,y:g.topLeft.y},c={x:g.bottomRight.x,y:g.bottomRight.y},e._dragStartCallback&&e._dragStartCallback(g)}),this._dragInteraction.onDrag(function(f,g){d?(c.x=g.x,c.y=g.y):(a.bottom?c.y=g.y:a.top&&(b.y=g.y),a.right?c.x=g.x:a.left&&(b.x=g.x)),e.bounds({topLeft:b,bottomRight:c}),e._dragCallback&&e._dragCallback(e.bounds())}),this._dragInteraction.onDragEnd(function(a,b){d&&a.x===b.x&&a.y===b.y&&e.boxVisible(!1),e._dragEndCallback&&e._dragEndCallback(e.bounds())})},c.prototype._setup=function(){var a=this;b.prototype._setup.call(this);var c=function(){return a._box.append("line").style({opacity:0,stroke:"pink"})};if(this._detectionEdgeT=c().classed("drag-edge-tb",!0),this._detectionEdgeB=c().classed("drag-edge-tb",!0),this._detectionEdgeL=c().classed("drag-edge-lr",!0),this._detectionEdgeR=c().classed("drag-edge-lr",!0),this._hasCorners){var d=function(){return a._box.append("circle").style({opacity:0,fill:"pink"})};this._detectionCornerTL=d().classed("drag-corner-tl",!0),this._detectionCornerTR=d().classed("drag-corner-tr",!0),this._detectionCornerBL=d().classed("drag-corner-bl",!0),this._detectionCornerBR=d().classed("drag-corner-br",!0)}},c.prototype._getResizingEdges=function(a){var b={top:!1,bottom:!1,left:!1,right:!1};if(!this.resizable())return b;var c=this.bounds(),d=c.topLeft.y,e=c.bottomRight.y,f=c.topLeft.x,g=c.bottomRight.x,h=this._detectionRadius;return f-h<=a.x&&a.x<=g+h&&(b.top=d-h<=a.y&&a.y<=d+h,b.bottom=e-h<=a.y&&a.y<=e+h),d-h<=a.y&&a.y<=e+h&&(b.left=f-h<=a.x&&a.x<=f+h,b.right=g-h<=a.x&&a.x<=g+h),b},c.prototype._doRender=function(){if(b.prototype._doRender.call(this),this.boxVisible()){var a=this.bounds(),c=a.topLeft.y,d=a.bottomRight.y,e=a.topLeft.x,f=a.bottomRight.x;this._detectionEdgeT.attr({x1:e,y1:c,x2:f,y2:c,"stroke-width":2*this._detectionRadius}),this._detectionEdgeB.attr({x1:e,y1:d,x2:f,y2:d,"stroke-width":2*this._detectionRadius}),this._detectionEdgeL.attr({x1:e,y1:c,x2:e,y2:d,"stroke-width":2*this._detectionRadius}),this._detectionEdgeR.attr({x1:f,y1:c,x2:f,y2:d,"stroke-width":2*this._detectionRadius}),this._hasCorners&&(this._detectionCornerTL.attr({cx:e,cy:c,r:this._detectionRadius}),this._detectionCornerTR.attr({cx:f,cy:c,r:this._detectionRadius}),this._detectionCornerBL.attr({cx:e,cy:d,r:this._detectionRadius}),this._detectionCornerBR.attr({cx:f,cy:d,r:this._detectionRadius}))}},c.prototype.detectionRadius=function(a){if(null==a)return this._detectionRadius;if(0>a)throw new Error("detection radius cannot be negative.");return this._detectionRadius=a,this._render(),this},c.prototype.resizable=function(a){return null==a?this._resizable:(this._resizable=a,this._setResizableClasses(a),this)},c.prototype._setResizableClasses=function(a){this.classed("x-resizable",a),this.classed("y-resizable",a)},c.prototype.onDragStart=function(a){return void 0===a?this._dragStartCallback:(this._dragStartCallback=a,this)},c.prototype.onDrag=function(a){return void 0===a?this._dragCallback:(this._dragCallback=a,this)},c.prototype.onDragEnd=function(a){return void 0===a?this._dragEndCallback:(this._dragEndCallback=a,this)},c}(b.SelectionBoxLayer);b.DragBoxLayer=c}(b=a.Component||(a.Component={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(a){var b=function(a){function b(){a.call(this),this.classed("x-drag-box-layer",!0),this._hasCorners=!1}return __extends(b,a),b.prototype._computeLayout=function(b,c,d,e){a.prototype._computeLayout.call(this,b,c,d,e),this.bounds(this.bounds())},b.prototype._setBounds=function(b){a.prototype._setBounds.call(this,{topLeft:{x:b.topLeft.x,y:0},bottomRight:{x:b.bottomRight.x,y:this.height()}})},b.prototype._setResizableClasses=function(a){this.classed("x-resizable",a)},b}(a.DragBoxLayer);a.XDragBoxLayer=b}(b=a.Component||(a.Component={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(a){var b=function(a){function b(){a.call(this),this.classed("y-drag-box-layer",!0),this._hasCorners=!1}return __extends(b,a),b.prototype._computeLayout=function(b,c,d,e){a.prototype._computeLayout.call(this,b,c,d,e),this.bounds(this.bounds())},b.prototype._setBounds=function(b){a.prototype._setBounds.call(this,{topLeft:{x:0,y:b.topLeft.y},bottomRight:{x:this.width(),y:b.bottomRight.y}})},b.prototype._setResizableClasses=function(a){this.classed("y-resizable",a)},b}(a.DragBoxLayer);a.YDragBoxLayer=b}(b=a.Component||(a.Component={}))}(Plottable||(Plottable={}));var SVGTypewriter;!function(a){!function(a){!function(a){function b(a,b){if(null==a||null==b)return a===b;if(a.length!==b.length)return!1;for(var c=0;c0&&"\n"===b[0]?"\n":"";if(g>=c){var i=g/3,j=Math.floor(c/i);return{wrappedToken:h+"...".substr(0,j),remainingToken:b}}for(;f+g>c;)e=a.Utils.StringMethods.trimEnd(e.substr(0,e.length-1)),f=d.measure(e).width;return{wrappedToken:h+e+"...",remainingToken:a.Utils.StringMethods.trimEnd(b.substring(e.length),"-").trim()}},b.prototype.wrapNextToken=function(b,c,d){if(!c.canFitText||c.availableLines===c.wrapping.noLines||!this.canFitToken(b,c.availableWidth,d))return this.finishWrapping(b,c,d); - -for(var e=b;e;){var f=this.breakTokenToFitInWidth(e,c.currentLine,c.availableWidth,d);if(c.currentLine=f.line,e=f.remainingToken,null!=e){if(c.wrapping.noBrokeWords+=+f.breakWord,++c.wrapping.noLines,c.availableLines===c.wrapping.noLines){var g=this.addEllipsis(c.currentLine,c.availableWidth,d);return c.wrapping.wrappedText+=g.wrappedToken,c.wrapping.truncatedText+=g.remainingToken+e,c.currentLine="\n",c}c.wrapping.wrappedText+=a.Utils.StringMethods.trimEnd(c.currentLine),c.currentLine="\n"}}return c},b.prototype.finishWrapping=function(a,b,c){if(b.canFitText&&b.availableLines!==b.wrapping.noLines&&this._allowBreakingWords&&"none"!==this._textTrimming){var d=this.addEllipsis(b.currentLine+a,b.availableWidth,c);b.wrapping.wrappedText+=d.wrappedToken,b.wrapping.truncatedText+=d.remainingToken,b.wrapping.noBrokeWords+=+(d.remainingToken.length0),b.currentLine=""}else b.wrapping.truncatedText+=a;return b.canFitText=!1,b},b.prototype.breakTokenToFitInWidth=function(a,b,c,d,e){if(void 0===e&&(e=this._breakingCharacter),d.measure(b+a).width<=c)return{remainingToken:null,line:b+a,breakWord:!1};if(""===a.trim())return{remainingToken:"",line:b,breakWord:!1};if(!this._allowBreakingWords)return{remainingToken:a,line:b,breakWord:!1};for(var f=0;f0&&(g=e),{remainingToken:a.substring(f),line:b+a.substring(0,f)+g,breakWord:f>0}},b}();b.Wrapper=c}(a.Wrappers||(a.Wrappers={}));a.Wrappers}(SVGTypewriter||(SVGTypewriter={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},SVGTypewriter;!function(a){!function(a){var b=function(a){function b(){a.apply(this,arguments)}return __extends(b,a),b.prototype.wrap=function(c,d,e,f){var g=this;void 0===f&&(f=1/0);var h=c.split("\n");if(h.length>1)throw new Error("SingleLineWrapper is designed to work only on single line");var i=function(b){return a.prototype.wrap.call(g,c,d,b,f)},j=i(e);if(j.noLines<2)return j;for(var k=0,l=e,m=0;mk;++m){var n=(l+k)/2,o=i(n);this.areSameResults(j,o)?(l=n,j=o):k=n}return j},b.prototype.areSameResults=function(a,b){return a.noLines===b.noLines&&a.truncatedText===b.truncatedText},b.NO_WRAP_ITERATIONS=5,b}(a.Wrapper);a.SingleLineWrapper=b}(a.Wrappers||(a.Wrappers={}));a.Wrappers}(SVGTypewriter||(SVGTypewriter={}));var SVGTypewriter;!function(a){!function(b){var c=function(){function b(a,c){this._writerID=b.nextID++,this._elementID=0,this.measurer(a),c&&this.wrapper(c),this.addTitleElement(!1)}return b.prototype.measurer=function(a){return this._measurer=a,this},b.prototype.wrapper=function(a){return this._wrapper=a,this},b.prototype.addTitleElement=function(a){return this._addTitleElement=a,this},b.prototype.writeLine=function(c,d,e,f,g){var h=d.append("text");h.text(c);var i=e*b.XOffsetFactor[f],j=b.AnchorConverter[f];h.attr("text-anchor",j).classed("text-line",!0),a.Utils.DOM.transform(h,i,g).attr("y","-0.25em")},b.prototype.writeText=function(a,c,d,e,f,g){var h=this,i=a.split("\n"),j=this._measurer.measure().height,k=b.YOffsetFactor[g]*(e-i.length*j);i.forEach(function(a,b){h.writeLine(a,c,d,f,(b+1)*j+k)})},b.prototype.write=function(a,c,d,e){if(-1===b.SupportedRotation.indexOf(e.textRotation))throw new Error("unsupported rotation - "+e.textRotation);var f=Math.abs(Math.abs(e.textRotation)-90)>45,g=f?c:d,h=f?d:c,i=e.selection.append("g").classed("text-container",!0);this._addTitleElement&&i.append("title").text(a);var j=i.append("g").classed("text-area",!0),k=this._wrapper?this._wrapper.wrap(a,this._measurer,g,h).wrappedText:a;this.writeText(k,j,g,h,e.xAlign,e.yAlign);var l=d3.transform(""),m=d3.transform("");switch(l.rotate=e.textRotation,e.textRotation){case 90:l.translate=[c,0],m.rotate=-90,m.translate=[0,200];break;case-90:l.translate=[0,d],m.rotate=90,m.translate=[c,0];break;case 180:l.translate=[c,d],m.translate=[c,d],m.rotate=180}j.attr("transform",l.toString()),this.addClipPath(i,m),e.animator&&e.animator.animate(i)},b.prototype.addClipPath=function(b,c){var d=this._elementID++,e=/MSIE [5-9]/.test(navigator.userAgent)?"":document.location.href;e=e.split("#")[0];var f="clipPath"+this._writerID+"_"+d;b.select(".text-area").attr("clip-path",'url("'+e+"#"+f+'")');var g=b.append("clipPath").attr("id",f),h=a.Utils.DOM.getBBox(b.select(".text-area")),i=g.append("rect");i.classed("clip-rect",!0).attr(h)},b.nextID=0,b.SupportedRotation=[-90,0,180,90],b.AnchorConverter={left:"start",center:"middle",right:"end"},b.XOffsetFactor={left:0,center:.5,right:1},b.YOffsetFactor={top:0,center:.5,bottom:1},b}();b.Writer=c}(a.Writers||(a.Writers={}));a.Writers}(SVGTypewriter||(SVGTypewriter={}));var SVGTypewriter;!function(a){!function(b){var c=function(){function b(a,b){this.textMeasurer=this.getTextMeasurer(a,b)}return b.prototype.checkSelectionIsText=function(a){return"text"===a[0][0].tagName||!a.select("text").empty()},b.prototype.getTextMeasurer=function(a,b){var c=this;if(this.checkSelectionIsText(a)){var d,e=a.node().parentNode;return d="text"===a[0][0].tagName?a:a.select("text"),a.remove(),function(b){e.appendChild(a.node());var f=c.measureBBox(d,b);return a.remove(),f}}var f=a.append("text");return b&&f.classed(b,!0),f.remove(),function(b){a.node().appendChild(f.node());var d=c.measureBBox(f,b);return f.remove(),d}},b.prototype.measureBBox=function(b,c){b.text(c);var d=a.Utils.DOM.getBBox(b);return{width:d.width,height:d.height}},b.prototype.measure=function(a){return void 0===a&&(a=b.HEIGHT_TEXT),this.textMeasurer(a)},b.HEIGHT_TEXT="bqpdl",b}();b.AbstractMeasurer=c}(a.Measurers||(a.Measurers={}));a.Measurers}(SVGTypewriter||(SVGTypewriter={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},SVGTypewriter;!function(a){!function(a){var b=function(b){function c(a,c,d){void 0===c&&(c=null),void 0===d&&(d=!1),b.call(this,a,c),this.useGuards=d}return __extends(c,b),c.prototype._addGuards=function(b){return a.AbstractMeasurer.HEIGHT_TEXT+b+a.AbstractMeasurer.HEIGHT_TEXT},c.prototype.getGuardWidth=function(){return null==this.guardWidth&&(this.guardWidth=b.prototype.measure.call(this).width),this.guardWidth},c.prototype._measureLine=function(a){var c=this.useGuards?this._addGuards(a):a,d=b.prototype.measure.call(this,c);return d.width-=this.useGuards?2*this.getGuardWidth():0,d},c.prototype.measure=function(b){var c=this;if(void 0===b&&(b=a.AbstractMeasurer.HEIGHT_TEXT),""===b.trim())return{width:0,height:0};var d=b.trim().split("\n").map(function(a){return c._measureLine(a)});return{width:d3.max(d,function(a){return a.width}),height:d3.sum(d,function(a){return a.height})}},c}(a.AbstractMeasurer);a.Measurer=b}(a.Measurers||(a.Measurers={}));a.Measurers}(SVGTypewriter||(SVGTypewriter={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},SVGTypewriter;!function(a){!function(a){var b=function(a){function b(){a.apply(this,arguments)}return __extends(b,a),b.prototype._measureCharacter=function(b){return a.prototype._measureLine.call(this,b)},b.prototype._measureLine=function(a){var b=this,c=a.split("").map(function(a){return b._measureCharacter(a)});return{width:d3.sum(c,function(a){return a.width}),height:d3.max(c,function(a){return a.height})}},b}(a.Measurer);a.CharacterMeasurer=b}(a.Measurers||(a.Measurers={}));a.Measurers}(SVGTypewriter||(SVGTypewriter={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},SVGTypewriter;!function(a){!function(b){var c=function(b){function c(c,d){var e=this;b.call(this,c,d),this.cache=new a.Utils.Cache(function(a){return e._measureCharacterNotFromCache(a)},a.Utils.Methods.objEq)}return __extends(c,b),c.prototype._measureCharacterNotFromCache=function(a){return b.prototype._measureCharacter.call(this,a)},c.prototype._measureCharacter=function(a){return this.cache.get(a)},c.prototype.reset=function(){this.cache.clear()},c}(b.CharacterMeasurer);b.CacheCharacterMeasurer=c}(a.Measurers||(a.Measurers={}));a.Measurers}(SVGTypewriter||(SVGTypewriter={})); \ No newline at end of file +var Plottable;!function(a){var b;!function(b){var c;!function(b){function c(a,b,c){return Math.min(b,c)<=a&&a<=Math.max(b,c)}function d(a,b,c){return Math.min(Math.max(b,a),c)}function e(b){a.Configs.SHOW_WARNINGS&&null!=window.console&&(null!=window.console.warn?console.warn(b):null!=window.console.log&&console.log(b))}function f(a,b){if(a.length!==b.length)throw new Error("attempted to add arrays of unequal length");return a.map(function(c,d){return a[d]+b[d]})}function g(a,b){var c=d3.set();return a.forEach(function(a){b.has(a)&&c.add(a)}),c}function h(a,b){var c=d3.set();return a.forEach(function(a){return c.add(a)}),b.forEach(function(a){return c.add(a)}),c}function i(a,b){var c=d3.map();return a.forEach(function(a,d){c.set(a,b(a,d))}),c}function j(a){var b=d3.set(),c=[];return a.forEach(function(a){b.has(a)||(b.add(a),c.push(a))}),c}function k(a,b){for(var c=[],d=0;b>d;d++)c[d]="function"==typeof a?a(d):a;return c}function l(a){return Array.prototype.concat.apply([],a)}function m(a,b){if(null==a||null==b)return a===b;if(a.length!==b.length)return!1;for(var c=0;cf;++f)e[f]=a+c*f;return e}function u(a,b){for(var c=[],d=2;db?"0"+c:c});if(4===d.length&&"00"===d[3])return null;var e="#"+d.join("");return a.classed(b,!1),e}function w(a,b){var c=d3.hsl(a).brighter(b);return c.rgb().toString()}function x(a,b){return Math.pow(b.y-a.y,2)+Math.pow(b.x-a.x,2)}function y(){var a=window.navigator.userAgent;return a.indexOf("MSIE ")>-1||a.indexOf("Trident/")>-1}function z(a,b,c,d){void 0===d&&(d=.5);var e=A(a),f=A(b);return c.x+c.width>=e.min-d&&c.x<=e.max+d&&c.y+c.height>=f.min-d&&c.y<=f.max+d}function A(a){if("number"==typeof a)return{min:a,max:a};if(a instanceof Object&&"min"in a&&"max"in a)return a;throw new Error("input '"+a+"' can't be parsed as an Extent")}b.inRange=c,b.clamp=d,b.warn=e,b.addArrays=f,b.intersection=g,b.union=h,b.populateMap=i,b.uniq=j,b.createFilledArray=k,b.flatten=l,b.arrayEq=m,b.objEq=n,b.max=o,b.min=p,b.isNaN=q,b.isValidNumber=r,b.copyMap=s,b.range=t,b.setTimeout=u,b.colorTest=v,b.lightenColor=w,b.distanceSquared=x,b.isIE=y,b.intersectsBBox=z,b.parseExtent=A}(c=b.Methods||(b.Methods={}))}(b=a.Utils||(a.Utils={}))}(Plottable||(Plottable={}));var Plottable;!function(a){var b;!function(a){var b=function(){function a(){this._keyValuePairs=[]}return a.prototype.set=function(a,b){if(a!==a)throw new Error("NaN may not be used as a key to the Map");for(var c=0;cb.right?!1:a.bottomb.bottom?!1:!0}function k(a,b){return Math.floor(b.left)<=Math.ceil(a.left)&&Math.floor(b.top)<=Math.ceil(a.top)&&Math.floor(a.right)<=Math.ceil(b.right)&&Math.floor(a.bottom)<=Math.ceil(b.bottom)}function l(a){var b=a.ownerSVGElement;return null!=b?b:"svg"===a.nodeName.toLowerCase()?a:null}function m(){return"plottableClipPath"+ ++n}a.getBBox=b,a.POLYFILL_TIMEOUT_MSEC=1e3/60,a.requestAnimationFramePolyfill=c,a.isSelectionRemovedFromSVG=e,a.getElementWidth=f,a.getElementHeight=g,a.getSVGPixelWidth=h,a.translate=i,a.boxesOverlap=j,a.boxIsInside=k,a.getBoundingSVG=l;var n=0;a.getUniqueClipPathId=m}(b=a.DOM||(a.DOM={}))}(b=a.Utils||(a.Utils={}))}(Plottable||(Plottable={}));var Plottable;!function(a){var b;!function(a){var b;!function(a){function b(a){var b=d3.rgb(a),c=function(a){return a/=255,.03928>=a?a/12.92:Math.pow((a+.055)/1.055,2.4)},d=c(b.r),e=c(b.g),f=c(b.b);return.2126*d+.7152*e+.0722*f}function c(a,c){var d=b(a)+.05,e=b(c)+.05;return d>e?d/e:e/d}a.contrast=c}(b=a.Colors||(a.Colors={}))}(b=a.Utils||(a.Utils={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(a){var b=function(a){function b(){a.apply(this,arguments)}return __extends(b,a),b.prototype.callCallbacks=function(){for(var a=this,b=[],c=0;ca&&(b="-"+b)),b}}function d(a){return void 0===a&&(a=3),m(a),function(b){return b.toFixed(a)}}function e(a){return void 0===a&&(a=3),m(a),function(b){if("number"==typeof b){var c=Math.pow(10,a);return String(Math.round(b*c)/c)}return String(b)}}function f(){return function(a){return String(a)}}function g(a){void 0===a&&(a=0);var c=b.fixed(a);return function(a){var b=100*a,d=a.toString(),e=Math.pow(10,d.length-(d.indexOf(".")+1));return b=parseInt((b*e).toString(),10)/e,c(b)+"%"}}function h(a){return void 0===a&&(a=3),m(a),function(b){return d3.format("."+a+"s")(b)}}function i(){var a=8,b={};return b[0]={format:".%L",filter:function(a){return 0!==a.getMilliseconds()}},b[1]={format:":%S",filter:function(a){return 0!==a.getSeconds()}},b[2]={format:"%I:%M",filter:function(a){return 0!==a.getMinutes()}},b[3]={format:"%I %p",filter:function(a){return 0!==a.getHours()}},b[4]={format:"%a %d",filter:function(a){return 0!==a.getDay()&&1!==a.getDate()}},b[5]={format:"%b %d",filter:function(a){return 1!==a.getDate()}},b[6]={format:"%b",filter:function(a){return 0!==a.getMonth()}},b[7]={format:"%Y",filter:function(){return!0}},function(c){for(var d=0;a>d;d++)if(b[d].filter(c))return d3.time.format(b[d].format)(c)}}function j(a){return d3.time.format(a)}function k(b){switch(b){case a.TimeInterval.second:return d3.time.second;case a.TimeInterval.minute:return d3.time.minute;case a.TimeInterval.hour:return d3.time.hour;case a.TimeInterval.day:return d3.time.day;case a.TimeInterval.week:return d3.time.week;case a.TimeInterval.month:return d3.time.month;case a.TimeInterval.year:return d3.time.year;default:throw Error("TimeInterval specified does not exist: "+b)}}function l(b,c,d){return void 0===b&&(b=0),void 0===c&&(c=a.MILLISECONDS_IN_ONE_DAY),void 0===d&&(d=""),function(a){var e=Math.round((a.valueOf()-b)/c);return e.toString()+d}}function m(a){if(0>a||a>20)throw new RangeError("Formatter precision must be between 0 and 20")}b.currency=c,b.fixed=d,b.general=e,b.identity=f,b.percentage=g,b.siSuffix=h,b.multiTime=i,b.time=j,b.timeIntervalToD3Time=k,b.relativeDate=l}(b=a.Formatters||(a.Formatters={}))}(Plottable||(Plottable={}));var Plottable;!function(a){var b;!function(a){function b(){return function(a){return d3.svg.symbol().type("circle").size(Math.PI*Math.pow(a/2,2))()}}function c(){return function(a){return d3.svg.symbol().type("square").size(Math.pow(a,2))()}}function d(){return function(a){return d3.svg.symbol().type("cross").size(5/9*Math.pow(a,2))()}}function e(){return function(a){return d3.svg.symbol().type("diamond").size(Math.tan(Math.PI/6)*Math.pow(a,2)/2)()}}function f(){return function(a){return d3.svg.symbol().type("triangle-up").size(Math.sqrt(3)*Math.pow(a/2,2))()}}function g(){return function(a){return d3.svg.symbol().type("triangle-down").size(Math.sqrt(3)*Math.pow(a/2,2))()}}a.circle=b,a.square=c,a.cross=d,a.diamond=e,a.triangleUp=f,a.triangleDown=g}(b=a.SymbolFactories||(a.SymbolFactories={}))}(Plottable||(Plottable={}));var Plottable;!function(a){var b;!function(a){var b=function(){function b(a){this._svg=a,this._measureRect=document.createElementNS(a.namespaceURI,"rect"),this._measureRect.setAttribute("class","measure-rect"),this._measureRect.setAttribute("style","opacity: 0; visibility: hidden;"),this._measureRect.setAttribute("width","1"),this._measureRect.setAttribute("height","1"),this._svg.appendChild(this._measureRect)}return b.getTranslator=function(c){var d=a.DOM.getBoundingSVG(c),e=d[b._TRANSLATOR_KEY];return null==e&&(e=new b(d),d[b._TRANSLATOR_KEY]=e),e},b.prototype.computePosition=function(a,b){this._measureRect.setAttribute("x","0"),this._measureRect.setAttribute("y","0");var c=this._measureRect.getBoundingClientRect(),d={x:c.left,y:c.top},e=100;this._measureRect.setAttribute("x",String(e)),this._measureRect.setAttribute("y",String(e)),c=this._measureRect.getBoundingClientRect();var f={x:c.left,y:c.top};if(d.x===f.x||d.y===f.y)return null;var g=(f.x-d.x)/e,h=(f.y-d.y)/e;this._measureRect.setAttribute("x",String((a-d.x)/g)),this._measureRect.setAttribute("y",String((b-d.y)/h)),c=this._measureRect.getBoundingClientRect();var i={x:c.left,y:c.top},j={x:(i.x-d.x)/g,y:(i.y-d.y)/h};return j},b._TRANSLATOR_KEY="__Plottable_ClientToSVGTranslator",b}();a.ClientToSVGTranslator=b}(b=a.Utils||(a.Utils={}))}(Plottable||(Plottable={}));var Plottable;!function(a){var b;!function(a){a.SHOW_WARNINGS=!0}(b=a.Configs||(a.Configs={}))}(Plottable||(Plottable={}));var Plottable;!function(a){a.version="1.0.0-rc1"}(Plottable||(Plottable={}));var Plottable;!function(a){var b;!function(a){var b=function(){function a(){}return a.CORAL_RED="#fd373e",a.INDIGO="#5279c7",a.ROBINS_EGG_BLUE="#06cccc",a.FERN="#63c261",a.BURNING_ORANGE="#ff7939",a.ROYAL_HEATH="#962565",a.CONIFER="#99ce50",a.CERISE_RED="#db2e65",a.BRIGHT_SUN="#fad419",a.JACARTA="#2c2b6f",a.PLOTTABLE_COLORS=[a.INDIGO,a.CORAL_RED,a.FERN,a.BRIGHT_SUN,a.JACARTA,a.BURNING_ORANGE,a.CERISE_RED,a.CONIFER,a.ROYAL_HEATH,a.ROBINS_EGG_BLUE],a}();a.Colors=b}(b=a.Core||(a.Core={}))}(Plottable||(Plottable={}));var Plottable;!function(a){var b=function(){function b(b,c){void 0===b&&(b=[]),void 0===c&&(c={}),this._data=b,this._metadata=c,this._accessor2cachedExtent=new a.Utils.Map,this._callbacks=new a.Utils.CallbackSet}return b.prototype.onUpdate=function(a){this._callbacks.add(a)},b.prototype.offUpdate=function(a){this._callbacks["delete"](a)},b.prototype.data=function(b){return null==b?this._data:(this._data=b,this._accessor2cachedExtent=new a.Utils.Map,this._callbacks.callCallbacks(this),this)},b.prototype.metadata=function(b){return null==b?this._metadata:(this._metadata=b,this._accessor2cachedExtent=new a.Utils.Map,this._callbacks.callCallbacks(this),this)},b}();a.Dataset=b}(Plottable||(Plottable={}));var Plottable;!function(a){var b;!function(b){var c=function(){function b(){}return b.prototype.render=function(){a.RenderController.flush()},b}();b.Immediate=c;var d=function(){function b(){}return b.prototype.render=function(){a.Utils.DOM.requestAnimationFramePolyfill(a.RenderController.flush)},b}();b.AnimationFrame=d;var e=function(){function b(){this._timeoutMsec=a.Utils.DOM.POLYFILL_TIMEOUT_MSEC}return b.prototype.render=function(){setTimeout(a.RenderController.flush,this._timeoutMsec)},b}();b.Timeout=e}(b=a.RenderPolicies||(a.RenderPolicies={}))}(Plottable||(Plottable={}));var Plottable;!function(a){var b;!function(b){function c(c){if("string"==typeof c)switch(c.toLowerCase()){case"immediate":c=new a.RenderPolicies.Immediate;break;case"animationframe":c=new a.RenderPolicies.AnimationFrame;break;case"timeout":c=new a.RenderPolicies.Timeout;break;default:return void a.Utils.Methods.warn("Unrecognized renderPolicy: "+c)}b._renderPolicy=c}function d(b){k&&a.Utils.Methods.warn("Registered to render while other components are flushing: request may be ignored"),h.add(b),f()}function e(a){i.add(a),h.add(a),f()}function f(){j||(j=!0,b._renderPolicy.render())}function g(){if(j){i.values().forEach(function(a){return a.computeLayout()}),h.values().forEach(function(a){return a.render()}),k=!0;var b=new a.Utils.Set;h.values().forEach(function(a){try{a.renderImmediately()}catch(c){window.setTimeout(function(){throw c},0),b.add(a)}}),i=new a.Utils.Set,h=b,j=!1,k=!1}}var h=new a.Utils.Set,i=new a.Utils.Set,j=!1,k=!1;b._renderPolicy=new a.RenderPolicies.AnimationFrame,b.setRenderPolicy=c,b.registerToRender=d,b.registerToComputeLayout=e,b.flush=g}(b=a.RenderController||(a.RenderController={}))}(Plottable||(Plottable={}));var Plottable;!function(){}(Plottable||(Plottable={}));var Plottable;!function(a){var b=function(){function b(b){this._doNice=!1,this._padProportion=0,this._combineExtents=b,this._paddingExceptions=new a.Utils.Map,this._includedValues=new a.Utils.Map}return b.prototype.computeDomain=function(b,c){var d;return d=null!=this._combineExtents?this._combineExtents(b):0===b.length?c._defaultExtent():[a.Utils.Methods.min(b,function(a){return a[0]},0),a.Utils.Methods.max(b,function(a){return a[1]},0)],d=this._includeDomain(d),d=this._padDomain(c,d),d=this._niceDomain(c,d)},b.prototype.pad=function(a){return void 0===a&&(a=.05),this._padProportion=a,this},b.prototype.addPaddingException=function(a,b){return this._paddingExceptions.set(a,b),this},b.prototype.removePaddingException=function(a){return this._paddingExceptions["delete"](a),this},b.prototype.addIncludedValue=function(a,b){return this._includedValues.set(a,b),this},b.prototype.removeIncludedValue=function(a){return this._includedValues["delete"](a),this},b.prototype.nice=function(a){return this._doNice=!0,this._niceCount=a,this},b.prototype._padDomain=function(a,c){var d=c[0],e=c[1];if(d.valueOf()===e.valueOf()&&this._padProportion>0){var f=d.valueOf();return d instanceof Date?[f-b._ONE_DAY,f+b._ONE_DAY]:[f-b._PADDING_FOR_IDENTICAL_DOMAIN,f+b._PADDING_FOR_IDENTICAL_DOMAIN]}var g=a.domain();if(g[0].valueOf()===g[1].valueOf())return c;var h=this._padProportion/2,i=a.invert(a.scale(d)-(a.scale(e)-a.scale(d))*h),j=a.invert(a.scale(e)+(a.scale(e)-a.scale(d))*h),k=this._paddingExceptions.values(),l=d3.set(k);return l.has(d)&&(i=d),l.has(e)&&(j=e),[i,j]},b.prototype._niceDomain=function(a,b){return this._doNice?a._niceDomain(b,this._niceCount):b},b.prototype._includeDomain=function(a){var b=this._includedValues.values();return b.reduce(function(a,b){return[Math.min(a[0],b),Math.max(a[1],b)]},a)},b._PADDING_FOR_IDENTICAL_DOMAIN=1,b._ONE_DAY=864e5,b}();a.Domainer=b}(Plottable||(Plottable={}));var Plottable;!function(a){var b=function(){function b(b){this._autoDomainAutomatically=!0,this._domainModificationInProgress=!1,this._d3Scale=b,this._callbacks=new a.Utils.CallbackSet,this._extentsProviders=new a.Utils.Set}return b.prototype._getAllExtents=function(){var a=this;return d3.merge(this._extentsProviders.values().map(function(b){return b(a)}))},b.prototype._getExtent=function(){return[]},b.prototype.onUpdate=function(a){return this._callbacks.add(a),this},b.prototype.offUpdate=function(a){return this._callbacks["delete"](a),this},b.prototype._dispatchUpdate=function(){this._callbacks.callCallbacks(this)},b.prototype.autoDomain=function(){return this._autoDomainAutomatically=!0,this._setDomain(this._getExtent()),this},b.prototype._autoDomainIfAutomaticMode=function(){this._autoDomainAutomatically&&this.autoDomain()},b.prototype.scale=function(a){return this._d3Scale(a)},b.prototype.domain=function(a){return null==a?this._getDomain():(this._autoDomainAutomatically=!1,this._setDomain(a),this)},b.prototype._getDomain=function(){return this._d3Scale.domain()},b.prototype._setDomain=function(a){this._domainModificationInProgress||(this._domainModificationInProgress=!0,this._d3Scale.domain(a),this._dispatchUpdate(),this._domainModificationInProgress=!1)},b.prototype.range=function(a){return null==a?this._d3Scale.range():(this._d3Scale.range(a),this)},b.prototype.addExtentsProvider=function(a){return this._extentsProviders.add(a),this},b.prototype.removeExtentsProvider=function(a){return this._extentsProviders["delete"](a),this},b}();a.Scale=b}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b=function(b){function c(c){b.call(this,c),this._userSetDomainer=!1,this._domainer=new a.Domainer,this._tickGenerator=function(a){return a.getDefaultTicks()}}return __extends(c,b),c.prototype._getExtent=function(){return this._domainer.computeDomain(this._getAllExtents(),this)},c.prototype.invert=function(a){return this._d3Scale.invert(a)},c.prototype.domain=function(a){return b.prototype.domain.call(this,a)},c.prototype._setDomain=function(c){var d=function(a){return a!==a||1/0===a||a===-1/0};return d(c[0])||d(c[1])?void a.Utils.Methods.warn("Warning: QuantitativeScales cannot take NaN or Infinity as a domain value. Ignoring."):void b.prototype._setDomain.call(this,c)},c.prototype.getDefaultTicks=function(){return this._d3Scale.ticks(c._DEFAULT_NUM_TICKS)},c.prototype.ticks=function(){return this._tickGenerator(this)},c.prototype._niceDomain=function(a,b){return this._d3Scale.copy().domain(a).nice(b).domain()},c.prototype.domainer=function(a){return null==a?this._domainer:(this._domainer=a,this._userSetDomainer=!0,this._autoDomainIfAutomaticMode(),this)},c.prototype._defaultExtent=function(){throw Error("The quantitative scale itself does not have a default extent")},c.prototype.tickGenerator=function(a){return null==a?this._tickGenerator:(this._tickGenerator=a,this)},c._DEFAULT_NUM_TICKS=10,c}(a.Scale);a.QuantitativeScale=b}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(a){function b(b){a.call(this,null==b?d3.scale.linear():b)}return __extends(b,a),b.prototype._defaultExtent=function(){return[0,1]},b}(a.QuantitativeScale);b.Linear=c}(b=a.Scales||(a.Scales={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(a){if(void 0===a&&(a=10),b.call(this,d3.scale.linear()),this._showIntermediateTicks=!1,this._base=a,this._pivot=this._base,this._setDomain(this._defaultExtent()),1>=a)throw new Error("ModifiedLogScale: The base must be > 1")}return __extends(c,b),c.prototype.adjustedLog=function(a){var b=0>a?-1:1;return a*=b,aa?-1:1;return a*=b,a=Math.pow(this._base,a),a=d&&e>=a}),m=j.concat(l).concat(k);return m.length<=1&&(m=d3.scale.linear().domain([d,e]).ticks(c._DEFAULT_NUM_TICKS)),m},c.prototype.logTicks=function(b,c){var d=this,e=this._howManyTicks(b,c);if(0===e)return[];var f=Math.floor(Math.log(b)/Math.log(this._base)),g=Math.ceil(Math.log(c)/Math.log(this._base)),h=d3.range(g,f,-Math.ceil((g-f)/e)),i=this._showIntermediateTicks?Math.floor(e/h.length):1,j=d3.range(this._base,1,-(this._base-1)/i).map(Math.floor),k=a.Utils.Methods.uniq(j),l=h.map(function(a){return k.map(function(b){return Math.pow(d._base,a-1)*b})}),m=a.Utils.Methods.flatten(l),n=m.filter(function(a){return a>=b&&c>=a}),o=n.sort(function(a,b){return a-b});return o},c.prototype._howManyTicks=function(b,d){var e=this.adjustedLog(a.Utils.Methods.min(this._untransformedDomain,0)),f=this.adjustedLog(a.Utils.Methods.max(this._untransformedDomain,0)),g=this.adjustedLog(b),h=this.adjustedLog(d),i=(h-g)/(f-e),j=Math.ceil(i*c._DEFAULT_NUM_TICKS);return j},c.prototype._niceDomain=function(a){return a},c.prototype.showIntermediateTicks=function(a){return null==a?this._showIntermediateTicks:void(this._showIntermediateTicks=a)},c.prototype._defaultExtent=function(){return[0,this._base]},c}(a.QuantitativeScale);b.ModifiedLog=c}(b=a.Scales||(a.Scales={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(a){void 0===a&&(a=d3.scale.ordinal()),b.call(this,a),this._range=[0,1];var d=.3;this._innerPadding=c._convertToPlottableInnerPadding(d),this._outerPadding=c._convertToPlottableOuterPadding(.5,d)}return __extends(c,b),c.prototype._getExtent=function(){var b=this._getAllExtents();return a.Utils.Methods.uniq(a.Utils.Methods.flatten(b))},c.prototype.domain=function(a){return b.prototype.domain.call(this,a)},c.prototype._setDomain=function(a){b.prototype._setDomain.call(this,a),this.range(this.range())},c.prototype.range=function(a){if(null==a)return this._range;this._range=a;var b=1-1/(1+this.innerPadding()),c=this.outerPadding()/(1+this.innerPadding());return this._d3Scale.rangeBands(a,b,c),this},c._convertToPlottableInnerPadding=function(a){return 1/(1-a)-1},c._convertToPlottableOuterPadding=function(a,b){return a/(1-b)},c.prototype.rangeBand=function(){return this._d3Scale.rangeBand()},c.prototype.stepWidth=function(){return this.rangeBand()*(1+this.innerPadding())},c.prototype.innerPadding=function(a){return null==a?this._innerPadding:(this._innerPadding=a,this.range(this.range()),this._dispatchUpdate(),this)},c.prototype.outerPadding=function(a){return null==a?this._outerPadding:(this._outerPadding=a,this.range(this.range()),this._dispatchUpdate(),this)},c.prototype.scale=function(a){return b.prototype.scale.call(this,a)+this.rangeBand()/2},c}(a.Scale);b.Category=c}(b=a.Scales||(a.Scales={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(a){var d;switch(a){case null:case void 0:d=d3.scale.ordinal().range(c._getPlottableColors());break;case"Category10":case"category10":case"10":d=d3.scale.category10();break;case"Category20":case"category20":case"20":d=d3.scale.category20();break;case"Category20b":case"category20b":case"20b":d=d3.scale.category20b();break;case"Category20c":case"category20c":case"20c":d=d3.scale.category20c();break;default:throw new Error("Unsupported ColorScale type")}b.call(this,d)}return __extends(c,b),c.prototype._getExtent=function(){var b=this._getAllExtents(),c=[];return b.forEach(function(a){c=c.concat(a)}),a.Utils.Methods.uniq(c)},c._getPlottableColors=function(){for(var b,c=[],d=d3.select("body").append("plottable-color-tester"),e=a.Utils.Methods.colorTest(d,""),f=0;null!==(b=a.Utils.Methods.colorTest(d,"plottable-colors-"+f))&&f0&&this._setDomain([a.Utils.Methods.min(b,function(a){return a[0]},0),a.Utils.Methods.max(b,function(a){return a[1]},0)]),this},c.REDS=["#FFFFFF","#FFF6E1","#FEF4C0","#FED976","#FEB24C","#FD8D3C","#FC4E2A","#E31A1C","#B10026"],c.BLUES=["#FFFFFF","#CCFFFF","#A5FFFD","#85F7FB","#6ED3EF","#55A7E0","#417FD0","#2545D3","#0B02E1"],c.POSNEG=["#0B02E1","#2545D3","#417FD0","#55A7E0","#6ED3EF","#85F7FB","#A5FFFD","#CCFFFF","#FFFFFF","#FFF6E1","#FEF4C0","#FED976","#FEB24C","#FD8D3C","#FC4E2A","#E31A1C","#B10026"],c}(a.Scale);b.InterpolatedColor=c}(b=a.Scales||(a.Scales={}))}(Plottable||(Plottable={}));var Plottable;!function(a){var b;!function(b){var c;!function(b){function c(b){if(0>=b)throw new Error("interval must be positive number");return function(c){var d=c.domain(),e=Math.min(d[0],d[1]),f=Math.max(d[0],d[1]),g=Math.ceil(e/b)*b,h=Math.floor((f-g)/b)+1,i=e%b===0?[]:[e],j=a.Utils.Methods.range(0,h).map(function(a){return g+a*b}),k=f%b===0?[]:[f];return i.concat(j).concat(k)}}function d(){return function(a){var b=a.getDefaultTicks();return b.filter(function(a,c){return a%1===0||0===c||c===b.length-1})}}b.intervalTickGenerator=c,b.integerTickGenerator=d}(c=b.TickGenerators||(b.TickGenerators={}))}(b=a.Scales||(a.Scales={}))}(Plottable||(Plottable={}));var Plottable;!function(a){var b;!function(b){var c=function(){function b(a){this.key=a}return b.prototype.setClass=function(a){return this._className=a,this},b.prototype.setup=function(a){this._renderArea=a},b.prototype.remove=function(){null!=this._getRenderArea()&&this._getRenderArea().remove()},b.prototype._enterData=function(){},b.prototype._drawStep=function(){},b.prototype._numberOfAnimationIterations=function(a){return a.length},b.prototype._applyMetadata=function(a,b,c){var d={};return d3.keys(a).forEach(function(e){d[e]=function(d,f){return a[e](d,f,b,c)}}),d},b.prototype._prepareDrawSteps=function(){},b.prototype._prepareData=function(a){return a},b.prototype.draw=function(b,c,d,e){var f=this,g=c.map(function(b){var c=f._applyMetadata(b.attrToProjector,d,e);return f._attrToProjector=a.Utils.Methods.copyMap(c),{attrToProjector:c,animator:b.animator}}),h=this._prepareData(b,g);this._prepareDrawSteps(g),this._enterData(h);var i=this._numberOfAnimationIterations(h),j=0;return g.forEach(function(b){a.Utils.Methods.setTimeout(function(){return f._drawStep(b)},j),j+=b.animator.getTiming(i)}),j},b.prototype._getRenderArea=function(){return this._renderArea +},b.prototype._getSelector=function(){return""},b.prototype._getPixelPoint=function(){return null},b.prototype._getSelection=function(a){var b=this._getRenderArea().selectAll(this._getSelector());return d3.select(b[0][a])},b}();b.AbstractDrawer=c}(b=a.Drawers||(a.Drawers={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(){b.apply(this,arguments)}return __extends(c,b),c.prototype._enterData=function(a){b.prototype._enterData.call(this,a),this._pathSelection.datum(a)},c.prototype.setup=function(a){this._pathSelection=a.append("path").classed(c.LINE_CLASS,!0).style({fill:"none","vector-effect":"non-scaling-stroke"}),b.prototype.setup.call(this,a)},c.prototype._createLine=function(a,b,c){return c||(c=function(){return!0}),d3.svg.line().x(a).y(b).defined(c)},c.prototype._numberOfAnimationIterations=function(){return 1},c.prototype._drawStep=function(d){b.prototype._drawStep.call(this,d);var e=a.Utils.Methods.copyMap(d.attrToProjector),f=e.defined,g=e.x,h=e.y;delete e.x,delete e.y,e.defined&&delete e.defined,e.d=this._createLine(g,h,f),e.fill&&this._pathSelection.attr("fill",e.fill),e["class"]&&(this._pathSelection.attr("class",e["class"]),this._pathSelection.classed(c.LINE_CLASS,!0),delete e["class"]),d.animator.animate(this._pathSelection,e)},c.prototype._getSelector=function(){return"."+c.LINE_CLASS},c.prototype._getPixelPoint=function(a,b){return{x:this._attrToProjector.x(a,b),y:this._attrToProjector.y(a,b)}},c.prototype._getSelection=function(){return this._getRenderArea().select(this._getSelector())},c.LINE_CLASS="line",c}(b.AbstractDrawer);b.Line=c}(b=a.Drawers||(a.Drawers={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(c){function d(){c.apply(this,arguments),this._drawLine=!0}return __extends(d,c),d.prototype._enterData=function(a){this._drawLine?c.prototype._enterData.call(this,a):b.AbstractDrawer.prototype._enterData.call(this,a),this._areaSelection.datum(a)},d.prototype.drawLine=function(a){return this._drawLine=a,this},d.prototype.setup=function(a){this._areaSelection=a.append("path").classed(d.AREA_CLASS,!0).style({stroke:"none"}),this._drawLine?c.prototype.setup.call(this,a):b.AbstractDrawer.prototype.setup.call(this,a)},d.prototype._createArea=function(a,b,c,d){return d||(d=function(){return!0}),d3.svg.area().x(a).y0(b).y1(c).defined(d)},d.prototype._drawStep=function(e){this._drawLine?c.prototype._drawStep.call(this,e):b.AbstractDrawer.prototype._drawStep.call(this,e);var f=a.Utils.Methods.copyMap(e.attrToProjector),g=f.x,h=f.y0,i=f.y,j=f.defined;delete f.x,delete f.y0,delete f.y,f.defined&&delete f.defined,f.d=this._createArea(g,h,i,j),f.fill&&this._areaSelection.attr("fill",f.fill),f["class"]&&(this._areaSelection.attr("class",f["class"]),this._areaSelection.classed(d.AREA_CLASS,!0),delete f["class"]),e.animator.animate(this._areaSelection,f)},d.prototype._getSelector=function(){return"path"},d.AREA_CLASS="area",d}(b.Line);b.Area=c}(b=a.Drawers||(a.Drawers={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(a){var b=function(a){function b(){a.apply(this,arguments)}return __extends(b,a),b.prototype.svgElement=function(a){return this._svgElement=a,this},b.prototype._getDrawSelection=function(){return this._getRenderArea().selectAll(this._svgElement)},b.prototype._drawStep=function(b){a.prototype._drawStep.call(this,b);var c=this._getDrawSelection();b.attrToProjector.fill&&c.attr("fill",b.attrToProjector.fill),b.animator.animate(c,b.attrToProjector)},b.prototype._enterData=function(b){a.prototype._enterData.call(this,b);var c=this._getDrawSelection().data(b);c.enter().append(this._svgElement),null!=this._className&&c.classed(this._className,!0),c.exit().remove()},b.prototype._filterDefinedData=function(a,b){return b?a.filter(b):a},b.prototype._prepareDrawSteps=function(b){a.prototype._prepareDrawSteps.call(this,b),b.forEach(function(a){a.attrToProjector.defined&&delete a.attrToProjector.defined})},b.prototype._prepareData=function(b,c){var d=this;return c.reduce(function(a,b){return d._filterDefinedData(a,b.attrToProjector.defined)},a.prototype._prepareData.call(this,b,c))},b.prototype._getSelector=function(){return this._svgElement},b}(a.AbstractDrawer);a.Element=b}(b=a.Drawers||(a.Drawers={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=5,d=5,e=function(b){function e(a,c){b.call(this,a),this._labelsTooWide=!1,this.svgElement("rect"),this._isVertical=c}return __extends(e,b),e.prototype.setup=function(a){b.prototype.setup.call(this,a.append("g").classed("bar-area",!0)),this._textArea=a.append("g").classed("bar-label-text-area",!0),this._measurer=new SVGTypewriter.Measurers.CacheCharacterMeasurer(this._textArea),this._writer=new SVGTypewriter.Writers.Writer(this._measurer)},e.prototype.removeLabels=function(){this._textArea.selectAll("g").remove()},e.prototype._getIfLabelsTooWide=function(){return this._labelsTooWide},e.prototype.drawText=function(b,e,f,g){var h=this,i=b.map(function(b,i){var j=e.label(b,i,f,g).toString(),k=e.width(b,i,f,g),l=e.height(b,i,f,g),m=e.x(b,i,f,g),n=e.y(b,i,f,g),o=e.positive(b,i,f,g),p=h._measurer.measure(j),q=e.fill(b,i,f,g),r=1.6*a.Utils.Colors.contrast("white",q)v;if(p.height<=l&&p.width<=k){var x=Math.min((s-t)/2,c);o||(x=-1*x),h._isVertical?n+=x:m+=x;var y=h._textArea.append("g").attr("transform","translate("+m+","+n+")"),z=r?"dark-label":"light-label";y.classed(z,!0);var A,B;h._isVertical?(A="center",B=o?"top":"bottom"):(A=o?"left":"right",B="center");var C={selection:y,xAlign:A,yAlign:B,textRotation:0};h._writer.write(j,k,l,C)}return w});this._labelsTooWide=i.some(function(a){return a})},e.prototype._getPixelPoint=function(a,b){var c=this._attrToProjector.x(a,b),d=this._attrToProjector.y(a,b),e=this._attrToProjector.width(a,b),f=this._attrToProjector.height(a,b),g=this._isVertical?c+e/2:c+e,h=this._isVertical?d:d+f/2;return{x:g,y:h}},e.prototype.draw=function(c,d,e,f){var g=d[0].attrToProjector,h=a.Utils.Methods.isValidNumber;return c=c.filter(function(a){return h(g.x(a,null,e,f))&&h(g.y(a,null,e,f))&&h(g.width(a,null,e,f))&&h(g.height(a,null,e,f))}),b.prototype.draw.call(this,c,d,e,f)},e}(b.Element);b.Rect=e}(b=a.Drawers||(a.Drawers={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(a){b.call(this,a),this._svgElement="path"}return __extends(c,b),c.prototype._createArc=function(a,b){return d3.svg.arc().innerRadius(a).outerRadius(b)},c.prototype.retargetProjectors=function(a){var b={};return d3.entries(a).forEach(function(a){b[a.key]=function(b,c){return a.value(b.data,c)}}),b},c.prototype._drawStep=function(c){var d=a.Utils.Methods.copyMap(c.attrToProjector);d=this.retargetProjectors(d),this._attrToProjector=this.retargetProjectors(this._attrToProjector);var e=d["inner-radius"],f=d["outer-radius"];return delete d["inner-radius"],delete d["outer-radius"],d.d=this._createArc(e,f),b.prototype._drawStep.call(this,{attrToProjector:d,animator:c.animator})},c.prototype.draw=function(c,d,e,f){var g=function(a,b){return d[0].attrToProjector["sector-value"](a,b,e,f)};c=c.filter(function(b){return a.Utils.Methods.isValidNumber(+g(b,null))});var h=d3.layout.pie().sort(null).value(g)(c);return d.forEach(function(a){return delete a.attrToProjector["sector-value"]}),h.forEach(function(b){b.value<0&&a.Utils.Methods.warn("Negative values will not render correctly in a pie chart.")}),b.prototype.draw.call(this,h,d,e,f)},c.prototype._getPixelPoint=function(a,b){var c=this._attrToProjector["inner-radius"],d=this._attrToProjector["outer-radius"],e=(c(a,b)+d(a,b))/2,f=+this._getSelection(b).datum().startAngle,g=+this._getSelection(b).datum().endAngle,h=(f+g)/2;return{x:e*Math.sin(h),y:-e*Math.cos(h)}},c}(b.Element);b.Arc=c}(b=a.Drawers||(a.Drawers={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(a){b.call(this,a),this._svgElement="path",this._className="symbol"}return __extends(c,b),c.prototype._drawStep=function(c){var d=c.attrToProjector;this._attrToProjector=a.Utils.Methods.copyMap(c.attrToProjector);var e=d.x,f=d.y;delete d.x,delete d.y;var g=d.size;delete d.size,d.transform=function(a,b){return"translate("+e(a,b)+","+f(a,b)+")"};var h=d.symbol;delete d.symbol,d.d=d.d||function(a,b){return h(a,b)(g(a,b))},b.prototype._drawStep.call(this,c)},c.prototype._getPixelPoint=function(a,b){return{x:this._attrToProjector.x(a,b),y:this._attrToProjector.y(a,b)}},c}(b.Element);b.Symbol=c}(b=a.Drawers||(a.Drawers={}))}(Plottable||(Plottable={}));var Plottable;!function(a){var b;!function(a){var b=function(){function a(){}return a.TOP="top",a.BOTTOM="bottom",a.LEFT="left",a.RIGHT="right",a.CENTER="center",a}();a.Alignment=b}(b=a.Components||(a.Components={}));var c=function(){function b(){this._clipPathEnabled=!1,this._origin={x:0,y:0},this._xAlignment="left",this._yAlignment="top",this._isSetup=!1,this._isAnchored=!1,this._boxes=[],this._isTopLevelComponent=!1,this._cssClasses=["component"],this._destroyed=!1,this._onAnchorCallbacks=new a.Utils.CallbackSet,this._onDetachCallbacks=new a.Utils.CallbackSet}return b.prototype.anchor=function(a){if(this._destroyed)throw new Error("Can't reuse destroy()-ed components!");return"svg"===a.node().nodeName.toLowerCase()&&(this._rootSVG=a,this._rootSVG.classed("plottable",!0),this._rootSVG.style("overflow","visible"),this._isTopLevelComponent=!0),null!=this._element?a.node().appendChild(this._element.node()):(this._element=a.append("g"),this._setup()),this._isAnchored=!0,this._onAnchorCallbacks.callCallbacks(this),this},b.prototype.onAnchor=function(a){return this._isAnchored&&a(this),this._onAnchorCallbacks.add(a),this},b.prototype.offAnchor=function(a){return this._onAnchorCallbacks["delete"](a),this},b.prototype._setup=function(){var a=this;this._isSetup||(this._cssClasses.forEach(function(b){a._element.classed(b,!0)}),this._cssClasses=null,this._backgroundContainer=this._element.append("g").classed("background-container",!0),this._addBox("background-fill",this._backgroundContainer),this._content=this._element.append("g").classed("content",!0),this._foregroundContainer=this._element.append("g").classed("foreground-container",!0),this._boxContainer=this._element.append("g").classed("box-container",!0),this._clipPathEnabled&&this._generateClipPath(),this._boundingBox=this._addBox("bounding-box"),this._isSetup=!0)},b.prototype.requestedSpace=function(){return{minWidth:0,minHeight:0}},b.prototype.computeLayout=function(c,d,e){var f=this;if(null==c||null==d||null==e){if(null==this._element)throw new Error("anchor() must be called before computeLayout()");if(!this._isTopLevelComponent)throw new Error("null arguments cannot be passed to computeLayout() on a non-root node");c={x:0,y:0},null==this._rootSVG.attr("width")&&this._rootSVG.attr("width","100%"),null==this._rootSVG.attr("height")&&this._rootSVG.attr("height","100%");var g=this._rootSVG.node();d=a.Utils.DOM.getElementWidth(g),e=a.Utils.DOM.getElementHeight(g)}var h=this._getSize(d,e);this._width=h.width,this._height=h.height;var i=b._xAlignToProportion[this._xAlignment],j=b._yAlignToProportion[this._yAlignment];return this._origin={x:c.x+(d-this.width())*i,y:c.y+(e-this.height())*j},this._element.attr("transform","translate("+this._origin.x+","+this._origin.y+")"),this._boxes.forEach(function(a){return a.attr("width",f.width()).attr("height",f.height())}),this},b.prototype._getSize=function(a,b){var c=this.requestedSpace(a,b);return{width:this.fixedWidth()?Math.min(a,c.minWidth):a,height:this.fixedHeight()?Math.min(b,c.minHeight):b}},b.prototype.render=function(){return this._isAnchored&&this._isSetup&&this.width()>=0&&this.height()>=0&&a.RenderController.registerToRender(this),this},b.prototype._scheduleComputeLayout=function(){this._isAnchored&&this._isSetup&&a.RenderController.registerToComputeLayout(this)},b.prototype.renderImmediately=function(){return this},b.prototype.redraw=function(){return this._isAnchored&&this._isSetup&&(this._isTopLevelComponent?this._scheduleComputeLayout():this.parent().redraw()),this},b.prototype.renderTo=function(b){if(this.detach(),null!=b){var c;if(c="string"==typeof b?d3.select(b):b,!c.node()||"svg"!==c.node().nodeName.toLowerCase())throw new Error("Plottable requires a valid SVG to renderTo");this.anchor(c)}if(null==this._element)throw new Error("If a component has never been rendered before, then renderTo must be given a node to render to, or a D3.Selection, or a selector string");return this.computeLayout(),this.render(),a.RenderController.flush(),this},b.prototype.xAlignment=function(a){if(null==a)return this._xAlignment;if(a=a.toLowerCase(),null==b._xAlignToProportion[a])throw new Error("Unsupported alignment: "+a);return this._xAlignment=a,this.redraw(),this},b.prototype.yAlignment=function(a){if(null==a)return this._yAlignment;if(a=a.toLowerCase(),null==b._yAlignToProportion[a])throw new Error("Unsupported alignment: "+a);return this._yAlignment=a,this.redraw(),this},b.prototype._addBox=function(a,b){if(null==this._element)throw new Error("Adding boxes before anchoring is currently disallowed");b=null==b?this._boxContainer:b;var c=b.append("rect");return null!=a&&c.classed(a,!0),this._boxes.push(c),null!=this.width()&&null!=this.height()&&c.attr("width",this.width()).attr("height",this.height()),c},b.prototype._generateClipPath=function(){var b=/MSIE [5-9]/.test(navigator.userAgent)?"":document.location.href;b=b.split("#")[0];var c=a.Utils.DOM.getUniqueClipPathId();this._element.attr("clip-path",'url("'+b+"#"+c+'")');var d=this._boxContainer.append("clipPath").attr("id",c);this._addBox("clip-rect",d)},b.prototype.classed=function(a,b){if(null==b)return null==a?!1:null==this._element?-1!==this._cssClasses.indexOf(a):this._element.classed(a);if(null==a)return this;if(null==this._element){var c=this._cssClasses.indexOf(a);b&&-1===c?this._cssClasses.push(a):b||-1===c||this._cssClasses.splice(c,1)}else this._element.classed(a,b);return this},b.prototype.fixedWidth=function(){return!1},b.prototype.fixedHeight=function(){return!1},b.prototype.detach=function(){return this.parent(null),this._isAnchored&&this._element.remove(),this._isAnchored=!1,this._onDetachCallbacks.callCallbacks(this),this},b.prototype.onDetach=function(a){return this._onDetachCallbacks.add(a),this},b.prototype.offDetach=function(a){return this._onDetachCallbacks["delete"](a),this},b.prototype.parent=function(a){if(void 0===a)return this._parent;if(null!==a&&!a.has(this))throw new Error("Passed invalid parent");return this._parent=a,this},b.prototype.destroy=function(){this._destroyed=!0,this.detach()},b.prototype.width=function(){return this._width},b.prototype.height=function(){return this._height},b.prototype.origin=function(){return{x:this._origin.x,y:this._origin.y}},b.prototype.originToSVG=function(){for(var a=this.origin(),b=this.parent();null!=b;){var c=b.origin();a.x+=c.x,a.y+=c.y,b=b.parent()}return a},b.prototype.foreground=function(){return this._foregroundContainer},b.prototype.content=function(){return this._content},b.prototype.background=function(){return this._backgroundContainer},b._xAlignToProportion={left:0,center:.5,right:1},b._yAlignToProportion={top:0,center:.5,bottom:1},b}();a.Component=c}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b=function(a){function b(){var b=this;a.call(this),this._detachCallback=function(a){return b.remove(a)}}return __extends(b,a),b.prototype.anchor=function(b){var c=this;return a.prototype.anchor.call(this,b),this._forEach(function(a){return a.anchor(c._content)}),this},b.prototype.render=function(){return this._forEach(function(a){return a.render()}),this},b.prototype.has=function(){throw new Error("has() is not implemented on ComponentContainer")},b.prototype._adoptAndAnchor=function(a){a.parent(this),a.onDetach(this._detachCallback),this._isAnchored&&a.anchor(this._content)},b.prototype.remove=function(a){return this.has(a)&&(a.offDetach(this._detachCallback),this._remove(a),a.detach(),this.redraw()),this},b.prototype._remove=function(){return!1},b.prototype._forEach=function(){throw new Error("_forEach() is not implemented on ComponentContainer")},b.prototype.destroy=function(){a.prototype.destroy.call(this),this._forEach(function(a){return a.destroy()})},b}(a.Component);a.ComponentContainer=b}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(a){var c=this;void 0===a&&(a=[]),b.call(this),this._components=[],this.classed("component-group",!0),a.forEach(function(a){return c.append(a)})}return __extends(c,b),c.prototype._forEach=function(a){this._components.forEach(a)},c.prototype.has=function(a){return this._components.indexOf(a)>=0},c.prototype.requestedSpace=function(b,c){var d=this._components.map(function(a){return a.requestedSpace(b,c)});return{minWidth:a.Utils.Methods.max(d,function(a){return a.minWidth},0),minHeight:a.Utils.Methods.max(d,function(a){return a.minHeight},0)}},c.prototype.computeLayout=function(a,c,d){var e=this;return b.prototype.computeLayout.call(this,a,c,d),this._forEach(function(a){a.computeLayout({x:0,y:0},e.width(),e.height())}),this},c.prototype._getSize=function(a,b){return{width:a,height:b}},c.prototype.fixedWidth=function(){return this._components.every(function(a){return a.fixedWidth()})},c.prototype.fixedHeight=function(){return this._components.every(function(a){return a.fixedHeight()})},c.prototype.components=function(){return this._components.slice()},c.prototype.append=function(a){return null==a||this.has(a)||(a.detach(),this._components.push(a),this._adoptAndAnchor(a),this.redraw()),this},c.prototype._remove=function(a){var b=this._components.indexOf(a);return b>=0?(this._components.splice(b,1),!0):!1},c}(a.ComponentContainer);b.Group=c}(b=a.Components||(a.Components={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b=function(b){function c(c,d,e){var f=this;if(void 0===e&&(e=a.Formatters.identity()),b.call(this),this._endTickLength=5,this._tickLength=5,this._tickLabelPadding=10,this._gutter=15,this._showEndTickLabels=!1,null==c||null==d)throw new Error("Axis requires a scale and orientation");this._scale=c,this.orientation(d),this._setDefaultAlignment(),this.classed("axis",!0),this._isHorizontal()?this.classed("x-axis",!0):this.classed("y-axis",!0),this.formatter(e),this._rescaleCallback=function(){return f._rescale()},this._scale.onUpdate(this._rescaleCallback)}return __extends(c,b),c.prototype.destroy=function(){b.prototype.destroy.call(this),this._scale.offUpdate(this._rescaleCallback)},c.prototype._isHorizontal=function(){return"top"===this._orientation||"bottom"===this._orientation},c.prototype._computeWidth=function(){return this._computedWidth=this._maxLabelTickLength(),this._computedWidth},c.prototype._computeHeight=function(){return this._computedHeight=this._maxLabelTickLength(),this._computedHeight},c.prototype.requestedSpace=function(){var a=0,b=0;return this._isHorizontal()?(null==this._computedHeight&&this._computeHeight(),b=this._computedHeight+this._gutter):(null==this._computedWidth&&this._computeWidth(),a=this._computedWidth+this._gutter),{minWidth:a,minHeight:b}},c.prototype.fixedHeight=function(){return this._isHorizontal()},c.prototype.fixedWidth=function(){return!this._isHorizontal()},c.prototype._rescale=function(){this.render()},c.prototype.computeLayout=function(a,c,d){return b.prototype.computeLayout.call(this,a,c,d),this._scale.range(this._isHorizontal()?[0,this.width()]:[this.height(),0]),this},c.prototype._setup=function(){b.prototype._setup.call(this),this._tickMarkContainer=this._content.append("g").classed(c.TICK_MARK_CLASS+"-container",!0),this._tickLabelContainer=this._content.append("g").classed(c.TICK_LABEL_CLASS+"-container",!0),this._baseline=this._content.append("line").classed("baseline",!0)},c.prototype._getTickValues=function(){return[]},c.prototype.renderImmediately=function(){var a=this._getTickValues(),b=this._tickMarkContainer.selectAll("."+c.TICK_MARK_CLASS).data(a);return b.enter().append("line").classed(c.TICK_MARK_CLASS,!0),b.attr(this._generateTickMarkAttrHash()),d3.select(b[0][0]).classed(c.END_TICK_MARK_CLASS,!0).attr(this._generateTickMarkAttrHash(!0)),d3.select(b[0][a.length-1]).classed(c.END_TICK_MARK_CLASS,!0).attr(this._generateTickMarkAttrHash(!0)),b.exit().remove(),this._baseline.attr(this._generateBaselineAttrHash()),this},c.prototype._generateBaselineAttrHash=function(){var a={x1:0,y1:0,x2:0,y2:0};switch(this._orientation){case"bottom":a.x2=this.width();break;case"top":a.x2=this.width(),a.y1=this.height(),a.y2=this.height();break;case"left":a.x1=this.width(),a.x2=this.width(),a.y2=this.height();break;case"right":a.y2=this.height()}return a},c.prototype._generateTickMarkAttrHash=function(a){var b=this;void 0===a&&(a=!1);var c={x1:0,y1:0,x2:0,y2:0},d=function(a){return b._scale.scale(a)};this._isHorizontal()?(c.x1=d,c.x2=d):(c.y1=d,c.y2=d);var e=a?this._endTickLength:this._tickLength;switch(this._orientation){case"bottom":c.y2=e;break;case"top":c.y1=this.height(),c.y2=this.height()-e;break;case"left":c.x1=this.width(),c.x2=this.width()-e;break;case"right":c.x2=e}return c},c.prototype.redraw=function(){return this._computedWidth=null,this._computedHeight=null,b.prototype.redraw.call(this)},c.prototype._setDefaultAlignment=function(){switch(this._orientation){case"bottom":this.yAlignment("top");break;case"top":this.yAlignment("bottom");break;case"left":this.xAlignment("right");break;case"right":this.xAlignment("left")}},c.prototype.formatter=function(a){return void 0===a?this._formatter:(this._formatter=a,this.redraw(),this)},c.prototype.tickLength=function(a){if(null==a)return this._tickLength;if(0>a)throw new Error("tick length must be positive");return this._tickLength=a,this.redraw(),this},c.prototype.endTickLength=function(a){if(null==a)return this._endTickLength;if(0>a)throw new Error("end tick length must be positive");return this._endTickLength=a,this.redraw(),this},c.prototype._maxLabelTickLength=function(){return this.showEndTickLabels()?Math.max(this.tickLength(),this.endTickLength()):this.tickLength()},c.prototype.tickLabelPadding=function(a){if(null==a)return this._tickLabelPadding;if(0>a)throw new Error("tick label padding must be positive");return this._tickLabelPadding=a,this.redraw(),this},c.prototype.gutter=function(a){if(null==a)return this._gutter;if(0>a)throw new Error("gutter size must be positive");return this._gutter=a,this.redraw(),this},c.prototype.orientation=function(a){if(null==a)return this._orientation;var b=a.toLowerCase();if("top"!==b&&"bottom"!==b&&"left"!==b&&"right"!==b)throw new Error("unsupported orientation");return this._orientation=b,this.redraw(),this},c.prototype.showEndTickLabels=function(a){return null==a?this._showEndTickLabels:(this._showEndTickLabels=a,this.render(),this)},c.END_TICK_MARK_CLASS="end-tick-mark",c.TICK_MARK_CLASS="tick-mark",c.TICK_LABEL_CLASS="tick-label",c}(a.Component);a.Axis=b}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(a){a.second="second",a.minute="minute",a.hour="hour",a.day="day",a.week="week",a.month="month",a.year="year"}(b=a.TimeInterval||(a.TimeInterval={}));var c;!function(c){var d=function(c){function d(a,b){c.call(this,a,b),this._tierLabelPositions=[],this.classed("time-axis",!0),this.tickLabelPadding(5),this.axisConfigurations(d._DEFAULT_TIME_AXIS_CONFIGURATIONS)}return __extends(d,c),d.prototype.tierLabelPositions=function(a){if(null==a)return this._tierLabelPositions;if(!a.every(function(a){return"between"===a.toLowerCase()||"center"===a.toLowerCase()}))throw new Error("Unsupported position for tier labels");return this._tierLabelPositions=a,this.redraw(),this},d.prototype.axisConfigurations=function(b){if(null==b)return this._possibleTimeAxisConfigurations;this._possibleTimeAxisConfigurations=b,this._numTiers=a.Utils.Methods.max(this._possibleTimeAxisConfigurations.map(function(a){return a.length}),0),this._isAnchored&&this._setupDomElements();for(var c=this.tierLabelPositions(),d=[],e=0;ed&&a.every(function(a){return b._checkTimeAxisTierConfigurationWidth(a)})&&(c=d)}),c===this._possibleTimeAxisConfigurations.length&&(a.Utils.Methods.warn("zoomed out too far: could not find suitable interval to display labels"),--c),c},d.prototype.orientation=function(a){if(a&&("right"===a.toLowerCase()||"left"===a.toLowerCase()))throw new Error(a+" is not a supported orientation for TimeAxis - only horizontal orientations are supported");return c.prototype.orientation.call(this,a)},d.prototype._computeHeight=function(){var a=this._measurer.measure().height;this._tierHeights=[];for(var b=0;bthis._scale.domain()[1])return this.width();var f=Math.abs(this._scale.scale(e)-this._scale.scale(c));return f},d.prototype._maxWidthForInterval=function(a){return this._measurer.measure(a.formatter(d._LONG_DATE)).width},d.prototype._checkTimeAxisTierConfigurationWidth=function(a){var b=this._maxWidthForInterval(a)+2*this.tickLabelPadding();return Math.min(this._getIntervalLength(a),this.width())>=b},d.prototype._getSize=function(a,b){var d=c.prototype._getSize.call(this,a,b);return d.height=this._tierHeights.reduce(function(a,b){return a+b>d.height?a:a+b}),d},d.prototype._setup=function(){c.prototype._setup.call(this),this._setupDomElements()},d.prototype._setupDomElements=function(){this._element.selectAll("."+d.TIME_AXIS_TIER_CLASS).remove(),this._tierLabelContainers=[],this._tierMarkContainers=[],this._tierBaselines=[],this._tickLabelContainer.remove(),this._baseline.remove();for(var b=0;b=f.length||g.push(new Date((f[b+1].valueOf()-f[b].valueOf())/2+f[b].valueOf()))}):g=f;var h=b.selectAll("."+a.Axis.TICK_LABEL_CLASS).data(g,function(a){return a.valueOf()}),i=h.enter().append("g").classed(a.Axis.TICK_LABEL_CLASS,!0);i.append("text");var j="center"===this._tierLabelPositions[d]||1===c.step?0:this.tickLabelPadding(),k="bottom"===this.orientation()?d3.sum(this._tierHeights.slice(0,d+1))-this.tickLabelPadding():this.height()-d3.sum(this._tierHeights.slice(0,d))-this.tickLabelPadding(),l=h.selectAll("text");l.size()>0&&a.Utils.DOM.translate(l,j,k),h.exit().remove(),h.attr("transform",function(a){return"translate("+e._scale.scale(a)+",0)"});var m="center"===this._tierLabelPositions[d]||1===c.step?"middle":"start";h.selectAll("text").text(c.formatter).style("text-anchor",m)},d.prototype._renderTickMarks=function(b,c){var d=this._tierMarkContainers[c].selectAll("."+a.Axis.TICK_MARK_CLASS).data(b);d.enter().append("line").classed(a.Axis.TICK_MARK_CLASS,!0);var e=this._generateTickMarkAttrHash(),f=this._tierHeights.slice(0,c).reduce(function(a,b){return a+b},0);"bottom"===this.orientation()?(e.y1=f,e.y2=f+("center"===this._tierLabelPositions[c]?this.tickLength():this._tierHeights[c])):(e.y1=this.height()-f,e.y2=this.height()-(f+("center"===this._tierLabelPositions[c]?this.tickLength():this._tierHeights[c]))),d.attr(e),"bottom"===this.orientation()?(e.y1=f,e.y2=f+this._tierHeights[c]):(e.y1=this.height()-f,e.y2=this.height()-(f+this._tierHeights[c])),d3.select(d[0][0]).attr(e),d3.select(d[0][0]).classed(a.Axis.END_TICK_MARK_CLASS,!0),d3.select(d[0][d.size()-1]).classed(a.Axis.END_TICK_MARK_CLASS,!0),d.exit().remove()},d.prototype._renderLabellessTickMarks=function(b){var c=this._tickMarkContainer.selectAll("."+a.Axis.TICK_MARK_CLASS).data(b);c.enter().append("line").classed(a.Axis.TICK_MARK_CLASS,!0);var d=this._generateTickMarkAttrHash();d.y2="bottom"===this.orientation()?this.tickLabelPadding():this.height()-this.tickLabelPadding(),c.attr(d),c.exit().remove()},d.prototype._generateLabellessTicks=function(){return this._mostPreciseConfigIndex<1?[]:this._getTickIntervalValues(this._possibleTimeAxisConfigurations[this._mostPreciseConfigIndex-1][0])},d.prototype.renderImmediately=function(){var a=this;this._mostPreciseConfigIndex=this._getMostPreciseConfigurationIndex();var b=this._possibleTimeAxisConfigurations[this._mostPreciseConfigIndex];this._cleanTiers(),b.forEach(function(b,c){return a._renderTierLabels(a._tierLabelContainers[c],b,c)});for(var c=b.map(function(b){return a._getTickValuesForConfiguration(b) +}),d=0,e=0;e=i&&(g=this._generateLabellessTicks()),this._renderLabellessTickMarks(g),this._hideOverflowingTiers(),e=0;e=c?"inherit":"hidden"})},d.prototype._hideOverlappingAndCutOffLabels=function(b){var c,d=this,e=this._element.select(".bounding-box")[0][0].getBoundingClientRect(),f=function(a){return Math.floor(e.left)<=Math.ceil(a.left)&&Math.floor(e.top)<=Math.ceil(a.top)&&Math.floor(a.right)<=Math.ceil(e.left+d.width())&&Math.floor(a.bottom)<=Math.ceil(e.top+d.height())},g=this._tierMarkContainers[b].selectAll("."+a.Axis.TICK_MARK_CLASS).filter(function(){var a=d3.select(this).style("visibility");return"visible"===a||"inherit"===a}),h=g[0].map(function(a){return a.getBoundingClientRect()}),i=this._tierLabelContainers[b].selectAll("."+a.Axis.TICK_LABEL_CLASS).filter(function(){var a=d3.select(this).style("visibility");return"visible"===a||"inherit"===a});i.each(function(b,d){var e=this.getBoundingClientRect(),g=d3.select(this),i=h[d],j=h[d+1];!f(e)||null!=c&&a.Utils.DOM.boxesOverlap(e,c)||i.right>e.left||j.left=b[1]?b[0]:b[1];return c===b[0]?a.ticks().filter(function(a){return a>=c&&d>=a}):a.ticks().filter(function(a){return a>=c&&d>=a}).reverse()},c.prototype._rescale=function(){if(this._isSetup){if(!this._isHorizontal()){var a=this._computeWidth();if(a>this.width()||a=f.left)return!1}else if(e.top-c<=f.bottom)return!1}return!0},c}(a.Axis);b.Numeric=c}(b=a.Axes||(a.Axes={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(c,d,e){void 0===d&&(d="bottom"),void 0===e&&(e=a.Formatters.identity()),b.call(this,c,d,e),this._tickLabelAngle=0,this.classed("category-axis",!0)}return __extends(c,b),c.prototype._setup=function(){b.prototype._setup.call(this),this._measurer=new SVGTypewriter.Measurers.CacheCharacterMeasurer(this._tickLabelContainer),this._wrapper=new SVGTypewriter.Wrappers.SingleLineWrapper,this._writer=new SVGTypewriter.Writers.Writer(this._measurer,this._wrapper)},c.prototype._rescale=function(){return this.redraw()},c.prototype.requestedSpace=function(a,b){var c=this._isHorizontal()?0:this._maxLabelTickLength()+this.tickLabelPadding()+this.gutter(),d=this._isHorizontal()?this._maxLabelTickLength()+this.tickLabelPadding()+this.gutter():0;if(0===this._scale.domain().length)return{minWidth:0,minHeight:0};var e=this._scale,f=this._measureTicks(a,b,e,e.domain());return{minWidth:f.usedWidth+c,minHeight:f.usedHeight+d}},c.prototype._getTickValues=function(){return this._scale.domain()},c.prototype.tickLabelAngle=function(a){if(null==a)return this._tickLabelAngle;if(0!==a&&90!==a&&-90!==a)throw new Error("Angle "+a+" not supported; only 0, 90, and -90 are valid values");return this._tickLabelAngle=a,this.redraw(),this},c.prototype._drawTicks=function(a,b,c,d){var e,f,g=this;switch(this.tickLabelAngle()){case 0:e={left:"right",right:"left",top:"center",bottom:"center"},f={left:"center",right:"center",top:"bottom",bottom:"top"};break;case 90:e={left:"center",right:"center",top:"right",bottom:"left"},f={left:"top",right:"bottom",top:"center",bottom:"center"};break;case-90:e={left:"center",right:"center",top:"left",bottom:"right"},f={left:"bottom",right:"top",top:"center",bottom:"center"}}d.each(function(d){var h=c.stepWidth(),i=g._isHorizontal()?h:a-g._maxLabelTickLength()-g.tickLabelPadding(),j=g._isHorizontal()?b-g._maxLabelTickLength()-g.tickLabelPadding():h,k={selection:d3.select(this),xAlign:e[g.orientation()],yAlign:f[g.orientation()],textRotation:g.tickLabelAngle()};g._writer.write(g.formatter()(d),i,j,k)})},c.prototype._measureTicks=function(b,c,d,e){var f=this,g=this._isHorizontal()?b:c,h=2*d.outerPadding(),i=(e.length-1)*d.innerPadding(),j=g/(h+i+e.length),k=j*(1+d.innerPadding()),l=e.map(function(a){var d=b-f._maxLabelTickLength()-f.tickLabelPadding();f._isHorizontal()&&(d=k,0!==f._tickLabelAngle&&(d=c-f._maxLabelTickLength()-f.tickLabelPadding()),d=Math.max(d,0));var e=k;return f._isHorizontal()&&(e=c-f._maxLabelTickLength()-f.tickLabelPadding(),0!==f._tickLabelAngle&&(e=b-f._maxLabelTickLength()-f.tickLabelPadding()),e=Math.max(e,0)),f._wrapper.wrap(f.formatter()(a),f._measurer,d,e)}),m=this._isHorizontal()&&0===this._tickLabelAngle?d3.sum:a.Utils.Methods.max,n=this._isHorizontal()&&0===this._tickLabelAngle?a.Utils.Methods.max:d3.sum,o=l.every(function(a){return!SVGTypewriter.Utils.StringMethods.isNotEmptyString(a.truncatedText)&&1===a.noLines}),p=m(l,function(a){return f._measurer.measure(a.wrappedText).width},0),q=n(l,function(a){return f._measurer.measure(a.wrappedText).height},0);if(0!==this._tickLabelAngle){var r=q;q=p,p=r}return{textFits:o,usedWidth:p,usedHeight:q}},c.prototype.renderImmediately=function(){var c=this;b.prototype.renderImmediately.call(this);var d=this._scale,e=this._tickLabelContainer.selectAll("."+a.Axis.TICK_LABEL_CLASS).data(this._scale.domain(),function(a){return a}),f=function(a){var b=d.stepWidth()-d.rangeBand(),e=d.scale(a)-d.rangeBand()/2-b/2,f=c._isHorizontal()?e:0,g=c._isHorizontal()?0:e;return"translate("+f+","+g+")"};e.enter().append("g").classed(a.Axis.TICK_LABEL_CLASS,!0),e.exit().remove(),e.attr("transform",f),e.text(""),this._drawTicks(this.width(),this.height(),d,e);var g="right"===this.orientation()?this._maxLabelTickLength()+this.tickLabelPadding():0,h="bottom"===this.orientation()?this._maxLabelTickLength()+this.tickLabelPadding():0;return a.Utils.DOM.translate(this._tickLabelContainer,g,h),this},c.prototype.computeLayout=function(a,c,d){return this._measurer.reset(),b.prototype.computeLayout.call(this,a,c,d)},c}(a.Axis);b.Category=c}(b=a.Axes||(a.Axes={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(a){function b(b,c){void 0===b&&(b=""),void 0===c&&(c="horizontal"),a.call(this),this.classed("label",!0),this.text(b),this.orientation(c),this.xAlignment("center").yAlignment("center"),this._padding=0}return __extends(b,a),b.prototype.requestedSpace=function(){var a=this._measurer.measure(this._text),b=("horizontal"===this.orientation()?a.width:a.height)+2*this.padding(),c=("horizontal"===this.orientation()?a.height:a.width)+2*this.padding();return{minWidth:b,minHeight:c}},b.prototype._setup=function(){a.prototype._setup.call(this),this._textContainer=this._content.append("g"),this._measurer=new SVGTypewriter.Measurers.Measurer(this._textContainer),this._wrapper=new SVGTypewriter.Wrappers.Wrapper,this._writer=new SVGTypewriter.Writers.Writer(this._measurer,this._wrapper),this.text(this._text)},b.prototype.text=function(a){return void 0===a?this._text:(this._text=a,this.redraw(),this)},b.prototype.orientation=function(a){if(null==a)return this._orientation;if(a=a.toLowerCase(),"horizontal"!==a&&"left"!==a&&"right"!==a)throw new Error(a+" is not a valid orientation for LabelComponent");return this._orientation=a,this.redraw(),this},b.prototype.padding=function(a){if(null==a)return this._padding;if(a=+a,0>a)throw new Error(a+" is not a valid padding value. Cannot be less than 0.");return this._padding=a,this.redraw(),this},b.prototype.fixedWidth=function(){return!0},b.prototype.fixedHeight=function(){return!0},b.prototype.renderImmediately=function(){a.prototype.renderImmediately.call(this),this._textContainer.selectAll("g").remove();var b=this._measurer.measure(this._text),c=Math.max(Math.min((this.height()-b.height)/2,this.padding()),0),d=Math.max(Math.min((this.width()-b.width)/2,this.padding()),0);this._textContainer.attr("transform","translate("+d+","+c+")");var e=this.width()-2*d,f=this.height()-2*c,g={horizontal:0,right:90,left:-90},h={selection:this._textContainer,xAlign:this.xAlignment(),yAlign:this.yAlignment(),textRotation:g[this.orientation()]};return this._writer.write(this._text,e,f,h),this},b.TITLE_LABEL_CLASS="title-label",b.AXIS_LABEL_CLASS="axis-label",b}(a.Component);b.Label=c}(b=a.Components||(a.Components={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(c){var d=this;if(b.call(this),this._padding=5,this.classed("legend",!0),this.maxEntriesPerRow(1),null==c)throw new Error("Legend requires a colorScale");this._scale=c,this._redrawCallback=function(){return d.redraw()},this._scale.onUpdate(this._redrawCallback),this.xAlignment("right").yAlignment("top"),this._sortFn=function(a,b){return d._scale.domain().indexOf(a)-d._scale.domain().indexOf(b)},this._symbolFactoryAccessor=function(){return a.SymbolFactories.circle()}}return __extends(c,b),c.prototype._setup=function(){b.prototype._setup.call(this);var a=this._content.append("g").classed(c.LEGEND_ROW_CLASS,!0),d=a.append("g").classed(c.LEGEND_ENTRY_CLASS,!0);d.append("text"),this._measurer=new SVGTypewriter.Measurers.Measurer(a),this._wrapper=(new SVGTypewriter.Wrappers.Wrapper).maxLines(1),this._writer=new SVGTypewriter.Writers.Writer(this._measurer,this._wrapper).addTitleElement(!0)},c.prototype.maxEntriesPerRow=function(a){return null==a?this._maxEntriesPerRow:(this._maxEntriesPerRow=a,this.redraw(),this)},c.prototype.sortFunction=function(a){return null==a?this._sortFn:(this._sortFn=a,this.redraw(),this)},c.prototype.scale=function(a){return null!=a?(this._scale.offUpdate(this._redrawCallback),this._scale=a,this._scale.onUpdate(this._redrawCallback),this.redraw(),this):this._scale},c.prototype.destroy=function(){b.prototype.destroy.call(this),this._scale.offUpdate(this._redrawCallback)},c.prototype._calculateLayoutInfo=function(a,b){var c=this,d=this._measurer.measure().height,e=Math.max(0,a-this._padding),f=this._scale.domain().slice();f.sort(this.sortFunction());var g=d3.map(),h=d3.map();f.forEach(function(a){var b=d+c._measurer.measure(a).width+c._padding,f=Math.min(b,e);g.set(a,f),h.set(a,b)});var i=this._packRows(e,f,g),j=Math.floor((b-2*this._padding)/d);return j!==j&&(j=0),{textHeight:d,entryLengths:g,untruncatedEntryLengths:h,rows:i,numRowsToDraw:Math.max(Math.min(j,i.length),0)}},c.prototype.requestedSpace=function(b,c){var d=this._calculateLayoutInfo(b,c),e=d.rows.map(function(a){return d3.sum(a,function(a){return d.untruncatedEntryLengths.get(a)})}),f=a.Utils.Methods.max(e,0);return{minWidth:this._padding+f,minHeight:d.rows.length*d.textHeight+2*this._padding}},c.prototype._packRows=function(a,b,c){var d=this,e=[],f=[],g=a;return b.forEach(function(b){var h=c.get(b);(h>g||f.length===d._maxEntriesPerRow)&&(e.push(f),f=[],g=a),f.push(b),g-=h}),0!==f.length&&e.push(f),e},c.prototype.getEntry=function(a){if(!this._isSetup)return d3.select();var b=d3.select(),d=this._calculateLayoutInfo(this.width(),this.height()),e=this._padding;return this._content.selectAll("g."+c.LEGEND_ROW_CLASS).each(function(f,g){var h=g*d.textHeight+e,i=(g+1)*d.textHeight+e,j=e,k=e;d3.select(this).selectAll("g."+c.LEGEND_ENTRY_CLASS).each(function(c){k+=d.entryLengths.get(c),k>=a.x&&j<=a.x&&i>=a.y&&h<=a.y&&(b=d3.select(this)),j+=d.entryLengths.get(c)})}),b},c.prototype.renderImmediately=function(){var a=this;b.prototype.renderImmediately.call(this);var d=this._calculateLayoutInfo(this.width(),this.height()),e=d.rows.slice(0,d.numRowsToDraw),f=this._content.selectAll("g."+c.LEGEND_ROW_CLASS).data(e);f.enter().append("g").classed(c.LEGEND_ROW_CLASS,!0),f.exit().remove(),f.attr("transform",function(b,c){return"translate(0, "+(c*d.textHeight+a._padding)+")"});var g=f.selectAll("g."+c.LEGEND_ENTRY_CLASS).data(function(a){return a}),h=g.enter().append("g").classed(c.LEGEND_ENTRY_CLASS,!0);h.append("path"),h.append("g").classed("text-container",!0),g.exit().remove();var i=this._padding;f.each(function(){var a=i,b=d3.select(this).selectAll("g."+c.LEGEND_ENTRY_CLASS);b.attr("transform",function(b){var c="translate("+a+", 0)";return a+=d.entryLengths.get(b),c})}),g.select("path").attr("d",function(b,c){return a.symbolFactoryAccessor()(b,c)(.6*d.textHeight)}).attr("transform","translate("+d.textHeight/2+","+d.textHeight/2+")").attr("fill",function(b){return a._scale.scale(b)}).classed(c.LEGEND_SYMBOL_CLASS,!0);var j=this._padding,k=g.select("g.text-container");k.text(""),k.append("title").text(function(a){return a});var l=this;return k.attr("transform","translate("+d.textHeight+", 0)").each(function(a){var b=d3.select(this),c=d.entryLengths.get(a)-d.textHeight-j,e={selection:b,xAlign:"left",yAlign:"top",textRotation:0};l._writer.write(a,c,l.height(),e)}),this},c.prototype.symbolFactoryAccessor=function(a){return null==a?this._symbolFactoryAccessor:(this._symbolFactoryAccessor=a,this.render(),this)},c.prototype.fixedWidth=function(){return!0},c.prototype.fixedHeight=function(){return!0},c.LEGEND_ROW_CLASS="legend-row",c.LEGEND_ENTRY_CLASS="legend-entry",c.LEGEND_SYMBOL_CLASS="legend-symbol",c}(a.Component);b.Legend=c}(b=a.Components||(a.Components={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(d,e,f){var g=this;if(void 0===e&&(e="horizontal"),void 0===f&&(f=a.Formatters.general()),b.call(this),this._padding=5,this._numSwatches=10,null==d)throw new Error("InterpolatedColorLegend requires a interpolatedColorScale");this._scale=d,this._redrawCallback=function(){return g.redraw()},this._scale.onUpdate(this._redrawCallback),this._formatter=f,this._orientation=c._ensureOrientation(e),this.classed("legend",!0).classed("interpolated-color-legend",!0)}return __extends(c,b),c.prototype.destroy=function(){b.prototype.destroy.call(this),this._scale.offUpdate(this._redrawCallback)},c.prototype.formatter=function(a){return void 0===a?this._formatter:(this._formatter=a,this.redraw(),this)},c._ensureOrientation=function(a){if(a=a.toLowerCase(),"horizontal"===a||"left"===a||"right"===a)return a;throw new Error('"'+a+'" is not a valid orientation for InterpolatedColorLegend')},c.prototype.orientation=function(a){return null==a?this._orientation:(this._orientation=c._ensureOrientation(a),this.redraw(),this)},c.prototype.fixedWidth=function(){return!0},c.prototype.fixedHeight=function(){return!0},c.prototype._generateTicks=function(){for(var a=this._scale.domain(),b=(a[1]-a[0])/this._numSwatches,c=[],d=0;d<=this._numSwatches;d++)c.push(a[0]+b*d);return c},c.prototype._setup=function(){b.prototype._setup.call(this),this._swatchContainer=this._content.append("g").classed("swatch-container",!0),this._swatchBoundingBox=this._content.append("rect").classed("swatch-bounding-box",!0),this._lowerLabel=this._content.append("g").classed(c.LEGEND_LABEL_CLASS,!0),this._upperLabel=this._content.append("g").classed(c.LEGEND_LABEL_CLASS,!0),this._measurer=new SVGTypewriter.Measurers.Measurer(this._content),this._wrapper=new SVGTypewriter.Wrappers.Wrapper,this._writer=new SVGTypewriter.Writers.Writer(this._measurer,this._wrapper)},c.prototype.requestedSpace=function(){var b,c,d=this,e=this._measurer.measure().height,f=this._generateTicks(),g=f.length,h=this._scale.domain(),i=h.map(function(a){return d._measurer.measure(d._formatter(a)).width});if(this._isVertical()){var j=a.Utils.Methods.max(i,0);c=this._padding+e+this._padding+j+this._padding,b=this._padding+g*e+this._padding}else b=this._padding+e+this._padding,c=this._padding+i[0]+this._padding+g*e+this._padding+i[1]+this._padding;return{minWidth:c,minHeight:b}},c.prototype._isVertical=function(){return"horizontal"!==this._orientation},c.prototype.renderImmediately=function(){var a=this;b.prototype.renderImmediately.call(this);var c,d,e,f,g=this._scale.domain(),h=this._formatter(g[0]),i=this._measurer.measure(h).width,j=this._formatter(g[1]),k=this._measurer.measure(j).width,l=this._generateTicks(),m=l.length,n=this._padding,o={x:0,y:0},p={x:0,y:0},q={selection:this._lowerLabel,xAlign:"center",yAlign:"center",textRotation:0},r={selection:this._upperLabel,xAlign:"center",yAlign:"center",textRotation:0},s={x:0,y:n,width:0,height:0};if(this._isVertical()){var t=Math.max(i,k);c=Math.max(this.width()-3*n-t,0),d=Math.max((this.height()-2*n)/m,0),f=function(a,b){return n+(m-(b+1))*d},r.yAlign="top",o.y=n,q.yAlign="bottom",p.y=-n,"left"===this._orientation?(e=function(){return n+t+n},r.xAlign="right",o.x=-(n+c+n),q.xAlign="right",p.x=-(n+c+n)):(e=function(){return n},r.xAlign="left",o.x=n+c+n,q.xAlign="left",p.x=n+c+n),s.width=c,s.height=m*d}else c=Math.max((this.width()-4*n-i-k)/m,0),d=Math.max(this.height()-2*n,0),e=function(a,b){return n+i+n+b*c},f=function(){return n},r.xAlign="right",o.x=-n,q.xAlign="left",p.x=n,s.width=m*c,s.height=d;s.x=e(null,0),this._upperLabel.text(""),this._writer.write(j,this.width(),this.height(),r);var u="translate("+o.x+", "+o.y+")";this._upperLabel.attr("transform",u),this._lowerLabel.text(""),this._writer.write(h,this.width(),this.height(),q);var v="translate("+p.x+", "+p.y+")";this._lowerLabel.attr("transform",v),this._swatchBoundingBox.attr(s);var w=this._swatchContainer.selectAll("rect.swatch").data(l);return w.enter().append("rect").classed("swatch",!0),w.exit().remove(),w.attr({fill:function(b){return a._scale.scale(b)},width:c,height:d,x:e,y:f}),this},c.LEGEND_LABEL_CLASS="legend-label",c}(a.Component);b.InterpolatedColorLegend=c}(b=a.Components||(a.Components={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(c,d){var e=this;if(null!=c&&!a.QuantitativeScale.prototype.isPrototypeOf(c))throw new Error("xScale needs to inherit from Scale.QuantitativeScale");if(null!=d&&!a.QuantitativeScale.prototype.isPrototypeOf(d))throw new Error("yScale needs to inherit from Scale.QuantitativeScale");b.call(this),this.classed("gridlines",!0),this._xScale=c,this._yScale=d,this._renderCallback=function(){return e.render()},this._xScale&&this._xScale.onUpdate(this._renderCallback),this._yScale&&this._yScale.onUpdate(this._renderCallback)}return __extends(c,b),c.prototype.destroy=function(){return b.prototype.destroy.call(this),this._xScale&&this._xScale.offUpdate(this._renderCallback),this._yScale&&this._yScale.offUpdate(this._renderCallback),this},c.prototype._setup=function(){b.prototype._setup.call(this),this._xLinesContainer=this._content.append("g").classed("x-gridlines",!0),this._yLinesContainer=this._content.append("g").classed("y-gridlines",!0)},c.prototype.renderImmediately=function(){return b.prototype.renderImmediately.call(this),this._redrawXLines(),this._redrawYLines(),this},c.prototype._redrawXLines=function(){var a=this;if(this._xScale){var b=this._xScale.ticks(),c=function(b){return a._xScale.scale(b)},d=this._xLinesContainer.selectAll("line").data(b);d.enter().append("line"),d.attr("x1",c).attr("y1",0).attr("x2",c).attr("y2",this.height()).classed("zeroline",function(a){return 0===a}),d.exit().remove()}},c.prototype._redrawYLines=function(){var a=this;if(this._yScale){var b=this._yScale.ticks(),c=function(b){return a._yScale.scale(b)},d=this._yLinesContainer.selectAll("line").data(b);d.enter().append("line"),d.attr("x1",0).attr("y1",c).attr("x2",this.width()).attr("y2",c).classed("zeroline",function(a){return 0===a}),d.exit().remove()}},c}(a.Component);b.Gridlines=c}(b=a.Components||(a.Components={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(a){var c=this;void 0===a&&(a=[]),b.call(this),this._rowPadding=0,this._columnPadding=0,this._rows=[],this._rowWeights=[],this._columnWeights=[],this._nRows=0,this._nCols=0,this._calculatedLayout=null,this.classed("table",!0),a.forEach(function(a,b){a.forEach(function(a,d){null!=a&&c.add(a,b,d)})})}return __extends(c,b),c.prototype._forEach=function(a){for(var b=0;b0&&f!==z,E=g>0&&g!==A;if(!D&&!E)break;if(t>5)break}return f=j-d3.sum(w.guaranteedWidths),g=k-d3.sum(w.guaranteedHeights),p=c._calcProportionalSpace(m,f),q=c._calcProportionalSpace(l,g),{colProportionalSpace:p,rowProportionalSpace:q,guaranteedWidths:w.guaranteedWidths,guaranteedHeights:w.guaranteedHeights,wantsWidth:x,wantsHeight:y}},c.prototype._determineGuarantees=function(b,c,d){void 0===d&&(d=!1);var e=a.Utils.Methods.createFilledArray(0,this._nCols),f=a.Utils.Methods.createFilledArray(0,this._nRows),g=a.Utils.Methods.createFilledArray(!1,this._nCols),h=a.Utils.Methods.createFilledArray(!1,this._nRows);return this._rows.forEach(function(a,i){a.forEach(function(a,j){var k;k=null!=a?a.requestedSpace(b[j],c[i]):{minWidth:0,minHeight:0};var l=d?Math.min(k.minWidth,b[j]):k.minWidth;e[j]=Math.max(e[j],l);var m=d?Math.min(k.minHeight,c[i]):k.minHeight;f[i]=Math.max(f[i],m);var n=k.minWidth>b[j];g[j]=g[j]||n;var o=k.minHeight>c[i];h[i]=h[i]||o})}),{guaranteedWidths:e,guaranteedHeights:f,wantsWidthArr:g,wantsHeightArr:h}},c.prototype.requestedSpace=function(a,b){return this._calculatedLayout=this._iterateLayout(a,b),{minWidth:d3.sum(this._calculatedLayout.guaranteedWidths),minHeight:d3.sum(this._calculatedLayout.guaranteedHeights)}},c.prototype.computeLayout=function(c,d,e){var f=this;b.prototype.computeLayout.call(this,c,d,e);var g=d3.sum(this._calculatedLayout.guaranteedWidths),h=d3.sum(this._calculatedLayout.guaranteedHeights),i=this._calculatedLayout;(g>this.width()||h>this.height())&&(i=this._iterateLayout(this.width(),this.height(),!0));var j=0,k=a.Utils.Methods.addArrays(i.rowProportionalSpace,i.guaranteedHeights),l=a.Utils.Methods.addArrays(i.colProportionalSpace,i.guaranteedWidths);return this._rows.forEach(function(a,b){var c=0;a.forEach(function(a,d){null!=a&&a.computeLayout({x:c,y:j},l[d],k[b]),c+=l[d]+f._columnPadding}),j+=k[b]+f._rowPadding}),this},c.prototype.rowPadding=function(a){return null==a?this._rowPadding:(this._rowPadding=a,this.redraw(),this)},c.prototype.columnPadding=function(a){return null==a?this._columnPadding:(this._columnPadding=a,this.redraw(),this)},c.prototype.rowWeight=function(a,b){return null==b?this._rowWeights[a]:(this._rowWeights[a]=b,this.redraw(),this)},c.prototype.columnWeight=function(a,b){return null==b?this._columnWeights[a]:(this._columnWeights[a]=b,this.redraw(),this)},c.prototype.fixedWidth=function(){var a=d3.transpose(this._rows);return c._fixedSpace(a,function(a){return null==a||a.fixedWidth()})},c.prototype.fixedHeight=function(){return c._fixedSpace(this._rows,function(a){return null==a||a.fixedHeight()})},c.prototype._padTableToSize=function(a,b){for(var c=0;a>c;c++){void 0===this._rows[c]&&(this._rows[c]=[],this._rowWeights[c]=null);for(var d=0;b>d;d++)void 0===this._rows[c][d]&&(this._rows[c][d]=null)}for(d=0;b>d;d++)void 0===this._columnWeights[d]&&(this._columnWeights[d]=null)},c._calcComponentWeights=function(a,b,c){return a.map(function(a,d){if(null!=a)return a;var e=b[d].map(c),f=e.reduce(function(a,b){return a&&b},!0);return f?0:1})},c._calcProportionalSpace=function(b,c){var d=d3.sum(b);return 0===d?a.Utils.Methods.createFilledArray(0,b.length):b.map(function(a){return c*a/d})},c._fixedSpace=function(a,b){var c=function(a){return a.reduce(function(a,b){return a&&b},!0)},d=function(a){return c(a.map(b))};return c(a.map(d))},c}(a.ComponentContainer);b.Table=c}(b=a.Components||(a.Components={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(a){function b(){a.call(this),this._boxVisible=!1,this._boxBounds={topLeft:{x:0,y:0},bottomRight:{x:0,y:0}},this.classed("selection-box-layer",!0)}return __extends(b,a),b.prototype._setup=function(){a.prototype._setup.call(this),this._box=this._content.append("g").classed("selection-box",!0).remove(),this._boxArea=this._box.append("rect").classed("selection-area",!0)},b.prototype._getSize=function(a,b){return{width:a,height:b}},b.prototype.bounds=function(a){return null==a?this._boxBounds:(this._setBounds(a),this.render(),this)},b.prototype._setBounds=function(a){var b={x:Math.min(a.topLeft.x,a.bottomRight.x),y:Math.min(a.topLeft.y,a.bottomRight.y)},c={x:Math.max(a.topLeft.x,a.bottomRight.x),y:Math.max(a.topLeft.y,a.bottomRight.y)};this._boxBounds={topLeft:b,bottomRight:c}},b.prototype.renderImmediately=function(){if(this._boxVisible){var a=this._boxBounds.topLeft.y,b=this._boxBounds.bottomRight.y,c=this._boxBounds.topLeft.x,d=this._boxBounds.bottomRight.x;this._boxArea.attr({x:c,y:a,width:d-c,height:b-a}),this._content.node().appendChild(this._box.node())}else this._box.remove();return this},b.prototype.boxVisible=function(a){return null==a?this._boxVisible:(this._boxVisible=a,this.render(),this)},b.prototype.fixedWidth=function(){return!0},b.prototype.fixedHeight=function(){return!0},b}(a.Component);b.SelectionBoxLayer=c}(b=a.Components||(a.Components={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(){}(b=a.Plots||(a.Plots={}));var c=function(b){function c(){var a=this;b.call(this),this._dataChanged=!1,this._animate=!1,this._animators={},this._animateOnNextRender=!0,this._clipPathEnabled=!0,this.classed("plot",!0),this._key2PlotDatasetKey=d3.map(),this._attrBindings=d3.map(),this._attrExtents=d3.map(),this._extentsProvider=function(b){return a._extentsForScale(b)},this._datasetKeysInOrder=[],this._nextSeriesIndex=0,this._renderCallback=function(){return a.render()},this._onDatasetUpdateCallback=function(){return a._onDatasetUpdate()},this._propertyBindings=d3.map(),this._propertyExtents=d3.map()}return __extends(c,b),c.prototype.anchor=function(a){return b.prototype.anchor.call(this,a),this._animateOnNextRender=!0,this._dataChanged=!0,this._updateExtents(),this},c.prototype._setup=function(){var a=this;b.prototype._setup.call(this),this._renderArea=this._content.append("g").classed("render-area",!0),this._getDrawersInOrder().forEach(function(b){return b.setup(a._renderArea.append("g"))})},c.prototype.destroy=function(){var a=this;b.prototype.destroy.call(this),this._scales().forEach(function(b){return b.offUpdate(a._renderCallback)}),this.datasets().forEach(function(b){return a.removeDataset(b)})},c.prototype.addDataset=function(a){var b="_"+this._nextSeriesIndex++;this._key2PlotDatasetKey.has(b)&&this.removeDataset(a);var c=this._getDrawer(b),d=this._getPlotMetadataForDataset(b),e={drawer:c,dataset:a,key:b,plotMetadata:d};return this._datasetKeysInOrder.push(b),this._key2PlotDatasetKey.set(b,e),this._isSetup&&c.setup(this._renderArea.append("g")),a.onUpdate(this._onDatasetUpdateCallback),this._onDatasetUpdate(),this},c.prototype._getDrawer=function(b){return new a.Drawers.AbstractDrawer(b)},c.prototype._getAnimator=function(b){return this._animate&&this._animateOnNextRender?this._animators[b]||new a.Animators.Null:new a.Animators.Null},c.prototype._onDatasetUpdate=function(){this._updateExtents(),this._animateOnNextRender=!0,this._dataChanged=!0,this.render()},c.prototype.attr=function(a,b,c){return null==b?this._attrBindings.get(a):(this._bindAttr(a,b,c),this.render(),this)},c.prototype._bindProperty=function(a,b,c){this._bind(a,b,c,this._propertyBindings,this._propertyExtents),this._updateExtentsForProperty(a)},c.prototype._bindAttr=function(a,b,c){this._bind(a,b,c,this._attrBindings,this._attrExtents),this._updateExtentsForAttr(a)},c.prototype._bind=function(a,b,c,d){var e=d.get(a),f=null!=e?e.scale:null;null!=f&&this._uninstallScaleForKey(f,a),null!=c&&this._installScaleForKey(c,a),d.set(a,{accessor:d3.functor(b),scale:c})},c.prototype._generateAttrToProjector=function(){var a={};this._attrBindings.forEach(function(b,c){var d=c.accessor,e=c.scale,f=e?function(a,b,c,f){return e.scale(d(a,b,c,f))}:d;a[b]=f});var b=this._generatePropertyToProjectors();return Object.keys(b).forEach(function(c){null==a[c]&&(a[c]=b[c])}),a},c.prototype.generateProjectors=function(a){var b={},c=this._keyForDataset(a);if(null!=c){var d=this._generateAttrToProjector(),e=this._key2PlotDatasetKey.get(c),f=e.plotMetadata;d3.entries(d).forEach(function(a){b[a.key]=function(b,c){return a.value(b,c,e.dataset,f)}})}return b},c.prototype.renderImmediately=function(){return this._isAnchored&&(this._paint(),this._dataChanged=!1,this._animateOnNextRender=!1),this},c.prototype.animate=function(a){return this._animate=a,this},c.prototype.detach=function(){return b.prototype.detach.call(this),this._updateExtents(),this},c.prototype._scales=function(){var a=[];return this._attrBindings.forEach(function(b,c){var d=c.scale;null!=d&&-1===a.indexOf(d)&&a.push(d)}),this._propertyBindings.forEach(function(b,c){var d=c.scale;null!=d&&-1===a.indexOf(d)&&a.push(d)}),a},c.prototype._updateExtents=function(){var a=this;this._attrBindings.forEach(function(b){return a._updateExtentsForAttr(b)}),this._propertyExtents.forEach(function(b){return a._updateExtentsForProperty(b)}),this._scales().forEach(function(a){return a._autoDomainIfAutomaticMode()})},c.prototype._updateExtentsForAttr=function(a){this._updateExtentsForKey(a,this._attrBindings,this._attrExtents,null)},c.prototype._updateExtentsForProperty=function(a){this._updateExtentsForKey(a,this._propertyBindings,this._propertyExtents,this._filterForProperty(a))},c.prototype._filterForProperty=function(){return null},c.prototype._updateExtentsForKey=function(a,b,c,d){var e=this,f=b.get(a);null!=f.accessor&&c.set(a,this._datasetKeysInOrder.map(function(a){var b=e._key2PlotDatasetKey.get(a),c=b.dataset,g=b.plotMetadata;return e._computeExtent(c,f.accessor,g,d)}))},c.prototype._computeExtent=function(b,c,d,e){var f=b.data();null!=e&&(f=f.filter(function(a,c){return e(a,c,b,d)}));var g=function(a,e){return c(a,e,b,d)},h=f.map(g);if(0===h.length)return[];if("string"==typeof h[0])return a.Utils.Methods.uniq(h);var i=d3.extent(h);return null==i[0]||null==i[1]?[]:i},c.prototype._extentsForProperty=function(a){return this._propertyExtents.get(a)},c.prototype._extentsForScale=function(a){var b=this;if(!this._isAnchored)return[];var c=[];return this._attrBindings.forEach(function(d,e){if(e.scale===a){var f=b._attrExtents.get(d);null!=f&&c.push(f)}}),this._propertyBindings.forEach(function(d,e){if(e.scale===a){var f=b._extentsForProperty(d);null!=f&&c.push(f)}}),d3.merge(c)},c.prototype.animator=function(a,b){return void 0===b?this._animators[a]:(this._animators[a]=b,this)},c.prototype.removeDataset=function(a){var b=this._keyForDataset(a);if(null!=b&&this._key2PlotDatasetKey.has(b)){var c=this._key2PlotDatasetKey.get(b);c.drawer.remove(),c.dataset.offUpdate(this._onDatasetUpdateCallback),this._datasetKeysInOrder.splice(this._datasetKeysInOrder.indexOf(b),1),this._key2PlotDatasetKey.remove(b),this._onDatasetUpdate()}return this},c.prototype._keyForDataset=function(a){return this._datasetKeysInOrder[this.datasets().indexOf(a)]},c.prototype._keysForDatasets=function(a){var b=this;return a.map(function(a){return b._keyForDataset(a)}).filter(function(a){return null!=a})},c.prototype.datasets=function(a){var b=this,c=this._datasetKeysInOrder.map(function(a){return b._key2PlotDatasetKey.get(a).dataset});return null==a?c:(c.forEach(function(a){return b.removeDataset(a)}),a.forEach(function(a){return b.addDataset(a)}),this)},c.prototype._getDrawersInOrder=function(){var a=this;return this._datasetKeysInOrder.map(function(b){return a._key2PlotDatasetKey.get(b).drawer})},c.prototype._generateDrawSteps=function(){return[{attrToProjector:this._generateAttrToProjector(),animator:new a.Animators.Null}]},c.prototype._additionalPaint=function(){},c.prototype._getDataToDraw=function(){var a=this,b=d3.map();return this._datasetKeysInOrder.forEach(function(c){b.set(c,a._key2PlotDatasetKey.get(c).dataset.data())}),b},c.prototype._getPlotMetadataForDataset=function(a){return{datasetKey:a}},c.prototype._paint=function(){var b=this,c=this._generateDrawSteps(),d=this._getDataToDraw(),e=this._getDrawersInOrder(),f=this._datasetKeysInOrder.map(function(a,f){return e[f].draw(d.get(a),c,b._key2PlotDatasetKey.get(a).dataset,b._key2PlotDatasetKey.get(a).plotMetadata)}),g=a.Utils.Methods.max(f,0);this._additionalPaint(g)},c.prototype.getAllSelections=function(a,b){var c=this;void 0===a&&(a=this.datasets()),void 0===b&&(b=!1);var d=this._keysForDatasets(a);if(b){var e=d3.set(d);d=this._datasetKeysInOrder.filter(function(a){return!e.has(a)})}var f=[];return d.forEach(function(a){var b=c._key2PlotDatasetKey.get(a);if(null!=b){var d=b.drawer;d._getRenderArea().selectAll(d._getSelector()).each(function(){f.push(this)})}}),d3.selectAll(f)},c.prototype.getAllPlotData=function(a){var b=this;void 0===a&&(a=this.datasets());var c=[],d=[],e=[];return this._keysForDatasets(a).forEach(function(a){var f=b._key2PlotDatasetKey.get(a);if(null!=f){var g=f.drawer;f.dataset.data().forEach(function(a,b){var f=g._getPixelPoint(a,b);f.x===f.x&&f.y===f.y&&(c.push(a),d.push(f),e.push(g._getSelection(b).node()))})}}),{data:c,pixelPoints:d,selection:d3.selectAll(e)}},c.prototype.getClosestPlotData=function(b){var c,d=this,e=1/0,f=this.getAllPlotData();return f.pixelPoints.forEach(function(g,h){var i=f.data[h],j=d3.select(f.selection[0][h]);if(d._isVisibleOnPlot(i,g,j)){var k=a.Utils.Methods.distanceSquared(g,b);e>k&&(e=k,c=h)}}),null==c?{data:[],pixelPoints:[],selection:d3.select()}:{data:[f.data[c]],pixelPoints:[f.pixelPoints[c]],selection:d3.select(f.selection[0][c])}},c.prototype._isVisibleOnPlot=function(a,b){return!(b.x<0||b.y<0||b.x>this.width()||b.y>this.height())},c.prototype._uninstallScaleForKey=function(a){a.offUpdate(this._renderCallback),a.removeExtentsProvider(this._extentsProvider),a._autoDomainIfAutomaticMode()},c.prototype._installScaleForKey=function(a){a.onUpdate(this._renderCallback),a.addExtentsProvider(this._extentsProvider),a._autoDomainIfAutomaticMode()},c.prototype._generatePropertyToProjectors=function(){var a={};return this._propertyBindings.forEach(function(b,c){var d=function(a,b,d,e){return c.scale.scale(c.accessor(a,b,d,e))};a[b]=null==c.scale?c.accessor:d}),a},c}(a.Component);a.Plot=c}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(){var c=this;b.call(this),this.innerRadius(0),this.outerRadius(function(){return Math.min(c.width(),c.height())/2}),this.classed("pie-plot",!0),this.attr("fill",function(a,b){return String(b)},new a.Scales.Color)}return __extends(c,b),c.prototype.computeLayout=function(a,c,d){b.prototype.computeLayout.call(this,a,c,d),this._renderArea.attr("transform","translate("+this.width()/2+","+this.height()/2+")");var e=Math.min(this.width(),this.height())/2;return null!=this.innerRadius().scale&&this.innerRadius().scale.range([0,e]),null!=this.outerRadius().scale&&this.outerRadius().scale.range([0,e]),this},c.prototype.addDataset=function(c){return 1===this._datasetKeysInOrder.length?(a.Utils.Methods.warn("Only one dataset is supported in Pie plots"),this):(b.prototype.addDataset.call(this,c),this)},c.prototype._getDrawer=function(b){return new a.Drawers.Arc(b).setClass("arc")},c.prototype.getAllPlotData=function(a){var c=this;void 0===a&&(a=this.datasets());var d=b.prototype.getAllPlotData.call(this,a);return d.pixelPoints.forEach(function(a){a.x=a.x+c.width()/2,a.y=a.y+c.height()/2}),d},c.prototype.sectorValue=function(a,b){return null==a?this._propertyBindings.get(c._SECTOR_VALUE_KEY):(this._bindProperty(c._SECTOR_VALUE_KEY,a,b),this.renderImmediately(),this)},c.prototype.innerRadius=function(a,b){return null==a?this._propertyBindings.get(c._INNER_RADIUS_KEY):(this._bindProperty(c._INNER_RADIUS_KEY,a,b),this.renderImmediately(),this)},c.prototype.outerRadius=function(a,b){return null==a?this._propertyBindings.get(c._OUTER_RADIUS_KEY):(this._bindProperty(c._OUTER_RADIUS_KEY,a,b),this.renderImmediately(),this)},c._INNER_RADIUS_KEY="inner-radius",c._OUTER_RADIUS_KEY="outer-radius",c._SECTOR_VALUE_KEY="sector-value",c}(a.Plot);b.Pie=c}(b=a.Plots||(a.Plots={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b=function(b){function c(a,d){var e=this;if(b.call(this),this._autoAdjustXScaleDomain=!1,this._autoAdjustYScaleDomain=!1,null==a||null==d)throw new Error("XYPlots require an xScale and yScale");this.classed("xy-plot",!0),this._propertyBindings.set(c._X_KEY,{accessor:null,scale:a}),this._propertyBindings.set(c._Y_KEY,{accessor:null,scale:d}),this._adjustYDomainOnChangeFromXCallback=function(){return e._adjustYDomainOnChangeFromX()},this._adjustXDomainOnChangeFromYCallback=function(){return e._adjustXDomainOnChangeFromY()},this._updateXDomainer(),a.onUpdate(this._adjustYDomainOnChangeFromXCallback),this._updateYDomainer(),d.onUpdate(this._adjustXDomainOnChangeFromYCallback)}return __extends(c,b),c.prototype.x=function(a,b){return null==a?this._propertyBindings.get(c._X_KEY):(this._bindProperty(c._X_KEY,a,b),this._autoAdjustYScaleDomain&&this._updateYExtentsAndAutodomain(),this._updateXDomainer(),this.renderImmediately(),this)},c.prototype.y=function(a,b){return null==a?this._propertyBindings.get(c._Y_KEY):(this._bindProperty(c._Y_KEY,a,b),this._autoAdjustXScaleDomain&&this._updateXExtentsAndAutodomain(),this._updateYDomainer(),this.renderImmediately(),this)},c.prototype._filterForProperty=function(a){return"x"===a&&this._autoAdjustXScaleDomain?this._makeFilterByProperty("y"):"y"===a&&this._autoAdjustYScaleDomain?this._makeFilterByProperty("x"):null},c.prototype._makeFilterByProperty=function(b){var c=this._propertyBindings.get(b);if(null!=c){var d=c.accessor,e=c.scale;if(null!=e)return function(b,c,f,g){var h=e.range();return a.Utils.Methods.inRange(e.scale(d(b,c,f,g)),h[0],h[1])}}return null},c.prototype._uninstallScaleForKey=function(a,d){b.prototype._uninstallScaleForKey.call(this,a,d);var e=d===c._X_KEY?this._adjustYDomainOnChangeFromXCallback:this._adjustXDomainOnChangeFromYCallback;a.offUpdate(e)},c.prototype._installScaleForKey=function(a,d){b.prototype._installScaleForKey.call(this,a,d);var e=d===c._X_KEY?this._adjustYDomainOnChangeFromXCallback:this._adjustXDomainOnChangeFromYCallback;a.onUpdate(e)},c.prototype.destroy=function(){return b.prototype.destroy.call(this),this.x().scale&&this.x().scale.offUpdate(this._adjustYDomainOnChangeFromXCallback),this.y().scale&&this.y().scale.offUpdate(this._adjustXDomainOnChangeFromYCallback),this},c.prototype.automaticallyAdjustYScaleOverVisiblePoints=function(a){return this._autoAdjustYScaleDomain=a,this._adjustYDomainOnChangeFromX(),this},c.prototype.automaticallyAdjustXScaleOverVisiblePoints=function(a){return this._autoAdjustXScaleDomain=a,this._adjustXDomainOnChangeFromY(),this},c.prototype._generatePropertyToProjectors=function(){var a=b.prototype._generatePropertyToProjectors.call(this),c=a.x,d=a.y;return a.defined=function(a,b,e,f){var g=c(a,b,e,f),h=d(a,b,e,f);return null!=g&&g===g&&null!=h&&h===h},a},c.prototype.computeLayout=function(c,d,e){b.prototype.computeLayout.call(this,c,d,e);var f=this.x().scale;null!=f&&f.range([0,this.width()]);var g=this.y().scale;return null!=g&&this.y().scale.range(this.y().scale instanceof a.Scales.Category?[0,this.height()]:[this.height(),0]),this},c.prototype._updateXDomainer=function(){if(this.x().scale instanceof a.QuantitativeScale){var b=this.x().scale;b._userSetDomainer||b.domainer().pad().nice()}},c.prototype._updateYDomainer=function(){if(this.y().scale instanceof a.QuantitativeScale){var b=this.y().scale;b._userSetDomainer||b.domainer().pad().nice()}},c.prototype._updateXExtentsAndAutodomain=function(){this._updateExtentsForProperty("x");var a=this.x().scale;null!=a&&a.autoDomain()},c.prototype._updateYExtentsAndAutodomain=function(){this._updateExtentsForProperty("y");var a=this.y().scale;null!=a&&a.autoDomain()},c.prototype.showAllData=function(){return this._updateXExtentsAndAutodomain(),this._updateYExtentsAndAutodomain(),this},c.prototype._adjustYDomainOnChangeFromX=function(){this._projectorsReady()&&this._autoAdjustYScaleDomain&&this._updateYExtentsAndAutodomain()},c.prototype._adjustXDomainOnChangeFromY=function(){this._projectorsReady()&&this._autoAdjustXScaleDomain&&this._updateXExtentsAndAutodomain()},c.prototype._projectorsReady=function(){return null!=this.x().accessor&&null!=this.y().accessor},c._X_KEY="x",c._Y_KEY="y",c}(a.Plot);a.XYPlot=b}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(c,d){b.call(this,c,d),this.classed("rectangle-plot",!0),this.attr("fill",(new a.Scales.Color).range()[0])}return __extends(c,b),c.prototype._getDrawer=function(b){return new a.Drawers.Rect(b,!0)},c.prototype._generateAttrToProjector=function(){var a=b.prototype._generateAttrToProjector.call(this),c=a.x1,d=a.y1,e=a.x2,f=a.y2;return a.width=function(a,b,d,f){return Math.abs(e(a,b,d,f)-c(a,b,d,f))},a.x=function(a,b,d,f){return Math.min(c(a,b,d,f),e(a,b,d,f))},a.height=function(a,b,c,e){return Math.abs(f(a,b,c,e)-d(a,b,c,e))},a.y=function(b,c,e,g){return Math.max(d(b,c,e,g),f(b,c,e,g))-a.height(b,c,e,g)},delete a.x1,delete a.y1,delete a.x2,delete a.y2,a},c.prototype._generateDrawSteps=function(){return[{attrToProjector:this._generateAttrToProjector(),animator:this._getAnimator("rectangles")}]},c.prototype.x1=function(a,b){return null==a?this._propertyBindings.get(c._X1_KEY):(this._bindProperty(c._X1_KEY,a,b),this.renderImmediately(),this)},c.prototype.x2=function(a,b){return null==a?this._propertyBindings.get(c._X2_KEY):(this._bindProperty(c._X2_KEY,a,b),this.renderImmediately(),this)},c.prototype.y1=function(a,b){return null==a?this._propertyBindings.get(c._Y1_KEY):(this._bindProperty(c._Y1_KEY,a,b),this.renderImmediately(),this)},c.prototype.y2=function(a,b){return null==a?this._propertyBindings.get(c._Y2_KEY):(this._bindProperty(c._Y2_KEY,a,b),this.renderImmediately(),this)},c._X1_KEY="x1",c._X2_KEY="x2",c._Y1_KEY="y1",c._Y2_KEY="y2",c}(a.XYPlot);b.Rectangle=c}(b=a.Plots||(a.Plots={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(c,d){b.call(this,c,d),this.classed("scatter-plot",!0),this.animator("symbols-reset",new a.Animators.Null),this.animator("symbols",(new a.Animators.Base).duration(250).delay(5)),this.attr("opacity",.6),this.attr("fill",(new a.Scales.Color).range()[0])}return __extends(c,b),c.prototype._getDrawer=function(b){return new a.Drawers.Symbol(b)},c.prototype._generateAttrToProjector=function(){var c=b.prototype._generateAttrToProjector.call(this);return c.size=c.size||d3.functor(6),c.symbol=c.symbol||function(){return a.SymbolFactories.circle()},c},c.prototype.size=function(a,b){return null==a?this._propertyBindings.get(c._SIZE_KEY):(this._bindProperty(c._SIZE_KEY,a,b),this.renderImmediately(),this)},c.prototype.symbol=function(a){return null==a?this._propertyBindings.get(c._SYMBOL_KEY):(this._propertyBindings.set(c._SYMBOL_KEY,{accessor:a}),this.renderImmediately(),this)},c.prototype._generateDrawSteps=function(){var a=[];if(this._dataChanged&&this._animate){var b=this._generateAttrToProjector();b.size=function(){return 0},a.push({attrToProjector:b,animator:this._getAnimator("symbols-reset")})}return a.push({attrToProjector:this._generateAttrToProjector(),animator:this._getAnimator("symbols")}),a},c.prototype._isVisibleOnPlot=function(b,c,d){var e={min:0,max:this.width()},f={min:0,max:this.height()},g=d3.transform(d.attr("transform")).translate,h=d[0][0].getBBox(),i={x:h.x+g[0],y:h.y+g[1],width:h.width,height:h.height};return a.Utils.Methods.intersectsBBox(e,f,i)},c._SIZE_KEY="size",c._SYMBOL_KEY="symbol",c}(a.XYPlot);b.Scatter=c}(b=a.Plots||(a.Plots={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(c,d){b.call(this,c,d),this.classed("grid-plot",!0),c instanceof a.Scales.Category&&c.innerPadding(0).outerPadding(0),d instanceof a.Scales.Category&&d.innerPadding(0).outerPadding(0),this.animator("cells",new a.Animators.Null)}return __extends(c,b),c.prototype.addDataset=function(c){return 1===this._datasetKeysInOrder.length?(a.Utils.Methods.warn("Only one dataset is supported in Grid plots"),this):(b.prototype.addDataset.call(this,c),this)},c.prototype._getDrawer=function(b){return new a.Drawers.Rect(b,!0)},c.prototype._generateDrawSteps=function(){return[{attrToProjector:this._generateAttrToProjector(),animator:this._getAnimator("cells")}]},c.prototype.x=function(c,d){var e=this;if(null==c)return b.prototype.x.call(this);if(null==d)b.prototype.x.call(this,c);else if(b.prototype.x.call(this,c,d),d instanceof a.Scales.Category){var f=d;this.x1(function(a,b,c,g){return d.scale(e.x().accessor(a,b,c,g))-f.rangeBand()/2}),this.x2(function(a,b,c,g){return d.scale(e.x().accessor(a,b,c,g))+f.rangeBand()/2})}else d instanceof a.QuantitativeScale&&this.x1(function(a,b,c,f){return d.scale(e.x().accessor(a,b,c,f))});return this},c.prototype.y=function(c,d){var e=this;if(null==c)return b.prototype.y.call(this);if(null==d)b.prototype.y.call(this,c);else if(b.prototype.y.call(this,c,d),d instanceof a.Scales.Category){var f=d;this.y1(function(a,b,c,g){return d.scale(e.y().accessor(a,b,c,g))-f.rangeBand()/2}),this.y2(function(a,b,c,g){return d.scale(e.y().accessor(a,b,c,g))+f.rangeBand()/2})}else d instanceof a.QuantitativeScale&&this.y1(function(a,b,c,f){return d.scale(e.y().accessor(a,b,c,f))});return this},c}(b.Rectangle);b.Grid=c}(b=a.Plots||(a.Plots={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(c,d,e){var f=this;void 0===e&&(e=!0),b.call(this,c,d),this._barAlignmentFactor=.5,this._labelFormatter=a.Formatters.identity(),this._labelsEnabled=!1,this._hideBarsIfAnyAreTooWide=!0,this.classed("bar-plot",!0),this.animator("bars-reset",new a.Animators.Null),this.animator("bars",new a.Animators.Base),this.animator("baseline",new a.Animators.Null),this._isVertical=e,this.baseline(0),this.attr("fill",(new a.Scales.Color).range()[0]),this.attr("width",function(){return f._getBarPixelWidth()})}return __extends(c,b),c.prototype._getDrawer=function(b){return new a.Drawers.Rect(b,this._isVertical)},c.prototype._setup=function(){b.prototype._setup.call(this),this._baseline=this._renderArea.append("line").classed("baseline",!0)},c.prototype.baseline=function(a){return null==a?this._baselineValue:(this._baselineValue=a,this._updateXDomainer(),this._updateYDomainer(),this.render(),this)},c.prototype.barAlignment=function(a){var b=a.toLowerCase(),c=this.constructor._BarAlignmentToFactor;if(void 0===c[b])throw new Error("unsupported bar alignment");return this._barAlignmentFactor=c[b],this.render(),this},c.prototype.labelsEnabled=function(a){return void 0===a?this._labelsEnabled:(this._labelsEnabled=a,this.render(),this)},c.prototype.labelFormatter=function(a){return null==a?this._labelFormatter:(this._labelFormatter=a,this.render(),this)},c.prototype.getClosestPlotData=function(b){var c=this,d=1/0,e=1/0,f=[],g=[],h=[],i=this._isVertical?b.x:b.y,j=this._isVertical?b.y:b.x,k=.5;return this.datasets().forEach(function(l){var m=c.getAllPlotData([l]);m.pixelPoints.forEach(function(l,n){var o=m.data[n],p=m.selection[0][n];if(c._isVisibleOnPlot(o,l,d3.select(p))){var q=0,r=0,s=p.getBBox();if(!a.Utils.Methods.intersectsBBox(b.x,b.y,s,k)){var t=c._isVertical?l.x:l.y;q=Math.abs(i-t);var u=c._isVertical?s.y:s.x,v=u+(c._isVertical?s.height:s.width);if(j>=u-k&&v+k>=j)r=0;else{var w=c._isVertical?l.y:l.x;r=Math.abs(j-w)}}(d>q||q===d&&e>r)&&(f=[],g=[],h=[],d=q,e=r),q===d&&r===e&&(f.push(o),g.push(l),h.push(p))}})}),{data:f,pixelPoints:g,selection:d3.selectAll(h)}},c.prototype._isVisibleOnPlot=function(b,c,d){var e={min:0,max:this.width()},f={min:0,max:this.height()},g=d[0][0].getBBox();return a.Utils.Methods.intersectsBBox(e,f,g)},c.prototype.getBars=function(a,b){var c=this;if(!this._isSetup)return d3.select();var d=this._datasetKeysInOrder.reduce(function(d,e){return d.concat(c._getBarsFromDataset(e,a,b))},[]);return d3.selectAll(d)},c.prototype._getBarsFromDataset=function(b,c,d){var e=[],f=this._key2PlotDatasetKey.get(b).drawer;return f._getRenderArea().selectAll("rect").each(function(){a.Utils.Methods.intersectsBBox(c,d,this.getBBox())&&e.push(this)}),e},c.prototype._updateDomainer=function(b){if(b instanceof a.QuantitativeScale){var c=b;c._userSetDomainer||(null!=this._baselineValue?c.domainer().addPaddingException(this,this._baselineValue).addIncludedValue(this,this._baselineValue):c.domainer().removePaddingException(this).removeIncludedValue(this),c.domainer().pad().nice()),c._autoDomainIfAutomaticMode()}},c.prototype._updateYDomainer=function(){this._isVertical?this._updateDomainer(this.y().scale):b.prototype._updateYDomainer.call(this)},c.prototype._updateXDomainer=function(){this._isVertical?b.prototype._updateXDomainer.call(this):this._updateDomainer(this.x().scale)},c.prototype._additionalPaint=function(b){var c=this,d=this._isVertical?this.y().scale:this.x().scale,e=d.scale(this._baselineValue),f={x1:this._isVertical?0:e,y1:this._isVertical?e:0,x2:this._isVertical?this.width():e,y2:this._isVertical?e:this.height()};this._getAnimator("baseline").animate(this._baseline,f);var g=this._getDrawersInOrder();g.forEach(function(a){return a.removeLabels()}),this._labelsEnabled&&a.Utils.Methods.setTimeout(function(){return c._drawLabels()},b)},c.prototype._drawLabels=function(){var a=this,b=this._getDrawersInOrder(),c=this._generateAttrToProjector(),d=this._getDataToDraw();this._datasetKeysInOrder.forEach(function(e,f){return b[f].drawText(d.get(e),c,a._key2PlotDatasetKey.get(e).dataset,a._key2PlotDatasetKey.get(e).plotMetadata)}),this._hideBarsIfAnyAreTooWide&&b.some(function(a){return a._getIfLabelsTooWide()})&&b.forEach(function(a){return a.removeLabels()})},c.prototype._generateDrawSteps=function(){var a=[];if(this._dataChanged&&this._animate){var b=this._generateAttrToProjector(),c=this._isVertical?this.y().scale:this.x().scale,d=c.scale(this._baselineValue),e=this._isVertical?"y":"x",f=this._isVertical?"height":"width";b[e]=function(){return d},b[f]=function(){return 0},a.push({attrToProjector:b,animator:this._getAnimator("bars-reset")})}return a.push({attrToProjector:this._generateAttrToProjector(),animator:this._getAnimator("bars")}),a},c.prototype._generateAttrToProjector=function(){var c=this,d=b.prototype._generateAttrToProjector.call(this),e=this._isVertical?this.y().scale:this.x().scale,f=this._isVertical?this.x().scale:this.y().scale,g=this._isVertical?"y":"x",h=this._isVertical?"x":"y",i=e.scale(this._baselineValue),j=d[h],k=d.width,l=d[g],m=function(a,b,c,d){return Math.abs(i-l(a,b,c,d)) +};d.width=this._isVertical?k:m,d.height=this._isVertical?m:k,d[h]=f instanceof a.Scales.Category?function(a,b,c,d){return j(a,b,c,d)-k(a,b,c,d)/2}:function(a,b,d,e){return j(a,b,d,e)-k(a,b,d,e)*c._barAlignmentFactor},d[g]=function(a,b,c,d){var e=l(a,b,c,d);return e>i?i:e};var n=this._propertyBindings.get(g).accessor;return this._labelsEnabled&&this._labelFormatter&&(d.label=function(a,b,d,e){return c._labelFormatter(n(a,b,d,e))},d.positive=function(a,b,c,d){return l(a,b,c,d)<=i}),d},c.prototype._getBarPixelWidth=function(){var b=this;if(!this._projectorsReady())return 0;var d,e=this._isVertical?this.x().scale:this.y().scale;if(e instanceof a.Scales.Category)d=e.rangeBand();else{var f=this._isVertical?this.x().accessor:this.y().accessor,g=d3.set(a.Utils.Methods.flatten(this._datasetKeysInOrder.map(function(a){var c=b._key2PlotDatasetKey.get(a).dataset,d=b._key2PlotDatasetKey.get(a).plotMetadata;return c.data().map(function(a,b){return f(a,b,c,d).valueOf()})}))).values().map(function(a){return+a});g.sort(function(a,b){return a-b});var h=d3.pairs(g),i=this._isVertical?this.width():this.height();d=a.Utils.Methods.min(h,function(a){return Math.abs(e.scale(a[1])-e.scale(a[0]))},i*c._SINGLE_BAR_DIMENSION_RATIO);var j=g.map(function(a){return e.scale(a)}),k=a.Utils.Methods.min(j,0);0!==this._barAlignmentFactor&&k>0&&(d=Math.min(d,k/this._barAlignmentFactor));var l=a.Utils.Methods.max(j,0);if(1!==this._barAlignmentFactor&&i>l){var m=i-l;d=Math.min(d,m/(1-this._barAlignmentFactor))}d*=c._BAR_WIDTH_RATIO}return d},c.prototype.getAllPlotData=function(a){void 0===a&&(a=this.datasets());var c=b.prototype.getAllPlotData.call(this,a),d=(this._isVertical?this.y().scale:this.x().scale).scale(this.baseline()),e=this._isVertical,f=this._barAlignmentFactor;return c.selection.each(function(a,b){var g=d3.select(this);e&&Math.floor(+g.attr("y"))>=Math.floor(d)?c.pixelPoints[b].y+=+g.attr("height"):!e&&Math.floor(+g.attr("x"))b&&b||c>0&&c||0,e=this.y().scale.scale(d);return function(){return e}},c.prototype._generateDrawSteps=function(){var a=[];if(this._dataChanged&&this._animate){var b=this._generateAttrToProjector();b.y=this._getResetYFunction(),a.push({attrToProjector:b,animator:this._getAnimator("reset")})}return a.push({attrToProjector:this._generateAttrToProjector(),animator:this._getAnimator("main")}),a},c.prototype._generateAttrToProjector=function(){var a=b.prototype._generateAttrToProjector.call(this),c=this._wholeDatumAttributes(),d=function(a){return-1===c.indexOf(a)},e=d3.keys(a).filter(d);return e.forEach(function(b){var c=a[b];a[b]=function(a,b,d,e){return a.length>0?c(a[0],b,d,e):null}}),a},c.prototype._wholeDatumAttributes=function(){return["x","y","defined"]},c.prototype.getAllPlotData=function(a){var b=this;void 0===a&&(a=this.datasets());var c=[],d=[],e=[];return this._keysForDatasets(a).forEach(function(a){var f=b._key2PlotDatasetKey.get(a);if(null!=f){var g=f.drawer;f.dataset.data().forEach(function(a,b){var e=g._getPixelPoint(a,b);e.x===e.x&&e.y===e.y&&(c.push(a),d.push(e))}),f.dataset.data().length>0&&e.push(g._getSelection(0).node())}}),{data:c,pixelPoints:d,selection:d3.selectAll(e)}},c.prototype.getClosestPlotData=function(a){var b=this,c=1/0,d=1/0,e=[],f=[],g=[];return this.datasets().forEach(function(h){var i=b.getAllPlotData([h]);i.pixelPoints.forEach(function(h,j){var k=i.data[j],l=i.selection[0][0];if(b._isVisibleOnPlot(k,h,d3.select(l))){var m=Math.abs(a.x-h.x),n=Math.abs(a.y-h.y);(c>m||m===c&&d>n)&&(e=[],f=[],g=[],c=m,d=n),m===c&&n===d&&(e.push(k),f.push(h),g.push(l))}})}),{data:e,pixelPoints:f,selection:d3.selectAll(g)}},c}(a.XYPlot);b.Line=c}(b=a.Plots||(a.Plots={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(c,d){b.call(this,c,d),this.classed("area-plot",!0),this.y0(0,d),this.animator("reset",new a.Animators.Null),this.animator("main",(new a.Animators.Base).duration(600).easing("exp-in-out"));var e=(new a.Scales.Color).range()[0];this.attr("fill-opacity",.25),this.attr("fill",e),this.attr("stroke",e)}return __extends(c,b),c.prototype.y0=function(a,b){return null==a?this._propertyBindings.get(c._Y0_KEY):(this._bindProperty(c._Y0_KEY,a,b),this._updateYDomainer(),this.renderImmediately(),this)},c.prototype._onDatasetUpdate=function(){b.prototype._onDatasetUpdate.call(this),null!=this.y().scale&&this._updateYDomainer()},c.prototype._getDrawer=function(b){return new a.Drawers.Area(b)},c.prototype._updateYDomainer=function(){b.prototype._updateYDomainer.call(this);var c=this._propertyExtents.get("y0"),d=a.Utils.Methods.flatten(c),e=a.Utils.Methods.uniq(d),f=1===e.length?e[0]:null,g=this.y().scale;g._userSetDomainer||(null!=f?g.domainer().addPaddingException(this,f):g.domainer().removePaddingException(this),g._autoDomainIfAutomaticMode())},c.prototype._getResetYFunction=function(){return this._generateAttrToProjector().y0},c.prototype._wholeDatumAttributes=function(){var a=b.prototype._wholeDatumAttributes.call(this);return a.push("y0"),a},c._Y0_KEY="y0",c}(b.Line);b.Area=c}(b=a.Plots||(a.Plots={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(a,c,d){void 0===d&&(d=!0),b.call(this,a,c,d)}return __extends(c,b),c.prototype._generateAttrToProjector=function(){var a=this,c=b.prototype._generateAttrToProjector.call(this),d=this._makeInnerScale(),e=function(){return d.rangeBand()};c.width=this._isVertical?e:c.width,c.height=this._isVertical?c.height:e;var f=c.x,g=c.y;return c.x=function(b,c,d,e){return a._isVertical?f(b,c,d,e)+e.position:f(b,c,d,e)},c.y=function(b,c,d,e){return a._isVertical?g(b,c,d,e):g(b,c,d,e)+e.position},c},c.prototype._updateClusterPosition=function(){var a=this,b=this._makeInnerScale();this._datasetKeysInOrder.forEach(function(c){var d=a._key2PlotDatasetKey.get(c).plotMetadata;d.position=b.scale(c)-b.rangeBand()/2})},c.prototype._makeInnerScale=function(){var b=new a.Scales.Category;if(b.domain(this._datasetKeysInOrder),this._attrBindings.get("width")){var c=this._attrBindings.get("width"),d=c.accessor,e=c.scale,f=e?function(a,b,c,f){return e.scale(d(a,b,c,f))}:d;b.range([0,f(null,0,null,null)])}else b.range([0,this._getBarPixelWidth()]);return b},c.prototype._getDataToDraw=function(){return this._updateClusterPosition(),b.prototype._getDataToDraw.call(this)},c.prototype._getPlotMetadataForDataset=function(a){var c=b.prototype._getPlotMetadataForDataset.call(this,a);return c.position=0,c},c}(b.Bar);b.ClusteredBar=c}(b=a.Plots||(a.Plots={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(){}(b=a.Plots||(a.Plots={}));var c=function(b){function c(){b.apply(this,arguments),this._stackedExtent=[0,0]}return __extends(c,b),c.prototype._getPlotMetadataForDataset=function(a){var c=b.prototype._getPlotMetadataForDataset.call(this,a);return c.offsets=d3.map(),c},c.prototype.x=function(a,c){return null==a?b.prototype.x.call(this):(null==c?b.prototype.x.call(this,a):b.prototype.x.call(this,a,c),null!=this.x().accessor&&null!=this.y().accessor&&this._updateStackOffsets(),this)},c.prototype.y=function(a,c){return null==a?b.prototype.y.call(this):(null==c?b.prototype.y.call(this,a):b.prototype.y.call(this,a,c),null!=this.x().accessor&&null!=this.y().accessor&&this._updateStackOffsets(),this)},c.prototype._onDatasetUpdate=function(){this._projectorsReady()&&this._updateStackOffsets(),b.prototype._onDatasetUpdate.call(this)},c.prototype._updateStackOffsets=function(){var b=this._generateDefaultMapArray(),c=this._getDomainKeys(),d=b.map(function(b){return a.Utils.Methods.populateMap(c,function(a){return{key:a,value:Math.max(0,b.get(a).value)||0}})}),e=b.map(function(b){return a.Utils.Methods.populateMap(c,function(a){return{key:a,value:Math.min(b.get(a).value,0)||0}})});this._setDatasetStackOffsets(this._stack(d),this._stack(e)),this._updateStackExtents()},c.prototype._updateStackExtents=function(){var b=this,c=this._valueAccessor(),d=this._keyAccessor(),e=this._filterForProperty(this._isVertical?"y":"x"),f=a.Utils.Methods.max(this._datasetKeysInOrder,function(f){var g=b._key2PlotDatasetKey.get(f).dataset,h=b._key2PlotDatasetKey.get(f).plotMetadata,i=g.data();return null!=e&&(i=i.filter(function(a,b){return e(a,b,g,h)})),a.Utils.Methods.max(i,function(a,b){return+c(a,b,g,h)+h.offsets.get(String(d(a,b,g,h)))},0)},0),g=a.Utils.Methods.min(this._datasetKeysInOrder,function(f){var g=b._key2PlotDatasetKey.get(f).dataset,h=b._key2PlotDatasetKey.get(f).plotMetadata,i=g.data();return null!=e&&(i=i.filter(function(a,b){return e(a,b,g,h)})),a.Utils.Methods.min(i,function(a,b){return+c(a,b,g,h)+h.offsets.get(String(d(a,b,g,h)))},0)},0);this._stackedExtent=[Math.min(g,0),Math.max(0,f)]},c.prototype._stack=function(a){var b=this,c=function(a,b){a.offset=b};return d3.layout.stack().x(function(a){return a.key}).y(function(a){return+a.value}).values(function(a){return b._getDomainKeys().map(function(b){return a.get(b)})}).out(c)(a),a},c.prototype._setDatasetStackOffsets=function(a,b){var c=this,d=this._keyAccessor(),e=this._valueAccessor();this._datasetKeysInOrder.forEach(function(f,g){var h=c._key2PlotDatasetKey.get(f).dataset,i=c._key2PlotDatasetKey.get(f).plotMetadata,j=a[g],k=b[g],l=h.data().every(function(a,b){return e(a,b,h,i)<=0});h.data().forEach(function(a,b){var c,f=String(d(a,b,h,i)),g=j.get(f).offset,m=k.get(f).offset,n=e(a,b,h,i);c=+n?n>0?g:m:l?m:g,i.offsets.set(f,c)})})},c.prototype._getDomainKeys=function(){var a=this,b=this._keyAccessor(),c=d3.set();return this._datasetKeysInOrder.forEach(function(d){var e=a._key2PlotDatasetKey.get(d).dataset,f=a._key2PlotDatasetKey.get(d).plotMetadata;e.data().forEach(function(a,d){c.add(b(a,d,e,f))})}),c.values()},c.prototype._generateDefaultMapArray=function(){var b=this,c=this._keyAccessor(),d=this._valueAccessor(),e=this._getDomainKeys(),f=this._datasetKeysInOrder.map(function(){return a.Utils.Methods.populateMap(e,function(a){return{key:a,value:0}})});return this._datasetKeysInOrder.forEach(function(a,e){var g=b._key2PlotDatasetKey.get(a).dataset,h=b._key2PlotDatasetKey.get(a).plotMetadata;g.data().forEach(function(a,b){var i=String(c(a,b,g,h)),j=d(a,b,g,h);f[e].set(i,{key:i,value:j})})}),f},c.prototype._updateExtentsForProperty=function(a){b.prototype._updateExtentsForProperty.call(this,a),"x"!==a&&"y"!==a||!this._projectorsReady()||this._updateStackExtents()},c.prototype._extentsForProperty=function(a){var c=b.prototype._extentsForProperty.call(this,a),d=this._isVertical?"y":"x";if(a===d&&this._stackedExtent){var e=c.slice();return e.push(this._stackedExtent),e}return c},c.prototype._keyAccessor=function(){return this._isVertical?this.x().accessor:this.y().accessor},c.prototype._valueAccessor=function(){return this._isVertical?this.y().accessor:this.x().accessor},c}(a.XYPlot);a.Stacked=c}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(a,c){b.call(this,a,c),this._baselineValue=0,this.classed("area-plot",!0),this._isVertical=!0,this.attr("fill-opacity",1)}return __extends(c,b),c.prototype._getDrawer=function(b){return new a.Drawers.Area(b).drawLine(!1)},c.prototype._getAnimator=function(){return new a.Animators.Null},c.prototype._setup=function(){b.prototype._setup.call(this),this._baseline=this._renderArea.append("line").classed("baseline",!0)},c.prototype.x=function(c,d){return null==c?b.prototype.x.call(this):(null==d?(b.prototype.x.call(this,c),a.Stacked.prototype.x.apply(this,[c])):(b.prototype.x.call(this,c,d),a.Stacked.prototype.x.apply(this,[c,d])),this)},c.prototype.y=function(c,d){return null==c?b.prototype.y.call(this):(null==d?(b.prototype.y.call(this,c),a.Stacked.prototype.y.apply(this,[c])):(b.prototype.y.call(this,c,d),a.Stacked.prototype.y.apply(this,[c,d])),this)},c.prototype._additionalPaint=function(){var a=this.y().scale.scale(this._baselineValue),b={x1:0,y1:a,x2:this.width(),y2:a};this._getAnimator("baseline").animate(this._baseline,b)},c.prototype._updateYDomainer=function(){b.prototype._updateYDomainer.call(this);var a=this.y().scale;a._userSetDomainer||(a.domainer().addPaddingException(this,0).addIncludedValue(this,0),a._autoDomainIfAutomaticMode())},c.prototype._onDatasetUpdate=function(){return b.prototype._onDatasetUpdate.call(this),a.Stacked.prototype._onDatasetUpdate.apply(this),this},c.prototype._generateAttrToProjector=function(){var a=this,c=b.prototype._generateAttrToProjector.call(this),d=this.y().accessor,e=this.x().accessor;return c.y=function(b,c,f,g){return a.y().scale.scale(+d(b,c,f,g)+g.offsets.get(e(b,c,f,g)))},c.y0=function(b,c,d,f){return a.y().scale.scale(f.offsets.get(e(b,c,d,f)))},c},c.prototype._wholeDatumAttributes=function(){return["x","y","defined"]},c.prototype._updateStackOffsets=function(){var b=this;if(this._projectorsReady()){var c=this._getDomainKeys(),d=this._isVertical?this.x().accessor:this.y().accessor,e=this._datasetKeysInOrder.map(function(a){var c=b._key2PlotDatasetKey.get(a).dataset,e=b._key2PlotDatasetKey.get(a).plotMetadata;return d3.set(c.data().map(function(a,b){return d(a,b,c,e).toString()})).values()});e.some(function(a){return a.length!==c.length})&&a.Utils.Methods.warn("the domains across the datasets are not the same. Plot may produce unintended behavior."),a.Stacked.prototype._updateStackOffsets.call(this)}},c.prototype._updateStackExtents=function(){a.Stacked.prototype._updateStackExtents.call(this)},c.prototype._stack=function(b){return a.Stacked.prototype._stack.call(this,b)},c.prototype._setDatasetStackOffsets=function(b,c){a.Stacked.prototype._setDatasetStackOffsets.call(this,b,c)},c.prototype._getDomainKeys=function(){return a.Stacked.prototype._getDomainKeys.call(this)},c.prototype._generateDefaultMapArray=function(){return a.Stacked.prototype._generateDefaultMapArray.call(this)},c.prototype._extentsForProperty=function(b){return a.Stacked.prototype._extentsForProperty.call(this,b)},c.prototype._keyAccessor=function(){return a.Stacked.prototype._keyAccessor.call(this)},c.prototype._valueAccessor=function(){return a.Stacked.prototype._valueAccessor.call(this)},c.prototype._getPlotMetadataForDataset=function(b){return a.Stacked.prototype._getPlotMetadataForDataset.call(this,b)},c.prototype._updateExtentsForProperty=function(b){a.Stacked.prototype._updateExtentsForProperty.call(this,b)},c}(b.Area);b.StackedArea=c}(b=a.Plots||(a.Plots={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(a,c,d){void 0===d&&(d=!0),b.call(this,a,c,d)}return __extends(c,b),c.prototype._getAnimator=function(b){if(this._animate&&this._animateOnNextRender){if(this.animator(b))return this.animator(b);if("stacked-bar"===b){var c=this._isVertical?this.y().scale:this.x().scale,d=c.scale(this.baseline());return new a.Animators.MovingRect(d,this._isVertical)}}return new a.Animators.Null},c.prototype.x=function(c,d){return null==c?b.prototype.x.call(this):(null==d?(b.prototype.x.call(this,c),a.Stacked.prototype.x.apply(this,[c])):(b.prototype.x.call(this,c,d),a.Stacked.prototype.x.apply(this,[c,d])),this)},c.prototype.y=function(c,d){return null==c?b.prototype.y.call(this):(null==d?(b.prototype.y.call(this,c),a.Stacked.prototype.y.apply(this,[c])):(b.prototype.y.call(this,c,d),a.Stacked.prototype.y.apply(this,[c,d])),this)},c.prototype._generateAttrToProjector=function(){var a=this,c=b.prototype._generateAttrToProjector.call(this),d=this._isVertical?"y":"x",e=this._isVertical?"x":"y",f=this._isVertical?this.y().scale:this.x().scale,g=this._propertyBindings.get(d).accessor,h=this._propertyBindings.get(e).accessor,i=function(a,b,c,d){return f.scale(d.offsets.get(h(a,b,c,d)))},j=function(a,b,c,d){return f.scale(+g(a,b,c,d)+d.offsets.get(h(a,b,c,d)))},k=function(a,b,c,d){return Math.abs(j(a,b,c,d)-i(a,b,c,d))},l=function(a,b,c,d){return+g(a,b,c,d)<0?i(a,b,c,d):j(a,b,c,d)};return c[d]=function(b,c,d,e){return a._isVertical?l(b,c,d,e):l(b,c,d,e)-k(b,c,d,e)},c},c.prototype._generateDrawSteps=function(){return[{attrToProjector:this._generateAttrToProjector(),animator:this._getAnimator("stacked-bar")}]},c.prototype._onDatasetUpdate=function(){return b.prototype._onDatasetUpdate.call(this),a.Stacked.prototype._onDatasetUpdate.apply(this),this},c.prototype._getPlotMetadataForDataset=function(b){return a.Stacked.prototype._getPlotMetadataForDataset.call(this,b)},c.prototype._updateExtentsForProperty=function(b){a.Stacked.prototype._updateExtentsForProperty.call(this,b)},c.prototype._updateStackOffsets=function(){a.Stacked.prototype._updateStackOffsets.call(this)},c.prototype._updateStackExtents=function(){a.Stacked.prototype._updateStackExtents.call(this)},c.prototype._stack=function(b){return a.Stacked.prototype._stack.call(this,b)},c.prototype._setDatasetStackOffsets=function(b,c){a.Stacked.prototype._setDatasetStackOffsets.call(this,b,c)},c.prototype._getDomainKeys=function(){return a.Stacked.prototype._getDomainKeys.call(this)},c.prototype._generateDefaultMapArray=function(){return a.Stacked.prototype._generateDefaultMapArray.call(this)},c.prototype._extentsForProperty=function(b){return a.Stacked.prototype._extentsForProperty.call(this,b)},c.prototype._keyAccessor=function(){return a.Stacked.prototype._keyAccessor.call(this)},c.prototype._valueAccessor=function(){return a.Stacked.prototype._valueAccessor.call(this)},c}(b.Bar);b.StackedBar=c}(b=a.Plots||(a.Plots={}))}(Plottable||(Plottable={}));var Plottable;!function(a){var b;!function(){}(b=a.Animators||(a.Animators={}))}(Plottable||(Plottable={}));var Plottable;!function(a){var b;!function(a){var b=function(){function a(){}return a.prototype.getTiming=function(){return 0},a.prototype.animate=function(a,b){return a.attr(b)},a}();a.Null=b}(b=a.Animators||(a.Animators={}))}(Plottable||(Plottable={}));var Plottable;!function(a){var b;!function(a){var b=function(){function a(){this._duration=a.DEFAULT_DURATION_MILLISECONDS,this._delay=a.DEFAULT_DELAY_MILLISECONDS,this._easing=a.DEFAULT_EASING,this._maxIterativeDelay=a.DEFAULT_MAX_ITERATIVE_DELAY_MILLISECONDS,this._maxTotalDuration=a.DEFAULT_MAX_TOTAL_DURATION_MILLISECONDS}return a.prototype.getTiming=function(a){var b=Math.max(this.maxTotalDuration()-this.duration(),0),c=Math.min(this.maxIterativeDelay(),b/Math.max(a-1,1)),d=c*a+this.delay()+this.duration();return d},a.prototype.animate=function(a,b){var c=this,d=a[0].length,e=Math.max(this.maxTotalDuration()-this.duration(),0),f=Math.min(this.maxIterativeDelay(),e/Math.max(d-1,1));return a.transition().ease(this.easing()).duration(this.duration()).delay(function(a,b){return c.delay()+f*b}).attr(b)},a.prototype.duration=function(a){return null==a?this._duration:(this._duration=a,this)},a.prototype.delay=function(a){return null==a?this._delay:(this._delay=a,this)},a.prototype.easing=function(a){return null==a?this._easing:(this._easing=a,this)},a.prototype.maxIterativeDelay=function(a){return null==a?this._maxIterativeDelay:(this._maxIterativeDelay=a,this)},a.prototype.maxTotalDuration=function(a){return null==a?this._maxTotalDuration:(this._maxTotalDuration=a,this)},a.DEFAULT_DURATION_MILLISECONDS=300,a.DEFAULT_DELAY_MILLISECONDS=0,a.DEFAULT_MAX_ITERATIVE_DELAY_MILLISECONDS=15,a.DEFAULT_MAX_TOTAL_DURATION_MILLISECONDS=600,a.DEFAULT_EASING="exp-out",a}();a.Base=b}(b=a.Animators||(a.Animators={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(a){var b=function(a){function b(b,c){void 0===b&&(b=!0),void 0===c&&(c=!1),a.call(this),this.isVertical=b,this.isReverse=c}return __extends(b,a),b.prototype.animate=function(c,d){var e={};return b.ANIMATED_ATTRIBUTES.forEach(function(a){return e[a]=d[a]}),e[this._getMovingAttr()]=this._startMovingProjector(d),e[this._getGrowingAttr()]=function(){return 0},c.attr(e),a.prototype.animate.call(this,c,d)},b.prototype._startMovingProjector=function(a){if(this.isVertical===this.isReverse)return a[this._getMovingAttr()];var b=a[this._getMovingAttr()],c=a[this._getGrowingAttr()];return function(a,d,e,f){return b(a,d,e,f)+c(a,d,e,f)}},b.prototype._getGrowingAttr=function(){return this.isVertical?"height":"width"},b.prototype._getMovingAttr=function(){return this.isVertical?"y":"x"},b.ANIMATED_ATTRIBUTES=["height","width","x","y","fill"],b}(a.Base);a.Rect=b}(b=a.Animators||(a.Animators={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(a){var b=function(a){function b(b,c){void 0===c&&(c=!0),a.call(this,c),this.startPixelValue=b}return __extends(b,a),b.prototype._startMovingProjector=function(){return d3.functor(this.startPixelValue)},b}(a.Rect);a.MovingRect=b}(b=a.Animators||(a.Animators={}))}(Plottable||(Plottable={}));var Plottable;!function(a){var b=function(){function a(){this._event2Callback={},this._callbacks=[],this._connected=!1}return a.prototype._hasNoListeners=function(){return this._callbacks.every(function(a){return 0===a.values().length})},a.prototype._connect=function(){var a=this;this._connected||(Object.keys(this._event2Callback).forEach(function(b){var c=a._event2Callback[b];document.addEventListener(b,c)}),this._connected=!0)},a.prototype._disconnect=function(){var a=this;this._connected&&this._hasNoListeners()&&(Object.keys(this._event2Callback).forEach(function(b){var c=a._event2Callback[b];document.removeEventListener(b,c)}),this._connected=!1)},a.prototype.setCallback=function(a,b){this._connect(),a.add(b)},a.prototype.unsetCallback=function(a,b){a["delete"](b),this._disconnect()},a}();a.Dispatcher=b}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(c){var d=this;b.call(this),this.translator=a.Utils.ClientToSVGTranslator.getTranslator(c),this._lastMousePosition={x:-1,y:-1},this._moveCallbacks=new a.Utils.CallbackSet,this._downCallbacks=new a.Utils.CallbackSet,this._upCallbacks=new a.Utils.CallbackSet,this._wheelCallbacks=new a.Utils.CallbackSet,this._dblClickCallbacks=new a.Utils.CallbackSet,this._callbacks=[this._moveCallbacks,this._downCallbacks,this._upCallbacks,this._wheelCallbacks,this._dblClickCallbacks];var e=function(a){return d._measureAndDispatch(a,d._moveCallbacks)};this._event2Callback.mouseover=e,this._event2Callback.mousemove=e,this._event2Callback.mouseout=e,this._event2Callback.mousedown=function(a){return d._measureAndDispatch(a,d._downCallbacks)},this._event2Callback.mouseup=function(a){return d._measureAndDispatch(a,d._upCallbacks)},this._event2Callback.wheel=function(a){return d._measureAndDispatch(a,d._wheelCallbacks)},this._event2Callback.dblclick=function(a){return d._measureAndDispatch(a,d._dblClickCallbacks)}}return __extends(c,b),c.getDispatcher=function(b){var d=a.Utils.DOM.getBoundingSVG(b),e=d[c._DISPATCHER_KEY];return null==e&&(e=new c(d),d[c._DISPATCHER_KEY]=e),e},c.prototype.onMouseMove=function(a){return this.setCallback(this._moveCallbacks,a),this},c.prototype.offMouseMove=function(a){return this.unsetCallback(this._moveCallbacks,a),this},c.prototype.onMouseDown=function(a){return this.setCallback(this._downCallbacks,a),this},c.prototype.offMouseDown=function(a){return this.unsetCallback(this._downCallbacks,a),this},c.prototype.onMouseUp=function(a){return this.setCallback(this._upCallbacks,a),this},c.prototype.offMouseUp=function(a){return this.unsetCallback(this._upCallbacks,a),this},c.prototype.onWheel=function(a){return this.setCallback(this._wheelCallbacks,a),this},c.prototype.offWheel=function(a){return this.unsetCallback(this._wheelCallbacks,a),this},c.prototype.onDblClick=function(a){return this.setCallback(this._dblClickCallbacks,a),this},c.prototype.offDblClick=function(a){return this.unsetCallback(this._dblClickCallbacks,a),this},c.prototype._measureAndDispatch=function(a,b){var c=this.translator.computePosition(a.clientX,a.clientY);null!=c&&(this._lastMousePosition=c,b.callCallbacks(this.getLastMousePosition(),a))},c.prototype.getLastMousePosition=function(){return this._lastMousePosition},c._DISPATCHER_KEY="__Plottable_Dispatcher_Mouse",c}(a.Dispatcher);b.Mouse=c}(b=a.Dispatchers||(a.Dispatchers={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(c){var d=this;b.call(this),this.translator=a.Utils.ClientToSVGTranslator.getTranslator(c),this._startCallbacks=new a.Utils.CallbackSet,this._moveCallbacks=new a.Utils.CallbackSet,this._endCallbacks=new a.Utils.CallbackSet,this._cancelCallbacks=new a.Utils.CallbackSet,this._callbacks=[this._moveCallbacks,this._startCallbacks,this._endCallbacks,this._cancelCallbacks],this._event2Callback.touchstart=function(a){return d._measureAndDispatch(a,d._startCallbacks)},this._event2Callback.touchmove=function(a){return d._measureAndDispatch(a,d._moveCallbacks)},this._event2Callback.touchend=function(a){return d._measureAndDispatch(a,d._endCallbacks)},this._event2Callback.touchcancel=function(a){return d._measureAndDispatch(a,d._cancelCallbacks)}}return __extends(c,b),c.getDispatcher=function(b){var d=a.Utils.DOM.getBoundingSVG(b),e=d[c._DISPATCHER_KEY];return null==e&&(e=new c(d),d[c._DISPATCHER_KEY]=e),e},c.prototype.onTouchStart=function(a){return this.setCallback(this._startCallbacks,a),this},c.prototype.offTouchStart=function(a){return this.unsetCallback(this._startCallbacks,a),this},c.prototype.onTouchMove=function(a){return this.setCallback(this._moveCallbacks,a),this},c.prototype.offTouchMove=function(a){return this.unsetCallback(this._moveCallbacks,a),this},c.prototype.onTouchEnd=function(a){return this.setCallback(this._endCallbacks,a),this},c.prototype.offTouchEnd=function(a){return this.unsetCallback(this._endCallbacks,a),this},c.prototype.onTouchCancel=function(a){return this.setCallback(this._cancelCallbacks,a),this},c.prototype.offTouchCancel=function(a){return this.unsetCallback(this._cancelCallbacks,a),this},c.prototype._measureAndDispatch=function(a,b){for(var c=a.changedTouches,d={},e=[],f=0;f0&&b.callCallbacks(e,d,a)},c._DISPATCHER_KEY="__Plottable_Dispatcher_Touch",c}(a.Dispatcher);b.Touch=c}(b=a.Dispatchers||(a.Dispatchers={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(){var c=this;b.call(this),this._event2Callback.keydown=function(a){return c._processKeydown(a)},this._keydownCallbacks=new a.Utils.CallbackSet,this._callbacks=[this._keydownCallbacks]}return __extends(c,b),c.getDispatcher=function(){var a=document[c._DISPATCHER_KEY];return null==a&&(a=new c,document[c._DISPATCHER_KEY]=a),a},c.prototype.onKeyDown=function(a){return this.setCallback(this._keydownCallbacks,a),this},c.prototype.offKeyDown=function(a){return this.unsetCallback(this._keydownCallbacks,a),this},c.prototype._processKeydown=function(a){this._keydownCallbacks.callCallbacks(a.keyCode,a)},c._DISPATCHER_KEY="__Plottable_Dispatcher_Key",c}(a.Dispatcher);b.Key=c}(b=a.Dispatchers||(a.Dispatchers={}))}(Plottable||(Plottable={}));var Plottable;!function(a){var b=function(){function a(){var a=this;this._anchorCallback=function(b){return a._anchor(b)}}return a.prototype._anchor=function(){this._isAnchored=!0},a.prototype._unanchor=function(){this._isAnchored=!1},a.prototype.attachTo=function(a){return this._componentAttachedTo&&this.detachFrom(this._componentAttachedTo),this._componentAttachedTo=a,a.onAnchor(this._anchorCallback),this},a.prototype.detachFrom=function(a){return this._isAnchored&&this._unanchor(),this._componentAttachedTo=null,a.offAnchor(this._anchorCallback),this},a.prototype._translateToComponentSpace=function(a){var b=this._componentAttachedTo.originToSVG();return{x:a.x-b.x,y:a.y-b.y}},a.prototype._isInsideComponent=function(a){return 0<=a.x&&0<=a.y&&a.x<=this._componentAttachedTo.width()&&a.y<=this._componentAttachedTo.height()},a}();a.Interaction=b}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(){var c=this;b.apply(this,arguments),this._clickedDown=!1,this._onClickCallbacks=new a.Utils.CallbackSet,this._mouseDownCallback=function(a){return c._handleClickDown(a)},this._mouseUpCallback=function(a){return c._handleClickUp(a)},this._touchStartCallback=function(a,b){return c._handleClickDown(b[a[0]])},this._touchEndCallback=function(a,b){return c._handleClickUp(b[a[0]])},this._touchCancelCallback=function(){return c._clickedDown=!1}}return __extends(c,b),c.prototype._anchor=function(c){b.prototype._anchor.call(this,c),this._mouseDispatcher=a.Dispatchers.Mouse.getDispatcher(c.content().node()),this._mouseDispatcher.onMouseDown(this._mouseDownCallback),this._mouseDispatcher.onMouseUp(this._mouseUpCallback),this._touchDispatcher=a.Dispatchers.Touch.getDispatcher(c.content().node()),this._touchDispatcher.onTouchStart(this._touchStartCallback),this._touchDispatcher.onTouchEnd(this._touchEndCallback),this._touchDispatcher.onTouchCancel(this._touchCancelCallback)},c.prototype._unanchor=function(){b.prototype._unanchor.call(this),this._mouseDispatcher.offMouseDown(this._mouseDownCallback),this._mouseDispatcher.offMouseUp(this._mouseUpCallback),this._mouseDispatcher=null,this._touchDispatcher.offTouchStart(this._touchStartCallback),this._touchDispatcher.offTouchEnd(this._touchEndCallback),this._touchDispatcher.offTouchCancel(this._touchCancelCallback),this._touchDispatcher=null +},c.prototype._handleClickDown=function(a){var b=this._translateToComponentSpace(a);this._isInsideComponent(b)&&(this._clickedDown=!0)},c.prototype._handleClickUp=function(a){var b=this._translateToComponentSpace(a);this._clickedDown&&this._isInsideComponent(b)&&this._onClickCallbacks.callCallbacks(b),this._clickedDown=!1},c.prototype.onClick=function(a){return this._onClickCallbacks.add(a),this},c.prototype.offClick=function(a){return this._onClickCallbacks["delete"](a),this},c}(a.Interaction);b.Click=c}(b=a.Interactions||(a.Interactions={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c;!function(a){a[a.NotClicked=0]="NotClicked",a[a.SingleClicked=1]="SingleClicked",a[a.DoubleClicked=2]="DoubleClicked"}(c||(c={}));var d=function(b){function c(){var c=this;b.apply(this,arguments),this._clickState=0,this._clickedDown=!1,this._onDoubleClickCallbacks=new a.Utils.CallbackSet,this._mouseDownCallback=function(a){return c._handleClickDown(a)},this._mouseUpCallback=function(a){return c._handleClickUp(a)},this._dblClickCallback=function(){return c._handleDblClick()},this._touchStartCallback=function(a,b){return c._handleClickDown(b[a[0]])},this._touchEndCallback=function(a,b){return c._handleClickUp(b[a[0]])},this._touchCancelCallback=function(){return c._handleClickCancel()}}return __extends(c,b),c.prototype._anchor=function(c){b.prototype._anchor.call(this,c),this._mouseDispatcher=a.Dispatchers.Mouse.getDispatcher(c.content().node()),this._mouseDispatcher.onMouseDown(this._mouseDownCallback),this._mouseDispatcher.onMouseUp(this._mouseUpCallback),this._mouseDispatcher.onDblClick(this._dblClickCallback),this._touchDispatcher=a.Dispatchers.Touch.getDispatcher(c.content().node()),this._touchDispatcher.onTouchStart(this._touchStartCallback),this._touchDispatcher.onTouchEnd(this._touchEndCallback),this._touchDispatcher.onTouchCancel(this._touchCancelCallback)},c.prototype._unanchor=function(){b.prototype._unanchor.call(this),this._mouseDispatcher.offMouseDown(this._mouseDownCallback),this._mouseDispatcher.offMouseUp(this._mouseUpCallback),this._mouseDispatcher.offDblClick(this._dblClickCallback),this._mouseDispatcher=null,this._touchDispatcher.offTouchStart(this._touchStartCallback),this._touchDispatcher.offTouchEnd(this._touchEndCallback),this._touchDispatcher.offTouchCancel(this._touchCancelCallback),this._touchDispatcher=null},c.prototype._handleClickDown=function(a){var b=this._translateToComponentSpace(a);this._isInsideComponent(b)&&(1===this._clickState&&c.pointsEqual(b,this._clickedPoint)||(this._clickState=0),this._clickedPoint=b,this._clickedDown=!0)},c.prototype._handleClickUp=function(a){var b=this._translateToComponentSpace(a);this._clickState=this._clickedDown&&c.pointsEqual(b,this._clickedPoint)?0===this._clickState?1:2:0,this._clickedDown=!1},c.prototype._handleDblClick=function(){2===this._clickState&&(this._onDoubleClickCallbacks.callCallbacks(this._clickedPoint),this._clickState=0)},c.prototype._handleClickCancel=function(){this._clickState=0,this._clickedDown=!1},c.pointsEqual=function(a,b){return a.x===b.x&&a.y===b.y},c.prototype.onDoubleClick=function(a){return this._onDoubleClickCallbacks.add(a),this},c.prototype.offDoubleClick=function(a){return this._onDoubleClickCallbacks["delete"](a),this},c}(a.Interaction);b.DoubleClick=d}(b=a.Interactions||(a.Interactions={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(){var a=this;b.apply(this,arguments),this._keyCodeCallbacks={},this._mouseMoveCallback=function(){return!1},this._keyDownCallback=function(b){return a._handleKeyEvent(b)}}return __extends(c,b),c.prototype._anchor=function(c){b.prototype._anchor.call(this,c),this._positionDispatcher=a.Dispatchers.Mouse.getDispatcher(this._componentAttachedTo._element.node()),this._positionDispatcher.onMouseMove(this._mouseMoveCallback),this._keyDispatcher=a.Dispatchers.Key.getDispatcher(),this._keyDispatcher.onKeyDown(this._keyDownCallback)},c.prototype._unanchor=function(){b.prototype._unanchor.call(this),this._positionDispatcher.offMouseMove(this._mouseMoveCallback),this._positionDispatcher=null,this._keyDispatcher.offKeyDown(this._keyDownCallback),this._keyDispatcher=null},c.prototype._handleKeyEvent=function(a){var b=this._translateToComponentSpace(this._positionDispatcher.getLastMousePosition());this._isInsideComponent(b)&&this._keyCodeCallbacks[a]&&this._keyCodeCallbacks[a].callCallbacks(a)},c.prototype.onKey=function(b,c){return this._keyCodeCallbacks[b]||(this._keyCodeCallbacks[b]=new a.Utils.CallbackSet),this._keyCodeCallbacks[b].add(c),this},c.prototype.offKey=function(a,b){return this._keyCodeCallbacks[a]["delete"](b),0===this._keyCodeCallbacks[a].values().length&&delete this._keyCodeCallbacks[a],this},c}(a.Interaction);b.Key=c}(b=a.Interactions||(a.Interactions={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(){var c=this;b.apply(this,arguments),this._overComponent=!1,this._pointerEnterCallbacks=new a.Utils.CallbackSet,this._pointerMoveCallbacks=new a.Utils.CallbackSet,this._pointerExitCallbacks=new a.Utils.CallbackSet,this._mouseMoveCallback=function(a){return c._handlePointerEvent(a)},this._touchStartCallback=function(a,b){return c._handlePointerEvent(b[a[0]])}}return __extends(c,b),c.prototype._anchor=function(c){b.prototype._anchor.call(this,c),this._mouseDispatcher=a.Dispatchers.Mouse.getDispatcher(this._componentAttachedTo.content().node()),this._mouseDispatcher.onMouseMove(this._mouseMoveCallback),this._touchDispatcher=a.Dispatchers.Touch.getDispatcher(this._componentAttachedTo.content().node()),this._touchDispatcher.onTouchStart(this._touchStartCallback)},c.prototype._unanchor=function(){b.prototype._unanchor.call(this),this._mouseDispatcher.offMouseMove(this._mouseMoveCallback),this._mouseDispatcher=null,this._touchDispatcher.offTouchStart(this._touchStartCallback),this._touchDispatcher=null},c.prototype._handlePointerEvent=function(a){var b=this._translateToComponentSpace(a);if(this._isInsideComponent(b)){var c=this._overComponent;this._overComponent=!0,c||this._pointerEnterCallbacks.callCallbacks(b),this._pointerMoveCallbacks.callCallbacks(b)}else this._overComponent&&(this._overComponent=!1,this._pointerExitCallbacks.callCallbacks(b))},c.prototype.onPointerEnter=function(a){return this._pointerEnterCallbacks.add(a),this},c.prototype.offPointerEnter=function(a){return this._pointerEnterCallbacks["delete"](a),this},c.prototype.onPointerMove=function(a){return this._pointerMoveCallbacks.add(a),this},c.prototype.offPointerMove=function(a){return this._pointerMoveCallbacks["delete"](a),this},c.prototype.onPointerExit=function(a){return this._pointerExitCallbacks.add(a),this},c.prototype.offPointerExit=function(a){return this._pointerExitCallbacks["delete"](a),this},c}(a.Interaction);b.Pointer=c}(b=a.Interactions||(a.Interactions={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(c){function d(a,d){var e=this;c.call(this),this._wheelCallback=function(a,b){return e._handleWheelEvent(a,b)},this._touchStartCallback=function(a,b,c){return e._handleTouchStart(a,b,c)},this._touchMoveCallback=function(a,b,c){return e._handlePinch(a,b,c)},this._touchEndCallback=function(a,b,c){return e._handleTouchEnd(a,b,c)},this._touchCancelCallback=function(a,b,c){return e._handleTouchEnd(a,b,c)},this._xScale=a,this._yScale=d,this._dragInteraction=new b.Drag,this._setupDragInteraction(),this._touchIds=d3.map()}return __extends(d,c),d.prototype._anchor=function(b){c.prototype._anchor.call(this,b),this._dragInteraction.attachTo(b),this._mouseDispatcher=a.Dispatchers.Mouse.getDispatcher(this._componentAttachedTo.content().node()),this._mouseDispatcher.onWheel(this._wheelCallback),this._touchDispatcher=a.Dispatchers.Touch.getDispatcher(this._componentAttachedTo.content().node()),this._touchDispatcher.onTouchStart(this._touchStartCallback),this._touchDispatcher.onTouchMove(this._touchMoveCallback),this._touchDispatcher.onTouchEnd(this._touchEndCallback),this._touchDispatcher.onTouchCancel(this._touchCancelCallback)},d.prototype._unanchor=function(){c.prototype._unanchor.call(this),this._mouseDispatcher.offWheel(this._wheelCallback),this._mouseDispatcher=null,this._touchDispatcher.offTouchStart(this._touchStartCallback),this._touchDispatcher.offTouchMove(this._touchMoveCallback),this._touchDispatcher.offTouchEnd(this._touchEndCallback),this._touchDispatcher.offTouchCancel(this._touchCancelCallback),this._touchDispatcher=null,this._dragInteraction.detachFrom(this._componentAttachedTo)},d.prototype._handleTouchStart=function(a,b){for(var c=0;c=2)){if(null!=a._xScale){var f=e.x-(null==b?c.x:b.x);d.translateScale(a._xScale,-f)}if(null!=a._yScale){var g=e.y-(null==b?c.y:b.y);d.translateScale(a._yScale,-g)}b=e}})},d.PIXELS_PER_LINE=120,d}(a.Interaction);b.PanZoom=c}(b=a.Interactions||(a.Interactions={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(){var c=this;b.apply(this,arguments),this._dragging=!1,this._constrain=!0,this._dragStartCallbacks=new a.Utils.CallbackSet,this._dragCallbacks=new a.Utils.CallbackSet,this._dragEndCallbacks=new a.Utils.CallbackSet,this._mouseDownCallback=function(a,b){return c._startDrag(a,b)},this._mouseMoveCallback=function(a,b){return c._doDrag(a,b)},this._mouseUpCallback=function(a,b){return c._endDrag(a,b)},this._touchStartCallback=function(a,b,d){return c._startDrag(b[a[0]],d)},this._touchMoveCallback=function(a,b,d){return c._doDrag(b[a[0]],d)},this._touchEndCallback=function(a,b,d){return c._endDrag(b[a[0]],d)}}return __extends(c,b),c.prototype._anchor=function(c){b.prototype._anchor.call(this,c),this._mouseDispatcher=a.Dispatchers.Mouse.getDispatcher(this._componentAttachedTo.content().node()),this._mouseDispatcher.onMouseDown(this._mouseDownCallback),this._mouseDispatcher.onMouseMove(this._mouseMoveCallback),this._mouseDispatcher.onMouseUp(this._mouseUpCallback),this._touchDispatcher=a.Dispatchers.Touch.getDispatcher(this._componentAttachedTo.content().node()),this._touchDispatcher.onTouchStart(this._touchStartCallback),this._touchDispatcher.onTouchMove(this._touchMoveCallback),this._touchDispatcher.onTouchEnd(this._touchEndCallback)},c.prototype._unanchor=function(){b.prototype._unanchor.call(this),this._mouseDispatcher.offMouseDown(this._mouseDownCallback),this._mouseDispatcher.offMouseMove(this._mouseMoveCallback),this._mouseDispatcher.offMouseUp(this._mouseUpCallback),this._mouseDispatcher=null,this._touchDispatcher.offTouchStart(this._touchStartCallback),this._touchDispatcher.offTouchMove(this._touchMoveCallback),this._touchDispatcher.offTouchEnd(this._touchEndCallback),this._touchDispatcher=null},c.prototype._translateAndConstrain=function(b){var c=this._translateToComponentSpace(b);return this._constrain?{x:a.Utils.Methods.clamp(c.x,0,this._componentAttachedTo.width()),y:a.Utils.Methods.clamp(c.y,0,this._componentAttachedTo.height())}:c},c.prototype._startDrag=function(a,b){if(!(b instanceof MouseEvent&&0!==b.button)){var c=this._translateToComponentSpace(a);this._isInsideComponent(c)&&(b.preventDefault(),this._dragging=!0,this._dragOrigin=c,this._dragStartCallbacks.callCallbacks(this._dragOrigin))}},c.prototype._doDrag=function(a){this._dragging&&this._dragCallbacks.callCallbacks(this._dragOrigin,this._translateAndConstrain(a))},c.prototype._endDrag=function(a,b){b instanceof MouseEvent&&0!==b.button||this._dragging&&(this._dragging=!1,this._dragEndCallbacks.callCallbacks(this._dragOrigin,this._translateAndConstrain(a)))},c.prototype.constrainToComponent=function(a){return null==a?this._constrain:(this._constrain=a,this)},c.prototype.onDragStart=function(a){return this._dragStartCallbacks.add(a),this},c.prototype.offDragStart=function(a){return this._dragStartCallbacks["delete"](a),this},c.prototype.onDrag=function(a){return this._dragCallbacks.add(a),this},c.prototype.offDrag=function(a){return this._dragCallbacks["delete"](a),this},c.prototype.onDragEnd=function(a){return this._dragEndCallbacks.add(a),this},c.prototype.offDragEnd=function(a){return this._dragEndCallbacks["delete"](a),this},c}(a.Interaction);b.Drag=c}(b=a.Interactions||(a.Interactions={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(b){var c=function(b){function c(){b.call(this),this._detectionRadius=3,this._resizable=!1,this._hasCorners=!0,this._clipPathEnabled=!0,this.classed("drag-box-layer",!0),this._dragInteraction=new a.Interactions.Drag,this._setUpCallbacks(),this._dragInteraction.attachTo(this),this._dragStartCallbacks=new a.Utils.CallbackSet,this._dragCallbacks=new a.Utils.CallbackSet,this._dragEndCallbacks=new a.Utils.CallbackSet}return __extends(c,b),c.prototype._setUpCallbacks=function(){var a,b,c,d,e=this;this._dragInteraction.onDragStart(function(f){a=e._getResizingEdges(f),e.boxVisible()&&(a.top||a.bottom||a.left||a.right)?d=!1:(e.bounds({topLeft:f,bottomRight:f}),d=!0),e.boxVisible(!0);var g=e.bounds();b={x:g.topLeft.x,y:g.topLeft.y},c={x:g.bottomRight.x,y:g.bottomRight.y},e._dragStartCallbacks.callCallbacks(g)}),this._dragInteraction.onDrag(function(f,g){d?(c.x=g.x,c.y=g.y):(a.bottom?c.y=g.y:a.top&&(b.y=g.y),a.right?c.x=g.x:a.left&&(b.x=g.x)),e.bounds({topLeft:b,bottomRight:c}),e._dragCallbacks.callCallbacks(e.bounds())}),this._dragInteraction.onDragEnd(function(a,b){d&&a.x===b.x&&a.y===b.y&&e.boxVisible(!1),e._dragEndCallbacks.callCallbacks(e.bounds())})},c.prototype._setup=function(){var a=this;b.prototype._setup.call(this);var c=function(){return a._box.append("line").style({opacity:0,stroke:"pink"})};if(this._detectionEdgeT=c().classed("drag-edge-tb",!0),this._detectionEdgeB=c().classed("drag-edge-tb",!0),this._detectionEdgeL=c().classed("drag-edge-lr",!0),this._detectionEdgeR=c().classed("drag-edge-lr",!0),this._hasCorners){var d=function(){return a._box.append("circle").style({opacity:0,fill:"pink"})};this._detectionCornerTL=d().classed("drag-corner-tl",!0),this._detectionCornerTR=d().classed("drag-corner-tr",!0),this._detectionCornerBL=d().classed("drag-corner-bl",!0),this._detectionCornerBR=d().classed("drag-corner-br",!0)}},c.prototype._getResizingEdges=function(a){var b={top:!1,bottom:!1,left:!1,right:!1};if(!this.resizable())return b;var c=this.bounds(),d=c.topLeft.y,e=c.bottomRight.y,f=c.topLeft.x,g=c.bottomRight.x,h=this._detectionRadius;return f-h<=a.x&&a.x<=g+h&&(b.top=d-h<=a.y&&a.y<=d+h,b.bottom=e-h<=a.y&&a.y<=e+h),d-h<=a.y&&a.y<=e+h&&(b.left=f-h<=a.x&&a.x<=f+h,b.right=g-h<=a.x&&a.x<=g+h),b},c.prototype.renderImmediately=function(){if(b.prototype.renderImmediately.call(this),this.boxVisible()){var a=this.bounds(),c=a.topLeft.y,d=a.bottomRight.y,e=a.topLeft.x,f=a.bottomRight.x;return this._detectionEdgeT.attr({x1:e,y1:c,x2:f,y2:c,"stroke-width":2*this._detectionRadius}),this._detectionEdgeB.attr({x1:e,y1:d,x2:f,y2:d,"stroke-width":2*this._detectionRadius}),this._detectionEdgeL.attr({x1:e,y1:c,x2:e,y2:d,"stroke-width":2*this._detectionRadius}),this._detectionEdgeR.attr({x1:f,y1:c,x2:f,y2:d,"stroke-width":2*this._detectionRadius}),this._hasCorners&&(this._detectionCornerTL.attr({cx:e,cy:c,r:this._detectionRadius}),this._detectionCornerTR.attr({cx:f,cy:c,r:this._detectionRadius}),this._detectionCornerBL.attr({cx:e,cy:d,r:this._detectionRadius}),this._detectionCornerBR.attr({cx:f,cy:d,r:this._detectionRadius})),this}},c.prototype.detectionRadius=function(a){if(null==a)return this._detectionRadius;if(0>a)throw new Error("detection radius cannot be negative.");return this._detectionRadius=a,this.render(),this},c.prototype.resizable=function(a){return null==a?this._resizable:(this._resizable=a,this._setResizableClasses(a),this)},c.prototype._setResizableClasses=function(a){this.classed("x-resizable",a),this.classed("y-resizable",a)},c.prototype.onDragStart=function(a){return this._dragStartCallbacks.add(a),this},c.prototype.offDragStart=function(a){return this._dragStartCallbacks["delete"](a),this},c.prototype.onDrag=function(a){return this._dragCallbacks.add(a),this},c.prototype.offDrag=function(a){return this._dragCallbacks["delete"](a),this},c.prototype.onDragEnd=function(a){return this._dragEndCallbacks.add(a),this},c.prototype.offDragEnd=function(a){return this._dragEndCallbacks["delete"](a),this},c}(b.SelectionBoxLayer);b.DragBoxLayer=c}(b=a.Components||(a.Components={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(a){var b=function(a){function b(){a.call(this),this.classed("x-drag-box-layer",!0),this._hasCorners=!1}return __extends(b,a),b.prototype.computeLayout=function(b,c,d){return a.prototype.computeLayout.call(this,b,c,d),this.bounds(this.bounds()),this},b.prototype._setBounds=function(b){a.prototype._setBounds.call(this,{topLeft:{x:b.topLeft.x,y:0},bottomRight:{x:b.bottomRight.x,y:this.height()}})},b.prototype._setResizableClasses=function(a){this.classed("x-resizable",a)},b}(a.DragBoxLayer);a.XDragBoxLayer=b}(b=a.Components||(a.Components={}))}(Plottable||(Plottable={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},Plottable;!function(a){var b;!function(a){var b=function(a){function b(){a.call(this),this.classed("y-drag-box-layer",!0),this._hasCorners=!1}return __extends(b,a),b.prototype.computeLayout=function(b,c,d){return a.prototype.computeLayout.call(this,b,c,d),this.bounds(this.bounds()),this},b.prototype._setBounds=function(b){a.prototype._setBounds.call(this,{topLeft:{x:0,y:b.topLeft.y},bottomRight:{x:this.width(),y:b.bottomRight.y}})},b.prototype._setResizableClasses=function(a){this.classed("y-resizable",a)},b}(a.DragBoxLayer);a.YDragBoxLayer=b}(b=a.Components||(a.Components={}))}(Plottable||(Plottable={}));var SVGTypewriter;!function(a){!function(a){!function(a){function b(a,b){if(null==a||null==b)return a===b;if(a.length!==b.length)return!1;for(var c=0;c0&&"\n"===b[0]?"\n":"";if(g>=c){var i=g/3,j=Math.floor(c/i);return{wrappedToken:h+"...".substr(0,j),remainingToken:b}}for(;f+g>c;)e=a.Utils.StringMethods.trimEnd(e.substr(0,e.length-1)),f=d.measure(e).width;return{wrappedToken:h+e+"...",remainingToken:a.Utils.StringMethods.trimEnd(b.substring(e.length),"-").trim()}},b.prototype.wrapNextToken=function(b,c,d){if(!c.canFitText||c.availableLines===c.wrapping.noLines||!this.canFitToken(b,c.availableWidth,d))return this.finishWrapping(b,c,d);for(var e=b;e;){var f=this.breakTokenToFitInWidth(e,c.currentLine,c.availableWidth,d);if(c.currentLine=f.line,e=f.remainingToken,null!=e){if(c.wrapping.noBrokeWords+=+f.breakWord,++c.wrapping.noLines,c.availableLines===c.wrapping.noLines){var g=this.addEllipsis(c.currentLine,c.availableWidth,d);return c.wrapping.wrappedText+=g.wrappedToken,c.wrapping.truncatedText+=g.remainingToken+e,c.currentLine="\n",c}c.wrapping.wrappedText+=a.Utils.StringMethods.trimEnd(c.currentLine),c.currentLine="\n"}}return c},b.prototype.finishWrapping=function(a,b,c){if(b.canFitText&&b.availableLines!==b.wrapping.noLines&&this._allowBreakingWords&&"none"!==this._textTrimming){var d=this.addEllipsis(b.currentLine+a,b.availableWidth,c);b.wrapping.wrappedText+=d.wrappedToken,b.wrapping.truncatedText+=d.remainingToken,b.wrapping.noBrokeWords+=+(d.remainingToken.length0),b.currentLine=""}else b.wrapping.truncatedText+=a;return b.canFitText=!1,b},b.prototype.breakTokenToFitInWidth=function(a,b,c,d,e){if(void 0===e&&(e=this._breakingCharacter),d.measure(b+a).width<=c)return{remainingToken:null,line:b+a,breakWord:!1};if(""===a.trim())return{remainingToken:"",line:b,breakWord:!1};if(!this._allowBreakingWords)return{remainingToken:a,line:b,breakWord:!1};for(var f=0;f0&&(g=e),{remainingToken:a.substring(f),line:b+a.substring(0,f)+g,breakWord:f>0}},b}();b.Wrapper=c}(a.Wrappers||(a.Wrappers={}));a.Wrappers}(SVGTypewriter||(SVGTypewriter={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},SVGTypewriter;!function(a){!function(a){var b=function(a){function b(){a.apply(this,arguments)}return __extends(b,a),b.prototype.wrap=function(c,d,e,f){var g=this;void 0===f&&(f=1/0);var h=c.split("\n");if(h.length>1)throw new Error("SingleLineWrapper is designed to work only on single line");var i=function(b){return a.prototype.wrap.call(g,c,d,b,f)},j=i(e);if(j.noLines<2)return j;for(var k=0,l=e,m=0;mk;++m){var n=(l+k)/2,o=i(n);this.areSameResults(j,o)?(l=n,j=o):k=n}return j},b.prototype.areSameResults=function(a,b){return a.noLines===b.noLines&&a.truncatedText===b.truncatedText},b.NO_WRAP_ITERATIONS=5,b}(a.Wrapper);a.SingleLineWrapper=b}(a.Wrappers||(a.Wrappers={}));a.Wrappers}(SVGTypewriter||(SVGTypewriter={}));var SVGTypewriter;!function(a){!function(b){var c=function(){function b(a,c){this._writerID=b.nextID++,this._elementID=0,this.measurer(a),c&&this.wrapper(c),this.addTitleElement(!1)}return b.prototype.measurer=function(a){return this._measurer=a,this},b.prototype.wrapper=function(a){return this._wrapper=a,this},b.prototype.addTitleElement=function(a){return this._addTitleElement=a,this},b.prototype.writeLine=function(c,d,e,f,g){var h=d.append("text");h.text(c);var i=e*b.XOffsetFactor[f],j=b.AnchorConverter[f];h.attr("text-anchor",j).classed("text-line",!0),a.Utils.DOM.transform(h,i,g).attr("y","-0.25em") +},b.prototype.writeText=function(a,c,d,e,f,g){var h=this,i=a.split("\n"),j=this._measurer.measure().height,k=b.YOffsetFactor[g]*(e-i.length*j);i.forEach(function(a,b){h.writeLine(a,c,d,f,(b+1)*j+k)})},b.prototype.write=function(a,c,d,e){if(-1===b.SupportedRotation.indexOf(e.textRotation))throw new Error("unsupported rotation - "+e.textRotation);var f=Math.abs(Math.abs(e.textRotation)-90)>45,g=f?c:d,h=f?d:c,i=e.selection.append("g").classed("text-container",!0);this._addTitleElement&&i.append("title").text(a);var j=i.append("g").classed("text-area",!0),k=this._wrapper?this._wrapper.wrap(a,this._measurer,g,h).wrappedText:a;this.writeText(k,j,g,h,e.xAlign,e.yAlign);var l=d3.transform(""),m=d3.transform("");switch(l.rotate=e.textRotation,e.textRotation){case 90:l.translate=[c,0],m.rotate=-90,m.translate=[0,200];break;case-90:l.translate=[0,d],m.rotate=90,m.translate=[c,0];break;case 180:l.translate=[c,d],m.translate=[c,d],m.rotate=180}j.attr("transform",l.toString()),this.addClipPath(i,m),e.animator&&e.animator.animate(i)},b.prototype.addClipPath=function(b){var c=this._elementID++,d=/MSIE [5-9]/.test(navigator.userAgent)?"":document.location.href;d=d.split("#")[0];var e="clipPath"+this._writerID+"_"+c;b.select(".text-area").attr("clip-path",'url("'+d+"#"+e+'")');var f=b.append("clipPath").attr("id",e),g=a.Utils.DOM.getBBox(b.select(".text-area")),h=f.append("rect");h.classed("clip-rect",!0).attr(g)},b.nextID=0,b.SupportedRotation=[-90,0,180,90],b.AnchorConverter={left:"start",center:"middle",right:"end"},b.XOffsetFactor={left:0,center:.5,right:1},b.YOffsetFactor={top:0,center:.5,bottom:1},b}();b.Writer=c}(a.Writers||(a.Writers={}));a.Writers}(SVGTypewriter||(SVGTypewriter={}));var SVGTypewriter;!function(a){!function(b){var c=function(){function b(a,b){this.textMeasurer=this.getTextMeasurer(a,b)}return b.prototype.checkSelectionIsText=function(a){return"text"===a[0][0].tagName||!a.select("text").empty()},b.prototype.getTextMeasurer=function(a,b){var c=this;if(this.checkSelectionIsText(a)){var d,e=a.node().parentNode;return d="text"===a[0][0].tagName?a:a.select("text"),a.remove(),function(b){e.appendChild(a.node());var f=c.measureBBox(d,b);return a.remove(),f}}var f=a.append("text");return b&&f.classed(b,!0),f.remove(),function(b){a.node().appendChild(f.node());var d=c.measureBBox(f,b);return f.remove(),d}},b.prototype.measureBBox=function(b,c){b.text(c);var d=a.Utils.DOM.getBBox(b);return{width:d.width,height:d.height}},b.prototype.measure=function(a){return void 0===a&&(a=b.HEIGHT_TEXT),this.textMeasurer(a)},b.HEIGHT_TEXT="bqpdl",b}();b.AbstractMeasurer=c}(a.Measurers||(a.Measurers={}));a.Measurers}(SVGTypewriter||(SVGTypewriter={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},SVGTypewriter;!function(a){!function(a){var b=function(b){function c(a,c,d){void 0===c&&(c=null),void 0===d&&(d=!1),b.call(this,a,c),this.useGuards=d}return __extends(c,b),c.prototype._addGuards=function(b){return a.AbstractMeasurer.HEIGHT_TEXT+b+a.AbstractMeasurer.HEIGHT_TEXT},c.prototype.getGuardWidth=function(){return null==this.guardWidth&&(this.guardWidth=b.prototype.measure.call(this).width),this.guardWidth},c.prototype._measureLine=function(a){var c=this.useGuards?this._addGuards(a):a,d=b.prototype.measure.call(this,c);return d.width-=this.useGuards?2*this.getGuardWidth():0,d},c.prototype.measure=function(b){var c=this;if(void 0===b&&(b=a.AbstractMeasurer.HEIGHT_TEXT),""===b.trim())return{width:0,height:0};var d=b.trim().split("\n").map(function(a){return c._measureLine(a)});return{width:d3.max(d,function(a){return a.width}),height:d3.sum(d,function(a){return a.height})}},c}(a.AbstractMeasurer);a.Measurer=b}(a.Measurers||(a.Measurers={}));a.Measurers}(SVGTypewriter||(SVGTypewriter={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},SVGTypewriter;!function(a){!function(a){var b=function(a){function b(){a.apply(this,arguments)}return __extends(b,a),b.prototype._measureCharacter=function(b){return a.prototype._measureLine.call(this,b)},b.prototype._measureLine=function(a){var b=this,c=a.split("").map(function(a){return b._measureCharacter(a)});return{width:d3.sum(c,function(a){return a.width}),height:d3.max(c,function(a){return a.height})}},b}(a.Measurer);a.CharacterMeasurer=b}(a.Measurers||(a.Measurers={}));a.Measurers}(SVGTypewriter||(SVGTypewriter={}));var __extends=this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);c.prototype=b.prototype,a.prototype=new c},SVGTypewriter;!function(a){!function(b){var c=function(b){function c(c,d){var e=this;b.call(this,c,d),this.cache=new a.Utils.Cache(function(a){return e._measureCharacterNotFromCache(a)},a.Utils.Methods.objEq)}return __extends(c,b),c.prototype._measureCharacterNotFromCache=function(a){return b.prototype._measureCharacter.call(this,a)},c.prototype._measureCharacter=function(a){return this.cache.get(a)},c.prototype.reset=function(){this.cache.clear()},c}(b.CharacterMeasurer);b.CacheCharacterMeasurer=c}(a.Measurers||(a.Measurers={}));a.Measurers}(SVGTypewriter||(SVGTypewriter={})); \ No newline at end of file diff --git a/plottable.zip b/plottable.zip index ecabe116cf..6c459812d0 100644 Binary files a/plottable.zip and b/plottable.zip differ diff --git a/quicktests/overlaying/overlaying.js b/quicktests/overlaying/overlaying.js index e28f6bc381..f1abee50a2 100644 --- a/quicktests/overlaying/overlaying.js +++ b/quicktests/overlaying/overlaying.js @@ -54,7 +54,7 @@ function showSizeControls(){ $( ".size-controls" ).slideDown( 300, function() { $(this).focus(); $("#expand").val("-"); - }); + }); } else{ $( ".size-controls" ).slideUp( 300, function() { @@ -221,7 +221,7 @@ function runQuickTest(result, svg, data, branch){ function loadAllQuickTests(quicktestsPaths, firstBranch, secondBranch){ var div = d3.select("#results"); - quicktestsPaths.forEach(function(path) { //for each quicktest + quicktestsPaths.forEach(function(path) { //for each quicktest var name = path.replace(/\w*\/|\.js/g , ''); d3.text("http://localhost:9999/" + path, function(error, text) { if (error !== null) { @@ -249,7 +249,7 @@ function loadAllQuickTests(quicktestsPaths, firstBranch, secondBranch){ function loadQuickTestsInCategory(quickTestNames, category, firstBranch, secondBranch){ var div = d3.select("#results"); - quickTestNames.forEach(function(q) { //for each quicktest + quickTestNames.forEach(function(q) { //for each quicktest var name = q; d3.text("/quicktests/overlaying/tests/" + category + "/" + name + ".js", function(error, text) { if (error !== null) { @@ -292,7 +292,7 @@ function filterQuickTests(category, branchList){ } }); } - + //retrieve different plottable objects then push to array function loadPlottableBranches(category, branchList){ @@ -312,12 +312,12 @@ function loadPlottableBranches(category, branchList){ } }); - $.getScript(listOfUrl[0], function(data, textStatus) { + $.getScript(listOfUrl[0], function(data, textStatus) { if(textStatus === "success"){ plottableBranches[branchName1] = $.extend(true, {}, Plottable); Plottable = null; - $.getScript(listOfUrl[1], function(data, testStatus){ //load second + $.getScript(listOfUrl[1], function(data, testStatus){ //load second if(textStatus === "success"){ plottableBranches[branchName2] = $.extend(true, {}, Plottable); Plottable = null; diff --git a/quicktests/overlaying/tests/animations/animate_area.js b/quicktests/overlaying/tests/animations/animate_area.js index a2fa8fefee..2f5796e9d5 100644 --- a/quicktests/overlaying/tests/animations/animate_area.js +++ b/quicktests/overlaying/tests/animations/animate_area.js @@ -12,21 +12,21 @@ function run(svg, data, Plottable) { var doAnimate = true; - var xScale = new Plottable.Scale.Linear(); - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); + var xScale = new Plottable.Scales.Linear(); + var xAxis = new Plottable.Axes.Numeric(xScale, "bottom"); - var yScale = new Plottable.Scale.Linear(); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); + var yScale = new Plottable.Scales.Linear(); + var yAxis = new Plottable.Axes.Numeric(yScale, "left"); var dataset = new Plottable.Dataset(data); - var areaRenderer = new Plottable.Plot.Area(xScale, yScale) + var areaRenderer = new Plottable.Plots.Area(xScale, yScale) .addDataset(dataset) .attr("opacity", 0.75) - .project("x", "x", xScale) - .project("y", "y", yScale) + .x(function(d) { return d.x; }, xScale) + .y(function(d) { return d.y; }, yScale) .animate(doAnimate); - var areaChart = new Plottable.Component.Table([[yAxis, areaRenderer], + var areaChart = new Plottable.Components.Table([[yAxis, areaRenderer], [null, xAxis]]); areaChart.renderTo(svg); @@ -36,5 +36,5 @@ function run(svg, data, Plottable) { dataset.data(d); }; - areaRenderer.registerInteraction(new Plottable.Interaction.Click().onClick(cb)); + new Plottable.Interactions.Click().onClick(cb).attachTo(areaRenderer); } diff --git a/quicktests/overlaying/tests/animations/animate_horizontalBar.js b/quicktests/overlaying/tests/animations/animate_horizontalBar.js index d6b92d1156..d1e8e2f7c3 100644 --- a/quicktests/overlaying/tests/animations/animate_horizontalBar.js +++ b/quicktests/overlaying/tests/animations/animate_horizontalBar.js @@ -9,21 +9,21 @@ function run(svg, data, Plottable) { var doAnimate = true; - var xScale = new Plottable.Scale.Linear(); - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); + var xScale = new Plottable.Scales.Linear(); + var xAxis = new Plottable.Axes.Numeric(xScale, "bottom"); - var yScale = new Plottable.Scale.Linear(); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); + var yScale = new Plottable.Scales.Linear(); + var yAxis = new Plottable.Axes.Numeric(yScale, "left"); var dataset = new Plottable.Dataset(data); - var hBarRenderer = new Plottable.Plot.Bar(xScale, yScale, false).addDataset(dataset); + var hBarRenderer = new Plottable.Plots.Bar(xScale, yScale, false).addDataset(dataset); hBarRenderer.attr("opacity", 0.75); - hBarRenderer.project("x", "x", xScale); - hBarRenderer.project("y", "y", yScale); + hBarRenderer.x(function(d) { return d.x; }, xScale); + hBarRenderer.y(function(d) { return d.y; }, yScale); hBarRenderer.animate(doAnimate); - var hBarChart = new Plottable.Component.Table([[yAxis, hBarRenderer], + var hBarChart = new Plottable.Components.Table([[yAxis, hBarRenderer], [null, xAxis]]); hBarChart.renderTo(svg); @@ -32,5 +32,5 @@ function run(svg, data, Plottable) { dataset.data(d); }; - hBarRenderer.registerInteraction(new Plottable.Interaction.Click().onClick(cb)); + new Plottable.Interactions.Click().onClick(cb).attachTo(hBarRenderer); } diff --git a/quicktests/overlaying/tests/animations/animate_line.js b/quicktests/overlaying/tests/animations/animate_line.js index 0faaea92fa..11b8595f70 100644 --- a/quicktests/overlaying/tests/animations/animate_line.js +++ b/quicktests/overlaying/tests/animations/animate_line.js @@ -10,21 +10,21 @@ function makeData() { function run(svg, data, Plottable) { "use strict"; var doAnimate = true; - var xScale = new Plottable.Scale.Linear(); - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); + var xScale = new Plottable.Scales.Linear(); + var xAxis = new Plottable.Axes.Numeric(xScale, "bottom"); - var yScale = new Plottable.Scale.Linear(); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); + var yScale = new Plottable.Scales.Linear(); + var yAxis = new Plottable.Axes.Numeric(yScale, "left"); var dataset = new Plottable.Dataset(data); - var lineRenderer = new Plottable.Plot.Line(xScale, yScale) + var lineRenderer = new Plottable.Plots.Line(xScale, yScale) .addDataset(dataset) - .project("x", "x", xScale) - .project("y", "y", yScale) + .x(function(d) { return d.x; }, xScale) + .y(function(d) { return d.y; }, yScale) .attr("opacity", 0.75) .animate(doAnimate); - var lineChart = new Plottable.Component.Table([[yAxis, lineRenderer], + var lineChart = new Plottable.Components.Table([[yAxis, lineRenderer], [null, xAxis]]); lineChart.renderTo(svg); @@ -33,5 +33,5 @@ function run(svg, data, Plottable) { dataset.data(d); }; - lineRenderer.registerInteraction(new Plottable.Interaction.Click().onClick(cb)); + new Plottable.Interactions.Click().onClick(cb).attachTo(lineRenderer); } diff --git a/quicktests/overlaying/tests/animations/animate_scatter.js b/quicktests/overlaying/tests/animations/animate_scatter.js index 3ba9fe0ab4..4ee68efc69 100644 --- a/quicktests/overlaying/tests/animations/animate_scatter.js +++ b/quicktests/overlaying/tests/animations/animate_scatter.js @@ -7,24 +7,24 @@ function makeData() { function run(svg, data, Plottable) { "use strict"; - var xScale = new Plottable.Scale.Linear(); - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); + var xScale = new Plottable.Scales.Linear(); + var xAxis = new Plottable.Axes.Numeric(xScale, "bottom"); - var yScale = new Plottable.Scale.Linear(); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); + var yScale = new Plottable.Scales.Linear(); + var yAxis = new Plottable.Axes.Numeric(yScale, "left"); var d1 = new Plottable.Dataset(data[0]); var d2 = new Plottable.Dataset(data[1]); - var circleRenderer = new Plottable.Plot.Scatter(xScale, yScale).addDataset(d1) + var circleRenderer = new Plottable.Plots.Scatter(xScale, yScale).addDataset(d1) .addDataset(d2) - .attr("size", 16) - .project("x", "x", xScale) - .project("y", "y", yScale) + .size(16) + .x(function(d) { return d.x; }, xScale) + .y(function(d) { return d.y; }, yScale) .attr("opacity", 0.75) .animate(true); - var circleChart = new Plottable.Component.Table([[yAxis, circleRenderer], + var circleChart = new Plottable.Components.Table([[yAxis, circleRenderer], [null, xAxis]]); circleChart.renderTo(svg); @@ -34,5 +34,5 @@ function run(svg, data, Plottable) { d2.data(tmp); }; - circleRenderer.registerInteraction(new Plottable.Interaction.Click().onClick(cb)); + new Plottable.Interactions.Click().onClick(cb).attachTo(circleRenderer); } diff --git a/quicktests/overlaying/tests/animations/animate_verticalBar.js b/quicktests/overlaying/tests/animations/animate_verticalBar.js index 9e16112fd4..915b16a5d7 100644 --- a/quicktests/overlaying/tests/animations/animate_verticalBar.js +++ b/quicktests/overlaying/tests/animations/animate_verticalBar.js @@ -10,21 +10,21 @@ function run(svg, data, Plottable) { var doAnimate = true; - var xScale = new Plottable.Scale.Linear(); - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); + var xScale = new Plottable.Scales.Linear(); + var xAxis = new Plottable.Axes.Numeric(xScale, "bottom"); - var yScale = new Plottable.Scale.Linear(); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); + var yScale = new Plottable.Scales.Linear(); + var yAxis = new Plottable.Axes.Numeric(yScale, "left"); var dataset = new Plottable.Dataset(data); - var verticalBarPlot = new Plottable.Plot.Bar(xScale, yScale, true) + var verticalBarPlot = new Plottable.Plots.Bar(xScale, yScale, true) .addDataset(dataset) - .project("x", "x", xScale) - .project("y", "y", yScale) + .x(function(d) { return d.x; }, xScale) + .y(function(d) { return d.y; }, yScale) .attr("opacity", 0.75) .animate(doAnimate); - var chart = new Plottable.Component.Table([[yAxis, verticalBarPlot], + var chart = new Plottable.Components.Table([[yAxis, verticalBarPlot], [null, xAxis]]); chart.renderTo(svg); @@ -34,5 +34,5 @@ function run(svg, data, Plottable) { dataset.data(d); }; - verticalBarPlot.registerInteraction(new Plottable.Interaction.Click().onClick(cb)); + new Plottable.Interactions.Click().onClick(cb).attachTo(verticalBarPlot); } diff --git a/quicktests/overlaying/tests/animations/project_scatter.js b/quicktests/overlaying/tests/animations/project_scatter.js deleted file mode 100644 index 0a9c028175..0000000000 --- a/quicktests/overlaying/tests/animations/project_scatter.js +++ /dev/null @@ -1,61 +0,0 @@ -function makeData() { - "use strict"; - - return [makeRandomData(50), makeRandomData(50)]; -} - -function run(svg, data, Plottable) { - "use strict"; - //data - - var dataseries1 = new Plottable.Dataset(data[0]); - - //Axis - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - - var widthProjector = function(d, i, m) { - return (25 - (d.y * 7)) * 2; - }; - - var colorProjector = function(d, i, m) { - var x = 22; - x += Math.floor(d.y * 30); - var y = 10; - y += Math.floor(d.x * 40); - return ("#11" + x + y); - }; - - var opacityProjector = function(d, i, m){ - return (d.x); - }; - - //rendering - var renderAreaD1 = new Plottable.Plot.Scatter(xScale, yScale).addDataset(dataseries1) - .attr("size", widthProjector) - .attr("fill", colorProjector) - .attr("opacity", opacityProjector) - .project("x", "x", xScale) - .project("y", "y", yScale) - .animate(true); - - //title + legend - var title1 = new Plottable.Component.TitleLabel( "Opacity, r, color", "horizontal"); - - var basicTable = new Plottable.Component.Table().addComponent(0,2, title1) - .addComponent(1, 1, yAxis) - .addComponent(1, 2, renderAreaD1) - .addComponent(2, 2, xAxis); - - basicTable.renderTo(svg); - - var cb = function(x, y){ - var d = dataseries1.data(); - dataseries1.data(d); - }; - - renderAreaD1.registerInteraction(new Plottable.Interaction.Click().onClick(cb)); - -} diff --git a/quicktests/overlaying/tests/basic/bars_on_numeric.js b/quicktests/overlaying/tests/basic/bars_on_numeric.js deleted file mode 100644 index 3d837249fa..0000000000 --- a/quicktests/overlaying/tests/basic/bars_on_numeric.js +++ /dev/null @@ -1,68 +0,0 @@ - -function makeData() { - "use strict"; - - var data1 = [{x: 0, y: 2, type: "q1"}, {x: -1, y: 1, type: "q1"}, {x: -2, y: 1, type: "q1"}]; - var data2 = [{x: 0, y: 0, type: "q2"}, {x: -1, y: 5, type: "q2"}, {x: -2, y: 2, type: "q2"}]; - var data3 = [{x: 0, y: 4, type: "q3"}, {x: -1, y: 0, type: "q3"}, {x: -2, y: 5, type: "q3"}]; - - return [data1, data2, data3]; -} - -function run(svg, data, Plottable) { - "use strict"; - - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - var colorScale = new Plottable.Scale.Color(); - - var xAxis1 = new Plottable.Axis.Numeric(xScale, "bottom"); - var yAxis1 = new Plottable.Axis.Numeric(yScale, "right"); - var xAxis2 = new Plottable.Axis.Numeric(xScale, "bottom"); - var yAxis2 = new Plottable.Axis.Numeric(yScale, "right"); - var xAxis3 = new Plottable.Axis.Numeric(xScale, "bottom"); - var yAxis3 = new Plottable.Axis.Numeric(yScale, "right"); - - var stackedBarRenderer = new Plottable.Plot.StackedBar(xScale, yScale) - .animate(true) - .project("x", "x", xScale) - .project("y", "y", yScale) - .project("fill", "type", colorScale) - .addDataset("d1", data[0]) - .addDataset("d2", data[1]) - .addDataset("d3", data[2]); - - var clusteredBarRenderer = new Plottable.Plot.ClusteredBar(xScale, yScale, true) - .animate(true) - .project("fill", "type", colorScale) - .addDataset("d1", data[0]) - .addDataset("d2", data[1]) - .addDataset("d3", data[2]) - .project("x", "x", xScale) - .project("y", "y", yScale); - - var verticalBarRenderer = new Plottable.Plot.Bar( xScale, yScale) - .animate(true) - .addDataset(data[0]) - .project("fill", "type", colorScale) - .project("x", "x", xScale) - .project("y", "y", yScale); - - var plot1 = new Plottable.Component.Table([ - [stackedBarRenderer, yAxis1], - [xAxis1, null]]); - var plot2 = new Plottable.Component.Table([ - [clusteredBarRenderer, yAxis2], - [xAxis2, null]]); - var plot3 = new Plottable.Component.Table([ - [verticalBarRenderer, yAxis3], - [xAxis3, null]]); - - var chart = new Plottable.Component.Table([ - [plot1], - [plot2], - [plot3]]); - - chart.renderTo(svg); - -} diff --git a/quicktests/overlaying/tests/basic/basic_allPlots.js b/quicktests/overlaying/tests/basic/basic_allPlots.js index 053d749410..bdd75fec06 100644 --- a/quicktests/overlaying/tests/basic/basic_allPlots.js +++ b/quicktests/overlaying/tests/basic/basic_allPlots.js @@ -7,35 +7,36 @@ function makeData() { function run(svg, data, Plottable) { "use strict"; //Axis - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); var axis_array = []; for(var i = 0; i < 5; i++){ - axis_array.push(new Plottable.Axis.Numeric(xScale, "bottom")); - axis_array.push(new Plottable.Axis.Numeric(yScale, "left")); + axis_array.push(new Plottable.Axes.Numeric(xScale, "bottom")); + axis_array.push(new Plottable.Axes.Numeric(yScale, "left")); } + var dataset = new Plottable.Dataset(data); //rendering - var scatterPlot = new Plottable.Plot.Scatter(xScale, yScale).addDataset(data).project("x", "x", xScale).project("y", "y", yScale); - var linePlot = new Plottable.Plot.Line(xScale, yScale).addDataset(data).project("x", "x", xScale).project("y", "y", yScale); - var areaPlot = new Plottable.Plot.Area(xScale, yScale).addDataset(data).project("x", "x", xScale).project("y", "y", yScale); - var vbarPlot = new Plottable.Plot.Bar(xScale, yScale, true).addDataset(data).project("x", "x", xScale).project("y", "y", yScale); - var hbarPlot = new Plottable.Plot.Bar(xScale, yScale, false).addDataset(data).project("x", "x", xScale).project("y", "y", yScale); + var scatterPlot = new Plottable.Plots.Scatter(xScale, yScale).addDataset(dataset).x(function(d) { return d.x; }, xScale).y(function(d) { return d.y; }, yScale); + var linePlot = new Plottable.Plots.Line(xScale, yScale).addDataset(dataset).x(function(d) { return d.x; }, xScale).y(function(d) { return d.y; }, yScale); + var areaPlot = new Plottable.Plots.Area(xScale, yScale).addDataset(dataset).x(function(d) { return d.x; }, xScale).y(function(d) { return d.y; }, yScale); + var vbarPlot = new Plottable.Plots.Bar(xScale, yScale, true).addDataset(dataset).x(function(d) { return d.x; }, xScale).y(function(d) { return d.y; }, yScale); + var hbarPlot = new Plottable.Plots.Bar(xScale, yScale, false).addDataset(dataset).x(function(d) { return d.x; }, xScale).y(function(d) { return d.y; }, yScale); //title + legend - var scatterTable = new Plottable.Component.Table([[axis_array[1], scatterPlot], + var scatterTable = new Plottable.Components.Table([[axis_array[1], scatterPlot], [null, axis_array[0]]]); - var lineTable = new Plottable.Component.Table([[axis_array[3], linePlot], + var lineTable = new Plottable.Components.Table([[axis_array[3], linePlot], [null, axis_array[2]]]); - var areaTable = new Plottable.Component.Table([[axis_array[5], areaPlot], + var areaTable = new Plottable.Components.Table([[axis_array[5], areaPlot], [null, axis_array[4]]]); - var vbarTable = new Plottable.Component.Table([[axis_array[7], vbarPlot], + var vbarTable = new Plottable.Components.Table([[axis_array[7], vbarPlot], [null, axis_array[6]]]); - var hbarTable = new Plottable.Component.Table([[axis_array[9], hbarPlot], + var hbarTable = new Plottable.Components.Table([[axis_array[9], hbarPlot], [null, axis_array[8]]]); - var bigTable = new Plottable.Component.Table([[scatterTable, lineTable], + var bigTable = new Plottable.Components.Table([[scatterTable, lineTable], [areaTable, vbarTable], [hbarTable, null]]); diff --git a/quicktests/overlaying/tests/basic/basic_area.js b/quicktests/overlaying/tests/basic/basic_area.js deleted file mode 100644 index 2ba1baf648..0000000000 --- a/quicktests/overlaying/tests/basic/basic_area.js +++ /dev/null @@ -1,59 +0,0 @@ -function makeData() { - "use strict"; - - return [makeRandomData(10), makeRandomData(10)]; -} - -function run(svg, data, Plottable) { - "use strict"; - - var yScales = []; - - // Will receive function arguments: (svg, data, Plottable) - function getY(d) { return d.y; } - - var dataseries = []; - deep_copy(data[0], dataseries); - var dataseries_top = []; - deep_copy(data[1], dataseries_top); - - for (var i = 0; i < 10; ++i) { - dataseries_top[i].x = dataseries[i].x; - dataseries_top[i].y += dataseries[i].y; - } - - var xScale = new Plottable.Scale.Linear(); - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); - - var yScale = new Plottable.Scale.Linear(); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - - var y0Accessor = function(d, i) { return dataseries[i].y; }; - - var areaPlot1 = new Plottable.Plot.Area(xScale, yScale) - .addDataset(dataseries).project("x", "x", xScale) - .project("y", "y", yScale); - - var areaPlot2 = new Plottable.Plot.Area(xScale, yScale) - .addDataset(dataseries_top) - .attr("y0", y0Accessor, yScale) - .project("x", "x", xScale) - .project("y", "y", yScale); - - var fillAccessor = function() { return "steelblue"; }; - var fillAccessorTop = function() { return "pink"; }; - areaPlot1.attr("fill", fillAccessor); - areaPlot2.attr("fill", fillAccessorTop); - - var gridlines = new Plottable.Component.Gridlines(xScale, yScale); - var renderGroup = new Plottable.Component.Group([gridlines, areaPlot1, areaPlot2]); - - new Plottable.Component.Table([ - [yAxis, renderGroup], - [null, xAxis] - ]).renderTo(svg); -} - -//this test projects a new 'y0' onto areaPlot2, -//which basically means that it's moving the bottom of areaPlot2 -//up to the top of areaPlot, so that there's no overlap diff --git a/quicktests/overlaying/tests/basic/basic_pie.js b/quicktests/overlaying/tests/basic/basic_pie.js deleted file mode 100644 index 9ef7848bc9..0000000000 --- a/quicktests/overlaying/tests/basic/basic_pie.js +++ /dev/null @@ -1,13 +0,0 @@ -function makeData() { - "use strict"; - return [{value: 1}, {value: 2}, {value: 0}, {value: 3}]; -} - -function run(svg, data, Plottable) { - "use strict"; - - new Plottable.Plot.Pie() - .addDataset(data) - .project("value", "value") - .renderTo(svg); -} diff --git a/quicktests/overlaying/tests/basic/categoryAxis_timeAxis.js b/quicktests/overlaying/tests/basic/categoryAxis_timeAxis.js index 2f3575f328..5c3ae53447 100644 --- a/quicktests/overlaying/tests/basic/categoryAxis_timeAxis.js +++ b/quicktests/overlaying/tests/basic/categoryAxis_timeAxis.js @@ -12,25 +12,25 @@ function makeData() { function run(svg, data, Plottable) { "use strict"; - var xScale = new Plottable.Scale.Time(); - var yScale = new Plottable.Scale.Category(); + var xScale = new Plottable.Scales.Time(); + var yScale = new Plottable.Scales.Category(); - var hBarPlot = new Plottable.Plot.Bar(xScale, yScale, false) - .addDataset(data) - .attr("x", function (d) { return d3.time.format("%x").parse(d.x); }, xScale) - .project("y", "y", yScale); + var hBarPlot = new Plottable.Plots.Bar(xScale, yScale, false) + .addDataset(new Plottable.Dataset(data)) + .x(function (d) { return d3.time.format("%x").parse(d.x); }, xScale) + .y(function(d) { return d.y; }, yScale); - var xAxis = new Plottable.Axis.Time(xScale, "bottom", Plottable.Formatters.multiTime()); - var yAxis = new Plottable.Axis.Category(yScale, "left"); + var xAxis = new Plottable.Axes.Time(xScale, "bottom", Plottable.Formatters.multiTime()); + var yAxis = new Plottable.Axes.Category(yScale, "left"); - var gridlines = new Plottable.Component.Gridlines(xScale, null); - var renderGroup = hBarPlot.above(gridlines); + var gridlines = new Plottable.Components.Gridlines(xScale, null); + var renderGroup = new Plottable.Components.Group([hBarPlot, gridlines]); - var chart = new Plottable.Component.Table([ + var chart = new Plottable.Components.Table([ [yAxis, renderGroup], [null, xAxis]]); chart.renderTo(svg); - hBarPlot.registerInteraction(new Plottable.Interaction.PanZoom(xScale, null)); + new Plottable.Interactions.PanZoom(xScale, null).attachTo(hBarPlot); } diff --git a/quicktests/overlaying/tests/basic/category_horizontalBar.js b/quicktests/overlaying/tests/basic/category_horizontalBar.js deleted file mode 100644 index 15165fbf10..0000000000 --- a/quicktests/overlaying/tests/basic/category_horizontalBar.js +++ /dev/null @@ -1,32 +0,0 @@ -function makeData() { - "use strict"; - - return [ - { name: "Spot", age: 8 }, - { name: "Poptart", age: 1 }, - { name: "Budoka", age: 3 }, - { name: "Sugar", age: 14 }, - { name: "Tac", age: -5 } - ]; -} - -function run(svg, data, Plottable) { - "use strict"; - - var ds = new Plottable.Dataset(data); - var yScale = new Plottable.Scale.Category(); - var yAxis = new Plottable.Axis.Category(yScale, "left"); - - var xScale = new Plottable.Scale.Linear(); - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); - - var barPlot = new Plottable.Plot.Bar(xScale, yScale, false) - .addDataset(ds) - .attr("y", "name", yScale) - .attr("x", "age", xScale) - .animate(true); - var chart = new Plottable.Component.Table([[new Plottable.Component.Label(""), yAxis, barPlot], - [null, null, xAxis]]); - chart.renderTo(svg); - -} diff --git a/quicktests/overlaying/tests/basic/category_project.js b/quicktests/overlaying/tests/basic/category_project.js deleted file mode 100644 index 68a851f9d0..0000000000 --- a/quicktests/overlaying/tests/basic/category_project.js +++ /dev/null @@ -1,45 +0,0 @@ -function makeData() { - "use strict"; - - return makeRandomData(5); -} - -function run(svg, data, Plottable) { - "use strict"; - - var change_x = function(elt, i) { - elt.x = i.toString(); // wtf is this code - }; - data.forEach(change_x); - var dataseries1 = new Plottable.Dataset(data); - - //Axis - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Linear(); - var xAxis = new Plottable.Axis.Category(xScale, "bottom"); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - - var widthProjector = function(d, i, m) { - return (d.x*3 + 3); - }; - - //rendering - var renderAreaD1 = new Plottable.Plot.Bar(xScale, yScale, true) - .addDataset(dataseries1) - .attr("width", widthProjector) - .project("x", "x", xScale) - .project("y", "y", yScale) - .animate(true); - - //title + legend - var title1 = new Plottable.Component.TitleLabel( "Category Axis", "horizontal"); - var label = new Plottable.Component.Label("Width is 3*d.x + 3", "horizontal"); - - var xAxisTable = new Plottable.Component.Table([[xAxis],[label]]); - var basicTable = new Plottable.Component.Table().addComponent(0,2, title1) - .addComponent(1, 1, yAxis) - .addComponent(1, 2, renderAreaD1) - .addComponent(2, 2, xAxisTable); - - basicTable.renderTo(svg); -} diff --git a/quicktests/overlaying/tests/basic/category_verticalBar.js b/quicktests/overlaying/tests/basic/category_verticalBar.js deleted file mode 100644 index 35efc2f581..0000000000 --- a/quicktests/overlaying/tests/basic/category_verticalBar.js +++ /dev/null @@ -1,35 +0,0 @@ -function makeData() { - "use strict"; - - return [ - { name: "Spot", age: 8 }, - { name: "Poptart", age: 1 }, - { name: "Budoka", age: 3 }, - { name: "Sugar", age: 14 }, - { name: "Tac", age: -5 } - ]; -} - -function run(svg, data, Plottable) { - "use strict"; - - var ds = new Plottable.Dataset(data); - - var xScale = new Plottable.Scale.Category(); - var xAxis = new Plottable.Axis.Category(xScale, "bottom"); - - var yScale = new Plottable.Scale.Linear(); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - - var barPlot = new Plottable.Plot.Bar(xScale, yScale, true) - .addDataset(ds) - .attr("x", "name", xScale) - .attr("y", "age", yScale) - .animate(true); - - var chart = new Plottable.Component.Table([[yAxis, barPlot], - [null, xAxis]]); - - chart.renderTo(svg); - -} diff --git a/quicktests/overlaying/tests/basic/clustered_bar.js b/quicktests/overlaying/tests/basic/clustered_bar.js deleted file mode 100644 index 9f945381fb..0000000000 --- a/quicktests/overlaying/tests/basic/clustered_bar.js +++ /dev/null @@ -1,37 +0,0 @@ -function makeData() { - "use strict"; - - var data1 = [{name: "jon", y: 1, type: "q1"}, {name: "dan", y: 2, type: "q1"}, {name: "zoo", y: 1, type: "q1"}]; - var data2 = [{name: "jon", y: 2, type: "q2"}, {name: "dan", y: 4, type: "q2"}, {name: "zoo", y: 2, type: "q2"}]; - var data3 = [{name: "jon", y: 4, type: "q3"}, {name: "dan", y: 15, type: "q3"}, {name: "zoo", y: 15, type: "q3"}]; - return [data1, data2, data3]; -} - -function run(svg, data, Plottable) { - "use strict"; - - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Linear(); - var colorScale = new Plottable.Scale.Color("10"); - - var xAxis = new Plottable.Axis.Category(xScale, "bottom"); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - var clusteredBarRenderer = new Plottable.Plot.ClusteredBar(xScale, yScale) - .addDataset("d1", data[0]) - .addDataset("d2", data[1]) - .addDataset("d3", data[2]) - .attr("x", "name", xScale) - .attr("y", "y", yScale) - .attr("fill", "type", colorScale) - .attr("type", "type") - .attr("yval", "y") - .barLabelsEnabled(true); - - var legend = new Plottable.Component.Legend(colorScale); - legend.maxEntriesPerRow(1); - var center = clusteredBarRenderer.below(legend); - - var horizChart = new Plottable.Component.Table([ - [yAxis, center], [null, xAxis] - ]).renderTo(svg); -} diff --git a/quicktests/overlaying/tests/basic/grid_plot.js b/quicktests/overlaying/tests/basic/grid_plot.js deleted file mode 100644 index 1253accbd1..0000000000 --- a/quicktests/overlaying/tests/basic/grid_plot.js +++ /dev/null @@ -1,32 +0,0 @@ - -function makeData() { - "use strict"; - - var data = [{name: "jon", y: 10, type: "q1"}, {name: "dan", y: 2, type: "q1"}, {name: "zoo", y: 1, type: "q1"}]; - - return data; -} - -function run(svg, data, Plottable) { - "use strict"; - - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Category(); - var colorScale = new Plottable.Scale.Color(); - - var xAxis = new Plottable.Axis.Category(xScale, "bottom"); - var yAxis = new Plottable.Axis.Category(yScale, "left"); - - var grid = new Plottable.Plot.Grid(xScale, yScale) - .project("x", "name", xScale) - .project("y", "y", yScale) - .attr("fill", "type", colorScale) - .addDataset(data); - - var chart = new Plottable.Component.Table([ - [yAxis, grid], - [null, xAxis] - ]); - - chart.renderTo(svg); -} diff --git a/quicktests/overlaying/tests/basic/heightWeight_project.js b/quicktests/overlaying/tests/basic/heightWeight_project.js deleted file mode 100644 index b705a944e9..0000000000 --- a/quicktests/overlaying/tests/basic/heightWeight_project.js +++ /dev/null @@ -1,38 +0,0 @@ - -function makeData() { - "use strict"; - - return [generateHeightWeightData(50), makeRandomData(50)]; - -} - -function run(svg, data, Plottable) { - "use strict"; - - var dataseries = data[0].slice(0, 30); - - var xScale = new Plottable.Scale.Linear(); - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); - - var yScale = new Plottable.Scale.Linear(); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - var xAccessor = function(d) { return d.age; }; - var yAccessor = function(d) { return d.height; }; - var sizeAccessor = function(d) { return d.weight / 10; }; - var opacityAccessor = function(d) { return 0.5; }; - var colorAccessor = function(d) { - return d.gender === "male" ? "#F35748" : "#2FA9E7"; - }; - - var renderer = new Plottable.Plot.Scatter(xScale, yScale); - renderer.addDataset(dataseries) - .attr("x", xAccessor, xScale) - .attr("y", yAccessor, yScale) - .attr("size", sizeAccessor) - .attr("fill", colorAccessor) - .attr("opacity", opacityAccessor); - var chartTable = new Plottable.Component.Table([[yAxis, renderer], - [null, xAxis]]); - chartTable.renderTo(svg); - -} diff --git a/quicktests/overlaying/tests/basic/horizontal_clustered_bar.js b/quicktests/overlaying/tests/basic/horizontal_clustered_bar.js deleted file mode 100644 index 6ef42ce0b2..0000000000 --- a/quicktests/overlaying/tests/basic/horizontal_clustered_bar.js +++ /dev/null @@ -1,35 +0,0 @@ -function makeData() { - "use strict"; - - var data1 = [{name: "jon", value: 1, type: "q1"}, {name: "dan", value: 2, type: "q1"}, {name: "zoo", value: 1, type: "q1"}]; - var data2 = [{name: "jon", value: 2, type: "q2"}, {name: "dan", value: 4, type: "q2"}, {name: "zoo", value: 2, type: "q2"}]; - var data3 = [{name: "jon", value: 4, type: "q3"}, {name: "dan", value: 15, type: "q3"}, {name: "zoo", value: 15, type: "q3"}]; - return [data1, data2, data3]; -} - -function run(svg, data, Plottable) { - "use strict"; - - var nameScale = new Plottable.Scale.Category(); - var valueScale = new Plottable.Scale.Linear(); - var colorScale = new Plottable.Scale.Color("10"); - - var nameAxis = new Plottable.Axis.Category(nameScale, "left"); - var valueAxis = new Plottable.Axis.Numeric(valueScale, "bottom"); - var clusteredBarRenderer = new Plottable.Plot.ClusteredBar(valueScale, nameScale, false) - .addDataset("d1", data[0]) - .addDataset("d2", data[1]) - .addDataset("d3", data[2]) - .attr("x", "value", valueScale) - .attr("y", "name", nameScale) - .attr("fill", "type", colorScale) - .attr("type", "type") - .attr("yval", "y") - .barLabelsEnabled(true); - - var center = clusteredBarRenderer.below(new Plottable.Component.Legend(colorScale)); - - var horizChart = new Plottable.Component.Table([ - [nameAxis, center], [null, valueAxis] - ]).renderTo(svg); -} diff --git a/quicktests/overlaying/tests/basic/horizontal_stacked.js b/quicktests/overlaying/tests/basic/horizontal_stacked.js deleted file mode 100644 index beb87ffe0d..0000000000 --- a/quicktests/overlaying/tests/basic/horizontal_stacked.js +++ /dev/null @@ -1,33 +0,0 @@ -function makeData() { - "use strict"; - var data1 = [{name: "jon", y: 0, type: "q1"}, {name: "dan", y: -2, type: "q1"}, {name: "zoo", y: 1, type: "q1"}]; - var data2 = [{name: "jon", y: 2, type: "q2"}, {name: "dan", y: -4, type: "q2"}, {name: "zoo", y: 2, type: "q2"}]; - var data3 = [{name: "jon", y: 4, type: "q3"}, {name: "dan", y: 0, type: "q3"}, {name: "zoo", y: 5, type: "q3"}]; - return [data1, data2, data3]; -} - -function run(svg, data, Plottable) { - "use strict"; - - var yScale = new Plottable.Scale.Category().domain(["jon", "dan", "zoo"]); - var xScale = new Plottable.Scale.Linear(); - var colorScale = new Plottable.Scale.Color(); - - var yAxis = new Plottable.Axis.Category(yScale, "left"); - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); - - var stackedBarRenderer = new Plottable.Plot.StackedBar(xScale, yScale, false) - .project("y", "name", yScale) - .project("x", "y", xScale) - .project("fill", "type", colorScale) - .addDataset("d1", data[0]) - .addDataset("d2", data[1]) - .addDataset("d3", data[2]) - .animate(true); - - var chart = new Plottable.Component.Table([ - [yAxis, stackedBarRenderer], - [null, xAxis] - ]); - chart.renderTo(svg); -} \ No newline at end of file diff --git a/quicktests/overlaying/tests/basic/layout_tables.js b/quicktests/overlaying/tests/basic/layout_tables.js index d42f1b0e74..f674fc554e 100644 --- a/quicktests/overlaying/tests/basic/layout_tables.js +++ b/quicktests/overlaying/tests/basic/layout_tables.js @@ -7,63 +7,63 @@ function makeData() { function run(svg, data, Plottable) { "use strict"; - var dataseries1 = data[0].slice(); - var dataseries2 = data[0].slice(); - - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - var xAxis0 = new Plottable.Axis.Numeric(xScale, "bottom"); - var xAxis1 = new Plottable.Axis.Numeric(xScale, "bottom"); - var xAxis2 = new Plottable.Axis.Numeric(xScale, "bottom"); - var xAxis3 = new Plottable.Axis.Numeric(xScale, "bottom"); - var yAxis0 = new Plottable.Axis.Numeric(yScale, "left"); - var yAxis1 = new Plottable.Axis.Numeric(yScale, "left"); - var yAxis2 = new Plottable.Axis.Numeric(yScale, "left"); - var yAxis3 = new Plottable.Axis.Numeric(yScale, "left"); + var dataset1 = new Plottable.Dataset(data[0]); + var dataset2 = new Plottable.Dataset(data[0]); + + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); + var xAxis0 = new Plottable.Axes.Numeric(xScale, "bottom"); + var xAxis1 = new Plottable.Axes.Numeric(xScale, "bottom"); + var xAxis2 = new Plottable.Axes.Numeric(xScale, "bottom"); + var xAxis3 = new Plottable.Axes.Numeric(xScale, "bottom"); + var yAxis0 = new Plottable.Axes.Numeric(yScale, "left"); + var yAxis1 = new Plottable.Axes.Numeric(yScale, "left"); + var yAxis2 = new Plottable.Axes.Numeric(yScale, "left"); + var yAxis3 = new Plottable.Axes.Numeric(yScale, "left"); //test Component constructor (default, should be no issues) - var renderAreaD0 = new Plottable.Plot.Line(xScale, yScale).addDataset(dataseries1); - renderAreaD0.project("x", "x", xScale).project("y", "y", yScale); - var renderAreaD1 = new Plottable.Plot.Line(xScale, yScale).addDataset(dataseries2) + var renderAreaD0 = new Plottable.Plots.Line(xScale, yScale).addDataset(dataset1); + renderAreaD0.x(function(d) { return d.x; }, xScale).y(function(d) { return d.y; }, yScale); + var renderAreaD1 = new Plottable.Plots.Line(xScale, yScale).addDataset(dataset2) .attr( "stroke", d3.functor("red")); - renderAreaD1.project("x", "x", xScale).project("y", "y", yScale); - var renderAreaD2 = new Plottable.Plot.Area(xScale, yScale).addDataset(dataseries1); - renderAreaD2.project("x", "x", xScale).project("y", "y", yScale); - var renderAreaD3 = new Plottable.Plot.Area(xScale, yScale).addDataset(dataseries2) + renderAreaD1.x(function(d) { return d.x; }, xScale).y(function(d) { return d.y; }, yScale); + var renderAreaD2 = new Plottable.Plots.Area(xScale, yScale).addDataset(dataset1); + renderAreaD2.x(function(d) { return d.x; }, xScale).y(function(d) { return d.y; }, yScale); + var renderAreaD3 = new Plottable.Plots.Area(xScale, yScale).addDataset(dataset2) .attr( "fill", d3.functor("red")); - renderAreaD3.project("x", "x", xScale).project("y", "y", yScale); + renderAreaD3.x(function(d) { return d.x; }, xScale).y(function(d) { return d.y; }, yScale); //test merge: //empty component + empty component - var basicTable0 = new Plottable.Component.Table().addComponent(0,0, yAxis0); + var basicTable0 = new Plottable.Components.Table().add(yAxis0, 0, 0); //empty component + XYRenderer - var basicTable1 = new Plottable.Component.Table().addComponent(0,1, renderAreaD0); + var basicTable1 = new Plottable.Components.Table().add(renderAreaD0, 0, 1); //XYRenderer + empty component - var basicTable2 = new Plottable.Component.Table().addComponent(0,0, yAxis2) - .addComponent(0,1, renderAreaD1) - .addComponent(1,1,xAxis2); + var basicTable2 = new Plottable.Components.Table().add(yAxis2, 0, 0) + .add(renderAreaD1, 0, 1) + .add(xAxis2, 1, 1); //XYRenderer + XYRenderer - var renderGroup3 = renderAreaD3.below(renderAreaD2); - var basicTable3 = new Plottable.Component.Table().addComponent(0,1, renderGroup3) - .addComponent(1,1,xAxis3); + var renderGroup3 = new Plottable.Components.Group([renderAreaD3, renderAreaD2]); + var basicTable3 = new Plottable.Components.Table().add(renderGroup3, 0, 1) + .add(xAxis3, 1, 1); - var bigtable = new Plottable.Component.Table(); + var bigtable = new Plottable.Components.Table(); - var line1 = new Plottable.Component.Label("Tables in Tables", "horizontal"); - var line2 = new Plottable.Component.Label("for Dan", "horizontal"); + var line1 = new Plottable.Components.Label("Tables in Tables", "horizontal"); + var line2 = new Plottable.Components.Label("for Dan", "horizontal"); - bigtable = new Plottable.Component.Table().addComponent(0,0, basicTable0) - .addComponent(0,2, basicTable1) - .addComponent(3,0, basicTable2) - .addComponent(3,2,basicTable3) - .addComponent(1, 1, line1) - .addComponent(2, 1, line2); + bigtable = new Plottable.Components.Table().add(basicTable0, 0, 0) + .add(basicTable1, 0, 2) + .add(basicTable2, 3, 0) + .add(basicTable3, 3, 2) + .add(line1, 1, 1) + .add(line2, 2, 1); bigtable.renderTo(svg); diff --git a/quicktests/overlaying/tests/basic/legend_basic.js b/quicktests/overlaying/tests/basic/legend_basic.js deleted file mode 100644 index a9b3a24396..0000000000 --- a/quicktests/overlaying/tests/basic/legend_basic.js +++ /dev/null @@ -1,52 +0,0 @@ -function makeData() { - "use strict"; - - return [makeRandomData(50), makeRandomData(50), makeRandomData(50), makeRandomData(50)]; -} - -function run(svg, data, Plottable) { - "use strict"; - - var dataseries1 = new Plottable.Dataset(data[0].slice(0, 20)); - dataseries1.metadata({name: "series1"}); - var dataseries2 = new Plottable.Dataset(data[1].slice(0, 20)); - dataseries2.metadata({name: "series2"}); - var dataseries3 = new Plottable.Dataset(data[2].slice(0, 20)); - dataseries3.metadata({name: "series3"}); - var dataseries4 = new Plottable.Dataset(data[3].slice(0, 20)); - dataseries4.metadata({name: "series4"}); - var colorScale1 = new Plottable.Scale.Color("10"); - colorScale1.domain(["series1", "series2", "series3", "series4"]); - - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - - var colorProjector = function(d, i, m) { - return colorScale1.scale(m.name); - }; - - var renderAreaD1 = new Plottable.Plot.Scatter(xScale, yScale).addDataset(dataseries1) - .addDataset(dataseries3) - .addDataset(dataseries4); - renderAreaD1.project("x", "x", xScale).project("y", "y", yScale); - var renderAreaD2 = new Plottable.Plot.Line(xScale, yScale).addDataset(dataseries2); - renderAreaD2.project("x", "x", xScale).project("y", "y", yScale); - renderAreaD1.attr("fill", colorProjector); - renderAreaD2.attr("stroke", colorProjector); - var renderAreas = renderAreaD1.above(renderAreaD2); - - - var title1 = new Plottable.Component.TitleLabel( "Four Data Series", "horizontal"); - var legend1 = new Plottable.Component.Legend(colorScale1).maxEntriesPerRow(1); - var titleTable = new Plottable.Component.Table().addComponent(0,0, title1) - .addComponent(0,1, legend1); - - var basicTable = new Plottable.Component.Table().addComponent(0,2, titleTable) - .addComponent(1, 1, yAxis) - .addComponent(1, 2, renderAreas) - .addComponent(2, 2, xAxis); - - basicTable.renderTo(svg); -} diff --git a/quicktests/overlaying/tests/basic/legend_reversed.js b/quicktests/overlaying/tests/basic/legend_reversed.js deleted file mode 100644 index b88d432770..0000000000 --- a/quicktests/overlaying/tests/basic/legend_reversed.js +++ /dev/null @@ -1,42 +0,0 @@ - -function makeData() { - "use strict"; - - return; -} - -function run(svg, data, Plottable) { - "use strict"; - - var xScale = new Plottable.Scale.Category(); - var xAxis = new Plottable.Axis.Category(xScale, "bottom"); - var yScale = new Plottable.Scale.Linear(); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - var bars = new Plottable.Plot.StackedBar(xScale, yScale) - .project("x", "name", xScale) - .project("y", "value", yScale); - - var colorScale = new Plottable.Scale.Color(); - - bars.addDataset("low", [{ name:"A", value:1 }]); - bars.addDataset("mid", [{ name:"A", value: 2 }]); - bars.addDataset("high", [{ name:"A", value: 3 }]); - - bars.project("fill", function(d, i, u, m) { return m.datasetKey; }, colorScale); - - var reverse = function reverseComp(a, b) { - var domain = colorScale.domain(); - return (domain.indexOf(b) - domain.indexOf(a)); - }; - - var legend = new Plottable.Component.Legend(colorScale) - .maxEntriesPerRow(1) - .sortFunction(reverse); - - var chart = new Plottable.Component.Table([ - [yAxis, bars, legend], - [null, xAxis, null], - ]); - - chart.renderTo(svg); -} diff --git a/quicktests/overlaying/tests/basic/missing_clustered_bar.js b/quicktests/overlaying/tests/basic/missing_clustered_bar.js index 9a04bf80c9..0b7245dc25 100644 --- a/quicktests/overlaying/tests/basic/missing_clustered_bar.js +++ b/quicktests/overlaying/tests/basic/missing_clustered_bar.js @@ -10,26 +10,26 @@ function makeData() { function run(svg, data, Plottable) { "use strict"; - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Linear(); - var colorScale = new Plottable.Scale.Color("10"); + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Linear(); + var colorScale = new Plottable.Scales.Color("10"); - var xAxis = new Plottable.Axis.Category(xScale, "bottom"); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - var clusteredBarRenderer = new Plottable.Plot.ClusteredBar(xScale, yScale) - .addDataset("d1", data[0]) - .addDataset("d2", data[1]) - .addDataset("d3", data[2]) - .attr("x", "name", xScale) - .attr("y", "y", yScale) - .attr("fill", "type", colorScale) - .attr("type", "type") - .attr("yval", "y") - .barLabelsEnabled(true); + var xAxis = new Plottable.Axes.Category(xScale, "bottom"); + var yAxis = new Plottable.Axes.Numeric(yScale, "left"); + var clusteredBarRenderer = new Plottable.Plots.ClusteredBar(xScale, yScale) + .addDataset(new Plottable.Dataset(data[0])) + .addDataset(new Plottable.Dataset(data[1])) + .addDataset(new Plottable.Dataset(data[2])) + .x(function(d) { return d.name; }, xScale) + .y(function(d) { return d.y; }, yScale) + .attr("fill", function(d) { return d.type; }, colorScale) + .attr("type", function(d) { return d.type; }) + .attr("yval", function(d) { return d.y; }) + .labelsEnabled(true); - var center = clusteredBarRenderer.below(new Plottable.Component.Legend(colorScale)); + var center = new Plottable.Components.Group([clusteredBarRenderer, new Plottable.Components.Legend(colorScale)]); - new Plottable.Component.Table([ + new Plottable.Components.Table([ [yAxis, center], [null, xAxis] ]).renderTo(svg); } diff --git a/quicktests/overlaying/tests/basic/missing_stacked_area.js b/quicktests/overlaying/tests/basic/missing_stacked_area.js index a992c48fc3..6e895ad868 100644 --- a/quicktests/overlaying/tests/basic/missing_stacked_area.js +++ b/quicktests/overlaying/tests/basic/missing_stacked_area.js @@ -10,26 +10,26 @@ function makeData() { function run(svg, data, Plottable) { "use strict"; - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Linear(); - var colorScale = new Plottable.Scale.Color("10"); + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Linear(); + var colorScale = new Plottable.Scales.Color("10"); - var xAxis = new Plottable.Axis.Category(xScale, "bottom"); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - var stackedAreaPlot = new Plottable.Plot.StackedArea(xScale, yScale) - .attr("x", "name", xScale) - .attr("y", "y", yScale) - .attr("fill", "type", colorScale) - .attr("type", "type") - .attr("yval", "y") - .addDataset("d1", data[0]) - .addDataset("d2", data[1]) - .addDataset("d3", data[2]) + var xAxis = new Plottable.Axes.Category(xScale, "bottom"); + var yAxis = new Plottable.Axes.Numeric(yScale, "left"); + var stackedAreaPlot = new Plottable.Plots.StackedArea(xScale, yScale) + .x(function(d) { return d.name; }, xScale) + .y(function(d) { return d.y; }, yScale) + .attr("fill", function(d) { return d.type; }, colorScale) + .attr("type", function(d) { return d.type; }) + .attr("yval", function(d) { return d.y; }) + .addDataset(new Plottable.Dataset(data[0])) + .addDataset(new Plottable.Dataset(data[1])) + .addDataset(new Plottable.Dataset(data[2])) .animate(true); - var center = stackedAreaPlot.below(new Plottable.Component.Legend(colorScale)); + var center = new Plottable.Components.Group([stackedAreaPlot, new Plottable.Components.Legend(colorScale)]); - var horizChart = new Plottable.Component.Table([ + var horizChart = new Plottable.Components.Table([ [yAxis, center], [null, xAxis] ]).renderTo(svg); } diff --git a/quicktests/overlaying/tests/basic/multi_area.js b/quicktests/overlaying/tests/basic/multi_area.js deleted file mode 100644 index 4e828cf627..0000000000 --- a/quicktests/overlaying/tests/basic/multi_area.js +++ /dev/null @@ -1,58 +0,0 @@ - -function makeData() { - "use strict"; - return [makeRandomData(25),makeRandomData(25),makeRandomData(25),makeRandomData(25)]; -} - -function makePowerFunction(pow) { - "use strict"; - return function(d) { - var newY = Math.pow(10 * d.y, pow); - return {x: d.x, y: newY, color: pow.toString()}; - }; -} - -function run(svg, data, Plottable) { - "use strict"; - - var d1 = data[0].map(makePowerFunction(2)); - var d2 = data[1].map(makePowerFunction(4)); - var d3 = data[2].map(makePowerFunction(8)); - var d4 = data[3].map(makePowerFunction(16)); - - var colorScale = new Plottable.Scale.Color(); - - //Axis - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.ModifiedLog(); - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - - //rendering - var plot = new Plottable.Plot.Area(xScale, yScale) - .addDataset(d1) - .addDataset(d2) - .addDataset(d3) - .addDataset(d4) - .attr("fill", "color", colorScale) - .attr("stroke", "color", colorScale) - .attr("r", function(d) {return d.x * 12;}) - .project("x", "x", xScale) - .project("y", "y", yScale); - - //title + legend - var title1 = new Plottable.Component.TitleLabel( "Two Data Series", "horizontal"); - var legend1 = new Plottable.Component.Legend(colorScale); - legend1.maxEntriesPerRow(1); - - var titleTable = new Plottable.Component.Table().addComponent(0,0, title1) - .addComponent(0,1, legend1); - - var basicTable = new Plottable.Component.Table().addComponent(0,2, titleTable) - .addComponent(1, 1, yAxis) - .addComponent(1, 2, plot) - .addComponent(2, 2, xAxis); - - basicTable.renderTo(svg); - -} diff --git a/quicktests/overlaying/tests/basic/multi_line.js b/quicktests/overlaying/tests/basic/multi_line.js deleted file mode 100644 index 324c750009..0000000000 --- a/quicktests/overlaying/tests/basic/multi_line.js +++ /dev/null @@ -1,57 +0,0 @@ - -function makeData() { - "use strict"; - return [makeRandomData(25),makeRandomData(25),makeRandomData(25),makeRandomData(25)]; -} - -function makePowerFunction(pow) { - "use strict"; - return function(d) { - var newY = Math.pow(10 * d.y, pow); - return {x: d.x, y: newY, color: pow.toString()}; - }; -} - -function run(svg, data, Plottable) { - "use strict"; - - var d1 = data[0].map(makePowerFunction(2)); - var d2 = data[1].map(makePowerFunction(4)); - var d3 = data[2].map(makePowerFunction(8)); - var d4 = data[3].map(makePowerFunction(16)); - - var colorScale = new Plottable.Scale.Color(); - - //Axis - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.ModifiedLog(); - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - - //rendering - var linePlot = new Plottable.Plot.Line(xScale, yScale) - .addDataset(d1) - .addDataset(d2) - .addDataset(d3) - .addDataset(d4) - .attr("stroke", "color", colorScale) - .attr("r", function(d) {return d.x * 12;}) - .project("x", "x", xScale) - .project("y", "y", yScale); - - //title + legend - var title1 = new Plottable.Component.TitleLabel( "Two Data Series", "horizontal"); - var legend1 = new Plottable.Component.Legend(colorScale); - legend1.maxEntriesPerRow(1); - - var titleTable = new Plottable.Component.Table().addComponent(0,0, title1) - .addComponent(0,1, legend1); - - var basicTable = new Plottable.Component.Table().addComponent(0,2, titleTable) - .addComponent(1, 1, yAxis) - .addComponent(1, 2, linePlot) - .addComponent(2, 2, xAxis); - - basicTable.renderTo(svg); - -} diff --git a/quicktests/overlaying/tests/basic/multi_scatter_with_modified_log.js b/quicktests/overlaying/tests/basic/multi_scatter_with_modified_log.js deleted file mode 100644 index e6664cd0b5..0000000000 --- a/quicktests/overlaying/tests/basic/multi_scatter_with_modified_log.js +++ /dev/null @@ -1,55 +0,0 @@ - -function makeData() { - "use strict"; - return [makeRandomData(25),makeRandomData(25),makeRandomData(25),makeRandomData(25)]; -} - -function makePowerFunction(pow) { - "use strict"; - return function(d) { - var newY = Math.pow(10 * d.y, pow); - return {x: d.x, y: newY, color: pow.toString()}; - }; -} - -function run(svg, data, Plottable) { - "use strict"; - - var d1 = data[0].map(makePowerFunction(2)); - var d2 = data[1].map(makePowerFunction(4)); - var d3 = data[2].map(makePowerFunction(8)); - var d4 = data[3].map(makePowerFunction(16)); - - var colorScale = new Plottable.Scale.Color(); - - //Axis - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.ModifiedLog(); - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - - //rendering - var scatterPlot = new Plottable.Plot.Scatter(xScale, yScale).addDataset(d1) - .addDataset(d2) - .addDataset(d3) - .addDataset(d4) - .attr("fill", "color", colorScale) - .attr("size", function(d) {return d.x * 24;}) - .project("x", "x", xScale) - .project("y", "y", yScale); - - //title + legend - var title1 = new Plottable.Component.TitleLabel( "Two Data Series", "horizontal"); - var legend1 = new Plottable.Component.Legend(colorScale); - legend1.maxEntriesPerRow(1); - - var titleTable = new Plottable.Component.Table().addComponent(0,0, title1) - .addComponent(0,1, legend1); - - var basicTable = new Plottable.Component.Table().addComponent(0,2, titleTable) - .addComponent(1, 1, yAxis) - .addComponent(1, 2, scatterPlot) - .addComponent(2, 2, xAxis); - - basicTable.renderTo(svg); -} diff --git a/quicktests/overlaying/tests/basic/negative_stacked_bar.js b/quicktests/overlaying/tests/basic/negative_stacked_bar.js index 8df811f1ab..c277b9f40e 100644 --- a/quicktests/overlaying/tests/basic/negative_stacked_bar.js +++ b/quicktests/overlaying/tests/basic/negative_stacked_bar.js @@ -12,58 +12,64 @@ function makeData() { function run(svg, data, Plottable) { "use strict"; - var xScale1 = new Plottable.Scale.Category(); - var yScale1 = new Plottable.Scale.Linear(); - var xScale2 = new Plottable.Scale.Linear(); - var yScale2 = new Plottable.Scale.Category(); + var xScale1 = new Plottable.Scales.Category(); + var yScale1 = new Plottable.Scales.Linear(); + var xScale2 = new Plottable.Scales.Linear(); + var yScale2 = new Plottable.Scales.Category(); - var colorScale = new Plottable.Scale.Color(); + var colorScale = new Plottable.Scales.Color(); - var xAxis1 = new Plottable.Axis.Category(xScale1, "bottom"); - var yAxis1 = new Plottable.Axis.Numeric(yScale1, "left"); - var xAxis2 = new Plottable.Axis.Numeric(xScale2, "bottom"); - var yAxis2 = new Plottable.Axis.Category(yScale2, "left"); + var xAxis1 = new Plottable.Axes.Category(xScale1, "bottom"); + var yAxis1 = new Plottable.Axes.Numeric(yScale1, "left"); + var xAxis2 = new Plottable.Axes.Numeric(xScale2, "bottom"); + var yAxis2 = new Plottable.Axes.Category(yScale2, "left"); - var legend = new Plottable.Component.Legend(colorScale); - legend.xAlign("center"); + var legend = new Plottable.Components.Legend(colorScale); + legend.xAlignment("center"); - var title = new Plottable.Component.TitleLabel("Sample Net Earnings by Teams"); + var title = new Plottable.Components.Label("Sample Net Earnings by Teams").classed("title-label", true); - var verticalPlot = new Plottable.Plot.StackedBar(xScale1, yScale1, true) - .project("x", "quarter", xScale1) - .project("y", "earnings", yScale1) - .project("fill", "team", colorScale) - .addDataset("d1", data[0]) - .addDataset("d2", data[1]) - .addDataset("d3", data[2]) - .addDataset("d4", data[3]) - .addDataset("d5", data[4]) - .barLabelsEnabled(true) - .barLabelFormatter(Plottable.Formatters.siSuffix()) + var dataset1 = new Plottable.Dataset(data[0]); + var dataset2 = new Plottable.Dataset(data[1]); + var dataset3 = new Plottable.Dataset(data[2]); + var dataset4 = new Plottable.Dataset(data[3]); + var dataset5 = new Plottable.Dataset(data[4]); + + var verticalPlot = new Plottable.Plots.StackedBar(xScale1, yScale1, true) + .x(function(d) { return d.quarter; }, xScale1) + .y(function(d) { return d.earnings; }, yScale1) + .attr("fill", function(d) { return d.team; }, colorScale) + .addDataset(dataset1) + .addDataset(dataset2) + .addDataset(dataset3) + .addDataset(dataset4) + .addDataset(dataset5) + .labelsEnabled(true) + .labelFormatter(Plottable.Formatters.siSuffix()) .animate(true); - var horizontalPlot = new Plottable.Plot.StackedBar(xScale2, yScale2, false) - .project("x", "earnings", xScale2) - .project("y", "quarter", yScale2) - .project("fill", "team", colorScale) - .addDataset("d1", data[0]) - .addDataset("d2", data[1]) - .addDataset("d3", data[2]) - .addDataset("d4", data[3]) - .addDataset("d5", data[4]) - .barLabelsEnabled(true) - .barLabelFormatter(Plottable.Formatters.siSuffix()) + var horizontalPlot = new Plottable.Plots.StackedBar(xScale2, yScale2, false) + .x(function(d) { return d.earnings; }, xScale2) + .y(function(d) { return d.quarter; }, yScale2) + .attr("fill", function(d) { return d.team; }, colorScale) + .addDataset(dataset1) + .addDataset(dataset2) + .addDataset(dataset3) + .addDataset(dataset4) + .addDataset(dataset5) + .labelsEnabled(true) + .labelFormatter(Plottable.Formatters.siSuffix()) .animate(true); - var chart1 = new Plottable.Component.Table([ + var chart1 = new Plottable.Components.Table([ [yAxis1, verticalPlot], [null, xAxis1] ]); - var chart2 = new Plottable.Component.Table([ + var chart2 = new Plottable.Components.Table([ [yAxis2, horizontalPlot], [null, xAxis2] ]); - var finalchart = new Plottable.Component.Table([ + var finalchart = new Plottable.Components.Table([ [title], [legend], [chart1], diff --git a/quicktests/overlaying/tests/basic/no_renderer.js b/quicktests/overlaying/tests/basic/no_renderer.js deleted file mode 100644 index d25fc8b0e8..0000000000 --- a/quicktests/overlaying/tests/basic/no_renderer.js +++ /dev/null @@ -1,50 +0,0 @@ -function makeData() { - "use strict"; - - return makeRandomData(8); -} - -function run(svg, data, Plottable) { - "use strict"; - - //Axis - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - - var axis_array = []; - for(var i = 0; i < 5; i++){ - axis_array.push(new Plottable.Axis.Numeric(xScale, "bottom")); - axis_array.push(new Plottable.Axis.Numeric(yScale, "left")); - } - - //rendering - var scatterPlot = new Plottable.Plot.Scatter(xScale, yScale) - .addDataset(data); - var linePlot = new Plottable.Plot.Line(xScale, yScale) - .addDataset(data); - var areaPlot = new Plottable.Plot.Area(xScale, yScale) - .addDataset(data); - var vbarPlot = new Plottable.Plot.Bar(xScale, yScale, true) - .addDataset(data); - var hbarPlot = new Plottable.Plot.Bar(xScale, yScale, false) - .addDataset(data); - - //title + legend - - var scatterTable = new Plottable.Component.Table([[axis_array[1], null], - [null, axis_array[0]]]); - var lineTable = new Plottable.Component.Table([[axis_array[3], null], - [null, axis_array[2]]]); - var areaTable = new Plottable.Component.Table([[axis_array[5], null], - [null, axis_array[4]]]); - var vbarTable = new Plottable.Component.Table([[axis_array[7], null], - [null, axis_array[6]]]); - var hbarTable = new Plottable.Component.Table([[axis_array[9], null], - [null, axis_array[8]]]); - var bigTable = new Plottable.Component.Table([[scatterTable, lineTable], - [areaTable, vbarTable], - [hbarTable, null]]); - - bigTable.renderTo(svg); - -} diff --git a/quicktests/overlaying/tests/basic/ordinal_scale_points_bar.js b/quicktests/overlaying/tests/basic/ordinal_scale_points_bar.js deleted file mode 100644 index f34ab18908..0000000000 --- a/quicktests/overlaying/tests/basic/ordinal_scale_points_bar.js +++ /dev/null @@ -1,30 +0,0 @@ -function makeData() { - "use strict"; - - var data = [{name: "1", y: 1, type: "q1"}, {name: "2", y: 2, type: "q1"}, {name: "4", y: 1, type: "q1"}]; - return data; -} - -function run(svg, data, Plottable) { - "use strict"; - - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Linear(); - var colorScale = new Plottable.Scale.Color("10"); - - var xAxis = new Plottable.Axis.Category(xScale, "bottom"); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - var clusteredBarRenderer = new Plottable.Plot.Bar(xScale, yScale, true) - .addDataset("d1", data) - .attr("x", "name", xScale) - .attr("y", "y", yScale) - .attr("fill", "type", colorScale) - .attr("type", "type") - .attr("yval", "y"); - - var center = clusteredBarRenderer.below(new Plottable.Component.Legend(colorScale)); - - new Plottable.Component.Table([ - [yAxis, center], [null, xAxis] - ]).renderTo(svg); -} diff --git a/quicktests/overlaying/tests/basic/project_width.js b/quicktests/overlaying/tests/basic/project_width.js deleted file mode 100644 index b75bfabac2..0000000000 --- a/quicktests/overlaying/tests/basic/project_width.js +++ /dev/null @@ -1,63 +0,0 @@ - -function makeData() { - "use strict"; - - return makeRandomData(50); -} - -function run(svg, data, Plottable) { - "use strict"; - - var alphabet = ["a", "b", "c", "d", "e", "f", "g", "h", "i", - "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", - "t", "u", "v", "w", "x", "y", "z"]; - - var ds = new Plottable.Dataset(); - var xScale = new Plottable.Scale.Category(); - var xAxis = new Plottable.Axis.Category(xScale, "bottom"); - - var yScale = new Plottable.Scale.Linear(); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - - var gridlines = new Plottable.Component.Gridlines(null, yScale); - var addLabel = new Plottable.Component.Label("add bar"); - var removeLabel = new Plottable.Component.Label("remove bar"); - - var widthPicker = function(){ - var availableSpace = xAxis.width(); - var numBars = ds.data().length; - var w = availableSpace * 0.7 / numBars; - return w; - }; - - var barRenderer = new Plottable.Plot.Bar(xScale, yScale, true) - .addDataset(ds) - .attr("x", "name", xScale) - .attr("y", "age", yScale) - .attr("width", widthPicker); - var chart = new Plottable.Component.Table([ - [yAxis, gridlines.above(barRenderer)], - [null, xAxis], - [addLabel, removeLabel] - ]).renderTo(svg); - - - function addBar() { - var d = ds.data(); - if(d.length < alphabet.length) { - d.push({ name: alphabet[d.length], age: data[d.length].y }); - } - ds.data(d); - barRenderer.attr("width", widthPicker); - } - - function removeBar() { - var data2 = ds.data(); - if(data2.length > 0){ data2.pop(); } - ds.data(data2); - barRenderer.attr("width", widthPicker); - } - - addLabel.registerInteraction(new Plottable.Interaction.Click().onClick(addBar)); - removeLabel.registerInteraction(new Plottable.Interaction.Click().onClick(removeBar)); -} diff --git a/quicktests/overlaying/tests/basic/scale_date.js b/quicktests/overlaying/tests/basic/scale_date.js deleted file mode 100644 index df414501fe..0000000000 --- a/quicktests/overlaying/tests/basic/scale_date.js +++ /dev/null @@ -1,28 +0,0 @@ -function makeData() { - "use strict"; - - return [makeRandomData(50), makeRandomData(50)]; -} - -function run(svg, data, Plottable) { - "use strict"; - - var dataPts = [ - {x: "05/02/2014", y: 2}, - {x: "05/03/2014", y: 3}, - {x: "05/06/2014", y: 4} - ]; - - var xScale = new Plottable.Scale.Time(); - var yScale = new Plottable.Scale.Linear(); - var linePlot = new Plottable.Plot.Line(xScale, yScale).addDataset(dataPts) - .attr("x", function (d) { return d3.time.format("%x").parse(d.x);}, xScale) - .project("y", "y", yScale); - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - - var chart = new Plottable.Component.Table([[yAxis, linePlot],[null, xAxis]]); - - chart.renderTo(svg); - -} diff --git a/quicktests/overlaying/tests/basic/stacked_area.js b/quicktests/overlaying/tests/basic/stacked_area.js deleted file mode 100644 index 4cff7cfece..0000000000 --- a/quicktests/overlaying/tests/basic/stacked_area.js +++ /dev/null @@ -1,37 +0,0 @@ -function makeData() { - "use strict"; - var data1 = [{name: "jon", y: 1, type: "q1"}, {name: "dan", y: 3, type: "q1"}, {name: "zoo", y: 1, type: "q1"}]; - var data2 = [{name: "jon", y: 2, type: "q2"}, {name: "dan", y: 4, type: "q2"}, {name: "zoo", y: 2, type: "q2"}]; - var data3 = [{name: "jon", y: 4, type: "q3"}, {name: "dan", y: 15, type: "q3"}, {name: "zoo", y: 15, type: "q3"}]; - return [data1, data2, data3]; -} - -function run(svg, data, Plottable) { - "use strict"; - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Linear(); - var colorScale = new Plottable.Scale.Color("10"); - - var xAxis = new Plottable.Axis.Category(xScale, "bottom"); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - - // if (!Plottable.Plot.Area) { - // return; - // } - var stackedAreaPlot = new Plottable.Plot.StackedArea(xScale, yScale) - .project("x", "name", xScale) - .project("y", "y", yScale) - .project("fill", "type", colorScale) - .addDataset("d1", data[0]) - .addDataset("d2", data[1]) - .addDataset("d3", data[2]) - .animate(true); - - var legend = new Plottable.Component.Legend(colorScale); - legend.maxEntriesPerRow(1); - var center = stackedAreaPlot.below(legend); - - new Plottable.Component.Table([ - [yAxis, center], [null, xAxis] - ]).renderTo(svg); -} diff --git a/quicktests/overlaying/tests/basic/stacked_bar.js b/quicktests/overlaying/tests/basic/stacked_bar.js deleted file mode 100644 index f75ce6f512..0000000000 --- a/quicktests/overlaying/tests/basic/stacked_bar.js +++ /dev/null @@ -1,38 +0,0 @@ -function makeData() { - "use strict"; - - var data1 = [{name: "jon", y: 0.5, type: "q1"}, {name: "dan", y: 2, type: "q1"}, {name: "zoo", y: 1, type: "q1"}]; - var data2 = [{name: "jon", y: 2, type: "q2"}, {name: "dan", y: 4, type: "q2"}, {name: "zoo", y: 2, type: "q2"}]; - var data3 = [{name: "jon", y: 4, type: "q3"}, {name: "dan", y: 15, type: "q3"}, {name: "zoo", y: 15, type: "q3"}]; - return [data1, data2, data3]; -} - -function run(svg, data, Plottable) { - "use strict"; - - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Linear(); - var colorScale = new Plottable.Scale.Color("10"); - - var xAxis = new Plottable.Axis.Category(xScale, "bottom"); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - var stackedBarPlot = new Plottable.Plot.StackedBar(xScale, yScale) - .attr("x", "name", xScale) - .attr("y", "y", yScale) - .attr("fill", "type", colorScale) - .attr("type", "type") - .attr("yval", "y") - .addDataset("d1", data[0]) - .addDataset("d2", data[1]) - .addDataset("d3", data[2]) - .animate(true) - .barLabelsEnabled(true); - - var legend = new Plottable.Component.Legend(colorScale); - legend.maxEntriesPerRow(1); - var center = stackedBarPlot.below(legend); - - var horizChart = new Plottable.Component.Table([ - [yAxis, center], [null, xAxis] - ]).renderTo(svg); -} diff --git a/quicktests/overlaying/tests/color/colorProjection.js b/quicktests/overlaying/tests/color/colorProjection.js deleted file mode 100644 index abe4765c09..0000000000 --- a/quicktests/overlaying/tests/color/colorProjection.js +++ /dev/null @@ -1,62 +0,0 @@ -function makeData() { - "use strict"; - - return makeRandomData(6); -} - -function run(svg, data, Plottable) { - "use strict"; - - var DotData = [ - { "x" : 1, - "y" : 4, - "r" : 0x99, - "g" : 0xBB, - "b" : 0x44 - }, - { "x" : 2, - "y" : 3, - "r" : 0x99, - "g" : 0x99, - "b" : 0xFD - }, - { "x" : 3, - "y" : 1, - "r" : 0xAA, - "g" : 0x00, - "b" : 0x33 - }, - { "x" : 4, - "y" : 5, - "r" : 0xB3, - "g" : 0x55, - "b" : 0xF3 - }, - { "x" : 5, - "y" : 3, - "r" : 0xDA, - "g" : 0xDB, - "b" : 0x00 - } - ]; - - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - - var scatterPlot = new Plottable.Plot.Scatter(xScale, yScale).addDataset(DotData) - .project("x", "x", xScale) - .project("y", "y", yScale) - .project("size", function(d){return 30;}) - .project("fill", function(d){return "#" + (d.r * 65536 + d.g * 256 + d.b).toString(16);}) - .project("stroke", function(d){return "#" + (16777216 - d.r * 65536 - d.g * 256 - d.b).toString(16);}) - .project("stroke-width", function(){ return 5; }); - - - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - - var chart = new Plottable.Component.Table([[yAxis, ScatterPlot], - [null, xAxis]]); - chart.renderTo(svg); - -} diff --git a/quicktests/overlaying/tests/color/colorScale.js b/quicktests/overlaying/tests/color/colorScale.js deleted file mode 100644 index b4e86e5313..0000000000 --- a/quicktests/overlaying/tests/color/colorScale.js +++ /dev/null @@ -1,34 +0,0 @@ -function makeData() { - "use strict"; - - return makeRandomData(6); -} - -function run(svg, data, Plottable) { - "use strict"; - - data = [ - {x: "A", y: 1, val: 0.1}, - {x: "B", y: 1, val: 0.2}, - {x: "C", y: 1, val: 0.3}, - {x: "D", y: 1, val: 0.4}, - {x: "E", y: 1, val: 0.5}, - {x: "A", y: 2, val: 0.6}, - {x: "B", y: 2, val: 0.7}, - {x: "C", y: 2, val: 0.8}, - {x: "D", y: 2, val: 0.9}, - {x: "E", y: 2, val: 1.0}, - ]; - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Category(); - - var colorScale = new Plottable.Scale.Color(); - colorScale.range(["blue", "teal", "purple"]); - var plot = new Plottable.Plot.Grid(xScale, yScale, colorScale); - plot.addDataset(data); - plot.project("x", "x", xScale).project("y", "y", yScale); - plot.project("fill", function(d) { return d.val; }, colorScale); - - plot.renderTo(svg); - -} diff --git a/quicktests/overlaying/tests/color/defaultColor.js b/quicktests/overlaying/tests/color/defaultColor.js deleted file mode 100644 index c0809d18ee..0000000000 --- a/quicktests/overlaying/tests/color/defaultColor.js +++ /dev/null @@ -1,33 +0,0 @@ -function makeData() { - "use strict"; - - return makeRandomData(6); -} - -function run(svg, data, Plottable) { - "use strict"; - - data = [ - {x: "A", y: 1, val: 0.1}, - {x: "B", y: 1, val: 0.2}, - {x: "C", y: 1, val: 0.3}, - {x: "D", y: 1, val: 0.4}, - {x: "E", y: 1, val: 0.5}, - {x: "A", y: 2, val: 0.6}, - {x: "B", y: 2, val: 0.7}, - {x: "C", y: 2, val: 0.8}, - {x: "D", y: 2, val: 0.9}, - {x: "E", y: 2, val: 1.0}, - ]; - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Category(); - - var colorScale = new Plottable.Scale.Color(); - var plot = new Plottable.Plot.Grid(xScale, yScale, colorScale); - plot.addDataset(data); - plot.project("x", "x", xScale).project("y", "y", yScale); - plot.project("fill", function(d) { return d.val; }, colorScale); - - plot.renderTo(svg); - -} diff --git a/quicktests/overlaying/tests/color/interpolatedBar.js b/quicktests/overlaying/tests/color/interpolatedBar.js deleted file mode 100644 index afa6ce1f44..0000000000 --- a/quicktests/overlaying/tests/color/interpolatedBar.js +++ /dev/null @@ -1,35 +0,0 @@ -function makeData() { - "use strict"; - - return makeRandomData(6); -} - -function run(svg, data, Plottable) { - "use strict"; - - data = [ - {x: "A", y: 1, val: 0.1}, - {x: "B", y: 1, val: 0.2}, - {x: "C", y: 1, val: 0.3}, - {x: "D", y: 1, val: 0.4}, - {x: "E", y: 1, val: 0.5}, - {x: "A", y: 1, val: 0.6}, - {x: "B", y: 1, val: 0.7}, - {x: "C", y: 1, val: 0.8}, - {x: "D", y: 1, val: 0.9}, - {x: "E", y: 1, val: 1.0}, - ]; - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Linear(); - - var colorScale = new Plottable.Scale.InterpolatedColor(); - colorScale.colorRange(["#76DE5D","#A12A23"]); - var plot = new Plottable.Plot.Bar(xScale, yScale, colorScale); - plot.addDataset(data); - plot.project("x", "val", xScale).project("y", "y", yScale); - plot.project("width", function(){ return 30;}); - plot.project("fill", function(d) { return d.val; }, colorScale); - - plot.renderTo(svg); - -} diff --git a/quicktests/overlaying/tests/color/interpolatedColor.js b/quicktests/overlaying/tests/color/interpolatedColor.js deleted file mode 100644 index a8b898d83a..0000000000 --- a/quicktests/overlaying/tests/color/interpolatedColor.js +++ /dev/null @@ -1,34 +0,0 @@ -function makeData() { - "use strict"; - - return makeRandomData(6); -} - -function run(svg, data, Plottable) { - "use strict"; - - data = [ - {x: "A", y: 1, val: 0.1}, - {x: "B", y: 1, val: 0.2}, - {x: "C", y: 1, val: 0.3}, - {x: "D", y: 1, val: 0.4}, - {x: "E", y: 1, val: 0.5}, - {x: "A", y: 2, val: 0.6}, - {x: "B", y: 2, val: 0.7}, - {x: "C", y: 2, val: 0.8}, - {x: "D", y: 2, val: 0.9}, - {x: "E", y: 2, val: 1.0}, - ]; - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Category(); - - var colorScale = new Plottable.Scale.InterpolatedColor(); - colorScale.colorRange(["#234567", "#76DE5D","#A12A23"]); - var plot = new Plottable.Plot.Grid(xScale, yScale, colorScale); - plot.addDataset(data); - plot.project("x", "x", xScale).project("y", "y", yScale); - plot.project("fill", function(d) { return d.val; }, colorScale); - - plot.renderTo(svg); - -} diff --git a/quicktests/overlaying/tests/color/interpolatedScatter.js b/quicktests/overlaying/tests/color/interpolatedScatter.js deleted file mode 100644 index ba5d65eca0..0000000000 --- a/quicktests/overlaying/tests/color/interpolatedScatter.js +++ /dev/null @@ -1,34 +0,0 @@ -function makeData() { - "use strict"; - - return makeRandomData(6); -} - -function run(svg, data, Plottable) { - "use strict"; - - data = [ - {x: "A", y: 1, val: 0.1}, - {x: "B", y: 1, val: 0.2}, - {x: "C", y: 1, val: 0.3}, - {x: "D", y: 1, val: 0.4}, - {x: "E", y: 1, val: 0.5}, - {x: "A", y: 2, val: 0.6}, - {x: "B", y: 2, val: 0.7}, - {x: "C", y: 2, val: 0.8}, - {x: "D", y: 2, val: 0.9}, - {x: "E", y: 2, val: 1.0}, - ]; - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Category(); - - var colorScale = new Plottable.Scale.InterpolatedColor(); - colorScale.colorRange(["#76DE5D","#A12A23"]); - var plot = new Plottable.Plot.Scatter(xScale, yScale).addDataset(data) - .project("x", "x", xScale).project("y", "y", yScale) - .project("size", function(){ return 20;}) - .project("fill", function(d) { return d.val; }, colorScale); - - plot.renderTo(svg); - -} diff --git a/quicktests/overlaying/tests/functional/base_animator.js b/quicktests/overlaying/tests/functional/base_animator.js index bdb3f478e4..51ad6c17db 100644 --- a/quicktests/overlaying/tests/functional/base_animator.js +++ b/quicktests/overlaying/tests/functional/base_animator.js @@ -2,7 +2,7 @@ function makeData() { "use strict"; - var data1 = [{x: 0, y: 0}, {x: 1, y: 1}, {x: 2, y: 1}, {x: 3, y: 2}, {x: 4, y: 3}, {x: 5, y: 4}, {x: 6, y: 5}, {x: 7, y: -2}]; + var data1 = [{x: "0", y: 0}, {x: "1", y: 1}, {x: "2", y: 1}, {x: "3", y: 2}, {x: "4", y: 3}, {x: "5", y: 4}, {x: "6", y: 5}, {x: "7", y: -2}]; return data1; } @@ -10,31 +10,31 @@ function makeData() { function run(svg, data, Plottable) { "use strict"; - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Linear(); - var colorScale = new Plottable.Scale.Color(); + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Linear(); + var colorScale = new Plottable.Scales.Color(); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - var xAxis = new Plottable.Axis.Category(xScale, "bottom"); - var animator = new Plottable.Animator.Base(); + var xAxis = new Plottable.Axes.Category(xScale, "bottom"); + var yAxis = new Plottable.Axes.Numeric(yScale, "left"); + var animator = new Plottable.Animators.Base(); animator.duration(1000); animator.maxTotalDuration(2000); animator.maxIterativeDelay(100); - var vbar = new Plottable.Plot.Bar(xScale, yScale) - .project("x", "x", xScale) - .project("y", "y", yScale) - .project("fill", "type", colorScale) - .barLabelsEnabled(true) - .barLabelFormatter(function(text){return text + "!";}) - .addDataset(data) + var vbar = new Plottable.Plots.Bar(xScale, yScale) + .x(function(d) { return d.x; }, xScale) + .y(function(d) { return d.y; }, yScale) + .attr("fill", function(d) { return d.type; }, colorScale) + .labelsEnabled(true) + .labelFormatter(function(text){return text + "!";}) + .addDataset(new Plottable.Dataset(data)) .animator( "bars", animator) .animate(true); - var chart = new Plottable.Component.Table([ + var chart = new Plottable.Components.Table([ [yAxis, vbar], [null, xAxis] ]); @@ -43,9 +43,9 @@ function run(svg, data, Plottable) { var cb = function(){ vbar.datasets()[0].data(data); }; - var click = new Plottable.Interaction.Click().onClick(cb); + var click = new Plottable.Interactions.Click().onClick(cb); - vbar.registerInteraction(click); + click.attachTo(vbar); chart.renderTo(svg); } diff --git a/quicktests/overlaying/tests/functional/categoryFormatter.js b/quicktests/overlaying/tests/functional/categoryFormatter.js index 9e348ae8cb..5a856da068 100644 --- a/quicktests/overlaying/tests/functional/categoryFormatter.js +++ b/quicktests/overlaying/tests/functional/categoryFormatter.js @@ -7,18 +7,18 @@ function makeData() { function run(svg, data, Plottable) { "use strict"; - data = [{x: 0, y: 1}, {x: 1, y: 2}, {x: 2, y: 4}, {x: 3, y: 6}, {x: 4, y: 5}, {x: 5, y: 3}, {x: 6, y: 0.5}]; + data = [{x: "0", y: 1}, {x: "1", y: 2}, {x: "2", y: 4}, {x: "3", y: 6}, {x: "4", y: 5}, {x: "5", y: 3}, {x: "6", y: 0.5}]; var DOW = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; var Emp = ["Justin", "Cassie", "Brandon", "Roger", "Dan", "Lewin", "Brian"]; - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Linear(); - var xAxis = new Plottable.Axis.Category(xScale, "bottom"); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Linear(); + var xAxis = new Plottable.Axes.Category(xScale, "bottom"); + var yAxis = new Plottable.Axes.Numeric(yScale, "left"); - var IdTitle = new Plottable.Component.Label("Identity"); - var DowTitle = new Plottable.Component.Label("Day of Week"); - var EmpIDTitle = new Plottable.Component.Label("Emp ID"); + var IdTitle = new Plottable.Components.Label("Identity"); + var DowTitle = new Plottable.Components.Label("Day of Week"); + var EmpIDTitle = new Plottable.Components.Label("Emp ID"); var DOWFormatter = function(d) { return DOW[d%7]; @@ -27,12 +27,12 @@ function run(svg, data, Plottable) { return Emp[d%7]; }; - var plot = new Plottable.Plot.Bar(xScale, yScale).addDataset(data); - plot.project("x", "x", xScale).project("y", "y", yScale); - var basicTable = new Plottable.Component.Table([[yAxis, plot], [null, xAxis]]); - var formatChoices = new Plottable.Component.Table([[IdTitle],[DowTitle],[EmpIDTitle]]); - var bigTable = new Plottable.Component.Table([[basicTable],[formatChoices]]); - formatChoices.xAlign("center"); + var plot = new Plottable.Plots.Bar(xScale, yScale).addDataset(new Plottable.Dataset(data)); + plot.x(function(d) { return d.x; }, xScale).y(function(d) { return d.y; }, yScale); + var basicTable = new Plottable.Components.Table([[yAxis, plot], [null, xAxis]]); + var formatChoices = new Plottable.Components.Table([[IdTitle],[DowTitle],[EmpIDTitle]]); + var bigTable = new Plottable.Components.Table([[basicTable],[formatChoices]]); + formatChoices.xAlignment("center"); bigTable.renderTo(svg); @@ -46,8 +46,8 @@ function run(svg, data, Plottable) { xAxis.formatter(EmpIDFormatter); } - IdTitle.registerInteraction(new Plottable.Interaction.Click().onClick(identity_frmt)); - DowTitle.registerInteraction(new Plottable.Interaction.Click().onClick(dow_frmt)); - EmpIDTitle.registerInteraction(new Plottable.Interaction.Click().onClick(emp_frmt)); + new Plottable.Interactions.Click().onClick(identity_frmt).attachTo(IdTitle); + new Plottable.Interactions.Click().onClick(dow_frmt).attachTo(DowTitle); + new Plottable.Interactions.Click().onClick(emp_frmt).attachTo(EmpIDTitle); } diff --git a/quicktests/overlaying/tests/functional/category_axis_rotated_ticks.js b/quicktests/overlaying/tests/functional/category_axis_rotated_ticks.js index 66de88d77e..ad319567a8 100644 --- a/quicktests/overlaying/tests/functional/category_axis_rotated_ticks.js +++ b/quicktests/overlaying/tests/functional/category_axis_rotated_ticks.js @@ -10,60 +10,62 @@ function makeData() { function run(svg, data, Plottable) { "use strict"; - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Linear(); - var colorScale = new Plottable.Scale.Color(); + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Linear(); + var colorScale = new Plottable.Scales.Color(); - var yAxis1 = new Plottable.Axis.Numeric(yScale, "left"); - var xAxis1 = new Plottable.Axis.Category(xScale, "bottom"); + var yAxis1 = new Plottable.Axes.Numeric(yScale, "left"); + var xAxis1 = new Plottable.Axes.Category(xScale, "bottom"); xAxis1.tickLabelAngle(-90); - - var yAxis2 = new Plottable.Axis.Numeric(yScale, "left"); - var xAxis2 = new Plottable.Axis.Category(xScale, "bottom"); + + var yAxis2 = new Plottable.Axes.Numeric(yScale, "left"); + var xAxis2 = new Plottable.Axes.Category(xScale, "bottom"); xAxis2.tickLabelAngle(90); - - var yAxis3 = new Plottable.Axis.Numeric(yScale, "left"); - var xAxis3 = new Plottable.Axis.Category(xScale, "bottom"); + + var yAxis3 = new Plottable.Axes.Numeric(yScale, "left"); + var xAxis3 = new Plottable.Axes.Category(xScale, "bottom"); xAxis3.tickLabelAngle(0); - - var plot1 = new Plottable.Plot.Scatter( xScale, yScale) - .project("x", "x", xScale) - .project("y", "y", yScale) - .project("fill", "type", colorScale) - .addDataset(data); - - var plot2 = new Plottable.Plot.Scatter( xScale, yScale) - .project("x", "x", xScale) - .project("y", "y", yScale) - .project("fill", "type", colorScale) - .addDataset(data); - - var plot3 = new Plottable.Plot.Scatter( xScale, yScale) - .project("x", "x", xScale) - .project("y", "y", yScale) - .project("fill", "type", colorScale) - .addDataset(data); - - var chart1 = new Plottable.Component.Table([ + + var dataset = new Plottable.Dataset(data); + + var plot1 = new Plottable.Plots.Scatter( xScale, yScale) + .x(function(d) { return d.x; }, xScale) + .y(function(d) { return d.y; }, yScale) + .attr("fill", function(d) { return d.type; }, colorScale) + .addDataset(dataset); + + var plot2 = new Plottable.Plots.Scatter( xScale, yScale) + .x(function(d) { return d.x; }, xScale) + .y(function(d) { return d.y; }, yScale) + .attr("fill", function(d) { return d.type; }, colorScale) + .addDataset(dataset); + + var plot3 = new Plottable.Plots.Scatter( xScale, yScale) + .x(function(d) { return d.x; }, xScale) + .y(function(d) { return d.y; }, yScale) + .attr("fill", function(d) { return d.type; }, colorScale) + .addDataset(dataset); + + var chart1 = new Plottable.Components.Table([ [yAxis1, plot1], [null, xAxis1] ]); - - var chart2 = new Plottable.Component.Table([ + + var chart2 = new Plottable.Components.Table([ [yAxis2, plot2], [null, xAxis2] - ]); - - var chart3 = new Plottable.Component.Table([ + ]); + + var chart3 = new Plottable.Components.Table([ [yAxis3, plot3], [null, xAxis3] ]); - - var chart = new Plottable.Component.Table([ + + var chart = new Plottable.Components.Table([ [chart1], [chart2], [chart3] ]); - + chart.renderTo(svg); } diff --git a/quicktests/overlaying/tests/functional/changeData_onClick.js b/quicktests/overlaying/tests/functional/changeData_onClick.js deleted file mode 100644 index 386ea707b9..0000000000 --- a/quicktests/overlaying/tests/functional/changeData_onClick.js +++ /dev/null @@ -1,59 +0,0 @@ -function makeData() { - "use strict"; - - return [makeRandomData(50), makeRandomData(50)]; -} - -//test for update on data change - -function run(svg, data, Plottable) { - "use strict"; - - var numPts = 5; - - var dataseries1 = new Plottable.Dataset(data[0].slice(0, 5)); - - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - - - var barPlot = new Plottable.Plot.Bar(xScale, yScale, true) - .addDataset(dataseries1) - .animate(true) - .project("x", "x", xScale) - .project("y", "y", yScale); - - var scatterPlot = new Plottable.Plot.Scatter(xScale, yScale) - .addDataset(dataseries1) - .attr("fill", function() { return "purple"; }) - .animate(true) - .project("x", "x", xScale) - .project("y", "y", yScale); - - var renderGroup = barPlot.below(scatterPlot); - - var basicTable = new Plottable.Component.Table() - .addComponent(2, 0, yAxis) - .addComponent(2, 1, renderGroup) - .addComponent(3, 1, xAxis); - - basicTable.renderTo(svg); - - - var cb = function(x, y){ - if(numPts === 5){ - dataseries1.data(data[1].slice(0, 10)); - numPts = 10; - } else { - dataseries1.data(data[0].slice(0, 5)); - numPts = 5; - } - }; - - renderGroup.registerInteraction( - new Plottable.Interaction.Click().callback(cb) - ); - -} diff --git a/quicktests/overlaying/tests/functional/domainChange.js b/quicktests/overlaying/tests/functional/domainChange.js deleted file mode 100644 index fec9687545..0000000000 --- a/quicktests/overlaying/tests/functional/domainChange.js +++ /dev/null @@ -1,69 +0,0 @@ - -function makeData() { - "use strict"; - - return [makeRandomData(50), makeRandomData(50)]; - -} - -function run(svg, data, Plottable) { - "use strict"; - - var boringData = function () { - return [{x: 0, y: 0}, {x: 0, y: 2}, {x: 1, y: 2}, {x: 1, y: 4}, {x: 2, y: 4}, {x: 2, y: 6}, {x: 30, y: 70}]; - }; - - var dataseries = new Plottable.Dataset(boringData()); - - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - xScale.domain([0, 3]); - yScale.domain([0, 8]); - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - var xAxis2 = new Plottable.Axis.Numeric(xScale, "bottom"); - var yAxis2 = new Plottable.Axis.Numeric(yScale, "left"); - - //rendering - var linePlot = new Plottable.Plot.Line(xScale, yScale).addDataset(dataseries); - linePlot.project("x", "x", xScale).project("y", "y", yScale); - var scatterPlot = new Plottable.Plot.Scatter(xScale, yScale).addDataset(dataseries); - scatterPlot.project("x", "x", xScale).project("y", "y", yScale); - - var autoXLabel = new Plottable.Component.Label("autodomain X"); - var focusXLabel = new Plottable.Component.Label("focus X"); - var autoYLabel = new Plottable.Component.Label("autodomain Y"); - var focusYLabel = new Plottable.Component.Label("focus Y"); - - var labelTable = new Plottable.Component.Table([ - [autoXLabel, focusXLabel], - [autoYLabel, focusYLabel] - ]); - - var basicTable = new Plottable.Component.Table([ - [yAxis, yAxis2, linePlot.below(scatterPlot)], - [null, null, xAxis], - [null, null, xAxis2], - [null, null, labelTable] - ]); - basicTable.renderTo(svg); - - function xAuto(){ - xScale.autoDomain(); - } - function yAuto(){ - yScale.autoDomain(); - } - function xFocus(){ - xScale.domain([0, 3]); - } - function yFocus(){ - yScale.domain([0, 8]); - } - - autoXLabel.registerInteraction(new Plottable.Interaction.Click().onClick(xAuto)); - autoYLabel.registerInteraction(new Plottable.Interaction.Click().onClick(yAuto)); - focusXLabel.registerInteraction(new Plottable.Interaction.Click().onClick(xFocus)); - focusYLabel.registerInteraction(new Plottable.Interaction.Click().onClick(yFocus)); - -} diff --git a/quicktests/overlaying/tests/functional/doubleAxes.js b/quicktests/overlaying/tests/functional/doubleAxes.js index 7a7800a99a..1cb805df3f 100644 --- a/quicktests/overlaying/tests/functional/doubleAxes.js +++ b/quicktests/overlaying/tests/functional/doubleAxes.js @@ -7,32 +7,35 @@ function makeData() { function run(svg, data, Plottable) { "use strict"; - var d1 = [{x: "200", y: 1}, {x: "250", y: 2}, {x: "400", y: 3}]; - var d2 = [{x: "200", y: 4}, {x: "300", y: 2}, {x: "400", y: 1}]; + var ds1 = new Plottable.Dataset([{x: "200", y: 1}, {x: "250", y: 2}, {x: "400", y: 3}]); + var ds2 = new Plottable.Dataset([{x: "200", y: 4}, {x: "300", y: 2}, {x: "400", y: 1}]); - var xScale1 = new Plottable.Scale.Category(); - var xScale2 = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Linear(); + var xScale1 = new Plottable.Scales.Category(); + var xScale2 = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Linear(); - var plot1 = new Plottable.Plot.Area(xScale1, yScale); - plot1.addDataset(d1); - plot1.project("x", "x", xScale1).project("y", "y", yScale); + var plot1 = new Plottable.Plots.Area(xScale1, yScale); + plot1.addDataset(ds1); + plot1.x(function(d) { return d.x; }, xScale1).y(function(d) { return d.y; }, yScale); - var plot2 = new Plottable.Plot.Line(xScale2, yScale); - plot2.addDataset(d2); - plot2.project("x", "x", xScale2).project("y", "y", yScale); + var plot2 = new Plottable.Plots.Line(xScale2, yScale); + plot2.addDataset(ds2); + plot2.x(function(d) { return d.x; }, xScale2).y(function(d) { return d.y; }, yScale); + var plots = new Plottable.Components.Group([plot1, plot2]); - var xAxis1 = new Plottable.Axis.Category(xScale1, "bottom"); - var xAxis2 = new Plottable.Axis.Category(xScale2, "bottom"); + var xAxis1 = new Plottable.Axes.Category(xScale1, "bottom"); + var xAxis2 = new Plottable.Axes.Category(xScale2, "bottom"); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - var xAxes = new Plottable.Component.Table([[xAxis1], - [new Plottable.Component.Label("")], + var yAxis = new Plottable.Axes.Numeric(yScale, "left"); + var xAxes = new Plottable.Components.Table([[xAxis1], + [new Plottable.Components.Label("")], [xAxis2], - [new Plottable.Component.Label("")]]); + [new Plottable.Components.Label("")]]); - var chart = new Plottable.Component.Table([[yAxis, plot1.below(plot2)], - [null, xAxes]]); + var chart = new Plottable.Components.Table([ + [yAxis, plots], + [null, xAxes] + ]); chart.renderTo(svg); diff --git a/quicktests/overlaying/tests/functional/formatter.js b/quicktests/overlaying/tests/functional/formatter.js index 91a6e09bcc..b4bc9d5d92 100644 --- a/quicktests/overlaying/tests/functional/formatter.js +++ b/quicktests/overlaying/tests/functional/formatter.js @@ -19,27 +19,27 @@ function run(svg, data, Plottable) { //Axis - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); + var xAxis = new Plottable.Axes.Numeric(xScale, "bottom"); + var yAxis = new Plottable.Axes.Numeric(yScale, "left"); - var IdTitle = new Plottable.Component.Label("Identity"); - var GenTitle = new Plottable.Component.Label("General"); - var FixTitle = new Plottable.Component.Label("Fixed"); - var CurrTitle = new Plottable.Component.Label("Currency"); - var PerTitle = new Plottable.Component.Label("Percentage"); - var SITitle = new Plottable.Component.Label("SI"); - var CustTitle = new Plottable.Component.Label("Custom"); + var IdTitle = new Plottable.Components.Label("Identity"); + var GenTitle = new Plottable.Components.Label("General"); + var FixTitle = new Plottable.Components.Label("Fixed"); + var CurrTitle = new Plottable.Components.Label("Currency"); + var PerTitle = new Plottable.Components.Label("Percentage"); + var SITitle = new Plottable.Components.Label("SI"); + var CustTitle = new Plottable.Components.Label("Custom"); var custFormatter = function(d) { return "= ' w ' ="; }; - var plot = new Plottable.Plot.Line(xScale, yScale).addDataset(dataseries1); - plot.project("x", "x", xScale).project("y", "y", yScale); - var basicTable = new Plottable.Component.Table([[yAxis, plot], [null, xAxis]]); - var formatChoices = new Plottable.Component.Table([[IdTitle, GenTitle, FixTitle],[CurrTitle, null, PerTitle], [SITitle, null, CustTitle]]); - var bigTable = new Plottable.Component.Table([[basicTable],[formatChoices]]); - formatChoices.xAlign("center"); + var plot = new Plottable.Plots.Line(xScale, yScale).addDataset(dataseries1); + plot.x(function(d) { return d.x; }, xScale).y(function(d) { return d.y; }, yScale); + var basicTable = new Plottable.Components.Table([[yAxis, plot], [null, xAxis]]); + var formatChoices = new Plottable.Components.Table([[IdTitle, GenTitle, FixTitle],[CurrTitle, null, PerTitle], [SITitle, null, CustTitle]]); + var bigTable = new Plottable.Components.Table([[basicTable],[formatChoices]]); + formatChoices.xAlignment("center"); bigTable.renderTo(svg); @@ -73,12 +73,12 @@ function run(svg, data, Plottable) { yAxis.formatter(custFormatter); } - IdTitle.registerInteraction(new Plottable.Interaction.Click().onClick(identity_frmt)); - GenTitle.registerInteraction(new Plottable.Interaction.Click().onClick(general_frmt)); - FixTitle.registerInteraction(new Plottable.Interaction.Click().onClick(fixed_frmt)); - CurrTitle.registerInteraction(new Plottable.Interaction.Click().onClick(currency_frmt)); - PerTitle.registerInteraction(new Plottable.Interaction.Click().onClick(percentage_frmt)); - SITitle.registerInteraction(new Plottable.Interaction.Click().onClick(SI_frmt)); - CustTitle.registerInteraction(new Plottable.Interaction.Click().onClick(custom_frmt)); + new Plottable.Interactions.Click().onClick(identity_frmt).attachTo(IdTitle); + new Plottable.Interactions.Click().onClick(general_frmt).attachTo(GenTitle); + new Plottable.Interactions.Click().onClick(fixed_frmt).attachTo(FixTitle); + new Plottable.Interactions.Click().onClick(currency_frmt).attachTo(CurrTitle); + new Plottable.Interactions.Click().onClick(percentage_frmt).attachTo(PerTitle); + new Plottable.Interactions.Click().onClick(SI_frmt).attachTo(SITitle); + new Plottable.Interactions.Click().onClick(custom_frmt).attachTo(CustTitle); } diff --git a/quicktests/overlaying/tests/functional/render_behavior.js b/quicktests/overlaying/tests/functional/render_behavior.js deleted file mode 100644 index b378bec205..0000000000 --- a/quicktests/overlaying/tests/functional/render_behavior.js +++ /dev/null @@ -1,61 +0,0 @@ -//broken - -function makeData() { - "use strict"; - return [makeRandomData(50), makeRandomData(50)]; -} - -function run(svg, data, Plottable) { - "use strict"; - - var d = data[0].slice(10, 15); - var dataseries = new Plottable.Dataset(d); - var i = 0; - - //Axis - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - - var areaPlot = new Plottable.Plot.Area(xScale, yScale).addDataset(dataseries).animate("true"); - areaPlot.project("x", "x", xScale).project("y", "y", yScale); - - var label1 = new Plottable.Component.Label("dataset.data()", "horizontal"); - var label2 = new Plottable.Component.Label("change width + resize", "horizontal"); - var label3 = new Plottable.Component.Label("remove + renderTo", "horizontal"); - var label4 = new Plottable.Component.Label("_render()", "horizontal"); - - var basicTable = new Plottable.Component.Table([[yAxis, areaPlot], - [null, xAxis], - [null, label1], - [null, label2], - [null, label3], - [null, label4]]); - - basicTable.renderTo(svg); - - function newData(){ - var d = data[i%2].slice(0,5); - dataseries.data(d); - i++; - } - function changeWidth() { - svg.attr("width", 300 + (i%5)*40); - basicTable.resize(); - i++; - } - - function removeAndRenderTo() { - basicTable.detach(); - basicTable.renderTo(svg); - } - - label1.registerInteraction(new Plottable.Interaction.Click().onClick(newData)); - - label2.registerInteraction(new Plottable.Interaction.Click().onClick(changeWidth)); - - label3.registerInteraction(new Plottable.Interaction.Click().onClick(removeAndRenderTo)); - - label4.registerInteraction(new Plottable.Interaction.Click().onClick(function(){basicTable._render();})); -} diff --git a/quicktests/overlaying/tests/functional/time_tier_config.js b/quicktests/overlaying/tests/functional/time_tier_config.js deleted file mode 100644 index b8e9081e95..0000000000 --- a/quicktests/overlaying/tests/functional/time_tier_config.js +++ /dev/null @@ -1,56 +0,0 @@ - -function makeData() { - "use strict"; - - var data1 = [{x: "11/11/14/05", name: "jon", y: -2, type: "q1"}, {x: "11/13/14/13", name: "dan", y: 10.5, type: "q1"}, {x: "11/15/14/14", name: "zoo", y: 1, type: "q1"}]; - - return data1; -} - -function run(svg, data, Plottable) { - "use strict"; - - var xScale = new Plottable.Scale.Time(); - var yScale = new Plottable.Scale.Linear(); - var colorScale = new Plottable.Scale.Color(); - - - var axisconf = [ - {interval: d3.time.hour, step: 1, formatter: d3.time.format(" %I o'clock")}, - {interval: d3.time.day, step: 1, formatter: d3.time.format("%d")}]; - var axisconf2 = [ - {interval: d3.time.day, step: 2, formatter: d3.time.format("%e")}, - {interval: d3.time.month, step: 1, formatter: d3.time.format("%b")}]; - var axisconf3 = [ - {interval: d3.time.month, step: 1, formatter: d3.time.format("%b")}, - {interval: d3.time.year, step: 1, formatter: d3.time.format("%y")}]; - var axisconf4 = [ - {interval: d3.time.year, step: 1, formatter: d3.time.format("%Y")}]; - var axisconf5 = {tierConfigurations: []}; - - - var xAxis1 = new Plottable.Axis.Time(xScale, "bottom").axisConfigurations([axisconf, axisconf2, axisconf3, axisconf4, axisconf5]); - var xAxis2 = new Plottable.Axis.Time(xScale, "top").axisConfigurations([axisconf, axisconf2, axisconf3, axisconf4, axisconf5]); - - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - - - var plot = new Plottable.Plot.Line(xScale, yScale) - .animate(true) - .project("x",function (d) { return d3.time.format("%m/%d/%y/%H").parse(d.x);}, xScale) - .project("y", "y", yScale) - .project("fill", "type", colorScale) - .addDataset("d1", data); - - - - var chart = new Plottable.Component.Table([ - [null, xAxis2], - [yAxis, plot], - [null, xAxis1] - ]); - chart.renderTo(svg); - - var pzi = new Plottable.Interaction.PanZoom(xScale, yScale); - plot.registerInteraction(pzi); -} \ No newline at end of file diff --git a/quicktests/overlaying/tests/functional/titleLegend_change.js b/quicktests/overlaying/tests/functional/titleLegend_change.js index 2207bf53aa..9d10e493ca 100644 --- a/quicktests/overlaying/tests/functional/titleLegend_change.js +++ b/quicktests/overlaying/tests/functional/titleLegend_change.js @@ -20,45 +20,44 @@ function run(svg, data, Plottable) { var dataseries6 = new Plottable.Dataset(data[1].slice(20, 30)); dataseries6.metadata({name: "grapes"}); - var colorScale1 = new Plottable.Scale.Color(); + var colorScale1 = new Plottable.Scales.Color(); colorScale1.domain(["series1", "series2", "apples", "oranges", "bananas", "grapes"]); //Axis var domainer_X = new Plottable.Domainer().addPaddingException(0); var domainer_Y = new Plottable.Domainer().addPaddingException(0); - var xScale = new Plottable.Scale.Linear().domainer(domainer_X); - var yScale = new Plottable.Scale.Linear().domainer(domainer_Y); - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); + var xScale = new Plottable.Scales.Linear().domainer(domainer_X); + var yScale = new Plottable.Scales.Linear().domainer(domainer_Y); + var xAxis = new Plottable.Axes.Numeric(xScale, "bottom"); + var yAxis = new Plottable.Axes.Numeric(yScale, "left"); - // metadata is broken - var colorProjector = function(d, i, m) { - return colorScale1.scale(m.name); + var colorProjector = function(d, i, dataset) { + return dataset.metadata().name; }; //rendering - var scatterPlot = new Plottable.Plot.Scatter(xScale, yScale).addDataset(dataseries1); - scatterPlot.project("x", "x", xScale).project("y", "y", yScale); - var linePlot = new Plottable.Plot.Line(xScale, yScale).addDataset(dataseries2); - linePlot.project("x", "x", xScale).project("y", "y", yScale); - var renderApple = new Plottable.Plot.Area(xScale, yScale).addDataset(dataseries3); - renderApple.project("x", "x", xScale).project("y", "y", yScale); - var renderBanana = new Plottable.Plot.Line(xScale, yScale).addDataset(dataseries4); - renderBanana.project("x", "x", xScale).project("y", "y", yScale); - var renderOrange = new Plottable.Plot.Scatter(xScale, yScale).addDataset(dataseries5); - renderOrange.project("x", "x", xScale).project("y", "y", yScale); - var renderGrape = new Plottable.Plot.Scatter(xScale, yScale).addDataset(dataseries6); - renderGrape.project("x", "x", xScale).project("y", "y", yScale); - - scatterPlot.attr("fill", colorProjector); - linePlot.attr("stroke", colorProjector); - renderApple.attr("fill", colorProjector); - renderBanana.attr("stroke", colorProjector); - renderOrange.attr("fill", colorProjector); - renderGrape.attr("fill", colorProjector); - - var renderArea = scatterPlot.below(linePlot); + var scatterPlot = new Plottable.Plots.Scatter(xScale, yScale).addDataset(dataseries1); + scatterPlot.x(function(d) { return d.x; }, xScale).y(function(d) { return d.y; }, yScale); + var linePlot = new Plottable.Plots.Line(xScale, yScale).addDataset(dataseries2); + linePlot.x(function(d) { return d.x; }, xScale).y(function(d) { return d.y; }, yScale); + var renderApple = new Plottable.Plots.Area(xScale, yScale).addDataset(dataseries3); + renderApple.x(function(d) { return d.x; }, xScale).y(function(d) { return d.y; }, yScale); + var renderBanana = new Plottable.Plots.Line(xScale, yScale).addDataset(dataseries4); + renderBanana.x(function(d) { return d.x; }, xScale).y(function(d) { return d.y; }, yScale); + var renderOrange = new Plottable.Plots.Scatter(xScale, yScale).addDataset(dataseries5); + renderOrange.x(function(d) { return d.x; }, xScale).y(function(d) { return d.y; }, yScale); + var renderGrape = new Plottable.Plots.Scatter(xScale, yScale).addDataset(dataseries6); + renderGrape.x(function(d) { return d.x; }, xScale).y(function(d) { return d.y; }, yScale); + + scatterPlot.attr("fill", colorProjector, colorScale1); + linePlot.attr("stroke", colorProjector, colorScale1); + renderApple.attr("fill", colorProjector, colorScale1); + renderBanana.attr("stroke", colorProjector, colorScale1); + renderOrange.attr("fill", colorProjector, colorScale1); + renderGrape.attr("fill", colorProjector, colorScale1); + + var renderArea = new Plottable.Components.Group([scatterPlot, linePlot]); function emptyTitle() { title1.text(""); } @@ -87,56 +86,54 @@ function run(svg, data, Plottable) { renderGrape.detach(); renderOrange.detach(); renderBanana.detach(); - renderArea - .below(scatterPlot) - .below(linePlot); + renderArea.append(scatterPlot).append(linePlot); } function sixPlots() { colorScale1.domain(["series1", "series2", "apples", "oranges", "bananas", "grapes"]); - renderArea - .below(renderApple) - .below(renderBanana) - .below(renderOrange) - .below(renderGrape) - .below(scatterPlot) - .below(linePlot); - basicTable.renderTo(); + renderArea.append(renderApple) + .append(renderBanana) + .append(renderOrange) + .append(renderGrape) + .append(scatterPlot) + .append(linePlot); } twoPlots(); //title + legend - var title1 = new Plottable.Component.TitleLabel( "Two Data Series", "horizontal"); - var legend1 = new Plottable.Component.Legend(colorScale1); + var title1 = new Plottable.Components.Label( "Two Data Series", "horizontal").classed("title-label", true); + var legend1 = new Plottable.Components.Legend(colorScale1); legend1.maxEntriesPerRow(1); - var titleTable = new Plottable.Component.Table([[title1, legend1]]); + var titleTable = new Plottable.Components.Table([[title1, legend1]]); - var noTitleLabel = new Plottable.Component.Label("no title", "horizontal"); - var shortTitleLabel = new Plottable.Component.Label("tiny title", "horizontal"); - var longTitleLabel = new Plottable.Component.Label("long title", "horizontal"); - var noPlotsLabel = new Plottable.Component.Label("no plots", "horizontal"); - var shortLegendLabel = new Plottable.Component.Label("two plots", "horizontal"); - var tallLegendLabel = new Plottable.Component.Label("six plots", "horizontal"); + var noTitleLabel = new Plottable.Components.Label("no title", "horizontal"); + var shortTitleLabel = new Plottable.Components.Label("tiny title", "horizontal"); + var longTitleLabel = new Plottable.Components.Label("long title", "horizontal"); + var noPlotsLabel = new Plottable.Components.Label("no plots", "horizontal"); + var shortLegendLabel = new Plottable.Components.Label("two plots", "horizontal"); + var tallLegendLabel = new Plottable.Components.Label("six plots", "horizontal"); - var labelTable = new Plottable.Component.Table([[noTitleLabel, noPlotsLabel], + var labelTable = new Plottable.Components.Table([[noTitleLabel, noPlotsLabel], [shortTitleLabel, shortLegendLabel], - [longTitleLabel, tallLegendLabel]]); + [longTitleLabel, tallLegendLabel] + ]); - var basicTable = new Plottable.Component.Table([[null, titleTable], - [yAxis, renderArea], - [null, xAxis], - [null, labelTable]]); + var basicTable = new Plottable.Components.Table([[null, titleTable], + [yAxis, renderArea], + [null, xAxis], + [null, labelTable] + ]); basicTable.renderTo(svg); - noTitleLabel.registerInteraction(new Plottable.Interaction.Click().onClick(emptyTitle)); - shortTitleLabel.registerInteraction(new Plottable.Interaction.Click().onClick(smallTitle)); - longTitleLabel.registerInteraction(new Plottable.Interaction.Click().onClick(longTitle)); - noPlotsLabel.registerInteraction(new Plottable.Interaction.Click().onClick(noPlots)); - shortLegendLabel.registerInteraction(new Plottable.Interaction.Click().onClick(twoPlots)); - tallLegendLabel.registerInteraction(new Plottable.Interaction.Click().onClick(sixPlots)); + new Plottable.Interactions.Click().onClick(emptyTitle).attachTo(noTitleLabel); + new Plottable.Interactions.Click().onClick(smallTitle).attachTo(shortTitleLabel); + new Plottable.Interactions.Click().onClick(longTitle).attachTo(longTitleLabel); + new Plottable.Interactions.Click().onClick(noPlots).attachTo(noPlotsLabel); + new Plottable.Interactions.Click().onClick(twoPlots).attachTo(shortLegendLabel); + new Plottable.Interactions.Click().onClick(sixPlots).attachTo(tallLegendLabel); } diff --git a/quicktests/overlaying/tests/interactions/add_time.js b/quicktests/overlaying/tests/interactions/add_time.js deleted file mode 100644 index 6fe9a8b361..0000000000 --- a/quicktests/overlaying/tests/interactions/add_time.js +++ /dev/null @@ -1,55 +0,0 @@ - -function makeData() { - "use strict"; - return [makeRandomData(50), makeRandomData(50)]; -} - -function run(svg, data, Plottable) { - "use strict"; - - var dates = []; - - var xScale = new Plottable.Scale.Time(); - var yScale = new Plottable.Scale.Linear(); - var ds = new Plottable.Dataset(dates); - var parse = function(d) {return d3.time.format("%x").parse(d.x);}; - var plot = new Plottable.Plot.Bar(xScale, yScale, true) - .addDataset(ds) - .attr("x", parse, xScale) - .project("y", "y", yScale); - - var xAxis = new Plottable.Axis.Time(xScale, "bottom"); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - - var title = new Plottable.Component.TitleLabel("Click to add data"); - - var gridlines = new Plottable.Component.Gridlines(xScale, yScale); - var renderGroup = plot.above(gridlines); - var titleTable = new Plottable.Component.Table([[title]]); - var contentTable = new Plottable.Component.Table([ - [yAxis, renderGroup], - [null, xAxis]]); - new Plottable.Component.Table([ - [titleTable], - [contentTable] - ]).renderTo(svg); - - - function addData(){ - var d = ds.data(); - var pts = d.length; - if(pts >= 50){return;} - var date = Math.floor((data[1][pts].x * 73) % 12) + 1; - date = date + "/"; - date = date + ((Math.floor(data[0][pts].y * 91) % 28) + 1); - date = date + "/"; - date = date + Math.floor(data[0][pts].x * 3000); - var obj = {x: date, y: data[1][pts].y * 500 - 250}; - - d.push(obj); - ds.data(d); - - } - var clickInteraction = new Plottable.Interaction.Click().onClick(addData); - title.registerInteraction(clickInteraction); -} diff --git a/quicktests/overlaying/tests/interactions/basic_moveToFront.js b/quicktests/overlaying/tests/interactions/basic_moveToFront.js deleted file mode 100644 index d299d0fcf9..0000000000 --- a/quicktests/overlaying/tests/interactions/basic_moveToFront.js +++ /dev/null @@ -1,84 +0,0 @@ -function makeData() { - "use strict"; - - return [makeRandomData(50), makeRandomData(50)]; -} - -function run(svg, data, Plottable) { - "use strict"; - - var backPlot = 0; - //data - var dataseries = data[0].slice(0, 10); - var colorScale1 = new Plottable.Scale.Color("20"); - colorScale1.domain(["scatter", "line", "area"]); - - //Axis - - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - - var colorProjector = function(d, i, m) { - return colorScale1.scale(m.name); - }; - - //rendering - var scatterPlot = new Plottable.Plot.Scatter(xScale, yScale).addDataset(dataseries) //0 - .attr("fill", colorScale1.scale("scatter")) - .attr("size", function(){return 20;}) - .project("x", "x", xScale) - .project("y", "y", yScale); - - var linePlot = new Plottable.Plot.Line(xScale, yScale) - .addDataset(dataseries) //1 - .attr("stroke", colorScale1.scale("line")) - .attr("stroke-width", function(){ return 5;}) - .project("x", "x", xScale) - .project("y", "y", yScale); - - var areaPlot = new Plottable.Plot.Area(xScale, yScale) - .addDataset(dataseries) //2 - .attr("fill", colorScale1.scale("area")) - .project("x", "x", xScale) - .project("y", "y", yScale); - - //title + legend - var title1 = new Plottable.Component.TitleLabel( "front: areaPlot", "horizontal"); - var legend1 = new Plottable.Component.Legend(colorScale1); - legend1.maxEntriesPerRow(1); - - var titleTable = new Plottable.Component.Table().addComponent(0,0, title1) - .addComponent(0,1, legend1); - - var plotGroup = scatterPlot.below(linePlot).below(areaPlot); - - var basicTable = new Plottable.Component.Table() - .addComponent(2, 0, yAxis) - .addComponent(2, 1, plotGroup) - .addComponent(3, 1, xAxis); - - var bigTable = new Plottable.Component.Table() - .addComponent(0, 0, titleTable) - .addComponent(1,0, basicTable); - - bigTable.renderTo(svg); - - - function cb() { - var plot; - if(backPlot === 0){ plot = scatterPlot; title1.text("front: scatterPlot");} - if(backPlot === 1){ plot = linePlot; title1.text("front: linePlot");} - if(backPlot === 2){ plot = areaPlot; title1.text("front: areaPlot");} - plot.detach(); - plotGroup.below(plot); - backPlot++; - if(backPlot === 3){ backPlot = 0; } - } - - plotGroup.registerInteraction( - new Plottable.Interaction.Click().onClick(cb) - ); - -} diff --git a/quicktests/overlaying/tests/interactions/drag_boxes.js b/quicktests/overlaying/tests/interactions/drag_boxes.js deleted file mode 100644 index a51e742c7c..0000000000 --- a/quicktests/overlaying/tests/interactions/drag_boxes.js +++ /dev/null @@ -1,40 +0,0 @@ - -function makeData() { - "use strict"; - return [makeRandomData(50), makeRandomData(50), makeRandomData(50)]; - -} - -function makeVerifyingDragBox(dragBox, plot) { - "use strict"; - dragBox.resizeEnabled(true); - var c1 = plot._foregroundContainer.append("circle").attr("r", 5).attr("fill", "green"); - var c2 = plot._foregroundContainer.append("circle").attr("r", 5).attr("fill", "red"); - var cb_drag = function(start, end) { - c1.attr({"cx": start.x, "cy": start.y}); - c2.attr({"cx": end.x, "cy": end.y}); - }; - dragBox.drag(cb_drag); - plot.registerInteraction(dragBox); -} - -function run(svg, data, Plottable) { - "use strict"; - - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - - var plot1 = new Plottable.Plot.Scatter(xScale, yScale).addDataset(data[0]); - plot1.project("x", "x", xScale).project("y", "y", yScale); - var plot2 = new Plottable.Plot.Scatter(xScale, yScale).addDataset(data[1]); - plot2.project("x", "x", xScale).project("y", "y", yScale); - var plot3 = new Plottable.Plot.Scatter(xScale, yScale).addDataset(data[2]); - plot3.project("x", "x", xScale).project("y", "y", yScale); - - var table = new Plottable.Component.Table([[plot1, plot2, plot3]]); - - table.renderTo(svg); - makeVerifyingDragBox(new Plottable.Interaction.DragBox(), plot1); - makeVerifyingDragBox(new Plottable.Interaction.DragBox() , plot2); - makeVerifyingDragBox(new Plottable.Interaction.DragBox() , plot3); -} diff --git a/quicktests/overlaying/tests/interactions/interaction_BarHover.js b/quicktests/overlaying/tests/interactions/interaction_BarHover.js index 79fbdcd61f..af1e94fc1f 100644 --- a/quicktests/overlaying/tests/interactions/interaction_BarHover.js +++ b/quicktests/overlaying/tests/interactions/interaction_BarHover.js @@ -6,39 +6,37 @@ function makeData() { function run(svg, data, Plottable) { "use strict"; - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Linear(); - var xAxis = new Plottable.Axis.Category(xScale, "bottom"); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - var title = new Plottable.Component.TitleLabel("Hover over bars"); - var colorScale = new Plottable.Scale.Color(); + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Linear(); + var xAxis = new Plottable.Axes.Category(xScale, "bottom"); + var yAxis = new Plottable.Axes.Numeric(yScale, "left"); + var title = new Plottable.Components.Label("Hover over bars").classed("title-label", true); + var colorScale = new Plottable.Scales.Color(); var ds = new Plottable.Dataset(data, { foo: "!" }); - var plot = new Plottable.Plot.Bar(xScale, yScale, true) + var plot = new Plottable.Plots.Bar(xScale, yScale, true) .addDataset(ds) - .project("x", function (d, i, u) { return d.name + u.foo; }, xScale) - .project("y", "y", yScale) - .project("fill", "name", colorScale); + .x(function (d, i, dataset) { return d.name + dataset.metadata().foo; }, xScale) + .y(function(d) { return d.y; }, yScale) + .attr("fill", function(d) { return d.name; }, colorScale); - var chart = new Plottable.Component.Table([ + var chart = new Plottable.Components.Table([ [null, title], [yAxis, plot], [null, xAxis]]); chart.renderTo(svg); - //callbacks - var hoverHandler = function(hoverData) { - title.text(hoverData.data[0].name); - }; - var unhoverHandler = function(hoverData) { + var pointer = new Plottable.Interactions.Pointer(); + pointer.onPointerMove(function(p) { + var cpd = plot.getClosestPlotData(p); + if (cpd.data.length > 0) { + title.text(cpd.data[0].name); + } else { title.text("Who?"); - }; - - //registering interaction - var bhi = new Plottable.Interaction.Hover() - .onHoverOver(hoverHandler) - .onHoverOut(unhoverHandler); - plot.registerInteraction(bhi); + } + }); + pointer.onPointerExit(function(p) { title.text("Who?"); }); + pointer.attachTo(plot); } diff --git a/quicktests/overlaying/tests/interactions/interaction_dragzoomx.js b/quicktests/overlaying/tests/interactions/interaction_dragzoomx.js deleted file mode 100644 index 6544c91ded..0000000000 --- a/quicktests/overlaying/tests/interactions/interaction_dragzoomx.js +++ /dev/null @@ -1,53 +0,0 @@ -function makeData() { - "use strict"; - - return makeRandomData(20); -} - -function run(svg, data, Plottable) { - "use strict"; - - var newData = JSON.parse(JSON.stringify(data)); - var dataSeries = newData; - - var xScale = new Plottable.Scale.Linear(); - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); - - var yScale = new Plottable.Scale.Linear(); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - - var plot = new Plottable.Plot.Area(xScale, yScale).addDataset(dataSeries); - var fillAccessor = function() { return "steelblue"; }; - plot.attr("fill", fillAccessor); - plot.project("x", "x", xScale).project("y", "y", yScale); - - var gridlines = new Plottable.Component.Gridlines(xScale, yScale); - var renderGroup = new Plottable.Component.Group([gridlines, plot]); - - var chart = new Plottable.Component.Table([ - [yAxis, renderGroup], - [null, xAxis]]); - - chart.renderTo(svg); - - var xDrag = new Plottable.Interaction.XDragBox(); - xDrag.dragend(function(start, end) { - xDrag.clearBox(); - if (start.x === end.x) { - return; - } - var scaledStartX = xScale.invert(start.x); - var scaledEndX = xScale.invert(end.x); - - var minX = Math.min(scaledStartX, scaledEndX); - var maxX = Math.max(scaledStartX, scaledEndX); - - xScale.domain([minX, maxX]); - }); - - renderGroup.registerInteraction(xDrag); - - var reset = new Plottable.Interaction.DoubleClick(); - reset.callback(function() { xScale.autoDomain(); }); - renderGroup.registerInteraction(reset); -} diff --git a/quicktests/overlaying/tests/interactions/interaction_hover_scatter.js b/quicktests/overlaying/tests/interactions/interaction_hover_scatter.js index 0f55122320..56f4fab130 100644 --- a/quicktests/overlaying/tests/interactions/interaction_hover_scatter.js +++ b/quicktests/overlaying/tests/interactions/interaction_hover_scatter.js @@ -8,23 +8,25 @@ function makeData() { function run(svg, data, Plottable) { "use strict"; - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - var title = new Plottable.Component.TitleLabel("Hover over points"); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); + var xAxis = new Plottable.Axes.Numeric(xScale, "bottom"); + var yAxis = new Plottable.Axes.Numeric(yScale, "left"); + var defaultTitleText = "Hover over points"; + var title = new Plottable.Components.Label(defaultTitleText).classed("title-label", true); var ds1 = new Plottable.Dataset(data[0], { color: "blue", size: 20 }); var ds2 = new Plottable.Dataset(data[1], { color: "red", size: 30 }); - var plot = new Plottable.Plot.Scatter(xScale, yScale).addDataset(ds1) - .addDataset(ds2) - .project("size", function(d, i, u) { return u.size; }) - .project("fill", function(d, i, u) { return u.color; }) - .project("x", function(d, i, u) { return d.x; }, xScale) - .project("y", "y", yScale); + var plot = new Plottable.Plots.Scatter(xScale, yScale); + plot.addDataset(ds1); + plot.addDataset(ds2); + plot.size(function(d, i, dataset) { return dataset.metadata().size; }); + plot.attr("fill", function(d, i, dataset) { return dataset.metadata().color; }); + plot.x(function(d, i, dataset) { return d.x; }, xScale); + plot.y(function(d) { return d.y; }, yScale); - var chart = new Plottable.Component.Table([ + var chart = new Plottable.Components.Table([ [null, title], [yAxis, plot], [null, xAxis]]); @@ -38,20 +40,25 @@ function run(svg, data, Plottable) { }) .style("visibility", "hidden"); - var hover = new Plottable.Interaction.Hover(); - hover.onHoverOver(function(hoverData) { - var xString = hoverData.data[0].x.toFixed(2); - var yString = hoverData.data[0].y.toFixed(2); - title.text("[ " + xString + ", " + yString + " ]"); - - hoverCircle.attr({ - "cx": hoverData.pixelPositions[0].x, - "cy": hoverData.pixelPositions[0].y - }).style("visibility", "visible"); + var pointer = new Plottable.Interactions.Pointer(); + pointer.onPointerMove(function(p) { + var cpd = plot.getClosestPlotData(p); + if (cpd.data.length > 0) { + var xString = cpd.data[0].x.toFixed(2); + var yString = cpd.data[0].y.toFixed(2); + title.text("[ " + xString + ", " + yString + " ]"); + hoverCircle.attr({ + "cx": cpd.pixelPoints[0].x, + "cy": cpd.pixelPoints[0].y + }).style("visibility", "visible"); + } else { + title.text(defaultTitleText); + hoverCircle.style("visibility", "hidden"); + } }); - hover.onHoverOut(function(hoverData) { - title.text("Hover over points"); + pointer.onPointerExit(function() { + title.text(defaultTitleText); hoverCircle.style("visibility", "hidden"); }); - plot.registerInteraction(hover); + pointer.attachTo(plot); } diff --git a/quicktests/overlaying/tests/interactions/interaction_panzoom.js b/quicktests/overlaying/tests/interactions/interaction_panzoom.js deleted file mode 100644 index 00bef3c0d2..0000000000 --- a/quicktests/overlaying/tests/interactions/interaction_panzoom.js +++ /dev/null @@ -1,28 +0,0 @@ -function makeData() { - "use strict"; - - return makeRandomData(20); -} - -function run(svg, data, Plottable) { - "use strict"; - - var xScale = new Plottable.Scale.Linear(); - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); - - var yScale = new Plottable.Scale.Linear(); - var yAxis = new Plottable.Axis.Numeric(yScale, "left").tickLabelPosition("bottom"); - - var plot = new Plottable.Plot.Scatter(xScale, yScale).addDataset(data); - plot.project("x", "x", xScale).project("y", "y", yScale); - var gridlines = new Plottable.Component.Gridlines(xScale, yScale); - var group = plot.above(gridlines); - var chart = new Plottable.Component.Table([[yAxis, group], - [null, xAxis]]); - - chart.renderTo(svg); - - plot.registerInteraction( - new Plottable.Interaction.PanZoom(xScale, yScale) - ); -} diff --git a/quicktests/overlaying/tests/interactions/key_interaction.js b/quicktests/overlaying/tests/interactions/key_interaction.js deleted file mode 100644 index 6df7d8f0e6..0000000000 --- a/quicktests/overlaying/tests/interactions/key_interaction.js +++ /dev/null @@ -1,38 +0,0 @@ - -function makeData() { - "use strict"; - return makeRandomData(25); -} - -function run(svg, data, Plottable) { - "use strict"; - - var xScale = new Plottable.Scale.Linear(); - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); - - var yScale = new Plottable.Scale.Linear(); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - - var scatterPlot = new Plottable.Plot.Scatter(xScale, yScale).addDataset(data); - scatterPlot.project("x", "x", xScale).project("y", "y", yScale); - var explanation = new Plottable.Component.TitleLabel("Press 'a' to reset domain"); - - var basicTable = new Plottable.Component.Table([[null, explanation], - [yAxis, scatterPlot], - [null, xAxis]]); - - basicTable.renderTo(svg); - - var pzi = new Plottable.Interaction.PanZoom(xScale, yScale); - scatterPlot.registerInteraction(pzi); - - var ki = new Plottable.Interaction.Key(); - // press "a" (keycode 65) to reset - ki.on(65, function() { - xScale.autoDomain(); - yScale.autoDomain(); - pzi.resetZoom(); - }); - scatterPlot.registerInteraction(ki); - -} diff --git a/quicktests/overlaying/tests/interactions/scale_interactive.js b/quicktests/overlaying/tests/interactions/scale_interactive.js index e99910a2f2..8110468850 100644 --- a/quicktests/overlaying/tests/interactions/scale_interactive.js +++ b/quicktests/overlaying/tests/interactions/scale_interactive.js @@ -7,46 +7,45 @@ function makeData() { function run(svg, data, Plottable) { "use strict"; - var dataseries = data[0].slice(0, 20); + var dataseries = new Plottable.Dataset(data[0].slice(0, 20)); - var xScale = new Plottable.Scale.Linear(); - var xAxisLeft = new Plottable.Axis.Numeric(xScale, "bottom").tickLabelPosition("left"); - var xAxisCenter = new Plottable.Axis.Numeric(xScale, "bottom"); - var xAxisRight = new Plottable.Axis.Numeric(xScale, "bottom").tickLabelPosition("right"); - var xAxisTable = new Plottable.Component.Table([[xAxisLeft], + var xScale = new Plottable.Scales.Linear(); + var xAxisLeft = new Plottable.Axes.Numeric(xScale, "bottom").tickLabelPosition("left"); + var xAxisCenter = new Plottable.Axes.Numeric(xScale, "bottom"); + var xAxisRight = new Plottable.Axes.Numeric(xScale, "bottom").tickLabelPosition("right"); + var xAxisTable = new Plottable.Components.Table([[xAxisLeft], [xAxisCenter], [xAxisRight]]); - var xAxisLeft2 = new Plottable.Axis.Numeric(xScale, "top").tickLabelPosition("left"); - var xAxisCenter2 = new Plottable.Axis.Numeric(xScale, "top"); - var xAxisRight2 = new Plottable.Axis.Numeric(xScale, "top").tickLabelPosition("right"); - var xAxisTable2 = new Plottable.Component.Table([[xAxisLeft2], + var xAxisLeft2 = new Plottable.Axes.Numeric(xScale, "top").tickLabelPosition("left"); + var xAxisCenter2 = new Plottable.Axes.Numeric(xScale, "top"); + var xAxisRight2 = new Plottable.Axes.Numeric(xScale, "top").tickLabelPosition("right"); + var xAxisTable2 = new Plottable.Components.Table([[xAxisLeft2], [xAxisCenter2], [xAxisRight2]]); - var yScale = new Plottable.Scale.Linear(); - var yAxisTop = new Plottable.Axis.Numeric(yScale, "left").tickLabelPosition("top"); - var yAxisMiddle = new Plottable.Axis.Numeric(yScale, "left"); - var yAxisBottom = new Plottable.Axis.Numeric(yScale, "left").tickLabelPosition("bottom"); - var yAxisTable = new Plottable.Component.Table([[yAxisTop, yAxisMiddle, yAxisBottom]]); + var yScale = new Plottable.Scales.Linear(); + var yAxisTop = new Plottable.Axes.Numeric(yScale, "left").tickLabelPosition("top"); + var yAxisMiddle = new Plottable.Axes.Numeric(yScale, "left"); + var yAxisBottom = new Plottable.Axes.Numeric(yScale, "left").tickLabelPosition("bottom"); + var yAxisTable = new Plottable.Components.Table([[yAxisTop, yAxisMiddle, yAxisBottom]]); - var yAxisTop2 = new Plottable.Axis.Numeric(yScale, "right").tickLabelPosition("top"); - var yAxisMiddle2 = new Plottable.Axis.Numeric(yScale, "right"); - var yAxisBottom2 = new Plottable.Axis.Numeric(yScale, "right").tickLabelPosition("bottom"); - var yAxisTable2 = new Plottable.Component.Table([[yAxisTop2, yAxisMiddle2, yAxisBottom2]]); + var yAxisTop2 = new Plottable.Axes.Numeric(yScale, "right").tickLabelPosition("top"); + var yAxisMiddle2 = new Plottable.Axes.Numeric(yScale, "right"); + var yAxisBottom2 = new Plottable.Axes.Numeric(yScale, "right").tickLabelPosition("bottom"); + var yAxisTable2 = new Plottable.Components.Table([[yAxisTop2, yAxisMiddle2, yAxisBottom2]]); - var renderAreaD1 = new Plottable.Plot.Scatter(xScale, yScale).addDataset(dataseries); - renderAreaD1.project("x", "x", xScale).project("y", "y", yScale); - var gridlines = new Plottable.Component.Gridlines(xScale, yScale); + var scatterPlot = new Plottable.Plots.Scatter(xScale, yScale).addDataset(dataseries); + scatterPlot.x(function(d) { return d.x; }, xScale).y(function(d) { return d.y; }, yScale); + var gridlines = new Plottable.Components.Gridlines(xScale, yScale); + var renderGroup = new Plottable.Components.Group([scatterPlot, gridlines]); - var basicTable = new Plottable.Component.Table([[null, xAxisTable2, null], - [yAxisTable, renderAreaD1.above(gridlines), yAxisTable2], + var basicTable = new Plottable.Components.Table([[null, xAxisTable2, null], + [yAxisTable, renderGroup, yAxisTable2], [null, xAxisTable, null]]); basicTable.renderTo(svg); - renderAreaD1.registerInteraction( - new Plottable.Interaction.PanZoom(xScale, yScale) - ); + new Plottable.Interactions.PanZoom(xScale, yScale).attachTo(renderGroup); } diff --git a/quicktests/overlaying/tests/interactions/select_bars.js b/quicktests/overlaying/tests/interactions/select_bars.js deleted file mode 100644 index 7445b396c9..0000000000 --- a/quicktests/overlaying/tests/interactions/select_bars.js +++ /dev/null @@ -1,62 +0,0 @@ - -function makeData() { - "use strict"; - - return [makeRandomData(50), makeRandomData(50)]; - -} - -function run(svg, data, Plottable) { - "use strict"; - - var dataseries = data[0].slice(0, 20); - - var xScale = new Plottable.Scale.Linear(); - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); - - var yScale = new Plottable.Scale.Linear(); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - - var barPlot = new Plottable.Plot.Bar(xScale, yScale, true).addDataset(dataseries); - barPlot.project("x", "x", xScale).project("y", "y", yScale); - var gridlines = new Plottable.Component.Gridlines(xScale, yScale); - var renderGroup = gridlines.below(barPlot); - var title = new Plottable.Component.TitleLabel("reset"); - - var chart = new Plottable.Component.Table([ - [null, title], - [yAxis, renderGroup], - [null, xAxis]]).renderTo(svg); - - //callbacks - var dragBox = new Plottable.Interaction.DragBox().resizeEnabled(true); - var cb_drag = function(start, end) { - var minX = Math.min(start.x, end.x); - var maxX = Math.max(start.x, end.x); - var minY = Math.min(start.y, end.y); - var maxY = Math.max(start.y, end.y); - - var bars = barPlot.getBars({min: minX, max: maxX}, - {min: minY, max: maxY}).classed("selected", true); - title.text(String(bars[0].length)); - }; - dragBox.dragend(cb_drag); - - var cb_click = function(p) { - var bars = barPlot.getBars(p.x, p.y).classed("selected", true); - title.text(String(bars[0].length)); - }; - - var cb_reset = function() { - barPlot.getAllSelections().classed("selected", false); - dragBox.clearBox(); - }; - - //register interactions - renderGroup.registerInteraction(dragBox); - - renderGroup.registerInteraction(new Plottable.Interaction.Click().onClick(cb_click)); - - title.registerInteraction(new Plottable.Interaction.Click().onClick(cb_reset)); - -} diff --git a/quicktests/overlaying/tests/interactions/select_clustered_bars.js b/quicktests/overlaying/tests/interactions/select_clustered_bars.js index b811037744..5d48f5b439 100644 --- a/quicktests/overlaying/tests/interactions/select_clustered_bars.js +++ b/quicktests/overlaying/tests/interactions/select_clustered_bars.js @@ -11,25 +11,25 @@ function makeData() { function run(svg, data, Plottable) { "use strict"; - var xScale = new Plottable.Scale.Category(); - var xAxis = new Plottable.Axis.Category(xScale, "bottom"); + var xScale = new Plottable.Scales.Category(); + var xAxis = new Plottable.Axes.Category(xScale, "bottom"); - var yScale = new Plottable.Scale.Linear(); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); + var yScale = new Plottable.Scales.Linear(); + var yAxis = new Plottable.Axes.Numeric(yScale, "left"); - var barPlot = new Plottable.Plot.ClusteredBar(xScale, yScale) - .addDataset(data[0]) - .addDataset(data[1]) - .addDataset(data[2]) - .project("x", "name", xScale) - .project("y", "y", yScale); + var barPlot = new Plottable.Plots.ClusteredBar(xScale, yScale) + .addDataset(new Plottable.Dataset(data[0])) + .addDataset(new Plottable.Dataset(data[1])) + .addDataset(new Plottable.Dataset(data[2])) + .x(function(d) { return d.name; }, xScale) + .y(function(d) { return d.y; }, yScale); - var chart = new Plottable.Component.Table([ + var chart = new Plottable.Components.Table([ [yAxis, barPlot], [null, xAxis]]).renderTo(svg); - var clickInteraction = new Plottable.Interaction.Click(); - barPlot.registerInteraction(clickInteraction); + var clickInteraction = new Plottable.Interactions.Click(); + clickInteraction.attachTo(barPlot); clickInteraction.onClick(function (p) { var bars = barPlot.getBars(p.x, p.y, true); if (bars == null) { diff --git a/quicktests/overlaying/tests/interactions/select_zoom.js b/quicktests/overlaying/tests/interactions/select_zoom.js deleted file mode 100644 index 7472bd7762..0000000000 --- a/quicktests/overlaying/tests/interactions/select_zoom.js +++ /dev/null @@ -1,81 +0,0 @@ -function makeData() { - "use strict"; - - return [makeRandomData(50), makeRandomData(50)]; -} - -function run(svg, data, Plottable) { - "use strict"; - - var renderers = []; - - var colors = new Plottable.Scale.Color("10").range(); - var numRenderers = 5; - var names = ["bat", "cat", "mat", "rat", "pat"]; - var colorScale = new Plottable.Scale.Color(); - colorScale.range(colors); - colorScale.domain(names); - - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - - for (var i=0; i 0){ - data.splice(data.length-1,1); - dataset.data(data); - } - }); - - plot.registerInteraction(hover); - plot.registerInteraction(key); -} diff --git a/quicktests/overlaying/tests/realistic/background.js b/quicktests/overlaying/tests/realistic/background.js deleted file mode 100644 index 83d82f3ff9..0000000000 --- a/quicktests/overlaying/tests/realistic/background.js +++ /dev/null @@ -1,98 +0,0 @@ -function makeData() { - "use strict"; - var create_data = function(step, x_init, y_init, decrease){ - var d = []; - while(x_init <= 10){ - var o = {}; - o.x = x_init; - x_init += step; - o.y = y_init; - y_init *= decrease; - d.push(o); - } - return d; - }; - - var data1 = create_data(0.5, 0, 10, 0.95); - var data2 = create_data(0.5, 0, 9, 0.90); - var data3 = create_data(0.5, 0, 8, 0.80); - - return [data1, data2, data3]; -} - -function run(svg, data, Plottable){ - "use strict"; - - var data1 = []; - var data2 = []; - var data3 = []; - deep_copy(data[0], data1); - deep_copy(data[1], data2); - deep_copy(data[2], data3); - - var xScale = new Plottable.Scale.Linear(); - xScale.domain([0, 10]); - var xAxis = new Plottable.Axis.Numeric(xScale, 'bottom'); - var yScale = new Plottable.Scale.Linear(); - var yAxis = new Plottable.Axis.Numeric(yScale, 'left'); - - var plot1 = new Plottable.Plot.Area(xScale, yScale); - plot1.addDataset(data1); - plot1.project("x", "x", xScale).project("y", "y", yScale); - plot1.project("fill", '#923458') - .project("opacity", 0.2) - .project("stroke-width", 0); - - var plot2 = new Plottable.Plot.Area(xScale, yScale); - plot2.addDataset(data2); - plot2.project("x", "x", xScale).project("y", "y", yScale); - plot2.project("fill", '#923458') - .project("opacity", 0.3) - .project("stroke-width", 0); - - var plot3 = new Plottable.Plot.Area(xScale, yScale); - plot3.addDataset(data3); - plot3.project("x", "x", xScale).project("y", "y", yScale); - plot3.project("fill", '#923458') - .project("opacity", 0.4) - .project("stroke-width", 0); - - var cs = new Plottable.Scale.Color(); - cs.domain(["#434343", "#923458", "#384658"]); - cs.range(["#434343", "#923458", "#38ff58"]); - var legend = new Plottable.Component.Legend(cs); - - var plots = plot1.above(plot2).above(plot3); - var label = new Plottable.Component.Label("TITLE LABEL"); - - var table = new Plottable.Component.Table([[null, label, null], - [yAxis, plots, legend], - [null, xAxis, null]]); - - table.renderTo(svg); - - plots.background() - .select('.background-fill') - .style('fill', '#923458') - .style('opacity', 0.1); - - xAxis.background() - .select('.background-fill') - .style('fill', '#120378') - .style('opacity', 0.1); - - yAxis.background() - .select('.background-fill') - .style('fill', '#bba411') - .style('opacity', 0.1); - - label.background() - .select('.background-fill') - .style('fill', '#358923') - .style('opacity', 0.3); - - legend.background() - .select('.background-fill') - .style('fill', '#33e4e9') - .style('opacity', 0.1); -} diff --git a/quicktests/overlaying/tests/realistic/basic_pie_real.js b/quicktests/overlaying/tests/realistic/basic_pie_real.js index 842731d3d3..3147141b92 100644 --- a/quicktests/overlaying/tests/realistic/basic_pie_real.js +++ b/quicktests/overlaying/tests/realistic/basic_pie_real.js @@ -24,48 +24,48 @@ function makeData() { function run(svg, data, Plottable) { "use strict"; - var colorScale = new Plottable.Scale.Color(); - var legend = new Plottable.Component.Legend(colorScale).xAlign("left"); + var colorScale = new Plottable.Scales.Color(); + var legend = new Plottable.Components.Legend(colorScale).xAlignment("left"); legend.maxEntriesPerRow(1); - var title = new Plottable.Component.TitleLabel("Sales by Region"); - var Alabel = new Plottable.Component.Label("Product A"); - var Blabel = new Plottable.Component.Label("Product B"); - var ABlabel = new Plottable.Component.Label("Combined"); + var title = new Plottable.Components.Label("Sales by Region").classed("title-label", true); + var Alabel = new Plottable.Components.Label("Product A"); + var Blabel = new Plottable.Components.Label("Product B"); + var ABlabel = new Plottable.Components.Label("Combined"); - var Aplot = new Plottable.Plot.Pie(); - Aplot.addDataset("d1", data[0]); - Aplot.project("value", "percent"); - Aplot.project("fill", "region", colorScale); - Aplot.project("inner-radius", 40); - Aplot.project("outer-radius", 80); - Aplot = Alabel.above(Aplot); + var Aplot = new Plottable.Plots.Pie(); + Aplot.addDataset(new Plottable.Dataset(data[0])); + Aplot.sectorValue(function(d) { return d.percent; }); + Aplot.attr("fill", function(d) { return d.region; }, colorScale); + Aplot.innerRadius(40); + Aplot.outerRadius(80); + var AGroup = new Plottable.Components.Group([Aplot, Alabel]); - var Bplot = new Plottable.Plot.Pie(); - Bplot.addDataset("d2", data[1]); - Bplot.project("value", "percent"); - Bplot.project("fill", "region", colorScale); - Bplot.project("inner-radius", 40); - Bplot.project("outer-radius", 80); - Bplot = Blabel.above(Bplot); + var Bplot = new Plottable.Plots.Pie(); + Bplot.addDataset(new Plottable.Dataset(data[1])); + Bplot.sectorValue(function(d) { return d.percent; }); + Bplot.attr("fill", function(d) { return d.region; }, colorScale); + Bplot.innerRadius(40); + Bplot.outerRadius(80); + var BGroup = new Plottable.Components.Group([Bplot, Blabel]); - var ABplot = new Plottable.Plot.Pie(); - ABplot.addDataset("d3", data[2]); - ABplot.project("value", "percent"); - ABplot.project("fill", "region", colorScale); - ABplot.project("inner-radius", 50); - ABplot.project("outer-radius", 100); - ABplot = ABlabel.above(ABplot); + var ABplot = new Plottable.Plots.Pie(); + ABplot.addDataset(new Plottable.Dataset(data[2])); + ABplot.sectorValue(function(d) { return d.percent; }); + ABplot.attr("fill", function(d) { return d.region; }, colorScale); + ABplot.innerRadius(50); + ABplot.outerRadius(100); + var ABGroup = new Plottable.Components.Group([ABplot, ABlabel]); - var productPlots = new Plottable.Component.Table([ - [Aplot], - [Bplot], + var productPlots = new Plottable.Components.Table([ + [AGroup], + [BGroup], ]); - var allPlots = new Plottable.Component.Table([ - [productPlots, ABplot, legend] + var allPlots = new Plottable.Components.Table([ + [productPlots, ABGroup, legend] ]); - var chart = new Plottable.Component.Table([ + var chart = new Plottable.Components.Table([ [title], [allPlots] ]); diff --git a/quicktests/overlaying/tests/realistic/grid.js b/quicktests/overlaying/tests/realistic/grid.js index ad365be37d..ab42881ecc 100644 --- a/quicktests/overlaying/tests/realistic/grid.js +++ b/quicktests/overlaying/tests/realistic/grid.js @@ -1,55 +1,55 @@ function makeData() { "use strict"; - return [{team: "Detroit Tigers", x1: "4/1/1901", x2: "8/1/2015", - y1: 0, y2: 1, + return [{team: "Detroit Tigers", x1: "4/1/1901", x2: "8/1/2015", + y1: 0, y2: 1, fill: "#DE4406", stroke: "#001742"}, - - {team: "Detroit Wolverines", x1: "4/1/1881", x2: "8/1/1888", - y1: 1, y2: 2, + + {team: "Detroit Wolverines", x1: "4/1/1881", x2: "8/1/1888", + y1: 1, y2: 2, fill: "#f5f5dc", stroke: "#CF0032"}, - + {team: "Detroit Stars", x1: "4/1/1919", x2: "8/1/1931", - y1: 2, y2: 3, - fill: "#00529B", stroke: "#CF0032"}, - - {team: "Detroit Stars", x1: "4/1/1933", x2: "8/1/1933", - y1: 2, y2: 3, - fill: "#00529B", stroke: "#CF0032"}, - + y1: 2, y2: 3, + fill: "#00529B", stroke: "#CF0032"}, + + {team: "Detroit Stars", x1: "4/1/1933", x2: "8/1/1933", + y1: 2, y2: 3, + fill: "#00529B", stroke: "#CF0032"}, + {team: "Detroit Stars", x1: "4/1/1937", x2: "8/1/1937", - y1: 2, y2: 3, - fill: "#00529B", stroke: "#CF0032"}, - - {team: "Detroit Stars", x1: "4/1/1954", x2: "8/1/1957", - y1: 2, y2: 3, - fill: "#00529B", stroke: "#CF0032"}, - - {team: "Detroit Stars", x1: "4/1/1959", x2: "8/1/1959", - y1: 2, y2: 3, - fill: "#00529B", stroke: "#CF0032"} + y1: 2, y2: 3, + fill: "#00529B", stroke: "#CF0032"}, + + {team: "Detroit Stars", x1: "4/1/1954", x2: "8/1/1957", + y1: 2, y2: 3, + fill: "#00529B", stroke: "#CF0032"}, + + {team: "Detroit Stars", x1: "4/1/1959", x2: "8/1/1959", + y1: 2, y2: 3, + fill: "#00529B", stroke: "#CF0032"} ]; } function run(svg, data, Plottable) { "use strict"; - - var timeFormatStart = function (data) { return d3.time.format("%m/%d/%Y").parse(data.x1);}; - var timeFormatEnd = function (data) { return d3.time.format("%m/%d/%Y").parse(data.x2);}; - - var xScale = new Plottable.Scale.Time(); - var yScale = new Plottable.Scale.Category(); + + var timeFormatStart = function (data) { return d3.time.format("%m/%d/%Y").parse(data.x1);}; + var timeFormatEnd = function (data) { return d3.time.format("%m/%d/%Y").parse(data.x2);}; + + var xScale = new Plottable.Scales.Time(); + var yScale = new Plottable.Scales.Category(); yScale.innerPadding(0.25).outerPadding(0.25); - var xAxis = new Plottable.Axis.Time(xScale, "bottom"); - var yAxis = new Plottable.Axis.Category(yScale, "left"); - var plot = new Plottable.Plot.Grid(xScale, yScale); - plot.addDataset(data); - plot.project("x", timeFormatStart, xScale) - .project("y", "team", yScale) - .project("x2", timeFormatEnd, xScale) - .project("fill", "fill") - .project("stroke", "stroke"); - - var table = new Plottable.Component.Table([[yAxis, plot], + var xAxis = new Plottable.Axes.Time(xScale, "bottom"); + var yAxis = new Plottable.Axes.Category(yScale, "left"); + var plot = new Plottable.Plots.Grid(xScale, yScale); + plot.addDataset(new Plottable.Dataset(data)); + plot.x(timeFormatStart, xScale) + .y(function(d) { return d.team; }, yScale) + .x2(timeFormatEnd, xScale) + .attr("fill", function(d) { return d.fill; }) + .attr("stroke", function(d) { return d.stroke; }); + + var table = new Plottable.Components.Table([[yAxis, plot], [null, xAxis]]); table.renderTo(svg); } diff --git a/quicktests/overlaying/tests/realistic/hbar.js b/quicktests/overlaying/tests/realistic/hbar.js deleted file mode 100644 index 5f470079e9..0000000000 --- a/quicktests/overlaying/tests/realistic/hbar.js +++ /dev/null @@ -1,64 +0,0 @@ -function makeData() { - "use strict"; - var data = [{x: 'Bicycle', y: 284}, - {x: 'Car - Truck', y: 253}, - {x: 'Car - Sedan', y: 115}, - {x: 'Car - Van', y: 58}, - {x: 'Scooter', y: 47}, - {x: 'Horse', y: 46}, - {x: 'Rocket Ship', y: 41}, - {x: 'Vespa', y: 38}, - {x: 'Motorcycle', y: 32}, - {x: 'Semi Truck', y: 31}, - {x: 'Airplane - Personal', y: 22}, - {x: 'Airplane - Commerical', y: 22}, - {x: 'Train', y: 21}, - {x: 'Subway - Above Ground', y: 21}, - {x: 'Subway - Underground', y: 20}, - ]; - - return [data]; -} - -function run(svg, data, Plottable){ - "use strict"; - - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Category(); - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); - var yAxis = new Plottable.Axis.Category(yScale, "left"); - var xLabel = new Plottable.Component.Label(""); - var yLabel = new Plottable.Component.Label(""); - - var plot = new Plottable.Plot.Bar(xScale, yScale, false); - plot.addDataset(data[0]); - plot.project("x", "y", xScale) - .project("y", "x", yScale); - - var matrix = [[yLabel, yAxis, plot], - [null, null, xAxis], - [null, null, xLabel]]; - - new Plottable.Component.Table(matrix) - .renderTo(svg); - - var flipDomain = function(){ - xScale.domain(xScale.domain().reverse()); - var tmp = matrix[0][0]; - matrix[0][0] = matrix[0][2]; - matrix[0][2] = tmp; - tmp = matrix[1][0]; - matrix[1][0] = matrix[1][2]; - matrix[1][2] = tmp; - tmp = matrix[2][0]; - matrix[2][0] = matrix[2][2]; - matrix[2][2] = tmp; - - yAxis.orient(yAxis.orient() === "left" ? "right" : "left"); - new Plottable.Component.Table(matrix) - .renderTo(svg); - }; - - var clickInteraction = new Plottable.Interaction.Click().callback(flipDomain); - plot.registerInteraction(clickInteraction); -} \ No newline at end of file diff --git a/quicktests/overlaying/tests/realistic/rainfall_ClusteredBar.js b/quicktests/overlaying/tests/realistic/rainfall_ClusteredBar.js index ca04462a0c..cf836ef4db 100644 --- a/quicktests/overlaying/tests/realistic/rainfall_ClusteredBar.js +++ b/quicktests/overlaying/tests/realistic/rainfall_ClusteredBar.js @@ -11,27 +11,27 @@ function makeData() { function run(svg, data, Plottable){ "use strict"; - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Linear(); - var colorScale = new Plottable.Scale.Color(); - - var xAxis = new Plottable.Axis.Category(xScale, "bottom"); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - - var clusteredPlot = new Plottable.Plot.ClusteredBar(xScale, yScale, true) - .addDataset(data[0]) - .addDataset(data[1]) - .addDataset(data[2]) - .project("x", "month", xScale) - .project("y", "avg", yScale) - .project("label", "avg") - .project("fill", "city", colorScale); - - var legend = new Plottable.Component.Legend(colorScale); - var title = new Plottable.Component.TitleLabel("Average Rainfall in Different Cities between 2013-2014", "horizontal" ); - var yUnitLabel = new Plottable.Component.AxisLabel("Inches", "left" ); - - var chart = new Plottable.Component.Table([ + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Linear(); + var colorScale = new Plottable.Scales.Color(); + + var xAxis = new Plottable.Axes.Category(xScale, "bottom"); + var yAxis = new Plottable.Axes.Numeric(yScale, "left"); + + var clusteredPlot = new Plottable.Plots.ClusteredBar(xScale, yScale, true) + .addDataset(new Plottable.Dataset(data[0])) + .addDataset(new Plottable.Dataset(data[1])) + .addDataset(new Plottable.Dataset(data[2])) + .x(function(d) { return d.month; }, xScale) + .y(function(d) { return d.avg; }, yScale) + .attr("label", function(d) { return d.avg; }) + .attr("fill", function(d) { return d.city; }, colorScale); + + var legend = new Plottable.Components.Legend(colorScale); + var title = new Plottable.Components.Label("Average Rainfall in Different Cities between 2013-2014", "horizontal" ).classed("title-label", true); + var yUnitLabel = new Plottable.Components.Label("Inches", "left" ).classed("axis-label", true); + + var chart = new Plottable.Components.Table([ [null , null , title ], [null , null , legend ], [yUnitLabel , yAxis , clusteredPlot], diff --git a/quicktests/overlaying/tests/realistic/rainfall_VerticalBar.js b/quicktests/overlaying/tests/realistic/rainfall_VerticalBar.js deleted file mode 100644 index 91c110f82e..0000000000 --- a/quicktests/overlaying/tests/realistic/rainfall_VerticalBar.js +++ /dev/null @@ -1,73 +0,0 @@ -function makeData() { - "use strict"; - - var data1 = [{month: "January", avg: 2.75, city: "Palo Alto"}, {month: "February", avg: 3.07, city: "Palo Alto"},{month: "March", avg: 2.26, city: "Palo Alto"},{month: "April", avg: 0.98, city: "Palo Alto"},{month: "May", avg: 0.47, city: "Palo Alto"},{month: "June", avg: 0.1, city: "Palo Alto"},{month: "July", avg: 0.02, city: "Palo Alto"},{month: "August", avg: 0.01, city: "Palo Alto"},{month: "September", avg: 0.18, city: "Palo Alto"},{month: "October", avg: 0.64, city: "Palo Alto"},{month: "November", avg: 1.64, city: "Palo Alto"},{month: "December", avg: 2.56, city: "Palo Alto"}]; - var data2 = [{month: "January", avg: 4.21, city: "San Francisco"}, {month: "February", avg: 4.10, city: "San Francisco"},{month: "March", avg: 2.74, city: "San Francisco"},{month: "April", avg: 1.18, city: "San Francisco"},{month: "May", avg: 0.72, city: "San Francisco"},{month: "June", avg: 0.15, city: "San Francisco"},{month: "July", avg: 0.01, city: "San Francisco"},{month: "August", avg: 0.04, city: "San Francisco"},{month: "September", avg: 0.19, city: "San Francisco"},{month: "October", avg: 0.94, city: "San Francisco"},{month: "November", avg: 2.50, city: "San Francisco"},{month: "December", avg: 4.00, city: "San Francisco"}]; - var data3 = [{month: "January", avg: 2.99, city: "San Jose"}, {month: "February", avg: 3.32, city: "San Jose"},{month: "March", avg: 2.04, city: "San Jose"},{month: "April", avg: 1.06, city: "San Jose"},{month: "May", avg: 0.39, city: "San Jose"},{month: "June", avg: 0.09, city: "San Jose"},{month: "July", avg: 0.00, city: "San Jose"},{month: "August", avg: 0.0, city: "San Jose"},{month: "September", avg: 0.23, city: "San Jose"},{month: "October", avg: 0.78, city: "San Jose"},{month: "November", avg: 1.88, city: "San Jose"},{month: "December", avg: 2.12, city: "San Jose"}]; - - return [data1, data2, data3]; -} - -function run(svg, data, Plottable){ - "use strict"; - - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Linear(); - var colorScale = new Plottable.Scale.Color(); - - yScale.domain([0, 5.5]).ticks(5); - - var xAxis = new Plottable.Axis.Category(xScale, "bottom"); - var yAxis1 = new Plottable.Axis.Numeric(yScale, "left"); - var yAxis2 = new Plottable.Axis.Numeric(yScale, "left"); - var yAxis3 = new Plottable.Axis.Numeric(yScale, "left"); - - - var paloAltoBar = new Plottable.Plot.Bar(xScale, yScale, true) - .addDataset(data[0]) - .animate(true) - .project("x", "month", xScale) - .project("y", "avg", yScale) - .project("fill", "city", colorScale); - - var sanFranciscoBar = new Plottable.Plot.Bar(xScale, yScale, true) - .addDataset(data[1]) - .animate(true) - .project("x", "month", xScale) - .project("y", "avg", yScale) - .project("fill", "city", colorScale); - - var sanJoseBar = new Plottable.Plot.Bar(xScale, yScale, true) - .addDataset(data[2]) - .animate(true) - .project("x", "month", xScale) - .project("y", "avg", yScale) - .project("fill", "city", colorScale); - - var legend = new Plottable.Component.Legend(colorScale); - var title = new Plottable.Component.TitleLabel("Average Rainfall in Different Cities between 2013-2014", "horizontal" ); - var yUnitLabel = new Plottable.Component.AxisLabel("Inches", "left" ); - - - legend.xAlign("right"); - - var g1 = new Plottable.Component.Gridlines(null, yScale); - var g2 = new Plottable.Component.Gridlines(null, yScale); - var g3 = new Plottable.Component.Gridlines(null, yScale); - var bar1 = g1.below(paloAltoBar); - var bar2 = g2.below(sanFranciscoBar); - var bar3 = g3.below(sanJoseBar); - - var chart = new Plottable.Component.Table([ - [null , title ], - [null , legend], - [yAxis1 , bar1 ], - [yAxis2 , bar2 ], - [yAxis3 , bar3 ], - [null , xAxis ]]); - - var finalchart = new Plottable.Component.Table([ - [yUnitLabel, chart]]); - - finalchart.renderTo(svg); -} diff --git a/quicktests/overlaying/tests/realistic/stocks.js b/quicktests/overlaying/tests/realistic/stocks.js index 98ea4d87d1..395820b9dd 100644 --- a/quicktests/overlaying/tests/realistic/stocks.js +++ b/quicktests/overlaying/tests/realistic/stocks.js @@ -44,57 +44,57 @@ function run(svg, data, Plottable) { }); } - var xScale = new Plottable.Scale.Time(); - var xAxis = new Plottable.Axis.Time(xScale, "bottom"); - var xAxisTop = new Plottable.Axis.Time(xScale, "top"); + var xScale = new Plottable.Scales.Time(); + var xAxis = new Plottable.Axes.Time(xScale, "bottom"); + var xAxisTop = new Plottable.Axes.Time(xScale, "top"); - var yScale_aapl = new Plottable.Scale.Linear(); - var yAxis_aapl = new Plottable.Axis.Numeric(yScale_aapl, "right").showEndTickLabels(true); - var label_aapl = new Plottable.Component.AxisLabel("AAPL", "right"); + var yScale_aapl = new Plottable.Scales.Linear(); + var yAxis_aapl = new Plottable.Axes.Numeric(yScale_aapl, "right").showEndTickLabels(true); + var label_aapl = new Plottable.Components.Label("AAPL", "right").classed("axis-label", true); - var yScale_goog = new Plottable.Scale.Linear(); - var yAxis_goog = new Plottable.Axis.Numeric(yScale_goog, "left").xAlign("right").showEndTickLabels(true); - var label_goog = new Plottable.Component.AxisLabel("GOOG", "left"); + var yScale_goog = new Plottable.Scales.Linear(); + var yAxis_goog = new Plottable.Axes.Numeric(yScale_goog, "left").xAlignment("right").showEndTickLabels(true); + var label_goog = new Plottable.Components.Label("GOOG", "left").classed("axis-label", true); - var colorScale = new Plottable.Scale.Color(); + var colorScale = new Plottable.Scales.Color(); var aaplSource = new Plottable.Dataset(aapl, {name: "AAPL"} ); var googSource = new Plottable.Dataset(goog, {name: "GOOG"} ); - var line_aapl = new Plottable.Plot.Line(xScale, yScale_aapl).animate(true) - .addDataset("aapl", aaplSource) - .project("x", "Date", xScale) - .project("y", "Adj Close", yScale_aapl) - .project("stroke", function(d, i, m) { return m.name; }, colorScale) + var line_aapl = new Plottable.Plots.Line(xScale, yScale_aapl).animate(true) + .addDataset(aaplSource) + .x(function(d) { return d.Date; }, xScale) + .y(function(d) { return d["Adj Close"]; }, yScale_aapl) + .attr("stroke", function(d, i, dataset) { return dataset.metadata().name; }, colorScale) .automaticallyAdjustYScaleOverVisiblePoints(true); - var line_goog = new Plottable.Plot.Line(xScale, yScale_goog).animate(true) - .addDataset("goog", googSource) - .project("x", "Date", xScale) - .project("y", "Adj Close", yScale_goog) - .project("stroke", function(d, i, m) { return m.name; }, colorScale) + var line_goog = new Plottable.Plots.Line(xScale, yScale_goog).animate(true) + .addDataset(googSource) + .x(function(d) { return d.Date; }, xScale) + .y(function(d) { return d["Adj Close"]; }, yScale_goog) + .attr("stroke", function(d, i, dataset) { return dataset.metadata().name; }, colorScale) .automaticallyAdjustYScaleOverVisiblePoints(true); // should be one line plot, pending #917 - var legend = new Plottable.Component.Legend(colorScale); + var legend = new Plottable.Components.Legend(colorScale); legend.maxEntriesPerRow(1); - legend.yAlign("top").xOffset(-5); - var plotArea = new Plottable.Component.Group([line_aapl, line_goog, legend]); + legend.yAlignment("top"); + var plotArea = new Plottable.Components.Group([line_aapl, line_goog, legend]); - var yScale_diff = new Plottable.Scale.Linear(); - var yAxis_diff = new Plottable.Axis.Numeric(yScale_diff, "left"); + var yScale_diff = new Plottable.Scales.Linear(); + var yAxis_diff = new Plottable.Axes.Numeric(yScale_diff, "left"); var DAY_MILLIS = 24 * 60 * 60 * 1000; - var bar_diff = new Plottable.Plot.Bar(xScale, yScale_diff, true).animate(true) - .addDataset(diffData) - .project("x", "Date", xScale) - .project("y", "net change", yScale_diff) - .project("width", function() { return xScale.scale(DAY_MILLIS) - xScale.scale(0); }) - .project("fill", function(d) { + var bar_diff = new Plottable.Plots.Bar(xScale, yScale_diff, true).animate(true) + .addDataset(new Plottable.Dataset(diffData)) + .x(function(d) { return d.Date; }, xScale) + .y(function(d) { return d["net change"]; }, yScale_diff) + .attr("width", function() { return xScale.scale(DAY_MILLIS) - xScale.scale(0); }) + .attr("fill", function(d) { return d["net change"] > 0 ? Plottable.Core.Colors.FERN : Plottable.Core.Colors.CERISE_RED; }); - var table = new Plottable.Component.Table([ + var table = new Plottable.Components.Table([ [null , null , xAxisTop, null , null ], [label_goog, yAxis_goog, plotArea, yAxis_aapl, label_aapl], [null , null , bar_diff, null , null ], @@ -104,15 +104,14 @@ function run(svg, data, Plottable) { table.renderTo(svg); - var pzi = new Plottable.Interaction.PanZoom(xScale, null); - plotArea.registerInteraction(pzi); - plotArea.registerInteraction( - new Plottable.Interaction.Key() - .on(65, function() { + var pzi = new Plottable.Interactions.PanZoom(xScale, null); + pzi.attachTo(plotArea); + var keyInteraction = new Plottable.Interactions.Key(); + keyInteraction.onKey(65, function() { xScale.autoDomain(); pzi.resetZoom(); - }) - ); + }); + keyInteraction.attachTo(plotArea); }); }); diff --git a/quicktests/overlaying/tests/realistic/symbols.js b/quicktests/overlaying/tests/realistic/symbols.js index 9d81b6944b..34a1b50b9f 100644 --- a/quicktests/overlaying/tests/realistic/symbols.js +++ b/quicktests/overlaying/tests/realistic/symbols.js @@ -12,60 +12,82 @@ function run(svg, data, Plottable){ deep_copy(data[0], d); var dataset = new Plottable.Dataset(d); - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - - var plot = new Plottable.Plot.Scatter(xScale, yScale); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); + var xAxis = new Plottable.Axes.Numeric(xScale, "bottom"); + var yAxis = new Plottable.Axes.Numeric(yScale, "left"); + + var plot = new Plottable.Plots.Scatter(xScale, yScale); plot.addDataset(dataset); - plot.project("x", "x", xScale) - .project("y", "y", yScale) - .project("r", 15) - .project("symbol", Plottable.SymbolGenerators.d3Symbol( function (datum) {return datum.y>0?(datum.x>0?"triangle-up":"circle"):(datum.x>0?"cross":"triangle-down");})) - .project("fill", function(datum){return datum.y>0?(datum.x>0?"#00bb00":"#bbbbbb"):(datum.x>0?"#bbbbbb":"#bb0000");}); - - var title = new Plottable.Component.Label("n = new point, d = delete point"); - var cs = new Plottable.Scale.Color(); - var legend = new Plottable.Component.Legend(cs); - cs.domain(["x+y+", "x+y-", "x-y+", "x-y-"]); + + var triangleUpFactory = Plottable.SymbolFactories.triangleUp(); + var circleFactory = Plottable.SymbolFactories.circle(); + var crossFactory = Plottable.SymbolFactories.cross(); + var triangleDownFactory = Plottable.SymbolFactories.triangleDown(); + var fourSymbolAccessor = function (datum) { + if (datum.y > 0) { + return (datum.x > 0) ? triangleUpFactory : circleFactory; + } else { + return (datum.x > 0) ? crossFactory : triangleDownFactory; + } + }; + var symbolSize = 15; + plot.x(function(d) { return d.x; }, xScale) + .y(function(d) { return d.y; }, yScale) + .size(symbolSize) + .symbol(fourSymbolAccessor) + .attr("fill", function(datum){return datum.y>0?(datum.x>0?"#00bb00":"#bbbbbb"):(datum.x>0?"#bbbbbb":"#bb0000");}); + + var title = new Plottable.Components.Label("n = new point, d = delete point"); + var cs = new Plottable.Scales.Color(); + var legend = new Plottable.Components.Legend(cs); + cs.domain(["x+y+", "x+y-", "x-y+", "x-y-"]); cs.range(["#00bb00", "#bbbbbb", "#bbbbbb", "#bb0000"]); - - legend.symbolGenerator(Plottable.SymbolGenerators.d3Symbol( function (d, i) { - if(d === "x+y+"){ return "triangle-up";} - if(d === "x+y-"){ return "cross";} - if(d === "x-y+") { return "circle";} - if(d === "x-y-"){ return "triangle-down";} - })); - - var table = new Plottable.Component.Table([[null, title, null], + + legend.symbolFactoryAccessor(function (d, i) { + if(d === "x+y+") { return triangleUpFactory; } + if(d === "x+y-") { return crossFactory; } + if(d === "x-y+") { return circleFactory; } + if(d === "x-y-") { return triangleDownFactory; } + }); + + var table = new Plottable.Components.Table([[null, title, null], [yAxis, plot, legend], [null, xAxis, null]]); table.renderTo(svg); - - var hover = new Plottable.Interaction.Hover(); - hover.onHoverOver(function(hoverData) { - var xString = hoverData.data[0].x.toFixed(2); - var yString = hoverData.data[0].y.toFixed(2); - title.text("[ " + xString + ", " + yString + " ]"); + + var pointer = new Plottable.Interactions.Pointer(); + var defaultTitleText = "n = new point, d = delete last point, c = log points"; + pointer.onPointerMove(function(p) { + var cpd = plot.getClosestPlotData(p); + if (cpd.data.length > 0) { + var dist = Math.sqrt(Math.pow((p.x - cpd.pixelPoints[0].x), 2) + Math.pow((p.y - cpd.pixelPoints[0].y), 2)); + if (dist < symbolSize / 2) { + var xString = cpd.data[0].x.toFixed(2); + var yString = cpd.data[0].y.toFixed(2); + title.text("[ " + xString + ", " + yString + " ]"); + return; + } + } + title.text(defaultTitleText); }); - hover.onHoverOut(function(hoverData) { - title.text("n = new point, d = delete point, c = log points"); + pointer.onPointerExit(function() { + title.text(defaultTitleText); }); - - var key = new Plottable.Interaction.Key(); - key.on(78, function(keyData){ + + var key = new Plottable.Interactions.Key(); + key.onKey(78, function(keyData){ d.push({x: Math.random() - 0.5, y: Math.random() - 0.5}); dataset.data(d); }); - key.on(68, function(keyData){ + key.onKey(68, function(keyData){ if(d.length > 0){ d.splice(d.length-1,1); dataset.data(d); - } + } }); - - plot.registerInteraction(hover); - plot.registerInteraction(key); + + pointer.attachTo(plot); + key.attachTo(plot); } diff --git a/quicktests/overlaying/tests/realistic/timeAxis_area_bar.js b/quicktests/overlaying/tests/realistic/timeAxis_area_bar.js deleted file mode 100644 index 5331dc6eae..0000000000 --- a/quicktests/overlaying/tests/realistic/timeAxis_area_bar.js +++ /dev/null @@ -1,115 +0,0 @@ - -function makeData() { - "use strict"; - - var data1 = [ - {date: "1/1/2015", y: 100000, type: "A"}, - {date: "1/1/2016", y: 200000, type: "A"}, - {date: "1/1/2017", y: 250000, type: "A"}, - {date: "1/1/2018", y: 220000, type: "A"}, - {date: "1/1/2019", y: 300000, type: "A"} - ]; - var data2 = [ - {date: "1/1/2015", y: 100000, type: "B"}, - {date: "1/1/2016", y: 200000, type: "B"}, - {date: "1/1/2017", y: 250000, type: "B"}, - {date: "1/1/2018", y: 220000, type: "B"}, - {date: "1/1/2019", y: 300000, type: "B"} - ]; - var data3 = [ - {date: "1/1/2015", y: 100000, type: "C"}, - {date: "1/1/2016", y: 200000, type: "C"}, - {date: "1/1/2017", y: 250000, type: "C"}, - {date: "1/1/2018", y: 220000, type: "C"}, - {date: "1/1/2019", y: 300000, type: "C"} - ]; - var data4 = [ - {date: "1/1/2015", y: 100000, type: "D"}, - {date: "1/1/2016", y: 200000, type: "D"}, - {date: "1/1/2017", y: 250000, type: "D"}, - {date: "1/1/2018", y: 220000, type: "D"}, - {date: "1/1/2019", y: 300000, type: "D"} - ]; - var data5 = [ - {date: "1/1/2015", y: 100000, type: "E"}, - {date: "1/1/2016", y: 200000, type: "E"}, - {date: "1/1/2017", y: 250000, type: "E"}, - {date: "1/1/2018", y: 220000, type: "E"}, - {date: "1/1/2019", y: 300000, type: "E"} - ]; - -return [data1, data2, data3, data4, data5]; - -} - -function run(svg, data, Plottable) { - "use strict"; - - var formatter = d3.time.format("%Y"); - var xScale = new Plottable.Scale.Time().numTicks(5); - var yScale1 = new Plottable.Scale.Linear(); - var yScale2 = new Plottable.Scale.Linear(); - - var xAxis1 = new Plottable.Axis.Numeric(xScale, "bottom", formatter); - var xAxis2 = new Plottable.Axis.Numeric(xScale, "bottom", formatter); - var xAxis3 = new Plottable.Axis.Numeric(xScale, "bottom", formatter); - var xAxis4 = new Plottable.Axis.Numeric(xScale, "bottom", formatter); - - var yAxis1 = new Plottable.Axis.Numeric(yScale1, "left"); - var yAxis2 = new Plottable.Axis.Numeric(yScale1, "left"); - var yAxis3 = new Plottable.Axis.Numeric(yScale2, "left"); - var yAxis4 = new Plottable.Axis.Numeric(yScale2, "left"); - - var timeFormat = function (data) { return d3.time.format("%m/%d/%Y").parse(data.date);}; - var colorScale = new Plottable.Scale.Color(); - var legend = new Plottable.Component.Legend(colorScale).xAlign("center"); - var title = new Plottable.Component.TitleLabel("Area & Bar on Time Axes"); - - var areaPlot = new Plottable.Plot.Area(xScale, yScale1) - .addDataset(data[0]) - .project("x", timeFormat, xScale) - .project("y", "y", yScale1); - - var barPlot = new Plottable.Plot.Bar(xScale, yScale1, true) - .addDataset(data[0]) - .project("x", timeFormat, xScale) - .project("y", "y", yScale1) - .project("width", 40) - .barAlignment("center"); - - var stackedArea = new Plottable.Plot.StackedArea(xScale, yScale2) - .project("x", timeFormat, xScale) - .project("y", "y", yScale2) - .project("fill", "type", colorScale) - .addDataset(data[0]) - .addDataset(data[1]) - .addDataset(data[2]) - .addDataset(data[3]) - .addDataset(data[4]); - - var stackedBar = new Plottable.Plot.StackedBar(xScale, yScale2) - .project("x", timeFormat, xScale) - .project("y", "y", yScale2) - .project("fill", "type", colorScale) - .project("width", 40) - .addDataset(data[0]) - .addDataset(data[1]) - .addDataset(data[2]) - .addDataset(data[3]) - .addDataset(data[4]); - - var upperChart = new Plottable.Component.Table([ - [yAxis1, areaPlot, yAxis2, barPlot], - [null, xAxis1, null, xAxis2] - ]); - - var lowerChart = new Plottable.Component.Table([ - [yAxis3, stackedArea, yAxis4, stackedBar], - [null, xAxis3, null, xAxis4] - ]); - - var chart = new Plottable.Component.Table([[title], [legend], [upperChart], [lowerChart]]); - - chart.renderTo(svg); - -} diff --git a/src/animators/baseAnimator.ts b/src/animators/baseAnimator.ts index 314fa15d29..d13dc226cc 100644 --- a/src/animators/baseAnimator.ts +++ b/src/animators/baseAnimator.ts @@ -1,7 +1,7 @@ /// module Plottable { -export module Animator { +export module Animators { /** * The base animator implementation with easing, duration, and delay. @@ -88,7 +88,7 @@ export module Animator { * @returns {Default} The calling Default Animator. */ public duration(duration: number): Base; - public duration(duration?: number): any{ + public duration(duration?: number): any { if (duration == null) { return this._duration; } else { @@ -110,7 +110,7 @@ export module Animator { * @returns {Default} The calling Default Animator. */ public delay(delay: number): Base; - public delay(delay?: number): any{ + public delay(delay?: number): any { if (delay == null) { return this._delay; } else { @@ -132,7 +132,7 @@ export module Animator { * @returns {Default} The calling Default Animator. */ public easing(easing: string): Base; - public easing(easing?: string): any{ + public easing(easing?: string): any { if (easing == null) { return this._easing; } else { diff --git a/src/animators/movingRectAnimator.ts b/src/animators/movingRectAnimator.ts index 23ddd4ded7..c37218999b 100644 --- a/src/animators/movingRectAnimator.ts +++ b/src/animators/movingRectAnimator.ts @@ -1,7 +1,7 @@ /// module Plottable { -export module Animator { +export module Animators { /** * A child class of RectAnimator that will move the rectangle diff --git a/src/animators/nullAnimator.ts b/src/animators/nullAnimator.ts index d5dbae8e68..091d8890f0 100644 --- a/src/animators/nullAnimator.ts +++ b/src/animators/nullAnimator.ts @@ -1,7 +1,7 @@ /// module Plottable { -export module Animator { +export module Animators { /** * An animator implementation with no animation. The attributes are diff --git a/src/animators/rectAnimator.ts b/src/animators/rectAnimator.ts index 7e4bbfe0b6..a65acfbfac 100644 --- a/src/animators/rectAnimator.ts +++ b/src/animators/rectAnimator.ts @@ -1,7 +1,7 @@ /// module Plottable { -export module Animator { +export module Animators { /** * The default animator implementation with easing, duration, and delay. @@ -36,7 +36,9 @@ export module Animator { } var movingAttrProjector = attrToProjector[this._getMovingAttr()]; var growingAttrProjector = attrToProjector[this._getGrowingAttr()]; - return (d: any, i: number, u: any, m: Plot.PlotMetadata) => movingAttrProjector(d, i, u, m) + growingAttrProjector(d, i, u, m); + return (d: any, i: number, dataset: Dataset, m: Plots.PlotMetadata) => { + return movingAttrProjector(d, i, dataset, m) + growingAttrProjector(d, i, dataset, m); + }; } private _getGrowingAttr() { diff --git a/src/components/abstractComponentContainer.ts b/src/components/abstractComponentContainer.ts deleted file mode 100644 index dab08444e9..0000000000 --- a/src/components/abstractComponentContainer.ts +++ /dev/null @@ -1,93 +0,0 @@ -/// - -module Plottable { -export module Component { - /* - * An abstract ComponentContainer class to encapsulate Table and ComponentGroup's shared functionality. - * It will not do anything if instantiated directly. - */ - export class AbstractComponentContainer extends AbstractComponent { - private _components: AbstractComponent[] = []; - - public _anchor(element: D3.Selection) { - super._anchor(element); - this.components().forEach((c) => c._anchor(this._content)); - } - - public _render() { - this._components.forEach((c) => c._render()); - } - - public _removeComponent(c: AbstractComponent) { - var removeIndex = this._components.indexOf(c); - if (removeIndex >= 0) { - this.components().splice(removeIndex, 1); - this._invalidateLayout(); - } - } - - public _addComponent(c: AbstractComponent, prepend = false): boolean { - if (!c || this._components.indexOf(c) >= 0) { - return false; - } - - if (prepend) { - this.components().unshift(c); - } else { - this.components().push(c); - } - c._parent(this); - if (this._isAnchored) { - c._anchor(this._content); - } - this._invalidateLayout(); - return true; - } - - /** - * Returns a list of components in the ComponentContainer. - * - * @returns {Component[]} the contained Components - */ - public components(): AbstractComponent[] { - return this._components; - } - - /** - * Returns true iff the ComponentContainer is empty. - * - * @returns {boolean} Whether the calling ComponentContainer is empty. - */ - public empty() { - return this._components.length === 0; - } - - /** - * Detaches all components contained in the ComponentContainer, and - * empties the ComponentContainer. - * - * @returns {ComponentContainer} The calling ComponentContainer - */ - public detachAll() { - // Calling c.remove() will mutate this._components because the component will call this._parent._removeComponent(this) - // Since mutating an array while iterating over it is dangerous, we instead iterate over a copy generated by Arr.slice() - this.components().slice().forEach((c: AbstractComponent) => c.detach()); - return this; - } - - public remove() { - super.remove(); - this.components().slice().forEach((c: AbstractComponent) => c.remove()); - } - - public _useLastCalculatedLayout(): boolean; - public _useLastCalculatedLayout(calculated: boolean) : AbstractComponent; - public _useLastCalculatedLayout(calculated?: boolean) : any { - if (calculated != null) { - this.components().slice().forEach((c: AbstractComponent) => c._useLastCalculatedLayout(calculated)); - } - return super._useLastCalculatedLayout(calculated); - } - } -} -} diff --git a/src/components/axes/abstractAxis.ts b/src/components/axes/axis.ts similarity index 79% rename from src/components/axes/abstractAxis.ts rename to src/components/axes/axis.ts index 1f5efe0d02..40a6fdc441 100644 --- a/src/components/axes/abstractAxis.ts +++ b/src/components/axes/axis.ts @@ -1,8 +1,7 @@ /// module Plottable { -export module Axis { - export class AbstractAxis extends Component.AbstractComponent { + export class Axis extends Component { /** * The css class applied to each end tick mark (the line on the end tick). */ @@ -18,7 +17,7 @@ export module Axis { protected _tickMarkContainer: D3.Selection; protected _tickLabelContainer: D3.Selection; protected _baseline: D3.Selection; - protected _scale: Scale.AbstractScale; + protected _scale: Scale; private _formatter: Formatter; private _orientation: string; protected _computedWidth: number; @@ -28,6 +27,7 @@ export module Axis { private _tickLabelPadding = 10; private _gutter = 15; private _showEndTickLabels = false; + private _rescaleCallback: ScaleCallback>; /** * Constructs an axis. An axis is a wrapper around a scale for rendering. @@ -40,11 +40,11 @@ export module Axis { * @param {Formatter} Data is passed through this formatter before being * displayed. */ - constructor(scale: Scale.AbstractScale, orientation: string, formatter = Formatters.identity()) { + constructor(scale: Scale, orientation: string, formatter = Formatters.identity()) { super(); if (scale == null || orientation == null) { throw new Error("Axis requires a scale and orientation"); } this._scale = scale; - this.orient(orientation); + this.orientation(orientation); this._setDefaultAlignment(); this.classed("axis", true); if (this._isHorizontal()) { @@ -55,12 +55,13 @@ export module Axis { this.formatter(formatter); - this._scale.broadcaster.registerListener(this, () => this._rescale()); + this._rescaleCallback = (scale) => this._rescale(); + this._scale.onUpdate(this._rescaleCallback); } - public remove() { - super.remove(); - this._scale.broadcaster.deregisterListener(this); + public destroy() { + super.destroy(); + this._scale.offUpdate(this._rescaleCallback); } protected _isHorizontal() { @@ -79,7 +80,7 @@ export module Axis { return this._computedHeight; } - public _requestedSpace(offeredWidth: number, offeredHeight: number): _SpaceRequest { + public requestedSpace(offeredWidth: number, offeredHeight: number): SpaceRequest { var requestedWidth = 0; var requestedHeight = 0; @@ -96,41 +97,40 @@ export module Axis { } return { - width : requestedWidth, - height: requestedHeight, - wantsWidth: !this._isHorizontal() && offeredWidth < requestedWidth, - wantsHeight: this._isHorizontal() && offeredHeight < requestedHeight + minWidth: requestedWidth, + minHeight: requestedHeight }; } - public _isFixedHeight() { + public fixedHeight() { return this._isHorizontal(); } - public _isFixedWidth() { + public fixedWidth() { return !this._isHorizontal(); } protected _rescale() { - // default implementation; subclasses may call _invalidateLayout() here - this._render(); + // default implementation; subclasses may call redraw() here + this.render(); } - public _computeLayout(offeredXOrigin?: number, offeredYOrigin?: number, availableWidth?: number, availableHeight?: number) { - super._computeLayout(offeredXOrigin, offeredYOrigin, availableWidth, availableHeight); + public computeLayout(origin?: Point, availableWidth?: number, availableHeight?: number) { + super.computeLayout(origin, availableWidth, availableHeight); if (this._isHorizontal()) { this._scale.range([0, this.width()]); } else { this._scale.range([this.height(), 0]); } + return this; } protected _setup() { super._setup(); this._tickMarkContainer = this._content.append("g") - .classed(AbstractAxis.TICK_MARK_CLASS + "-container", true); + .classed(Axis.TICK_MARK_CLASS + "-container", true); this._tickLabelContainer = this._content.append("g") - .classed(AbstractAxis.TICK_LABEL_CLASS + "-container", true); + .classed(Axis.TICK_LABEL_CLASS + "-container", true); this._baseline = this._content.append("line").classed("baseline", true); } @@ -138,21 +138,22 @@ export module Axis { * Function for generating tick values in data-space (as opposed to pixel values). * To be implemented by subclasses. */ - protected _getTickValues(): any[] { + protected _getTickValues(): D[] { return []; } - public _doRender() { + public renderImmediately() { var tickMarkValues = this._getTickValues(); - var tickMarks = this._tickMarkContainer.selectAll("." + AbstractAxis.TICK_MARK_CLASS).data(tickMarkValues); - tickMarks.enter().append("line").classed(AbstractAxis.TICK_MARK_CLASS, true); + var tickMarks = this._tickMarkContainer.selectAll("." + Axis.TICK_MARK_CLASS).data(tickMarkValues); + tickMarks.enter().append("line").classed(Axis.TICK_MARK_CLASS, true); tickMarks.attr(this._generateTickMarkAttrHash()); - d3.select(tickMarks[0][0]).classed(AbstractAxis.END_TICK_MARK_CLASS, true) + d3.select(tickMarks[0][0]).classed(Axis.END_TICK_MARK_CLASS, true) .attr(this._generateTickMarkAttrHash(true)); - d3.select(tickMarks[0][tickMarkValues.length - 1]).classed(AbstractAxis.END_TICK_MARK_CLASS, true) + d3.select(tickMarks[0][tickMarkValues.length - 1]).classed(Axis.END_TICK_MARK_CLASS, true) .attr(this._generateTickMarkAttrHash(true)); tickMarks.exit().remove(); this._baseline.attr(this._generateBaselineAttrHash()); + return this; } protected _generateBaselineAttrHash() { @@ -163,7 +164,7 @@ export module Axis { y2: 0 }; - switch(this._orientation) { + switch (this._orientation) { case "bottom": baselineAttrHash.x2 = this.width(); break; @@ -207,7 +208,7 @@ export module Axis { var tickLength = isEndTickMark ? this._endTickLength : this._tickLength; - switch(this._orientation) { + switch (this._orientation) { case "bottom": tickMarkAttrHash["y2"] = tickLength; break; @@ -230,28 +231,28 @@ export module Axis { return tickMarkAttrHash; } - public _invalidateLayout() { + public redraw() { this._computedWidth = null; this._computedHeight = null; - super._invalidateLayout(); + return super.redraw(); } protected _setDefaultAlignment() { - switch(this._orientation) { + switch (this._orientation) { case "bottom": - this.yAlign("top"); + this.yAlignment("top"); break; case "top": - this.yAlign("bottom"); + this.yAlignment("bottom"); break; case "left": - this.xAlign("right"); + this.xAlignment("right"); break; case "right": - this.xAlign("left"); + this.xAlignment("left"); break; } } @@ -271,13 +272,13 @@ export module Axis { * @param {Formatter} formatter If provided, data will be passed though `formatter(data)`. * @returns {Axis} The calling Axis. */ - public formatter(formatter: Formatter): AbstractAxis; + public formatter(formatter: Formatter): Axis; public formatter(formatter?: Formatter): any { if (formatter === undefined) { return this._formatter; } this._formatter = formatter; - this._invalidateLayout(); + this.redraw(); return this; } @@ -293,7 +294,7 @@ export module Axis { * @param {number} length If provided, length of each tick. * @returns {Axis} The calling Axis. */ - public tickLength(length: number): AbstractAxis; + public tickLength(length: number): Axis; public tickLength(length?: number): any { if (length == null) { return this._tickLength; @@ -302,7 +303,7 @@ export module Axis { throw new Error("tick length must be positive"); } this._tickLength = length; - this._invalidateLayout(); + this.redraw(); return this; } } @@ -319,7 +320,7 @@ export module Axis { * @param {number} length If provided, the length of the end ticks. * @returns {BaseAxis} The calling Axis. */ - public endTickLength(length: number): AbstractAxis; + public endTickLength(length: number): Axis; public endTickLength(length?: number): any { if (length == null) { return this._endTickLength; @@ -328,7 +329,7 @@ export module Axis { throw new Error("end tick length must be positive"); } this._endTickLength = length; - this._invalidateLayout(); + this.redraw(); return this; } } @@ -354,7 +355,7 @@ export module Axis { * @param {number} padding If provided, the desired padding. * @returns {Axis} The calling Axis. */ - public tickLabelPadding(padding: number): AbstractAxis; + public tickLabelPadding(padding: number): Axis; public tickLabelPadding(padding?: number): any { if (padding == null) { return this._tickLabelPadding; @@ -363,7 +364,7 @@ export module Axis { throw new Error("tick label padding must be positive"); } this._tickLabelPadding = padding; - this._invalidateLayout(); + this.redraw(); return this; } } @@ -383,7 +384,7 @@ export module Axis { * @param {number} size If provided, the desired gutter. * @returns {Axis} The calling Axis. */ - public gutter(size: number): AbstractAxis; + public gutter(size: number): Axis; public gutter(size?: number): any { if (size == null) { return this._gutter; @@ -392,7 +393,7 @@ export module Axis { throw new Error("gutter size must be positive"); } this._gutter = size; - this._invalidateLayout(); + this.redraw(); return this; } } @@ -402,7 +403,7 @@ export module Axis { * * @returns {number} the current orientation. */ - public orient(): string; + public orientation(): string; /** * Sets the orientation of the Axis. * @@ -410,12 +411,12 @@ export module Axis { * (top/bottom/left/right). * @returns {Axis} The calling Axis. */ - public orient(newOrientation: string): AbstractAxis; - public orient(newOrientation?: string): any { - if (newOrientation == null) { + public orientation(orientation: string): Axis; + public orientation(orientation?: string): any { + if (orientation == null) { return this._orientation; } else { - var newOrientationLC = newOrientation.toLowerCase(); + var newOrientationLC = orientation.toLowerCase(); if (newOrientationLC !== "top" && newOrientationLC !== "bottom" && newOrientationLC !== "left" && @@ -423,7 +424,7 @@ export module Axis { throw new Error("unsupported orientation"); } this._orientation = newOrientationLC; - this._invalidateLayout(); + this.redraw(); return this; } } @@ -444,15 +445,14 @@ export module Axis { * labels. * @returns {Axis} The calling Axis. */ - public showEndTickLabels(show: boolean): AbstractAxis; + public showEndTickLabels(show: boolean): Axis; public showEndTickLabels(show?: boolean): any { if (show == null) { return this._showEndTickLabels; } this._showEndTickLabels = show; - this._render(); + this.render(); return this; } } } -} diff --git a/src/components/axes/categoryAxis.ts b/src/components/axes/categoryAxis.ts index 6799e5ac71..828dc90744 100644 --- a/src/components/axes/categoryAxis.ts +++ b/src/components/axes/categoryAxis.ts @@ -1,8 +1,8 @@ /// module Plottable { -export module Axis { - export class Category extends AbstractAxis { +export module Axes { + export class Category extends Axis { private _tickLabelAngle = 0; private _measurer: SVGTypewriter.Measurers.CacheCharacterMeasurer; private _wrapper: SVGTypewriter.Wrappers.SingleLineWrapper; @@ -20,7 +20,7 @@ export module Axis { * @param {string} orientation The orientation of the Axis (top/bottom/left/right) (default = "bottom"). * @param {Formatter} formatter The Formatter for the Axis (default Formatters.identity()) */ - constructor(scale: Scale.Category, orientation = "bottom", formatter = Formatters.identity()) { + constructor(scale: Scales.Category, orientation = "bottom", formatter = Formatters.identity()) { super(scale, orientation, formatter); this.classed("category-axis", true); } @@ -33,40 +33,38 @@ export module Axis { } protected _rescale() { - return this._invalidateLayout(); + return this.redraw(); } - public _requestedSpace(offeredWidth: number, offeredHeight: number): _SpaceRequest { + public requestedSpace(offeredWidth: number, offeredHeight: number): SpaceRequest { var widthRequiredByTicks = this._isHorizontal() ? 0 : this._maxLabelTickLength() + this.tickLabelPadding() + this.gutter(); var heightRequiredByTicks = this._isHorizontal() ? this._maxLabelTickLength() + this.tickLabelPadding() + this.gutter() : 0; if (this._scale.domain().length === 0) { - return {width: 0, height: 0, wantsWidth: false, wantsHeight: false }; + return { + minWidth: 0, + minHeight: 0 + }; } - var categoryScale: Scale.Category = this._scale; - var fakeScale = categoryScale.copy(); - if (this._isHorizontal()) { - fakeScale.range([0, offeredWidth]); - } else { - fakeScale.range([offeredHeight, 0]); - } - var textResult = this._measureTicks(offeredWidth, - offeredHeight, - fakeScale, - categoryScale.domain()); + var categoryScale = this._scale; + var measureResult = this._measureTicks(offeredWidth, offeredHeight, categoryScale, categoryScale.domain()); + return { - width : textResult.usedWidth + widthRequiredByTicks, - height: textResult.usedHeight + heightRequiredByTicks, - wantsWidth : !textResult.textFits, - wantsHeight: !textResult.textFits + minWidth: measureResult.usedWidth + widthRequiredByTicks, + minHeight: measureResult.usedHeight + heightRequiredByTicks }; } - protected _getTickValues(): string[] { + protected _getTickValues() { return this._scale.domain(); } + /** + * Gets the tick label angle + * @returns {number} the tick label angle + */ + public tickLabelAngle(): number; /** * Sets the angle for the tick labels. Right now vertical-left (-90), horizontal (0), and vertical-right (90) are the only options. * @param {number} angle The angle for the ticks @@ -76,11 +74,6 @@ export module Axis { * See tracking at https://github.com/palantir/plottable/issues/504 */ public tickLabelAngle(angle: number): Category; - /** - * Gets the tick label angle - * @returns {number} the tick label angle - */ - public tickLabelAngle(): number; public tickLabelAngle(angle?: number): any { if (angle == null) { return this._tickLabelAngle; @@ -89,7 +82,7 @@ export module Axis { throw new Error("Angle " + angle + " not supported; only 0, 90, and -90 are valid values"); } this._tickLabelAngle = angle; - this._invalidateLayout(); + this.redraw(); return this; } @@ -97,11 +90,11 @@ export module Axis { * Measures the size of the ticks while also writing them to the DOM. * @param {D3.Selection} ticks The tick elements to be written to. */ - private _drawTicks(axisWidth: number, axisHeight: number, scale: Scale.Category, ticks: D3.Selection) { + private _drawTicks(axisWidth: number, axisHeight: number, scale: Scales.Category, ticks: D3.Selection) { var self = this; var xAlign: {[s: string]: string}; var yAlign: {[s: string]: string}; - switch(this.tickLabelAngle()) { + switch (this.tickLabelAngle()) { case 0: xAlign = {left: "right", right: "left", top: "center", bottom: "center"}; yAlign = {left: "center", right: "center", top: "bottom", bottom: "top"}; @@ -121,8 +114,8 @@ export module Axis { var height = self._isHorizontal() ? axisHeight - self._maxLabelTickLength() - self.tickLabelPadding() : bandWidth; var writeOptions = { selection: d3.select(this), - xAlign: xAlign[self.orient()], - yAlign: yAlign[self.orient()], + xAlign: xAlign[self.orientation()], + yAlign: yAlign[self.orientation()], textRotation: self.tickLabelAngle() }; self._writer.write(self.formatter()(d), width, height, writeOptions); @@ -135,14 +128,19 @@ export module Axis { * * @param {string[]} ticks The strings that will be printed on the ticks. */ - private _measureTicks(axisWidth: number, axisHeight: number, scale: Scale.Category, ticks: string[]) { + private _measureTicks(axisWidth: number, axisHeight: number, scale: Scales.Category, ticks: string[]) { + var axisSpace = this._isHorizontal() ? axisWidth : axisHeight; + var totalOuterPaddingRatio = 2 * scale.outerPadding(); + var totalInnerPaddingRatio = (ticks.length - 1) * scale.innerPadding(); + var expectedRangeBand = axisSpace / (totalOuterPaddingRatio + totalInnerPaddingRatio + ticks.length); + var stepWidth = expectedRangeBand * (1 + scale.innerPadding()); + var wrappingResults = ticks.map((s: string) => { - var bandWidth = scale.stepWidth(); // HACKHACK: https://github.com/palantir/svg-typewriter/issues/25 var width = axisWidth - this._maxLabelTickLength() - this.tickLabelPadding(); // default for left/right if (this._isHorizontal()) { // case for top/bottom - width = bandWidth; // defaults to the band width + width = stepWidth; // defaults to the band width if (this._tickLabelAngle !== 0) { // rotated label width = axisHeight - this._maxLabelTickLength() - this.tickLabelPadding(); // use the axis height } @@ -151,7 +149,7 @@ export module Axis { } // HACKHACK: https://github.com/palantir/svg-typewriter/issues/25 - var height = bandWidth; // default for left/right + var height = stepWidth; // default for left/right if (this._isHorizontal()) { // case for top/bottom height = axisHeight - this._maxLabelTickLength() - this.tickLabelPadding(); if (this._tickLabelAngle !== 0) { // rotated label @@ -165,8 +163,8 @@ export module Axis { }); // HACKHACK: https://github.com/palantir/svg-typewriter/issues/25 - var widthFn = (this._isHorizontal() && this._tickLabelAngle === 0) ? d3.sum : _Util.Methods.max; - var heightFn = (this._isHorizontal() && this._tickLabelAngle === 0) ? _Util.Methods.max : d3.sum; + var widthFn = (this._isHorizontal() && this._tickLabelAngle === 0) ? d3.sum : Utils.Methods.max; + var heightFn = (this._isHorizontal() && this._tickLabelAngle === 0) ? Utils.Methods.max : d3.sum; var textFits = wrappingResults.every((t: SVGTypewriter.Wrappers.WrappingResult) => !SVGTypewriter.Utils.StringMethods.isNotEmptyString(t.truncatedText) && t.noLines === 1); @@ -190,10 +188,10 @@ export module Axis { }; } - public _doRender() { - super._doRender(); - var catScale = this._scale; - var tickLabels = this._tickLabelContainer.selectAll("." + AbstractAxis.TICK_LABEL_CLASS).data(this._scale.domain(), (d) => d); + public renderImmediately() { + super.renderImmediately(); + var catScale = this._scale; + var tickLabels = this._tickLabelContainer.selectAll("." + Axis.TICK_LABEL_CLASS).data(this._scale.domain(), (d) => d); var getTickLabelTransform = (d: string, i: number) => { var innerPaddingWidth = catScale.stepWidth() - catScale.rangeBand(); @@ -202,26 +200,25 @@ export module Axis { var y = this._isHorizontal() ? 0 : scaledValue; return "translate(" + x + "," + y + ")"; }; - tickLabels.enter().append("g").classed(AbstractAxis.TICK_LABEL_CLASS, true); + tickLabels.enter().append("g").classed(Axis.TICK_LABEL_CLASS, true); tickLabels.exit().remove(); tickLabels.attr("transform", getTickLabelTransform); // erase all text first, then rewrite tickLabels.text(""); this._drawTicks(this.width(), this.height(), catScale, tickLabels); - var translate = this._isHorizontal() ? [catScale.rangeBand() / 2, 0] : [0, catScale.rangeBand() / 2]; - var xTranslate = this.orient() === "right" ? this._maxLabelTickLength() + this.tickLabelPadding() : 0; - var yTranslate = this.orient() === "bottom" ? this._maxLabelTickLength() + this.tickLabelPadding() : 0; - _Util.DOM.translate(this._tickLabelContainer, xTranslate, yTranslate); + var xTranslate = this.orientation() === "right" ? this._maxLabelTickLength() + this.tickLabelPadding() : 0; + var yTranslate = this.orientation() === "bottom" ? this._maxLabelTickLength() + this.tickLabelPadding() : 0; + Utils.DOM.translate(this._tickLabelContainer, xTranslate, yTranslate); return this; } - public _computeLayout(offeredXOrigin?: number, offeredYOrigin?: number, availableWidth?: number, availableHeight?: number) { - // When anyone calls _invalidateLayout, _computeLayout will be called + public computeLayout(origin?: Point, availableWidth?: number, availableHeight?: number) { + // When anyone calls redraw(), computeLayout() will be called // on everyone, including this. Since CSS or something might have // affected the size of the characters, clear the cache. this._measurer.reset(); - return super._computeLayout(offeredXOrigin, offeredYOrigin, availableWidth, availableHeight); + return super.computeLayout(origin, availableWidth, availableHeight); } } } diff --git a/src/components/axes/numericAxis.ts b/src/components/axes/numericAxis.ts index 956e8df9e2..29fe9c764d 100644 --- a/src/components/axes/numericAxis.ts +++ b/src/components/axes/numericAxis.ts @@ -1,8 +1,8 @@ -// +/// module Plottable { -export module Axis { - export class Numeric extends AbstractAxis { +export module Axes { + export class Numeric extends Axis { private _tickLabelPositioning = "center"; // Whether or not first/last tick label will still be displayed even if @@ -23,24 +23,24 @@ export module Axis { * @param {string} orientation The orientation of the QuantitativeScale (top/bottom/left/right) * @param {Formatter} formatter A function to format tick labels (default Formatters.general()). */ - constructor(scale: Scale.AbstractQuantitative, orientation: string, formatter = Formatters.general()) { + constructor(scale: QuantitativeScale, orientation: string, formatter = Formatters.general()) { super(scale, orientation, formatter); } protected _setup() { super._setup(); - this._measurer = new SVGTypewriter.Measurers.Measurer(this._tickLabelContainer, AbstractAxis.TICK_LABEL_CLASS); + this._measurer = new SVGTypewriter.Measurers.Measurer(this._tickLabelContainer, Axis.TICK_LABEL_CLASS); this._wrapper = new SVGTypewriter.Wrappers.Wrapper().maxLines(1); } - public _computeWidth() { + protected _computeWidth() { var tickValues = this._getTickValues(); var textLengths = tickValues.map((v: any) => { var formattedValue = this.formatter()(v); return this._measurer.measure(formattedValue).width; }); - var maxTextLength = _Util.Methods.max(textLengths, 0); + var maxTextLength = Utils.Methods.max(textLengths, 0); if (this._tickLabelPositioning === "center") { this._computedWidth = this._maxLabelTickLength() + this.tickLabelPadding() + maxTextLength; @@ -51,7 +51,7 @@ export module Axis { return this._computedWidth; } - public _computeHeight() { + protected _computeHeight() { var textHeight = this._measurer.measure().height; if (this._tickLabelPositioning === "center") { @@ -63,8 +63,8 @@ export module Axis { return this._computedHeight; } - protected _getTickValues(): any[] { - var scale = (> this._scale); + protected _getTickValues() { + var scale = (> this._scale); var domain = scale.domain(); var min = domain[0] <= domain[1] ? domain[0] : domain[1]; var max = domain[0] >= domain[1] ? domain[0] : domain[1]; @@ -83,16 +83,16 @@ export module Axis { if (!this._isHorizontal()) { var reComputedWidth = this._computeWidth(); if (reComputedWidth > this.width() || reComputedWidth < (this.width() - this.gutter())) { - this._invalidateLayout(); + this.redraw(); return; } } - this._render(); + this.render(); } - public _doRender() { - super._doRender(); + public renderImmediately() { + super.renderImmediately(); var tickLabelAttrHash = { x: 0, @@ -111,7 +111,7 @@ export module Axis { var labelGroupShiftX = 0; var labelGroupShiftY = 0; if (this._isHorizontal()) { - switch(this._tickLabelPositioning) { + switch (this._tickLabelPositioning) { case "left": tickLabelTextAnchor = "end"; labelGroupTransformX = -tickLabelPadding; @@ -127,7 +127,7 @@ export module Axis { break; } } else { - switch(this._tickLabelPositioning) { + switch (this._tickLabelPositioning) { case "top": tickLabelAttrHash["dy"] = "-0.3em"; labelGroupShiftX = tickLabelPadding; @@ -145,7 +145,7 @@ export module Axis { } var tickMarkAttrHash = this._generateTickMarkAttrHash(); - switch(this.orient()) { + switch (this.orientation()) { case "bottom": tickLabelAttrHash["x"] = tickMarkAttrHash["x1"]; tickLabelAttrHash["dy"] = "0.95em"; @@ -173,9 +173,9 @@ export module Axis { var tickLabelValues = this._getTickValues(); var tickLabels = this._tickLabelContainer - .selectAll("." + AbstractAxis.TICK_LABEL_CLASS) + .selectAll("." + Axis.TICK_LABEL_CLASS) .data(tickLabelValues); - tickLabels.enter().append("text").classed(AbstractAxis.TICK_LABEL_CLASS, true); + tickLabels.enter().append("text").classed(Axis.TICK_LABEL_CLASS, true); tickLabels.exit().remove(); tickLabels.style("text-anchor", tickLabelTextAnchor) @@ -209,23 +209,23 @@ export module Axis { this._tickLabelPositioning === "right") { this._hideTickMarksWithoutLabel(); } + return this; } private _showAllTickMarks() { - var visibleTickMarks = this._tickMarkContainer - .selectAll("." + AbstractAxis.TICK_MARK_CLASS) - .each(function() { - d3.select(this).style("visibility", "inherit"); - }); + this._tickMarkContainer.selectAll("." + Axis.TICK_MARK_CLASS) + .each(function() { + d3.select(this).style("visibility", "inherit"); + }); } /** * Hides the Tick Marks which have no corresponding Tick Labels */ private _hideTickMarksWithoutLabel() { - var visibleTickMarks = this._tickMarkContainer.selectAll("." + AbstractAxis.TICK_MARK_CLASS); + var visibleTickMarks = this._tickMarkContainer.selectAll("." + Axis.TICK_MARK_CLASS); var visibleTickLabels = this._tickLabelContainer - .selectAll("." + AbstractAxis.TICK_LABEL_CLASS) + .selectAll("." + Axis.TICK_LABEL_CLASS) .filter(function(d: any, i: number) { var visibility = d3.select(this).style("visibility"); return (visibility === "inherit") || (visibility === "visible"); @@ -272,7 +272,7 @@ export module Axis { } } this._tickLabelPositioning = positionLC; - this._invalidateLayout(); + this.redraw(); return this; } } @@ -308,7 +308,7 @@ export module Axis { return this._showFirstTickLabel; } else { this._showFirstTickLabel = show; - this._render(); + this.render(); return this; } } else if ((this._isHorizontal() && orientation === "right") || @@ -317,7 +317,7 @@ export module Axis { return this._showLastTickLabel; } else { this._showLastTickLabel = show; - this._render(); + this.render(); return this; } } else { @@ -329,16 +329,16 @@ export module Axis { private _hideEndTickLabels() { var boundingBox = this._boundingBox.node().getBoundingClientRect(); - var tickLabels = this._tickLabelContainer.selectAll("." + AbstractAxis.TICK_LABEL_CLASS); + var tickLabels = this._tickLabelContainer.selectAll("." + Axis.TICK_LABEL_CLASS); if (tickLabels[0].length === 0) { return; } var firstTickLabel = tickLabels[0][0]; - if (!_Util.DOM.boxIsInside(firstTickLabel.getBoundingClientRect(), boundingBox)) { + if (!Utils.DOM.boxIsInside(firstTickLabel.getBoundingClientRect(), boundingBox)) { d3.select(firstTickLabel).style("visibility", "hidden"); } var lastTickLabel = tickLabels[0][tickLabels[0].length - 1]; - if (!_Util.DOM.boxIsInside(lastTickLabel.getBoundingClientRect(), boundingBox)) { + if (!Utils.DOM.boxIsInside(lastTickLabel.getBoundingClientRect(), boundingBox)) { d3.select(lastTickLabel).style("visibility", "hidden"); } } @@ -346,12 +346,12 @@ export module Axis { // Responsible for hiding any tick labels that break out of the bounding container private _hideOverflowingTickLabels() { var boundingBox = this._boundingBox.node().getBoundingClientRect(); - var tickLabels = this._tickLabelContainer.selectAll("." + AbstractAxis.TICK_LABEL_CLASS); + var tickLabels = this._tickLabelContainer.selectAll("." + Axis.TICK_LABEL_CLASS); if (tickLabels.empty()) { return; } tickLabels.each(function(d: any, i: number) { - if (!_Util.DOM.boxIsInside(this.getBoundingClientRect(), boundingBox)) { + if (!Utils.DOM.boxIsInside(this.getBoundingClientRect(), boundingBox)) { d3.select(this).style("visibility", "hidden"); } }); @@ -359,12 +359,11 @@ export module Axis { private _hideOverlappingTickLabels() { var visibleTickLabels = this._tickLabelContainer - .selectAll("." + AbstractAxis.TICK_LABEL_CLASS) + .selectAll("." + Axis.TICK_LABEL_CLASS) .filter(function(d: any, i: number) { var visibility = d3.select(this).style("visibility"); return (visibility === "inherit") || (visibility === "visible"); }); - var lastLabelClientRect: ClientRect; var visibleTickLabelRects = visibleTickLabels[0].map((label: HTMLScriptElement) => label.getBoundingClientRect()); var interval = 1; diff --git a/src/components/axes/timeAxis.ts b/src/components/axes/timeAxis.ts index e011ee8b20..78dde54f71 100644 --- a/src/components/axes/timeAxis.ts +++ b/src/components/axes/timeAxis.ts @@ -1,7 +1,18 @@ /// module Plottable { -export module Axis { + +export module TimeInterval { + export var second = "second"; + export var minute = "minute"; + export var hour = "hour"; + export var day = "day"; + export var week = "week"; + export var month = "month"; + export var year = "year"; +}; + +export module Axes { /** * Defines a configuration for a time axis tier. * For details on how ticks are generated see: https://github.com/mbostock/d3/wiki/Time-Scales#ticks @@ -10,7 +21,7 @@ export module Axis { * formatter - formatter used to format tick labels. */ export type TimeAxisTierConfiguration = { - interval: D3.Time.Interval; + interval: string; step: number; formatter: Formatter; }; @@ -22,7 +33,7 @@ export module Axis { */ export type TimeAxisConfiguration = TimeAxisTierConfiguration[]; - export class Time extends AbstractAxis { + export class Time extends Axis { /** * The css class applied to each time axis tier */ @@ -33,111 +44,111 @@ export module Axis { */ private static _DEFAULT_TIME_AXIS_CONFIGURATIONS: TimeAxisConfiguration[] = [ [ - {interval: d3.time.second, step: 1, formatter: Formatters.time("%I:%M:%S %p")}, - {interval: d3.time.day, step: 1, formatter: Formatters.time("%B %e, %Y")} + {interval: TimeInterval.second, step: 1, formatter: Formatters.time("%I:%M:%S %p")}, + {interval: TimeInterval.day, step: 1, formatter: Formatters.time("%B %e, %Y")} ], [ - {interval: d3.time.second, step: 5, formatter: Formatters.time("%I:%M:%S %p")}, - {interval: d3.time.day, step: 1, formatter: Formatters.time("%B %e, %Y")} + {interval: TimeInterval.second, step: 5, formatter: Formatters.time("%I:%M:%S %p")}, + {interval: TimeInterval.day, step: 1, formatter: Formatters.time("%B %e, %Y")} ], [ - {interval: d3.time.second, step: 10, formatter: Formatters.time("%I:%M:%S %p")}, - {interval: d3.time.day, step: 1, formatter: Formatters.time("%B %e, %Y")} + {interval: TimeInterval.second, step: 10, formatter: Formatters.time("%I:%M:%S %p")}, + {interval: TimeInterval.day, step: 1, formatter: Formatters.time("%B %e, %Y")} ], [ - {interval: d3.time.second, step: 15, formatter: Formatters.time("%I:%M:%S %p")}, - {interval: d3.time.day, step: 1, formatter: Formatters.time("%B %e, %Y")} + {interval: TimeInterval.second, step: 15, formatter: Formatters.time("%I:%M:%S %p")}, + {interval: TimeInterval.day, step: 1, formatter: Formatters.time("%B %e, %Y")} ], [ - {interval: d3.time.second, step: 30, formatter: Formatters.time("%I:%M:%S %p")}, - {interval: d3.time.day, step: 1, formatter: Formatters.time("%B %e, %Y")} + {interval: TimeInterval.second, step: 30, formatter: Formatters.time("%I:%M:%S %p")}, + {interval: TimeInterval.day, step: 1, formatter: Formatters.time("%B %e, %Y")} ], [ - {interval: d3.time.minute, step: 1, formatter: Formatters.time("%I:%M %p")}, - {interval: d3.time.day, step: 1, formatter: Formatters.time("%B %e, %Y")} + {interval: TimeInterval.minute, step: 1, formatter: Formatters.time("%I:%M %p")}, + {interval: TimeInterval.day, step: 1, formatter: Formatters.time("%B %e, %Y")} ], [ - {interval: d3.time.minute, step: 5, formatter: Formatters.time("%I:%M %p")}, - {interval: d3.time.day, step: 1, formatter: Formatters.time("%B %e, %Y")} + {interval: TimeInterval.minute, step: 5, formatter: Formatters.time("%I:%M %p")}, + {interval: TimeInterval.day, step: 1, formatter: Formatters.time("%B %e, %Y")} ], [ - {interval: d3.time.minute, step: 10, formatter: Formatters.time("%I:%M %p")}, - {interval: d3.time.day, step: 1, formatter: Formatters.time("%B %e, %Y")} + {interval: TimeInterval.minute, step: 10, formatter: Formatters.time("%I:%M %p")}, + {interval: TimeInterval.day, step: 1, formatter: Formatters.time("%B %e, %Y")} ], [ - {interval: d3.time.minute, step: 15, formatter: Formatters.time("%I:%M %p")}, - {interval: d3.time.day, step: 1, formatter: Formatters.time("%B %e, %Y")} + {interval: TimeInterval.minute, step: 15, formatter: Formatters.time("%I:%M %p")}, + {interval: TimeInterval.day, step: 1, formatter: Formatters.time("%B %e, %Y")} ], [ - {interval: d3.time.minute, step: 30, formatter: Formatters.time("%I:%M %p")}, - {interval: d3.time.day, step: 1, formatter: Formatters.time("%B %e, %Y")} + {interval: TimeInterval.minute, step: 30, formatter: Formatters.time("%I:%M %p")}, + {interval: TimeInterval.day, step: 1, formatter: Formatters.time("%B %e, %Y")} ], [ - {interval: d3.time.hour, step: 1, formatter: Formatters.time("%I %p")}, - {interval: d3.time.day, step: 1, formatter: Formatters.time("%B %e, %Y")} + {interval: TimeInterval.hour, step: 1, formatter: Formatters.time("%I %p")}, + {interval: TimeInterval.day, step: 1, formatter: Formatters.time("%B %e, %Y")} ], [ - {interval: d3.time.hour, step: 3, formatter: Formatters.time("%I %p")}, - {interval: d3.time.day, step: 1, formatter: Formatters.time("%B %e, %Y")} + {interval: TimeInterval.hour, step: 3, formatter: Formatters.time("%I %p")}, + {interval: TimeInterval.day, step: 1, formatter: Formatters.time("%B %e, %Y")} ], [ - {interval: d3.time.hour, step: 6, formatter: Formatters.time("%I %p")}, - {interval: d3.time.day, step: 1, formatter: Formatters.time("%B %e, %Y")} + {interval: TimeInterval.hour, step: 6, formatter: Formatters.time("%I %p")}, + {interval: TimeInterval.day, step: 1, formatter: Formatters.time("%B %e, %Y")} ], [ - {interval: d3.time.hour, step: 12, formatter: Formatters.time("%I %p")}, - {interval: d3.time.day, step: 1, formatter: Formatters.time("%B %e, %Y")} + {interval: TimeInterval.hour, step: 12, formatter: Formatters.time("%I %p")}, + {interval: TimeInterval.day, step: 1, formatter: Formatters.time("%B %e, %Y")} ], [ - {interval: d3.time.day, step: 1, formatter: Formatters.time("%a %e")}, - {interval: d3.time.month, step: 1, formatter: Formatters.time("%B %Y")} + {interval: TimeInterval.day, step: 1, formatter: Formatters.time("%a %e")}, + {interval: TimeInterval.month, step: 1, formatter: Formatters.time("%B %Y")} ], [ - {interval: d3.time.day, step: 1, formatter: Formatters.time("%e")}, - {interval: d3.time.month, step: 1, formatter: Formatters.time("%B %Y")} + {interval: TimeInterval.day, step: 1, formatter: Formatters.time("%e")}, + {interval: TimeInterval.month, step: 1, formatter: Formatters.time("%B %Y")} ], [ - {interval: d3.time.month, step: 1, formatter: Formatters.time("%B")}, - {interval: d3.time.year, step: 1, formatter: Formatters.time("%Y")} + {interval: TimeInterval.month, step: 1, formatter: Formatters.time("%B")}, + {interval: TimeInterval.year, step: 1, formatter: Formatters.time("%Y")} ], [ - {interval: d3.time.month, step: 1, formatter: Formatters.time("%b")}, - {interval: d3.time.year, step: 1, formatter: Formatters.time("%Y")} + {interval: TimeInterval.month, step: 1, formatter: Formatters.time("%b")}, + {interval: TimeInterval.year, step: 1, formatter: Formatters.time("%Y")} ], [ - {interval: d3.time.month, step: 3, formatter: Formatters.time("%b")}, - {interval: d3.time.year, step: 1, formatter: Formatters.time("%Y")} + {interval: TimeInterval.month, step: 3, formatter: Formatters.time("%b")}, + {interval: TimeInterval.year, step: 1, formatter: Formatters.time("%Y")} ], [ - {interval: d3.time.month, step: 6, formatter: Formatters.time("%b")}, - {interval: d3.time.year, step: 1, formatter: Formatters.time("%Y")} + {interval: TimeInterval.month, step: 6, formatter: Formatters.time("%b")}, + {interval: TimeInterval.year, step: 1, formatter: Formatters.time("%Y")} ], [ - {interval: d3.time.year, step: 1, formatter: Formatters.time("%Y")} + {interval: TimeInterval.year, step: 1, formatter: Formatters.time("%Y")} ], [ - {interval: d3.time.year, step: 1, formatter: Formatters.time("%y")} + {interval: TimeInterval.year, step: 1, formatter: Formatters.time("%y")} ], [ - {interval: d3.time.year, step: 5, formatter: Formatters.time("%Y")} + {interval: TimeInterval.year, step: 5, formatter: Formatters.time("%Y")} ], [ - {interval: d3.time.year, step: 25, formatter: Formatters.time("%Y")} + {interval: TimeInterval.year, step: 25, formatter: Formatters.time("%Y")} ], [ - {interval: d3.time.year, step: 50, formatter: Formatters.time("%Y")} + {interval: TimeInterval.year, step: 50, formatter: Formatters.time("%Y")} ], [ - {interval: d3.time.year, step: 100, formatter: Formatters.time("%Y")} + {interval: TimeInterval.year, step: 100, formatter: Formatters.time("%Y")} ], [ - {interval: d3.time.year, step: 200, formatter: Formatters.time("%Y")} + {interval: TimeInterval.year, step: 200, formatter: Formatters.time("%Y")} ], [ - {interval: d3.time.year, step: 500, formatter: Formatters.time("%Y")} + {interval: TimeInterval.year, step: 500, formatter: Formatters.time("%Y")} ], [ - {interval: d3.time.year, step: 1000, formatter: Formatters.time("%Y")} + {interval: TimeInterval.year, step: 1000, formatter: Formatters.time("%Y")} ] ]; @@ -164,7 +175,7 @@ export module Axis { * @param {TimeScale} scale The scale to base the Axis on. * @param {string} orientation The orientation of the Axis (top/bottom) */ - constructor(scale: Scale.Time, orientation: string) { + constructor(scale: Scales.Time, orientation: string) { super(scale, orientation); this.classed("time-axis", true); this.tickLabelPadding(5); @@ -181,7 +192,7 @@ export module Axis { throw new Error("Unsupported position for tier labels"); } this._tierLabelPositions = newPositions; - this._invalidateLayout(); + this.redraw(); return this; } } @@ -202,11 +213,11 @@ export module Axis { */ public axisConfigurations(configurations: TimeAxisConfiguration[]): Time; public axisConfigurations(configurations?: any): any { - if(configurations == null){ + if (configurations == null) { return this._possibleTimeAxisConfigurations; } this._possibleTimeAxisConfigurations = configurations; - this._numTiers = _Util.Methods.max(this._possibleTimeAxisConfigurations.map((config: TimeAxisConfiguration) => config.length), 0); + this._numTiers = Utils.Methods.max(this._possibleTimeAxisConfigurations.map((config: TimeAxisConfiguration) => config.length), 0); if (this._isAnchored) { this._setupDomElements(); @@ -219,7 +230,7 @@ export module Axis { } this.tierLabelPositions(newLabelPositions); - this._invalidateLayout(); + this.redraw(); return this; } @@ -236,23 +247,23 @@ export module Axis { }); if (mostPreciseIndex === this._possibleTimeAxisConfigurations.length) { - _Util.Methods.warn("zoomed out too far: could not find suitable interval to display labels"); + Utils.Methods.warn("zoomed out too far: could not find suitable interval to display labels"); --mostPreciseIndex; } return mostPreciseIndex; } - public orient(): string; - public orient(orientation: string): Time; - public orient(orientation?: string): any { + public orientation(): string; + public orientation(orientation: string): Time; + public orientation(orientation?: string): any { if (orientation && (orientation.toLowerCase() === "right" || orientation.toLowerCase() === "left")) { throw new Error(orientation + " is not a supported orientation for TimeAxis - only horizontal orientations are supported"); } - return super.orient(orientation); // maintains getter-setter functionality + return super.orientation(orientation); // maintains getter-setter functionality } - public _computeHeight() { + protected _computeHeight() { var textHeight = this._measurer.measure().height; this._tierHeights = []; @@ -267,7 +278,8 @@ export module Axis { private _getIntervalLength(config: TimeAxisTierConfiguration) { var startDate = this._scale.domain()[0]; - var endDate = config.interval.offset(startDate, config.step); + var d3Interval = Formatters.timeIntervalToD3Time(config.interval); + var endDate = d3Interval.offset(startDate, config.step); if (endDate > this._scale.domain()[1]) { // this offset is too large, so just return available width return this.width(); @@ -316,8 +328,8 @@ export module Axis { for (var i = 0; i < this._numTiers; ++i) { var tierContainer = this._content.append("g").classed(Time.TIME_AXIS_TIER_CLASS, true); - this._tierLabelContainers.push(tierContainer.append("g").classed(AbstractAxis.TICK_LABEL_CLASS + "-container", true)); - this._tierMarkContainers.push(tierContainer.append("g").classed(AbstractAxis.TICK_MARK_CLASS + "-container", true)); + this._tierLabelContainers.push(tierContainer.append("g").classed(Axis.TICK_LABEL_CLASS + "-container", true)); + this._tierMarkContainers.push(tierContainer.append("g").classed(Axis.TICK_MARK_CLASS + "-container", true)); this._tierBaselines.push(tierContainer.append("line").classed("baseline", true)); } @@ -325,10 +337,10 @@ export module Axis { } private _getTickIntervalValues(config: TimeAxisTierConfiguration): any[] { - return ( this._scale).tickInterval(config.interval, config.step); + return ( this._scale).tickInterval(config.interval, config.step); } - protected _getTickValues(): any[] { + protected _getTickValues() { return this._possibleTimeAxisConfigurations[this._mostPreciseConfigIndex].reduce( (ticks: any[], config: TimeAxisTierConfiguration) => ticks.concat(this._getTickIntervalValues(config)), [] @@ -337,14 +349,14 @@ export module Axis { private _cleanTiers() { for (var index = 0; index < this._tierLabelContainers.length; index++) { - this._tierLabelContainers[index].selectAll("." + AbstractAxis.TICK_LABEL_CLASS).remove(); - this._tierMarkContainers[index].selectAll("." + AbstractAxis.TICK_MARK_CLASS).remove(); + this._tierLabelContainers[index].selectAll("." + Axis.TICK_LABEL_CLASS).remove(); + this._tierMarkContainers[index].selectAll("." + Axis.TICK_MARK_CLASS).remove(); this._tierBaselines[index].style("visibility", "hidden"); } } private _getTickValuesForConfiguration(config: TimeAxisTierConfiguration) { - var tickPos = ( this._scale).tickInterval(config.interval, config.step); + var tickPos = ( this._scale).tickInterval(config.interval, config.step); var domain = this._scale.domain(); var tickPosValues = tickPos.map((d: Date) => d.valueOf()); // can't indexOf with objects if (tickPosValues.indexOf(domain[0].valueOf()) === -1) { @@ -370,18 +382,17 @@ export module Axis { labelPos = tickPos; } - var tickLabels = container.selectAll("." + AbstractAxis.TICK_LABEL_CLASS).data(labelPos, (d) => d.valueOf()); - var tickLabelsEnter = tickLabels.enter().append("g").classed(AbstractAxis.TICK_LABEL_CLASS, true); + var tickLabels = container.selectAll("." + Axis.TICK_LABEL_CLASS).data(labelPos, (d) => d.valueOf()); + var tickLabelsEnter = tickLabels.enter().append("g").classed(Axis.TICK_LABEL_CLASS, true); tickLabelsEnter.append("text"); var xTranslate = (this._tierLabelPositions[index] === "center" || config.step === 1) ? 0 : this.tickLabelPadding(); - var markLength = this._measurer.measure().height; - var yTranslate = this.orient() === "bottom" ? + var yTranslate = this.orientation() === "bottom" ? d3.sum(this._tierHeights.slice(0, index + 1)) - this.tickLabelPadding() : this.height() - d3.sum(this._tierHeights.slice(0, index)) - this.tickLabelPadding(); var textSelection = tickLabels.selectAll("text"); if (textSelection.size() > 0) { - _Util.DOM.translate(textSelection, xTranslate, yTranslate); + Utils.DOM.translate(textSelection, xTranslate, yTranslate); } tickLabels.exit().remove(); tickLabels.attr("transform", (d: any) => "translate(" + this._scale.scale(d) + ",0)"); @@ -390,11 +401,11 @@ export module Axis { } private _renderTickMarks(tickValues: Date[], index: number) { - var tickMarks = this._tierMarkContainers[index].selectAll("." + AbstractAxis.TICK_MARK_CLASS).data(tickValues); - tickMarks.enter().append("line").classed(AbstractAxis.TICK_MARK_CLASS, true); + var tickMarks = this._tierMarkContainers[index].selectAll("." + Axis.TICK_MARK_CLASS).data(tickValues); + tickMarks.enter().append("line").classed(Axis.TICK_MARK_CLASS, true); var attr = this._generateTickMarkAttrHash(); var offset = this._tierHeights.slice(0, index).reduce((translate: number, height: number) => translate + height, 0); - if (this.orient() === "bottom") { + if (this.orientation() === "bottom") { attr["y1"] = offset; attr["y2"] = offset + (this._tierLabelPositions[index] === "center" ? this.tickLength() : this._tierHeights[index]); } else { @@ -403,7 +414,7 @@ export module Axis { this.tickLength() : this._tierHeights[index])); } tickMarks.attr(attr); - if (this.orient() === "bottom") { + if (this.orientation() === "bottom") { attr["y1"] = offset; attr["y2"] = offset + this._tierHeights[index]; } else { @@ -413,17 +424,17 @@ export module Axis { d3.select(tickMarks[0][0]).attr(attr); // Add end-tick classes to first and last tick for CSS customization purposes - d3.select(tickMarks[0][0]).classed(AbstractAxis.END_TICK_MARK_CLASS, true); - d3.select(tickMarks[0][tickMarks.size() - 1]).classed(AbstractAxis.END_TICK_MARK_CLASS, true); + d3.select(tickMarks[0][0]).classed(Axis.END_TICK_MARK_CLASS, true); + d3.select(tickMarks[0][tickMarks.size() - 1]).classed(Axis.END_TICK_MARK_CLASS, true); tickMarks.exit().remove(); } private _renderLabellessTickMarks(tickValues: Date[]) { - var tickMarks = this._tickMarkContainer.selectAll("." + AbstractAxis.TICK_MARK_CLASS).data(tickValues); - tickMarks.enter().append("line").classed(AbstractAxis.TICK_MARK_CLASS, true); + var tickMarks = this._tickMarkContainer.selectAll("." + Axis.TICK_MARK_CLASS).data(tickValues); + tickMarks.enter().append("line").classed(Axis.TICK_MARK_CLASS, true); var attr = this._generateTickMarkAttrHash(); - attr["y2"] = (this.orient() === "bottom") ? this.tickLabelPadding() : this.height() - this.tickLabelPadding(); + attr["y2"] = (this.orientation() === "bottom") ? this.tickLabelPadding() : this.height() - this.tickLabelPadding(); tickMarks.attr(attr); tickMarks.exit().remove(); } @@ -436,7 +447,7 @@ export module Axis { return this._getTickIntervalValues(this._possibleTimeAxisConfigurations[this._mostPreciseConfigIndex - 1][0]); } - public _doRender() { + public renderImmediately() { this._mostPreciseConfigIndex = this._getMostPreciseConfigurationIndex(); var tierConfigs = this._possibleTimeAxisConfigurations[this._mostPreciseConfigIndex]; @@ -452,7 +463,7 @@ export module Axis { var baselineOffset = 0; for (var i = 0; i < Math.max(tierConfigs.length, 1); ++i) { var attr = this._generateBaselineAttrHash(); - attr["y1"] += (this.orient() === "bottom") ? baselineOffset : -baselineOffset; + attr["y1"] += (this.orientation() === "bottom") ? baselineOffset : -baselineOffset; attr["y2"] = attr["y1"]; this._tierBaselines[i].attr(attr).style("visibility", "inherit"); baselineOffset += this._tierHeights[i]; @@ -501,7 +512,7 @@ export module Axis { }; var visibleTickMarks = this._tierMarkContainers[index] - .selectAll("." + AbstractAxis.TICK_MARK_CLASS) + .selectAll("." + Axis.TICK_MARK_CLASS) .filter(function(d: Element, i: number) { var visibility = d3.select(this).style("visibility"); return visibility === "visible" || visibility === "inherit"; @@ -511,7 +522,7 @@ export module Axis { var visibleTickMarkRects = visibleTickMarks[0].map((mark: Element) => mark.getBoundingClientRect() ); var visibleTickLabels = this._tierLabelContainers[index] - .selectAll("." + AbstractAxis.TICK_LABEL_CLASS) + .selectAll("." + Axis.TICK_LABEL_CLASS) .filter(function(d: Element, i: number) { var visibility = d3.select(this).style("visibility"); return visibility === "visible" || visibility === "inherit"; @@ -523,7 +534,7 @@ export module Axis { var tickLabel = d3.select(this); var leadingTickMark = visibleTickMarkRects[i]; var trailingTickMark = visibleTickMarkRects[i + 1]; - if (!isInsideBBox(clientRect) || (lastLabelClientRect != null && _Util.DOM.boxesOverlap(clientRect, lastLabelClientRect)) + if (!isInsideBBox(clientRect) || (lastLabelClientRect != null && Utils.DOM.boxesOverlap(clientRect, lastLabelClientRect)) || (leadingTickMark.right > clientRect.left || trailingTickMark.left < clientRect.right)) { tickLabel.style("visibility", "hidden"); } else { diff --git a/src/components/abstractComponent.ts b/src/components/component.ts similarity index 51% rename from src/components/abstractComponent.ts rename to src/components/component.ts index c497091264..b9647793b4 100644 --- a/src/components/abstractComponent.ts +++ b/src/components/component.ts @@ -1,52 +1,68 @@ /// module Plottable { -export module Component { - export class AbstractComponent extends Core.PlottableObject { + + export type ComponentCallback = (component: Component) => any; + + export module Components { + export class Alignment { + static TOP = "top"; + static BOTTOM = "bottom"; + static LEFT = "left"; + static RIGHT = "right"; + static CENTER = "center"; + } + } + export class Component { protected _element: D3.Selection; protected _content: D3.Selection; protected _boundingBox: D3.Selection; private _backgroundContainer: D3.Selection; private _foregroundContainer: D3.Selection; - public clipPathEnabled = false; - private _xOrigin: number; // Origin of the coordinate space for the component. Passed down from parent - private _yOrigin: number; - - private _parentElement: AbstractComponentContainer; - private _xAlignProportion = 0; // What % along the free space do we want to position (0 = left, .5 = center, 1 = right) - private _yAlignProportion = 0; - protected _fixedHeightFlag = false; - protected _fixedWidthFlag = false; + protected _clipPathEnabled = false; + private _origin: Point = { x: 0, y: 0 }; // Origin of the coordinate space for the Component. + + private _parent: ComponentContainer; + private _xAlignment: string = "left"; + private static _xAlignToProportion: { [alignment: string]: number } = { + "left": 0, + "center": 0.5, + "right": 1 + }; + private _yAlignment: string = "top"; + private static _yAlignToProportion: { [alignment: string]: number } = { + "top": 0, + "center": 0.5, + "bottom": 1 + }; protected _isSetup = false; protected _isAnchored = false; - private _hitBox: D3.Selection; - private _interactionsToRegister: Interaction.AbstractInteraction[] = []; private _boxes: D3.Selection[] = []; private _boxContainer: D3.Selection; private _rootSVG: D3.Selection; private _isTopLevelComponent = false; private _width: number; // Width and height of the component. Used to size the hitbox, bounding box, etc private _height: number; - private _xOffset = 0; // Offset from Origin, used for alignment and floating positioning - private _yOffset = 0; private _cssClasses: string[] = ["component"]; - private _removed = false; - private _usedLastLayout = false; + private _destroyed = false; + private _onAnchorCallbacks = new Utils.CallbackSet(); + private _onDetachCallbacks = new Utils.CallbackSet(); /** - * Attaches the Component as a child of a given a DOM element. Usually only directly invoked on root-level Components. + * Attaches the Component as a child of a given D3 Selection. * - * @param {D3.Selection} element A D3 selection consisting of the element to anchor under. + * @param {D3.Selection} selection The Selection containing the Element to anchor under. + * @returns {Component} The calling Component. */ - public _anchor(element: D3.Selection) { - if (this._removed) { - throw new Error("Can't reuse remove()-ed components!"); + public anchor(selection: D3.Selection) { + if (this._destroyed) { + throw new Error("Can't reuse destroy()-ed components!"); } - if (element.node().nodeName.toLowerCase() === "svg") { + if (selection.node().nodeName.toLowerCase() === "svg") { // svg node gets the "plottable" CSS class - this._rootSVG = element; + this._rootSVG = selection; this._rootSVG.classed("plottable", true); // visible overflow for firefox https://stackoverflow.com/questions/5926986/why-does-firefox-appear-to-truncate-embedded-svgs this._rootSVG.style("overflow", "visible"); @@ -55,17 +71,48 @@ export module Component { if (this._element != null) { // reattach existing element - element.node().appendChild(this._element.node()); + selection.node().appendChild(this._element.node()); } else { - this._element = element.append("g"); + this._element = selection.append("g"); this._setup(); } this._isAnchored = true; + this._onAnchorCallbacks.callCallbacks(this); + return this; + } + + /** + * Adds a callback to be called on anchoring the Component to the DOM. + * If the component is already anchored, the callback is called immediately. + * + * @param {ComponentCallback} callback The callback to be added. + * + * @return {Component} + */ + public onAnchor(callback: ComponentCallback) { + if (this._isAnchored) { + callback(this); + } + this._onAnchorCallbacks.add(callback); + return this; + } + + /** + * Removes a callback to be called on anchoring the Component to the DOM. + * The callback is identified by reference equality. + * + * @param {ComponentCallback} callback The callback to be removed. + * + * @return {Component} + */ + public offAnchor(callback: ComponentCallback) { + this._onAnchorCallbacks.delete(callback); + return this; } /** * Creates additional elements as necessary for the Component to function. - * Called during _anchor() if the Component's element has not been created yet. + * Called during anchor() if the Component's element has not been created yet. * Override in subclasses to provide additional functionality. */ protected _setup() { @@ -83,19 +130,20 @@ export module Component { this._foregroundContainer = this._element.append("g").classed("foreground-container", true); this._boxContainer = this._element.append("g").classed("box-container", true); - if (this.clipPathEnabled) { + if (this._clipPathEnabled) { this._generateClipPath(); }; this._boundingBox = this._addBox("bounding-box"); - this._interactionsToRegister.forEach((r) => this.registerInteraction(r)); - this._interactionsToRegister = null; this._isSetup = true; } - public _requestedSpace(availableWidth : number, availableHeight: number): _SpaceRequest { - return {width: 0, height: 0, wantsWidth: false, wantsHeight: false}; + public requestedSpace(availableWidth: number, availableHeight: number): SpaceRequest { + return { + minWidth: 0, + minHeight: 0 + }; } /** @@ -103,19 +151,18 @@ export module Component { * If no parameters are supplied and the Component is a root node, * they are inferred from the size of the Component's element. * - * @param {number} offeredXOrigin x-coordinate of the origin of the space offered the Component - * @param {number} offeredYOrigin y-coordinate of the origin of the space offered the Component - * @param {number} availableWidth available width for the Component to render in - * @param {number} availableHeight available height for the Component to render in + * @param {Point} origin Origin of the space offered to the Component. + * @param {number} availableWidth + * @param {number} availableHeight + * @returns {Component} The calling Component. */ - public _computeLayout(offeredXOrigin?: number, offeredYOrigin?: number, availableWidth?: number, availableHeight?: number) { - if (offeredXOrigin == null || offeredYOrigin == null || availableWidth == null || availableHeight == null) { + public computeLayout(origin?: Point, availableWidth?: number, availableHeight?: number) { + if (origin == null || availableWidth == null || availableHeight == null) { if (this._element == null) { - throw new Error("anchor must be called before computeLayout"); + throw new Error("anchor() must be called before computeLayout()"); } else if (this._isTopLevelComponent) { // we are the root node, retrieve height/width from root SVG - offeredXOrigin = 0; - offeredYOrigin = 0; + origin = { x: 0, y: 0 }; // Set width/height to 100% if not specified, to allow accurate size calculation // see http://www.w3.org/TR/CSS21/visudet.html#block-replaced-width @@ -128,63 +175,74 @@ export module Component { } var elem: HTMLScriptElement = ( this._rootSVG.node()); - availableWidth = _Util.DOM.getElementWidth(elem); - availableHeight = _Util.DOM.getElementHeight(elem); + availableWidth = Utils.DOM.getElementWidth(elem); + availableHeight = Utils.DOM.getElementHeight(elem); } else { - throw new Error("null arguments cannot be passed to _computeLayout() on a non-root node"); + throw new Error("null arguments cannot be passed to computeLayout() on a non-root node"); } } var size = this._getSize(availableWidth, availableHeight); this._width = size.width; this._height = size.height; - this._xOrigin = offeredXOrigin + this._xOffset + (availableWidth - this.width()) * this._xAlignProportion; - this._yOrigin = offeredYOrigin + this._yOffset + (availableHeight - this.height()) * this._yAlignProportion; - this._element.attr("transform", "translate(" + this._xOrigin + "," + this._yOrigin + ")"); + var xAlignProportion = Component._xAlignToProportion[this._xAlignment]; + var yAlignProportion = Component._yAlignToProportion[this._yAlignment]; + this._origin = { + x: origin.x + (availableWidth - this.width()) * xAlignProportion, + y: origin.y + (availableHeight - this.height()) * yAlignProportion + }; + this._element.attr("transform", "translate(" + this._origin.x + "," + this._origin.y + ")"); this._boxes.forEach((b: D3.Selection) => b.attr("width", this.width()).attr("height", this.height())); + return this; } protected _getSize(availableWidth: number, availableHeight: number) { - var requestedSpace = this._requestedSpace(availableWidth, availableHeight); + var requestedSpace = this.requestedSpace(availableWidth, availableHeight); return { - width: this._isFixedWidth() ? Math.min(availableWidth , requestedSpace.width) : availableWidth, - height: this._isFixedHeight() ? Math.min(availableHeight, requestedSpace.height) : availableHeight + width: this.fixedWidth() ? Math.min(availableWidth , requestedSpace.minWidth) : availableWidth, + height: this.fixedHeight() ? Math.min(availableHeight, requestedSpace.minHeight) : availableHeight }; } - public _render() { + /** + * Queues the Component for rendering. Set immediately to true if the Component should be rendered + * immediately as opposed to queued to the RenderController. + * + * @returns {Component} The calling Component + */ + public render() { if (this._isAnchored && this._isSetup && this.width() >= 0 && this.height() >= 0) { - Core.RenderController.registerToRender(this); + RenderController.registerToRender(this); } + return this; } private _scheduleComputeLayout() { if (this._isAnchored && this._isSetup) { - Core.RenderController.registerToComputeLayout(this); + RenderController.registerToComputeLayout(this); } } - public _doRender() {/* overwrite */} - - public _useLastCalculatedLayout(): boolean; - public _useLastCalculatedLayout(useLast: boolean) : AbstractComponent; - public _useLastCalculatedLayout(useLast?: boolean) : any { - if (useLast == null) { - return this._usedLastLayout; - } else { - this._usedLastLayout = useLast; - return this; - } + public renderImmediately() { + return this; } - public _invalidateLayout() { - this._useLastCalculatedLayout(false); + /** + * Causes the Component to recompute layout and redraw. + * + * This function should be called when CSS changes could influence the size + * of the components, e.g. changing the font size. + * + * @returns {Component} The calling Component. + */ + public redraw() { if (this._isAnchored && this._isSetup) { if (this._isTopLevelComponent) { this._scheduleComputeLayout(); } else { - this._parent()._invalidateLayout(); + this.parent().redraw(); } } + return this; } /** @@ -193,7 +251,7 @@ export module Component { * @param {String|D3.Selection} element A D3 selection or a selector for getting the element to render into. * @returns {Component} The calling component. */ - public renderTo(element: String | D3.Selection): AbstractComponent { + public renderTo(element: String | D3.Selection): Component { this.detach(); if (element != null) { var selection: D3.Selection; @@ -205,109 +263,70 @@ export module Component { if (!selection.node() || selection.node().nodeName.toLowerCase() !== "svg") { throw new Error("Plottable requires a valid SVG to renderTo"); } - this._anchor(selection); + this.anchor(selection); } if (this._element == null) { throw new Error("If a component has never been rendered before, then renderTo must be given a node to render to, \ or a D3.Selection, or a selector string"); } - this._computeLayout(); - this._render(); + this.computeLayout(); + this.render(); // flush so that consumers can immediately attach to stuff we create in the DOM - Core.RenderController.flush(); + RenderController.flush(); return this; } /** - * Causes the Component to recompute layout and redraw. + * Gets the x alignment of the Component. * - * This function should be called when CSS changes could influence the size - * of the components, e.g. changing the font size. - * - * @returns {Component} The calling component. + * @returns {string} The current x alignment. */ - public redraw(): AbstractComponent { - this._invalidateLayout(); - return this; - } - + public xAlignment(): string; /** - * Sets the x alignment of the Component. This will be used if the - * Component is given more space than it needs. - * - * For example, you may want to make a Legend postition itself it the top - * right, so you would call `legend.xAlign("right")` and - * `legend.yAlign("top")`. + * Sets the x alignment of the Component. * * @param {string} alignment The x alignment of the Component (one of ["left", "center", "right"]). * @returns {Component} The calling Component. */ - public xAlign(alignment: string): AbstractComponent { - alignment = alignment.toLowerCase(); - if (alignment === "left") { - this._xAlignProportion = 0; - } else if (alignment === "center") { - this._xAlignProportion = 0.5; - } else if (alignment === "right") { - this._xAlignProportion = 1; - } else { - throw new Error("Unsupported alignment"); + public xAlignment(xAlignment: string): Component; + public xAlignment(xAlignment?: string): any { + if (xAlignment == null) { + return this._xAlignment; } - this._invalidateLayout(); - return this; - } - /** - * Sets the y alignment of the Component. This will be used if the - * Component is given more space than it needs. - * - * For example, you may want to make a Legend postition itself it the top - * right, so you would call `legend.xAlign("right")` and - * `legend.yAlign("top")`. - * - * @param {string} alignment The x alignment of the Component (one of ["top", "center", "bottom"]). - * @returns {Component} The calling Component. - */ - public yAlign(alignment: string): AbstractComponent { - alignment = alignment.toLowerCase(); - if (alignment === "top") { - this._yAlignProportion = 0; - } else if (alignment === "center") { - this._yAlignProportion = 0.5; - } else if (alignment === "bottom") { - this._yAlignProportion = 1; - } else { - throw new Error("Unsupported alignment"); + xAlignment = xAlignment.toLowerCase(); + if (Component._xAlignToProportion[xAlignment] == null) { + throw new Error("Unsupported alignment: " + xAlignment); } - this._invalidateLayout(); + this._xAlignment = xAlignment; + this.redraw(); return this; } /** - * Sets the x offset of the Component. This will be used if the Component - * is given more space than it needs. + * Gets the y alignment of the Component. * - * @param {number} offset The desired x offset, in pixels, from the left - * side of the container. - * @returns {Component} The calling Component. + * @returns {string} The current y alignment. */ - public xOffset(offset: number): AbstractComponent { - this._xOffset = offset; - this._invalidateLayout(); - return this; - } - + public yAlignment(): string; /** - * Sets the y offset of the Component. This will be used if the Component - * is given more space than it needs. + * Sets the y alignment of the Component. * - * @param {number} offset The desired y offset, in pixels, from the top - * side of the container. + * @param {string} alignment The y alignment of the Component (one of ["top", "center", "bottom"]). * @returns {Component} The calling Component. */ - public yOffset(offset: number): AbstractComponent { - this._yOffset = offset; - this._invalidateLayout(); + public yAlignment(yAlignment: string): Component; + public yAlignment(yAlignment?: string): any { + if (yAlignment == null) { + return this._yAlignment; + } + + yAlignment = yAlignment.toLowerCase(); + if (Component._yAlignToProportion[yAlignment] == null) { + throw new Error("Unsupported alignment: " + yAlignment); + } + this._yAlignment = yAlignment; + this.redraw(); return this; } @@ -332,34 +351,13 @@ export module Component { // They don't need the current URL in the clip path reference. var prefix = /MSIE [5-9]/.test(navigator.userAgent) ? "" : document.location.href; prefix = prefix.split("#")[0]; // To fix cases where an anchor tag was used - this._element.attr("clip-path", "url(\"" + prefix + "#clipPath" + this.getID() + "\")"); + var clipPathId = Utils.DOM.getUniqueClipPathId(); + this._element.attr("clip-path", "url(\"" + prefix + "#" + clipPathId + "\")"); var clipPathParent = this._boxContainer.append("clipPath") - .attr("id", "clipPath" + this.getID()); + .attr("id", clipPathId); this._addBox("clip-rect", clipPathParent); } - /** - * Attaches an Interaction to the Component, so that the Interaction will listen for events on the Component. - * - * @param {Interaction} interaction The Interaction to attach to the Component. - * @returns {Component} The calling Component. - */ - public registerInteraction(interaction: Interaction.AbstractInteraction) { - // Interactions can be registered before or after anchoring. If registered before, they are - // pushed to this._interactionsToRegister and registered during anchoring. If after, they are - // registered immediately - if (this._element) { - if (!this._hitBox && interaction._requiresHitbox()) { - this._hitBox = this._addBox("hit-box"); - this._hitBox.style("fill", "#ffffff").style("opacity", 0); // We need to set these so Chrome will register events - } - interaction._anchor(this, this._hitBox); - } else { - this._interactionsToRegister.push(interaction); - } - return this; - } - /** * Checks if the Component has a given CSS class. * @@ -372,9 +370,9 @@ export module Component { * * @param {string} cssClass The CSS class to add or remove. * @param {boolean} addClass If true, adds the provided CSS class; otherwise, removes it. - * @returns {AbstractComponent} The calling Component. + * @returns {Component} The calling Component. */ - public classed(cssClass: string, addClass: boolean): AbstractComponent; + public classed(cssClass: string, addClass: boolean): Component; public classed(cssClass: string, addClass?: boolean): any { if (addClass == null) { if (cssClass == null) { @@ -408,8 +406,8 @@ export module Component { * * @returns {boolean} Whether the component has a fixed width. */ - public _isFixedWidth(): boolean { - return this._fixedWidthFlag; + public fixedWidth() { + return false; } /** @@ -418,55 +416,8 @@ export module Component { * * @returns {boolean} Whether the component has a fixed height. */ - public _isFixedHeight(): boolean { - return this._fixedHeightFlag; - } - - public _merge(c: AbstractComponent, below: boolean): Component.Group { - var cg: Component.Group; - if (Plottable.Component.Group.prototype.isPrototypeOf(c)) { - cg = ( c); - cg._addComponent(this, below); - return cg; - } else { - var mergedComponents = below ? [this, c] : [c, this]; - cg = new Plottable.Component.Group(mergedComponents); - return cg; - } - } - - /** - * Merges this Component above another Component, returning a - * ComponentGroup. This is used to layer Components on top of each other. - * - * There are four cases: - * Component + Component: Returns a ComponentGroup with the first component after the second component. - * ComponentGroup + Component: Returns the ComponentGroup with the Component prepended. - * Component + ComponentGroup: Returns the ComponentGroup with the Component appended. - * ComponentGroup + ComponentGroup: Returns a new ComponentGroup with the first group after the second group. - * - * @param {Component} c The component to merge in. - * @returns {ComponentGroup} The relevant ComponentGroup out of the above four cases. - */ - public above(c: AbstractComponent): Component.Group { - return this._merge(c, false); - } - - /** - * Merges this Component below another Component, returning a - * ComponentGroup. This is used to layer Components on top of each other. - * - * There are four cases: - * Component + Component: Returns a ComponentGroup with the first component before the second component. - * ComponentGroup + Component: Returns the ComponentGroup with the Component appended. - * Component + ComponentGroup: Returns the ComponentGroup with the Component prepended. - * ComponentGroup + ComponentGroup: Returns a new ComponentGroup with the first group before the second group. - * - * @param {Component} c The component to merge in. - * @returns {ComponentGroup} The relevant ComponentGroup out of the above four cases. - */ - public below(c: AbstractComponent): Component.Group { - return this._merge(c, true); + public fixedHeight() { + return false; } /** @@ -478,37 +429,59 @@ export module Component { * @returns The calling Component. */ public detach() { + this.parent(null); + if (this._isAnchored) { this._element.remove(); } + this._isAnchored = false; - var parent: AbstractComponentContainer = this._parent(); + this._onDetachCallbacks.callCallbacks(this); + return this; + } - if (parent != null) { - parent._removeComponent(this); - } - this._isAnchored = false; - this._parentElement = null; + /** + * Adds a callback to be called when th Component is detach()-ed. + * + * @param {ComponentCallback} callback The callback to be added. + * @return {Component} The calling Component. + */ + public onDetach(callback: ComponentCallback) { + this._onDetachCallbacks.add(callback); return this; } - public _parent(): AbstractComponentContainer; - public _parent(parentElement: AbstractComponentContainer): any; - public _parent(parentElement?: AbstractComponentContainer): any { - if (parentElement === undefined) { - return this._parentElement; - } + /** + * Removes a callback to be called when th Component is detach()-ed. + * The callback is identified by reference equality. + * + * @param {ComponentCallback} callback The callback to be removed. + * @return {Component} The calling Component. + */ + public offDetach(callback: ComponentCallback) { + this._onDetachCallbacks.delete(callback); + return this; + } - this.detach(); - this._parentElement = parentElement; + public parent(): ComponentContainer; + public parent(parent: ComponentContainer): Component; + public parent(parent?: ComponentContainer): any { + if (parent === undefined) { + return this._parent; + } + if (parent !== null && !parent.has(this)) { + throw new Error("Passed invalid parent"); + } + this._parent = parent; + return this; } /** * Removes a Component from the DOM and disconnects it from everything it's * listening to (effectively destroying it). */ - public remove() { - this._removed = true; + public destroy() { + this._destroyed = true; this.detach(); } @@ -537,8 +510,8 @@ export module Component { */ public origin(): Point { return { - x: this._xOrigin, - y: this._yOrigin + x: this._origin.x, + y: this._origin.y }; } @@ -549,12 +522,12 @@ export module Component { */ public originToSVG(): Point { var origin = this.origin(); - var ancestor = this._parent(); + var ancestor = this.parent(); while (ancestor != null) { var ancestorOrigin = ancestor.origin(); origin.x += ancestorOrigin.x; origin.y += ancestorOrigin.y; - ancestor = ancestor._parent(); + ancestor = ancestor.parent(); } return origin; } @@ -594,18 +567,5 @@ export module Component { public background(): D3.Selection { return this._backgroundContainer; } - - /** - * Returns the hitbox selection for the component - * (A selection in front of the foreground used mainly for interactions) - * - * Will return undefined if the component has not been anchored - * - * @return {D3.Selection} hitbox selection for the component - */ - public hitBox(): D3.Selection { - return this._hitBox; - } } } -} diff --git a/src/components/componentContainer.ts b/src/components/componentContainer.ts new file mode 100644 index 0000000000..0e9d2344c0 --- /dev/null +++ b/src/components/componentContainer.ts @@ -0,0 +1,80 @@ +/// + +module Plottable { + /* + * ComponentContainer class encapsulates Table and ComponentGroup's shared functionality. + * It will not do anything if instantiated directly. + */ + export class ComponentContainer extends Component { + private _detachCallback: ComponentCallback; + + constructor() { + super(); + this._detachCallback = (component: Component) => this.remove(component); + } + + public anchor(selection: D3.Selection) { + super.anchor(selection); + this._forEach((c) => c.anchor(this._content)); + return this; + } + + public render() { + this._forEach((c) => c.render()); + return this; + } + + /** + * Checks whether the specified Component is in the ComponentContainer. + */ + public has(component: Component): boolean { + throw new Error("has() is not implemented on ComponentContainer"); + } + + protected _adoptAndAnchor(component: Component) { + component.parent(this); + component.onDetach(this._detachCallback); + if (this._isAnchored) { + component.anchor(this._content); + } + } + + /** + * Removes the specified Component from the ComponentContainer. + */ + public remove(component: Component) { + if (this.has(component)) { + component.offDetach(this._detachCallback); + this._remove(component); + component.detach(); + this.redraw(); + } + return this; + } + + /** + * Carry out the actual removal of a Component. + * Implementation dependent on the type of container. + * + * @return {boolean} true if the Component was successfully removed, false otherwise. + */ + protected _remove(component: Component) { + return false; + } + + /** + * Invokes a callback on each Component in the ComponentContainer. + */ + protected _forEach(callback: (component: Component) => void) { + throw new Error("_forEach() is not implemented on ComponentContainer"); + } + + /** + * Destroys the ComponentContainer and all Components within it. + */ + public destroy() { + super.destroy(); + this._forEach((c: Component) => c.destroy()); + } + } +} diff --git a/src/components/componentGroup.ts b/src/components/componentGroup.ts deleted file mode 100644 index 0b2af65084..0000000000 --- a/src/components/componentGroup.ts +++ /dev/null @@ -1,68 +0,0 @@ -/// - -module Plottable { -export module Component { - export class Group extends AbstractComponentContainer { - - /** - * Constructs a Component.Group. - * - * A Component.Group is a set of Components that will be rendered on top of - * each other. When you call Component.above(Component) or Component.below(Component), - * it creates and returns a Component.Group. - * - * Note that the order of the components will determine placement on the z-axis, - * with the previous items rendered below the later items. - * - * @constructor - * @param {Component[]} components The Components in the resultant Component.Group (default = []). - */ - constructor(components: AbstractComponent[] = []){ - super(); - this.classed("component-group", true); - components.forEach((c: AbstractComponent) => this._addComponent(c)); - } - - public _requestedSpace(offeredWidth: number, offeredHeight: number): _SpaceRequest { - var requests = this.components().map((c: AbstractComponent) => c._requestedSpace(offeredWidth, offeredHeight)); - return { - width : _Util.Methods.max<_SpaceRequest, number>(requests, (request: _SpaceRequest) => request.width, 0), - height: _Util.Methods.max<_SpaceRequest, number>(requests, (request: _SpaceRequest) => request.height, 0), - wantsWidth : requests.map((r: _SpaceRequest) => r.wantsWidth ).some((x: boolean) => x), - wantsHeight: requests.map((r: _SpaceRequest) => r.wantsHeight).some((x: boolean) => x) - }; - } - - public _merge(c: AbstractComponent, below: boolean): Group { - this._addComponent(c, !below); - return this; - } - - public _computeLayout(offeredXOrigin?: number, - offeredYOrigin?: number, - availableWidth?: number, - availableHeight?: number): Group { - super._computeLayout(offeredXOrigin, offeredYOrigin, availableWidth, availableHeight); - this.components().forEach((c) => { - c._computeLayout(0, 0, this.width(), this.height()); - }); - return this; - } - - protected _getSize(availableWidth: number, availableHeight: number) { - return { - width: availableWidth, - height: availableHeight - }; - } - - public _isFixedWidth(): boolean { - return this.components().every((c) => c._isFixedWidth()); - } - - public _isFixedHeight(): boolean { - return this.components().every((c) => c._isFixedHeight()); - } - } -} -} diff --git a/src/components/dragBoxLayer.ts b/src/components/dragBoxLayer.ts index aa244d654e..634bf2e3d4 100644 --- a/src/components/dragBoxLayer.ts +++ b/src/components/dragBoxLayer.ts @@ -1,7 +1,10 @@ /// module Plottable { -export module Component { + +export type DragBoxCallback = (bounds: Bounds) => any; + +export module Components { type _EdgeIndicator = { top: boolean; bottom: boolean; @@ -9,8 +12,8 @@ export module Component { right: boolean; } - export class DragBoxLayer extends Component.SelectionBoxLayer { - private _dragInteraction: Interaction.Drag; + export class DragBoxLayer extends Components.SelectionBoxLayer { + private _dragInteraction: Interactions.Drag; private _detectionEdgeT: D3.Selection; private _detectionEdgeB: D3.Selection; private _detectionEdgeL: D3.Selection; @@ -24,9 +27,9 @@ export module Component { private _resizable = false; protected _hasCorners = true; - private _dragStartCallback: (b: Bounds) => any; - private _dragCallback: (b: Bounds) => any; - private _dragEndCallback: (b: Bounds) => any; + private _dragStartCallbacks: Utils.CallbackSet; + private _dragCallbacks: Utils.CallbackSet; + private _dragEndCallbacks: Utils.CallbackSet; constructor() { super(); @@ -36,12 +39,16 @@ export module Component { * user's cursor from changing outside the DragBoxLayer, where they * wouldn't be able to grab the edges or corners for resizing. */ - this.clipPathEnabled = true; + this._clipPathEnabled = true; this.classed("drag-box-layer", true); - this._dragInteraction = new Interaction.Drag(); + this._dragInteraction = new Interactions.Drag(); this._setUpCallbacks(); - this.registerInteraction(this._dragInteraction); + this._dragInteraction.attachTo(this); + + this._dragStartCallbacks = new Utils.CallbackSet(); + this._dragCallbacks = new Utils.CallbackSet(); + this._dragEndCallbacks = new Utils.CallbackSet(); } private _setUpCallbacks() { @@ -71,9 +78,7 @@ export module Component { // copy points so changes to topLeft and bottomRight don't mutate bounds topLeft = { x: bounds.topLeft.x, y: bounds.topLeft.y }; bottomRight = { x: bounds.bottomRight.x, y: bounds.bottomRight.y }; - if (this._dragStartCallback) { - this._dragStartCallback(bounds); - } + this._dragStartCallbacks.callCallbacks(bounds); }); this._dragInteraction.onDrag((s: Point, e: Point) => { @@ -99,9 +104,7 @@ export module Component { bottomRight: bottomRight }); - if (this._dragCallback) { - this._dragCallback(this.bounds()); - } + this._dragCallbacks.callCallbacks(this.bounds()); }); this._dragInteraction.onDragEnd((s: Point, e: Point) => { @@ -109,9 +112,7 @@ export module Component { this.boxVisible(false); } - if (this._dragEndCallback) { - this._dragEndCallback(this.bounds()); - } + this._dragEndCallbacks.callCallbacks(this.bounds()); }); } @@ -172,8 +173,8 @@ export module Component { return edges; } - public _doRender() { - super._doRender(); + public renderImmediately() { + super.renderImmediately(); if (this.boxVisible()) { var bounds = this.bounds(); var t = bounds.topLeft.y; @@ -204,6 +205,7 @@ export module Component { this._detectionCornerBL.attr({ cx: l, cy: b, r: this._detectionRadius }); this._detectionCornerBR.attr({ cx: r, cy: b, r: this._detectionRadius }); } + return this; } } @@ -228,7 +230,7 @@ export module Component { throw new Error("detection radius cannot be negative."); } this._detectionRadius = r; - this._render(); + this.render(); return this; } @@ -260,70 +262,70 @@ export module Component { this.classed("y-resizable", canResize); } - /** - * Gets the callback that is called when dragging starts. - * - * @returns {(b: Bounds) => any} The callback called when dragging starts. - */ - public onDragStart(): (b: Bounds) => any; /** * Sets the callback to be called when dragging starts. * - * @param {(b: Bounds) => any} cb The callback to be called. Passed the current Bounds in pixels. + * @param {DragBoxCallback} callback The callback to be called. Passed the current Bounds in pixels. * @returns {DragBoxLayer} The calling DragBoxLayer. */ - public onDragStart(cb: (b: Bounds) => any): DragBoxLayer; - public onDragStart(cb?: (b: Bounds) => any): any { - if (cb === undefined) { - return this._dragStartCallback; - } else { - this._dragStartCallback = cb; - return this; - } + public onDragStart(callback: DragBoxCallback) { + this._dragStartCallbacks.add(callback); + return this; } /** - * Gets the callback that is called during dragging. + * Removes a callback to be called when dragging starts. * - * @returns {(b: Bounds) => any} The callback called during dragging. + * @param {DragBoxCallback} callback The callback to be removed. + * @returns {DragBoxLayer} The calling DragBoxLayer. */ - public onDrag(): (b: Bounds) => any; + public offDragStart(callback: DragBoxCallback) { + this._dragStartCallbacks.delete(callback); + return this; + } + /** * Sets a callback to be called during dragging. * - * @param {(b: Bounds) => any} cb The callback to be called. Passed the current Bounds in pixels. + * @param {DragBoxCallback} callback The callback to be called. Passed the current Bounds in pixels. * @returns {DragBoxLayer} The calling DragBoxLayer. */ - public onDrag(cb: (b: Bounds) => any): DragBoxLayer; - public onDrag(cb?: (b: Bounds) => any): any { - if (cb === undefined) { - return this._dragCallback; - } else { - this._dragCallback = cb; - return this; - } + public onDrag(callback: DragBoxCallback) { + this._dragCallbacks.add(callback); + return this; } /** - * Gets the callback that is called when dragging ends. + * Removes a callback to be called during dragging. * - * @returns {(b: Bounds) => any} The callback called when dragging ends. + * @param {DragBoxCallback} callback The callback to be removed. + * @returns {DragBoxLayer} The calling DragBoxLayer. */ - public onDragEnd(): (b: Bounds) => any; + public offDrag(callback: DragBoxCallback) { + this._dragCallbacks.delete(callback); + return this; + } + /** * Sets a callback to be called when the dragging ends. * - * @param {(b: Bounds) => any} cb The callback to be called. Passed the current Bounds in pixels. + * @param {DragBoxCallback} callback The callback to be called. Passed the current Bounds in pixels. * @returns {DragBoxLayer} The calling DragBoxLayer. */ - public onDragEnd(cb: (b: Bounds) => any): DragBoxLayer; - public onDragEnd(cb?: (b: Bounds) => any): any { - if (cb === undefined) { - return this._dragEndCallback; - } else { - this._dragEndCallback = cb; - return this; - } + public onDragEnd(callback: DragBoxCallback) { + this._dragEndCallbacks.add(callback); + return this; + } + + /** + * Removes a callback to be called when the dragging ends. + * + * @param {DragBoxCallback} callback The callback to be removed. + * @returns {DragBoxLayer} The calling DragBoxLayer. + */ + public offDragEnd(callback: DragBoxCallback) { + this._dragEndCallbacks.delete(callback); + return this; } } diff --git a/src/components/gridlines.ts b/src/components/gridlines.ts index 176f0f8a1e..480c6b0b43 100644 --- a/src/components/gridlines.ts +++ b/src/components/gridlines.ts @@ -1,13 +1,15 @@ /// module Plottable { -export module Component { - export class Gridlines extends AbstractComponent { - private _xScale: Scale.AbstractQuantitative; - private _yScale: Scale.AbstractQuantitative; +export module Components { + export class Gridlines extends Component { + private _xScale: QuantitativeScale; + private _yScale: QuantitativeScale; private _xLinesContainer: D3.Selection; private _yLinesContainer: D3.Selection; + private _renderCallback: ScaleCallback>; + /** * Creates a set of Gridlines. * @constructor @@ -15,32 +17,33 @@ export module Component { * @param {QuantitativeScale} xScale The scale to base the x gridlines on. Pass null if no gridlines are desired. * @param {QuantitativeScale} yScale The scale to base the y gridlines on. Pass null if no gridlines are desired. */ - constructor(xScale: Scale.AbstractQuantitative, yScale: Scale.AbstractQuantitative) { - if (xScale != null && !(Scale.AbstractQuantitative.prototype.isPrototypeOf(xScale))) { - throw new Error("xScale needs to inherit from Scale.AbstractQuantitative"); + constructor(xScale: QuantitativeScale, yScale: QuantitativeScale) { + if (xScale != null && !(QuantitativeScale.prototype.isPrototypeOf(xScale))) { + throw new Error("xScale needs to inherit from Scale.QuantitativeScale"); } - if (yScale != null && !(Scale.AbstractQuantitative.prototype.isPrototypeOf(yScale))) { - throw new Error("yScale needs to inherit from Scale.AbstractQuantitative"); + if (yScale != null && !(QuantitativeScale.prototype.isPrototypeOf(yScale))) { + throw new Error("yScale needs to inherit from Scale.QuantitativeScale"); } super(); this.classed("gridlines", true); this._xScale = xScale; this._yScale = yScale; + this._renderCallback = (scale) => this.render(); if (this._xScale) { - this._xScale.broadcaster.registerListener(this, () => this._render()); + this._xScale.onUpdate(this._renderCallback); } if (this._yScale) { - this._yScale.broadcaster.registerListener(this, () => this._render()); + this._yScale.onUpdate(this._renderCallback); } } - public remove() { - super.remove(); + public destroy() { + super.destroy(); if (this._xScale) { - this._xScale.broadcaster.deregisterListener(this); + this._xScale.offUpdate(this._renderCallback); } if (this._yScale) { - this._yScale.broadcaster.deregisterListener(this); + this._yScale.offUpdate(this._renderCallback); } return this; } @@ -51,10 +54,11 @@ export module Component { this._yLinesContainer = this._content.append("g").classed("y-gridlines", true); } - public _doRender() { - super._doRender(); + public renderImmediately() { + super.renderImmediately(); this._redrawXLines(); this._redrawYLines(); + return this; } private _redrawXLines() { diff --git a/src/components/group.ts b/src/components/group.ts new file mode 100644 index 0000000000..69be3806c6 --- /dev/null +++ b/src/components/group.ts @@ -0,0 +1,93 @@ +/// + +module Plottable { +export module Components { + export class Group extends ComponentContainer { + private _components: Component[] = []; + + /** + * Constructs a Component.Group. + * + * A Component.Group is a set of Components that will be rendered on top of + * each other. Components added later will be rendered on top of existing Components. + * + * @constructor + * @param {Component[]} components The Components in the resultant Component.Group (default = []). + */ + constructor(components: Component[] = []) { + super(); + this.classed("component-group", true); + components.forEach((c: Component) => this.append(c)); + } + + protected _forEach(callback: (component: Component) => any) { + this._components.forEach(callback); + } + + /** + * Checks whether the specified Component is in the Group. + */ + public has(component: Component) { + return this._components.indexOf(component) >= 0; + } + + public requestedSpace(offeredWidth: number, offeredHeight: number): SpaceRequest { + var requests = this._components.map((c: Component) => c.requestedSpace(offeredWidth, offeredHeight)); + return { + minWidth: Utils.Methods.max(requests, (request) => request.minWidth, 0), + minHeight: Utils.Methods.max(requests, (request) => request.minHeight, 0) + }; + } + + public computeLayout(origin?: Point, availableWidth?: number, availableHeight?: number) { + super.computeLayout(origin, availableWidth, availableHeight); + this._forEach((component) => { + component.computeLayout({ x: 0, y: 0 }, this.width(), this.height()); + }); + return this; + } + + protected _getSize(availableWidth: number, availableHeight: number) { + return { + width: availableWidth, + height: availableHeight + }; + } + + public fixedWidth(): boolean { + return this._components.every((c) => c.fixedWidth()); + } + + public fixedHeight(): boolean { + return this._components.every((c) => c.fixedHeight()); + } + + /** + * @return {Component[]} The Components in this Group. + */ + public components(): Component[] { + return this._components.slice(); + } + + public append(component: Component) { + if (component != null && !this.has(component)) { + component.detach(); + this._components.push(component); + this._adoptAndAnchor(component); + this.redraw(); + } + return this; + } + + protected _remove(component: Component) { + var removeIndex = this._components.indexOf(component); + if (removeIndex >= 0) { + this._components.splice(removeIndex, 1); + return true; + } + return false; + } + + } +} +} diff --git a/src/components/interpolatedColorLegend.ts b/src/components/interpolatedColorLegend.ts index bcd24f6540..c72b87bb51 100644 --- a/src/components/interpolatedColorLegend.ts +++ b/src/components/interpolatedColorLegend.ts @@ -1,12 +1,12 @@ /// module Plottable { -export module Component { - export class InterpolatedColorLegend extends AbstractComponent { +export module Components { + export class InterpolatedColorLegend extends Component { private _measurer: SVGTypewriter.Measurers.Measurer; private _wrapper: SVGTypewriter.Wrappers.Wrapper; private _writer: SVGTypewriter.Writers.Writer; - private _scale: Scale.InterpolatedColor; + private _scale: Scales.InterpolatedColor; private _orientation: String ; private _padding = 5; private _numSwatches = 10; @@ -16,6 +16,7 @@ export module Component { private _swatchBoundingBox: D3.Selection; private _lowerLabel: D3.Selection; private _upperLabel: D3.Selection; + private _redrawCallback: ScaleCallback; /** * The css class applied to the legend labels. @@ -34,24 +35,23 @@ export module Component { * @param {string} orientation (horizontal/left/right). * @param {Formatter} The labels are formatted using this function. */ - constructor(interpolatedColorScale: Scale.InterpolatedColor, orientation = "horizontal", formatter = Formatters.general()) { + constructor(interpolatedColorScale: Scales.InterpolatedColor, orientation = "horizontal", formatter = Formatters.general()) { super(); if (interpolatedColorScale == null ) { throw new Error("InterpolatedColorLegend requires a interpolatedColorScale"); } this._scale = interpolatedColorScale; - this._scale.broadcaster.registerListener(this, () => this._invalidateLayout()); + this._redrawCallback = (scale) => this.redraw(); + this._scale.onUpdate(this._redrawCallback); this._formatter = formatter; this._orientation = InterpolatedColorLegend._ensureOrientation(orientation); - this._fixedWidthFlag = true; - this._fixedHeightFlag = true; this.classed("legend", true).classed("interpolated-color-legend", true); } - public remove() { - super.remove(); - this._scale.broadcaster.deregisterListener(this); + public destroy() { + super.destroy(); + this._scale.offUpdate(this._redrawCallback); } /** @@ -72,7 +72,7 @@ export module Component { return this._formatter; } this._formatter = formatter; - this._invalidateLayout(); + this.redraw(); return this; } @@ -90,7 +90,7 @@ export module Component { * * @returns {string} The current orientation. */ - public orient(): string; + public orientation(): string; /** * Sets the orientation of the InterpolatedColorLegend. * @@ -98,17 +98,25 @@ export module Component { * * @returns {InterpolatedColorLegend} The calling InterpolatedColorLegend. */ - public orient(newOrientation: string): InterpolatedColorLegend; - public orient(newOrientation?: string): any { - if (newOrientation == null) { + public orientation(orientation: string): InterpolatedColorLegend; + public orientation(orientation?: string): any { + if (orientation == null) { return this._orientation; } else { - this._orientation = InterpolatedColorLegend._ensureOrientation(newOrientation); - this._invalidateLayout(); + this._orientation = InterpolatedColorLegend._ensureOrientation(orientation); + this.redraw(); return this; } } + public fixedWidth() { + return true; + } + + public fixedHeight() { + return true; + } + private _generateTicks() { var domain = this._scale.domain(); var slope = (domain[1] - domain[0]) / this._numSwatches; @@ -132,7 +140,7 @@ export module Component { this._writer = new SVGTypewriter.Writers.Writer(this._measurer, this._wrapper); } - public _requestedSpace(offeredWidth: number, offeredHeight: number): _SpaceRequest { + public requestedSpace(offeredWidth: number, offeredHeight: number): SpaceRequest { var textHeight = this._measurer.measure().height; var ticks = this._generateTicks(); @@ -144,7 +152,7 @@ export module Component { var desiredHeight: number; var desiredWidth: number; if (this._isVertical()) { - var longestWidth = _Util.Methods.max(labelWidths, 0); + var longestWidth = Utils.Methods.max(labelWidths, 0); desiredWidth = this._padding + textHeight + this._padding + longestWidth + this._padding; desiredHeight = this._padding + numSwatches * textHeight + this._padding; } else { @@ -155,10 +163,8 @@ export module Component { } return { - width : desiredWidth, - height: desiredHeight, - wantsWidth: offeredWidth < desiredWidth, - wantsHeight: offeredHeight < desiredHeight + minWidth: desiredWidth, + minHeight: desiredHeight }; } @@ -166,12 +172,11 @@ export module Component { return this._orientation !== "horizontal"; } - public _doRender() { - super._doRender(); + public renderImmediately() { + super.renderImmediately(); var domain = this._scale.domain(); - var textHeight = this._measurer.measure().height; var text0 = this._formatter(domain[0]); var text0Width = this._measurer.measure(text0).width; var text1 = this._formatter(domain[1]); @@ -273,6 +278,7 @@ export module Component { "x": swatchX, "y": swatchY }); + return this; } } diff --git a/src/components/label.ts b/src/components/label.ts index 88e78eb43b..b4ff2ec107 100644 --- a/src/components/label.ts +++ b/src/components/label.ts @@ -1,16 +1,21 @@ /// module Plottable { -export module Component { - export class Label extends AbstractComponent { +export module Components { + export class Label extends Component { + + // Css class for labels that are made for rendering titles. + public static TITLE_LABEL_CLASS = "title-label"; + + // Css class for labels that are made for rendering axis titles. + public static AXIS_LABEL_CLASS = "axis-label"; + private _textContainer: D3.Selection; private _text: string; // text assigned to the Label; may not be the actual text displayed due to truncation private _orientation: string; private _measurer: SVGTypewriter.Measurers.Measurer; private _wrapper: SVGTypewriter.Wrappers.Wrapper; private _writer: SVGTypewriter.Writers.Writer; - private _xAlignment: string; - private _yAlignment: string; private _padding: number; /** @@ -27,51 +32,19 @@ export module Component { super(); this.classed("label", true); this.text(displayText); - this.orient(orientation); - this.xAlign("center").yAlign("center"); - this._fixedHeightFlag = true; - this._fixedWidthFlag = true; + this.orientation(orientation); + this.xAlignment("center").yAlignment("center"); this._padding = 0; } - /** - * Sets the horizontal side the label will go to given the label is given more space that it needs - * - * @param {string} alignment The new setting, one of `["left", "center", - * "right"]`. Defaults to `"center"`. - * @returns {Label} The calling Label. - */ - public xAlign(alignment: string): Label { - var alignmentLC = alignment.toLowerCase(); - super.xAlign(alignmentLC); - this._xAlignment = alignmentLC; - return this; - } - - /** - * Sets the vertical side the label will go to given the label is given more space that it needs - * - * @param {string} alignment The new setting, one of `["top", "center", - * "bottom"]`. Defaults to `"center"`. - * @returns {Label} The calling Label. - */ - public yAlign(alignment: string): Label { - var alignmentLC = alignment.toLowerCase(); - super.yAlign(alignmentLC); - this._yAlignment = alignmentLC; - return this; - } - - public _requestedSpace(offeredWidth: number, offeredHeight: number): _SpaceRequest { + public requestedSpace(offeredWidth: number, offeredHeight: number): SpaceRequest { var desiredWH = this._measurer.measure(this._text); - var desiredWidth = (this.orient() === "horizontal" ? desiredWH.width : desiredWH.height) + 2 * this.padding(); - var desiredHeight = (this.orient() === "horizontal" ? desiredWH.height : desiredWH.width) + 2 * this.padding(); + var desiredWidth = (this.orientation() === "horizontal" ? desiredWH.width : desiredWH.height) + 2 * this.padding(); + var desiredHeight = (this.orientation() === "horizontal" ? desiredWH.height : desiredWH.width) + 2 * this.padding(); return { - width : desiredWidth, - height: desiredHeight, - wantsWidth : desiredWidth > offeredWidth, - wantsHeight: desiredHeight > offeredHeight + minWidth: desiredWidth, + minHeight: desiredHeight }; } @@ -102,7 +75,7 @@ export module Component { return this._text; } else { this._text = displayText; - this._invalidateLayout(); + this.redraw(); return this; } } @@ -112,7 +85,7 @@ export module Component { * * @returns {string} the current orientation. */ - public orient(): string; + public orientation(): string; /** * Sets the orientation of the Label. * @@ -120,18 +93,18 @@ export module Component { * (horizontal/left/right). * @returns {Label} The calling Label. */ - public orient(newOrientation: string): Label; - public orient(newOrientation?: string): any { - if (newOrientation == null) { + public orientation(orientation: string): Label; + public orientation(orientation?: string): any { + if (orientation == null) { return this._orientation; } else { - newOrientation = newOrientation.toLowerCase(); - if (newOrientation === "horizontal" || newOrientation === "left" || newOrientation === "right") { - this._orientation = newOrientation; + orientation = orientation.toLowerCase(); + if (orientation === "horizontal" || orientation === "left" || orientation === "right") { + this._orientation = orientation; } else { - throw new Error(newOrientation + " is not a valid orientation for LabelComponent"); + throw new Error(orientation + " is not a valid orientation for LabelComponent"); } - this._invalidateLayout(); + this.redraw(); return this; } } @@ -158,13 +131,21 @@ export module Component { throw new Error(padAmount + " is not a valid padding value. Cannot be less than 0."); } this._padding = padAmount; - this._invalidateLayout(); + this.redraw(); return this; } } - public _doRender() { - super._doRender(); + public fixedWidth() { + return true; + } + + public fixedHeight() { + return true; + } + + public renderImmediately() { + super.renderImmediately(); // HACKHACK SVGTypewriter should remove existing content - #21 on SVGTypewriter. this._textContainer.selectAll("g").remove(); var textMeasurement = this._measurer.measure(this._text); @@ -176,35 +157,12 @@ export module Component { var textRotation: {[s: string]: number} = {horizontal: 0, right: 90, left: -90}; var writeOptions = { selection: this._textContainer, - xAlign: this._xAlignment, - yAlign: this._yAlignment, - textRotation: textRotation[this.orient()] + xAlign: this.xAlignment(), + yAlign: this.yAlignment(), + textRotation: textRotation[this.orientation()] }; this._writer.write(this._text, writeWidth, writeHeight, writeOptions); - } - } - - export class TitleLabel extends Label { - /** - * Creates a TitleLabel, a type of label made for rendering titles. - * - * @constructor - */ - constructor(text?: string, orientation?: string) { - super(text, orientation); - this.classed("title-label", true); - } - } - - export class AxisLabel extends Label { - /** - * Creates a AxisLabel, a type of label made for rendering axis labels. - * - * @constructor - */ - constructor(text?: string, orientation?: string) { - super(text, orientation); - this.classed("axis-label", true); + return this; } } } diff --git a/src/components/legend.ts b/src/components/legend.ts index 43b4bbdd8a..5f9a47b69f 100644 --- a/src/components/legend.ts +++ b/src/components/legend.ts @@ -1,8 +1,8 @@ /// module Plottable { -export module Component { - export class Legend extends AbstractComponent { +export module Components { + export class Legend extends Component { /** * The css class applied to each legend row */ @@ -17,13 +17,14 @@ export module Component { public static LEGEND_SYMBOL_CLASS = "legend-symbol"; private _padding = 5; - private _scale: Scale.Color; + private _scale: Scales.Color; private _maxEntriesPerRow: number; private _sortFn: (a: string, b: string) => number; private _measurer: SVGTypewriter.Measurers.Measurer; private _wrapper: SVGTypewriter.Wrappers.Wrapper; private _writer: SVGTypewriter.Writers.Writer; private _symbolFactoryAccessor: (datum: any, index: number) => SymbolFactory; + private _redrawCallback: ScaleCallback; /** * Creates a Legend. @@ -34,7 +35,7 @@ export module Component { * @constructor * @param {Scale.Color} colorScale */ - constructor(colorScale: Scale.Color) { + constructor(colorScale: Scales.Color) { super(); this.classed("legend", true); this.maxEntriesPerRow(1); @@ -44,11 +45,10 @@ export module Component { } this._scale = colorScale; - this._scale.broadcaster.registerListener(this, () => this._invalidateLayout()); + this._redrawCallback = (scale) => this.redraw(); + this._scale.onUpdate(this._redrawCallback); - this.xAlign("right").yAlign("top"); - this._fixedWidthFlag = true; - this._fixedHeightFlag = true; + this.xAlignment("right").yAlignment("top"); this._sortFn = (a: string, b: string) => this._scale.domain().indexOf(a) - this._scale.domain().indexOf(b); this._symbolFactoryAccessor = () => SymbolFactories.circle(); } @@ -80,7 +80,7 @@ export module Component { return this._maxEntriesPerRow; } else { this._maxEntriesPerRow = numEntries; - this._invalidateLayout(); + this.redraw(); return this; } } @@ -102,7 +102,7 @@ export module Component { return this._sortFn; } else { this._sortFn = newFn; - this._invalidateLayout(); + this.redraw(); return this; } } @@ -112,45 +112,49 @@ export module Component { * * @returns {ColorScale} The current color scale. */ - public scale(): Scale.Color; + public scale(): Scales.Color; /** * Assigns a new color scale to the Legend. * * @param {Scale.Color} scale If provided, the new scale. * @returns {Legend} The calling Legend. */ - public scale(scale: Scale.Color): Legend; - public scale(scale?: Scale.Color): any { + public scale(scale: Scales.Color): Legend; + public scale(scale?: Scales.Color): any { if (scale != null) { - this._scale.broadcaster.deregisterListener(this); + this._scale.offUpdate(this._redrawCallback); this._scale = scale; - this._scale.broadcaster.registerListener(this, () => this._invalidateLayout()); - this._invalidateLayout(); + this._scale.onUpdate(this._redrawCallback); + this.redraw(); return this; } else { return this._scale; } } - public remove() { - super.remove(); - this._scale.broadcaster.deregisterListener(this); + public destroy() { + super.destroy(); + this._scale.offUpdate(this._redrawCallback); } private _calculateLayoutInfo(availableWidth: number, availableHeight: number) { var textHeight = this._measurer.measure().height; var availableWidthForEntries = Math.max(0, (availableWidth - this._padding)); - var measureEntry = (entryText: string) => { - var originalEntryLength = (textHeight + this._measurer.measure(entryText).width + this._padding); - return Math.min(originalEntryLength, availableWidthForEntries); - }; - var entries = this._scale.domain().slice(); - entries.sort(this.sortFunction()); - var entryLengths = _Util.Methods.populateMap(entries, measureEntry); + var entryNames = this._scale.domain().slice(); + entryNames.sort(this.sortFunction()); + + var entryLengths: D3.Map = d3.map(); + var untruncatedEntryLengths: D3.Map = d3.map(); + entryNames.forEach((entryName) => { + var untruncatedEntryLength = textHeight + this._measurer.measure(entryName).width + this._padding; + var entryLength = Math.min(untruncatedEntryLength, availableWidthForEntries); + entryLengths.set(entryName, entryLength); + untruncatedEntryLengths.set(entryName, untruncatedEntryLength); + }); - var rows = this._packRows(availableWidthForEntries, entries, entryLengths); + var rows = this._packRows(availableWidthForEntries, entryNames, entryLengths); var rowsAvailable = Math.floor((availableHeight - 2 * this._padding) / textHeight); if (rowsAvailable !== rowsAvailable) { // rowsAvailable can be NaN if this.textHeight = 0 @@ -160,33 +164,23 @@ export module Component { return { textHeight: textHeight, entryLengths: entryLengths, + untruncatedEntryLengths: untruncatedEntryLengths, rows: rows, numRowsToDraw: Math.max(Math.min(rowsAvailable, rows.length), 0) }; } - public _requestedSpace(offeredWidth: number, offeredHeight: number): _SpaceRequest { + public requestedSpace(offeredWidth: number, offeredHeight: number): SpaceRequest { var estimatedLayout = this._calculateLayoutInfo(offeredWidth, offeredHeight); - var rowLengths = estimatedLayout.rows.map((row: string[]) => { - return d3.sum(row, (entry: string) => estimatedLayout.entryLengths.get(entry)); - }); - - var longestRowLength = _Util.Methods.max(rowLengths, 0); - var longestUntruncatedEntryLength = _Util.Methods.max(this._scale.domain(), (d: string) => - this._measurer.measure(d).width, 0); - longestUntruncatedEntryLength += estimatedLayout.textHeight + this._padding; - var desiredWidth = this._padding + Math.max(longestRowLength, longestUntruncatedEntryLength); + var untruncatedRowLengths = estimatedLayout.rows.map((row) => { + return d3.sum(row, (entry) => estimatedLayout.untruncatedEntryLengths.get(entry)); + }); + var longestUntruncatedRowLength = Utils.Methods.max(untruncatedRowLengths, 0); - var acceptableHeight = estimatedLayout.numRowsToDraw * estimatedLayout.textHeight + 2 * this._padding; - var desiredHeight = estimatedLayout.rows.length * estimatedLayout.textHeight + 2 * this._padding; - var desiredNumRows = Math.max(Math.ceil(this._scale.domain().length / this._maxEntriesPerRow), 1); - var wantsFitMoreEntriesInRow = estimatedLayout.rows.length > desiredNumRows; return { - width : this._padding + longestRowLength, - height: acceptableHeight, - wantsWidth: offeredWidth < desiredWidth || wantsFitMoreEntriesInRow, - wantsHeight: offeredHeight < desiredHeight + minWidth: this._padding + longestUntruncatedRowLength, + minHeight: estimatedLayout.rows.length * estimatedLayout.textHeight + 2 * this._padding }; } @@ -205,7 +199,7 @@ export module Component { spaceLeft -= entryLength; }); - if(currentRow.length !== 0) { + if (currentRow.length !== 0) { rows.push(currentRow); } return rows; @@ -243,8 +237,8 @@ export module Component { return entry; } - public _doRender() { - super._doRender(); + public renderImmediately() { + super.renderImmediately(); var layout = this._calculateLayoutInfo(this.width(), this.height()); @@ -295,6 +289,7 @@ export module Component { self._writer.write(value, maxTextLength, self.height(), writeOptions); }); + return this; } /** @@ -316,10 +311,18 @@ export module Component { return this._symbolFactoryAccessor; } else { this._symbolFactoryAccessor = symbolFactoryAccessor; - this._render(); + this.render(); return this; } } + + public fixedWidth() { + return true; + } + + public fixedHeight() { + return true; + } } } } diff --git a/src/components/plots/abstractPlot.ts b/src/components/plots/abstractPlot.ts deleted file mode 100644 index c76f64637b..0000000000 --- a/src/components/plots/abstractPlot.ts +++ /dev/null @@ -1,564 +0,0 @@ -/// - -module Plottable { -export module Plot { - /** - * A key that is also coupled with a dataset, a drawer and a metadata in Plot. - */ - export type PlotDatasetKey = { - dataset: Dataset; - drawer: _Drawer.AbstractDrawer; - plotMetadata: PlotMetadata; - key: string; - } - - export interface PlotMetadata { - datasetKey: string - } - - export type PlotData = { - data: any[]; - pixelPoints: Point[]; - selection: D3.Selection; - } - - export class AbstractPlot extends Component.AbstractComponent { - protected _dataChanged = false; - protected _key2PlotDatasetKey: D3.Map; - protected _datasetKeysInOrder: string[]; - - protected _renderArea: D3.Selection; - protected _projections: { [attrToSet: string]: _Projection; } = {}; - - protected _animate: boolean = false; - private _animators: Animator.PlotAnimatorMap = {}; - protected _animateOnNextRender = true; - private _nextSeriesIndex: number; - - /** - * Constructs a Plot. - * - * Plots render data. Common example include Plot.Scatter, Plot.Bar, and Plot.Line. - * - * A bare Plot has a DataSource and any number of projectors, which take - * data and "project" it onto the Plot, such as "x", "y", "fill", "r". - * - * @constructor - * @param {any[]|Dataset} [dataset] If provided, the data or Dataset to be associated with this Plot. - */ - constructor() { - super(); - this.clipPathEnabled = true; - this.classed("plot", true); - this._key2PlotDatasetKey = d3.map(); - this._datasetKeysInOrder = []; - this._nextSeriesIndex = 0; - } - - public _anchor(element: D3.Selection) { - super._anchor(element); - this._animateOnNextRender = true; - this._dataChanged = true; - this._updateScaleExtents(); - } - - protected _setup() { - super._setup(); - this._renderArea = this._content.append("g").classed("render-area", true); - // HACKHACK on 591 - this._getDrawersInOrder().forEach((d) => d.setup(this._renderArea.append("g"))); - } - - public remove() { - super.remove(); - this._datasetKeysInOrder.forEach((k) => this.removeDataset(k)); - // deregister from all scales - var properties = Object.keys(this._projections); - properties.forEach((property) => { - var projector = this._projections[property]; - if (projector.scale) { - projector.scale.broadcaster.deregisterListener(this); - } - }); - } - - /** - * Adds a dataset to this plot. Identify this dataset with a key. - * - * A key is automatically generated if not supplied. - * - * @param {string} [key] The key of the dataset. - * @param {Dataset | any[]} dataset dataset to add. - * @returns {Plot} The calling Plot. - */ - public addDataset(dataset: Dataset | any[]): AbstractPlot; - public addDataset(key: string, dataset: Dataset | any[]): AbstractPlot; - public addDataset(keyOrDataset: any, dataset?: any): AbstractPlot { - if (typeof(keyOrDataset) !== "string" && dataset !== undefined) { - throw new Error("invalid input to addDataset"); - } - if (typeof(keyOrDataset) === "string" && keyOrDataset[0] === "_") { - _Util.Methods.warn("Warning: Using _named series keys may produce collisions with unlabeled data sources"); - } - var key = typeof(keyOrDataset) === "string" ? keyOrDataset : "_" + this._nextSeriesIndex++; - var data = typeof(keyOrDataset) !== "string" ? keyOrDataset : dataset; - dataset = (data instanceof Dataset) ? data : new Dataset(data); - - this._addDataset(key, dataset); - return this; - } - - private _addDataset(key: string, dataset: Dataset) { - if (this._key2PlotDatasetKey.has(key)) { - this.removeDataset(key); - }; - var drawer = this._getDrawer(key); - var metadata = this._getPlotMetadataForDataset(key); - var pdk = {drawer: drawer, dataset: dataset, key: key, plotMetadata: metadata}; - this._datasetKeysInOrder.push(key); - this._key2PlotDatasetKey.set(key, pdk); - - if (this._isSetup) { - drawer.setup(this._renderArea.append("g")); - } - dataset.broadcaster.registerListener(this, () => this._onDatasetUpdate()); - this._onDatasetUpdate(); - } - - protected _getDrawer(key: string): _Drawer.AbstractDrawer { - return new _Drawer.AbstractDrawer(key); - } - - protected _getAnimator(key: string): Animator.PlotAnimator { - if (this._animate && this._animateOnNextRender) { - return this._animators[key] || new Animator.Null(); - } else { - return new Animator.Null(); - } - } - - protected _onDatasetUpdate() { - this._updateScaleExtents(); - this._animateOnNextRender = true; - this._dataChanged = true; - this._render(); - } - - /** - * Sets an attribute of every data point. - * - * Here's a common use case: - * ```typescript - * plot.attr("x", function(d) { return d.foo; }, xScale); - * ``` - * This will set the x accessor of each datum `d` to be `d.foo`, - * scaled in accordance with `xScale` - * - * @param {string} attrToSet The attribute to set across each data - * point. Popular examples include "x", "y". - * - * @param {Function|string|any} accessor Function to apply to each element - * of the dataSource. If a Function, use `accessor(d, i)`. If a string, - * `d[accessor]` is used. If anything else, use `accessor` as a constant - * across all data points. - * - * @param {Scale.AbstractScale} scale If provided, the result of the accessor - * is passed through the scale, such as `scale.scale(accessor(d, i))`. - * - * @returns {Plot} The calling Plot. - */ - public attr(attrToSet: string, accessor: any, scale?: Scale.AbstractScale) { - return this.project(attrToSet, accessor, scale); - } - - /** - * Identical to plot.attr - */ - public project(attrToSet: string, accessor: any, scale?: Scale.AbstractScale) { - attrToSet = attrToSet.toLowerCase(); - var currentProjection = this._projections[attrToSet]; - var existingScale = currentProjection && currentProjection.scale; - - if (existingScale) { - this._datasetKeysInOrder.forEach((key) => { - existingScale._removeExtent(this.getID().toString() + "_" + key, attrToSet); - existingScale.broadcaster.deregisterListener(this); - }); - } - - if (scale) { - scale.broadcaster.registerListener(this, () => this._render()); - } - accessor = _Util.Methods.accessorize(accessor); - this._projections[attrToSet] = {accessor: accessor, scale: scale, attribute: attrToSet}; - this._updateScaleExtent(attrToSet); - this._render(); // queue a re-render upon changing projector - return this; - } - - protected _generateAttrToProjector(): AttributeToProjector { - var h: AttributeToProjector = {}; - d3.keys(this._projections).forEach((a) => { - var projection = this._projections[a]; - var accessor = projection.accessor; - var scale = projection.scale; - var fn = scale ? (d: any, i: number, u: any, m: PlotMetadata) => scale.scale(accessor(d, i, u, m)) : accessor; - h[a] = fn; - }); - return h; - } - - /** - * Generates a dictionary mapping an attribute to a function that calculate that attribute's value - * in accordance with the given datasetKey. - * - * Note that this will return all of the data attributes, which may not perfectly align to svg attributes - * - * @param {datasetKey} the key of the dataset to generate the dictionary for - * @returns {AttributeToAppliedProjector} A dictionary mapping attributes to functions - */ - public generateProjectors(datasetKey: string): AttributeToAppliedProjector { - var attrToProjector = this._generateAttrToProjector(); - var plotDatasetKey = this._key2PlotDatasetKey.get(datasetKey); - var plotMetadata = plotDatasetKey.plotMetadata; - var userMetadata = plotDatasetKey.dataset.metadata(); - var attrToAppliedProjector: AttributeToAppliedProjector = {}; - d3.entries(attrToProjector).forEach((keyValue: any) => { - attrToAppliedProjector[keyValue.key] = (datum: any, index: number) => keyValue.value(datum, index, userMetadata, plotMetadata); - }); - return attrToAppliedProjector; - } - - public _doRender() { - if (this._isAnchored) { - this._paint(); - this._dataChanged = false; - this._animateOnNextRender = false; - } - } - - /** - * Enables or disables animation. - * - * @param {boolean} enabled Whether or not to animate. - */ - public animate(enabled: boolean) { - this._animate = enabled; - return this; - } - - public detach() { - super.detach(); - // make the domain resize - this._updateScaleExtents(); - return this; - } - - /** - * This function makes sure that all of the scales in this._projections - * have an extent that includes all the data that is projected onto them. - */ - protected _updateScaleExtents() { - d3.keys(this._projections).forEach((attr: string) => this._updateScaleExtent(attr)); - } - - public _updateScaleExtent(attr: string) { - var projector = this._projections[attr]; - if (projector.scale) { - this._datasetKeysInOrder.forEach((key) => { - var plotDatasetKey = this._key2PlotDatasetKey.get(key); - var dataset = plotDatasetKey.dataset; - var plotMetadata = plotDatasetKey.plotMetadata; - var extent = dataset._getExtent(projector.accessor, projector.scale._typeCoercer, plotMetadata); - var scaleKey = this.getID().toString() + "_" + key; - if (extent.length === 0 || !this._isAnchored) { - projector.scale._removeExtent(scaleKey, attr); - } else { - projector.scale._updateExtent(scaleKey, attr, extent); - } - }); - } - } - - /** - * Get the animator associated with the specified Animator key. - * - * @return {PlotAnimator} The Animator for the specified key. - */ - public animator(animatorKey: string): Animator.PlotAnimator; - /** - * Set the animator associated with the specified Animator key. - * - * @param {string} animatorKey The key for the Animator. - * @param {PlotAnimator} animator An Animator to be assigned to - * the specified key. - * @returns {Plot} The calling Plot. - */ - public animator(animatorKey: string, animator: Animator.PlotAnimator): AbstractPlot; - public animator(animatorKey: string, animator?: Animator.PlotAnimator): any { - if (animator === undefined){ - return this._animators[animatorKey]; - } else { - this._animators[animatorKey] = animator; - return this; - } - } - - /** - * Gets the dataset order by key - * - * @returns {string[]} A string array of the keys in order - */ - public datasetOrder(): string[]; - /** - * Sets the dataset order by key - * - * @param {string[]} order If provided, a string array which represents the order of the keys. - * This must be a permutation of existing keys. - * - * @returns {Plot} The calling Plot. - */ - public datasetOrder(order: string[]): AbstractPlot; - public datasetOrder(order?: string[]): any { - if (order === undefined) { - return this._datasetKeysInOrder; - } - function isPermutation(l1: string[], l2: string[]) { - var intersection = _Util.Methods.intersection(d3.set(l1), d3.set(l2)); - var size = ( intersection).size(); // HACKHACK pending on borisyankov/definitelytyped/ pr #2653 - return size === l1.length && size === l2.length; - } - if (isPermutation(order, this._datasetKeysInOrder)) { - this._datasetKeysInOrder = order; - this._onDatasetUpdate(); - } else { - _Util.Methods.warn("Attempted to change datasetOrder, but new order is not permutation of old. Ignoring."); - } - return this; - } - - /** - * Removes a dataset by the given identifier - * - * @param {string | Dataset | any[]} datasetIdentifer The identifier as the key of the Dataset to remove - * If string is inputted, it is interpreted as the dataset key to remove. - * If Dataset is inputted, the first Dataset in the plot that is the same will be removed. - * If any[] is inputted, the first data array in the plot that is the same will be removed. - * @returns {AbstractPlot} The calling AbstractPlot. - */ - public removeDataset(datasetIdentifier: string | Dataset | any[]): AbstractPlot { - var key: string; - if (typeof datasetIdentifier === "string") { - key = datasetIdentifier; - } else if (typeof datasetIdentifier === "object") { - - var index = -1; - if (datasetIdentifier instanceof Dataset) { - var datasetArray = this.datasets(); - index = datasetArray.indexOf( datasetIdentifier); - } else if (datasetIdentifier instanceof Array) { - var dataArray = this.datasets().map(d => d.data()); - index = dataArray.indexOf( datasetIdentifier); - } - if (index !== -1) { - key = this._datasetKeysInOrder[index]; - } - - } - - return this._removeDataset(key); - } - - private _removeDataset(key: string): AbstractPlot { - if (key != null && this._key2PlotDatasetKey.has(key)) { - var pdk = this._key2PlotDatasetKey.get(key); - pdk.drawer.remove(); - - var projectors = d3.values(this._projections); - var scaleKey = this.getID().toString() + "_" + key; - projectors.forEach((p) => { - if (p.scale != null) { - p.scale._removeExtent(scaleKey, p.attribute); - } - }); - - pdk.dataset.broadcaster.deregisterListener(this); - this._datasetKeysInOrder.splice(this._datasetKeysInOrder.indexOf(key), 1); - this._key2PlotDatasetKey.remove(key); - this._onDatasetUpdate(); - } - return this; - } - - public datasets(): Dataset[] { - return this._datasetKeysInOrder.map((k) => this._key2PlotDatasetKey.get(k).dataset); - } - - protected _getDrawersInOrder(): _Drawer.AbstractDrawer[] { - return this._datasetKeysInOrder.map((k) => this._key2PlotDatasetKey.get(k).drawer); - } - - protected _generateDrawSteps(): _Drawer.DrawStep[] { - return [{attrToProjector: this._generateAttrToProjector(), animator: new Animator.Null()}]; - } - - protected _additionalPaint(time: number) { - // no-op - } - - protected _getDataToDraw() { - var datasets: D3.Map = d3.map(); - this._datasetKeysInOrder.forEach((key: string) => { - datasets.set(key, this._key2PlotDatasetKey.get(key).dataset.data()); - }); - return datasets; - } - - /** - * Gets the new plot metadata for new dataset with provided key - * - * @param {string} key The key of new dataset - */ - protected _getPlotMetadataForDataset(key: string): PlotMetadata { - return { - datasetKey: key - }; - } - - private _paint() { - var drawSteps = this._generateDrawSteps(); - var dataToDraw = this._getDataToDraw(); - var drawers = this._getDrawersInOrder(); - - // TODO: Use metadata instead of dataToDraw #1297. - var times = this._datasetKeysInOrder.map((k, i) => - drawers[i].draw( - dataToDraw.get(k), - drawSteps, - this._key2PlotDatasetKey.get(k).dataset.metadata(), - this._key2PlotDatasetKey.get(k).plotMetadata - )); - var maxTime = _Util.Methods.max(times, 0); - this._additionalPaint(maxTime); - } - - /** - * Retrieves all of the selections of this plot for the specified dataset(s) - * - * @param {string | string[]} datasetKeys The dataset(s) to retrieve the selections from. - * If not provided, all selections will be retrieved. - * @param {boolean} exclude If set to true, all datasets will be queried excluding the keys referenced - * in the previous datasetKeys argument (default = false). - * @returns {D3.Selection} The retrieved selections. - */ - public getAllSelections(datasetKeys: string | string[] = this.datasetOrder(), exclude = false): D3.Selection { - var datasetKeyArray: string[] = []; - if (typeof(datasetKeys) === "string") { - datasetKeyArray = [ datasetKeys]; - } else { - datasetKeyArray = datasetKeys; - } - - if (exclude) { - var excludedDatasetKeys = d3.set(datasetKeyArray); - datasetKeyArray = this.datasetOrder().filter((datasetKey) => !excludedDatasetKeys.has(datasetKey)); - } - - var allSelections: EventTarget[] = []; - - datasetKeyArray.forEach((datasetKey) => { - var plotDatasetKey = this._key2PlotDatasetKey.get(datasetKey); - if (plotDatasetKey == null) { return; } - var drawer = plotDatasetKey.drawer; - drawer._getRenderArea().selectAll(drawer._getSelector()).each(function () { - allSelections.push(this); - }); - }); - - return d3.selectAll(allSelections); - } - - /** - * Retrieves all of the PlotData of this plot for the specified dataset(s) - * - * @param {string | string[]} datasetKeys The dataset(s) to retrieve the selections from. - * If not provided, all selections will be retrieved. - * @returns {PlotData} The retrieved PlotData. - */ - public getAllPlotData(datasetKeys: string | string[] = this.datasetOrder()): PlotData { - var datasetKeyArray: string[] = []; - if (typeof(datasetKeys) === "string") { - datasetKeyArray = [ datasetKeys]; - } else { - datasetKeyArray = datasetKeys; - } - - return this._getAllPlotData(datasetKeyArray); - } - - protected _getAllPlotData(datasetKeys: string[]): PlotData { - var data: any[] = []; - var pixelPoints: Point[] = []; - var allElements: EventTarget[] = []; - - datasetKeys.forEach((datasetKey) => { - var plotDatasetKey = this._key2PlotDatasetKey.get(datasetKey); - if (plotDatasetKey == null) { return; } - var drawer = plotDatasetKey.drawer; - plotDatasetKey.dataset.data().forEach((datum: any, index: number) => { - var pixelPoint = drawer._getPixelPoint(datum, index); - if (pixelPoint.x !== pixelPoint.x || pixelPoint.y !== pixelPoint.y) { - return; - } - data.push(datum); - pixelPoints.push(pixelPoint); - allElements.push(drawer._getSelection(index).node()); - }); - }); - - return { data: data, pixelPoints: pixelPoints, selection: d3.selectAll(allElements) }; - } - - /** - * Retrieves PlotData with the lowest distance, where distance is defined - * to be the Euclidiean norm. - * - * @param {Point} queryPoint The point to which plot data should be compared - * - * @returns {PlotData} The PlotData closest to queryPoint - */ - public getClosestPlotData(queryPoint: Point): PlotData { - var closestDistanceSquared = Infinity; - var closestIndex: number; - var plotData = this.getAllPlotData(); - plotData.pixelPoints.forEach((pixelPoint: Point, index: number) => { - var datum = plotData.data[index]; - var selection = d3.select(plotData.selection[0][index]); - - if (!this._isVisibleOnPlot(datum, pixelPoint, selection)) { - return; - } - - var distance = _Util.Methods.distanceSquared(pixelPoint, queryPoint); - if (distance < closestDistanceSquared) { - closestDistanceSquared = distance; - closestIndex = index; - } - }); - - if (closestIndex == null) { - return {data: [], pixelPoints: [], selection: d3.select()}; - } - - return {data: [plotData.data[closestIndex]], - pixelPoints: [plotData.pixelPoints[closestIndex]], - selection: d3.select(plotData.selection[0][closestIndex])}; - } - - protected _isVisibleOnPlot(datum: any, pixelPoint: Point, selection: D3.Selection): boolean { - return !(pixelPoint.x < 0 || pixelPoint.y < 0 || - pixelPoint.x > this.width() || pixelPoint.y > this.height()); - } - } -} -} diff --git a/src/components/plots/abstractStackedPlot.ts b/src/components/plots/abstractStackedPlot.ts deleted file mode 100644 index abc46d713e..0000000000 --- a/src/components/plots/abstractStackedPlot.ts +++ /dev/null @@ -1,227 +0,0 @@ -/// - -module Plottable { -export module Plot { - export interface StackedPlotMetadata extends PlotMetadata { - offsets: D3.Map; - } - - export type StackedDatum = { - key: any; - value: number; - offset?: number; - } - - export class AbstractStacked extends AbstractXYPlot { - private _stackedExtent = [0, 0]; - protected _isVertical: boolean; - - public _getPlotMetadataForDataset(key: string): StackedPlotMetadata { - var metadata = super._getPlotMetadataForDataset(key); - metadata.offsets = d3.map(); - return metadata; - } - - public project(attrToSet: string, accessor: any, scale?: Scale.AbstractScale) { - super.project(attrToSet, accessor, scale); - if (this._projections["x"] && this._projections["y"] && (attrToSet === "x" || attrToSet === "y")) { - this._updateStackOffsets(); - } - return this; - } - - public _onDatasetUpdate() { - if (this._projectorsReady()) { - this._updateStackOffsets(); - } - super._onDatasetUpdate(); - } - - public _updateStackOffsets() { - var dataMapArray = this._generateDefaultMapArray(); - var domainKeys = this._getDomainKeys(); - - var positiveDataMapArray: D3.Map[] = dataMapArray.map((dataMap) => { - return _Util.Methods.populateMap(domainKeys, (domainKey) => { - return { key: domainKey, value: Math.max(0, dataMap.get(domainKey).value) || 0 }; - }); - }); - - var negativeDataMapArray: D3.Map[] = dataMapArray.map((dataMap) => { - return _Util.Methods.populateMap(domainKeys, (domainKey) => { - return { key: domainKey, value: Math.min(dataMap.get(domainKey).value, 0) || 0 }; - }); - }); - - this._setDatasetStackOffsets(this._stack(positiveDataMapArray), this._stack(negativeDataMapArray)); - this._updateStackExtents(); - } - - public _updateStackExtents() { - var datasets = this.datasets(); - var valueAccessor = this._valueAccessor(); - var keyAccessor = this._keyAccessor(); - var maxStackExtent = _Util.Methods.max(this._datasetKeysInOrder, (k: string) => { - var dataset = this._key2PlotDatasetKey.get(k).dataset; - var plotMetadata = this._key2PlotDatasetKey.get(k).plotMetadata; - return _Util.Methods.max(dataset.data(), (datum: any, i: number) => { - return +valueAccessor(datum, i, dataset.metadata(), plotMetadata) + - plotMetadata.offsets.get(keyAccessor(datum, i, dataset.metadata(), plotMetadata)); - }, 0); - }, 0); - - var minStackExtent = _Util.Methods.min(this._datasetKeysInOrder, (k: string) => { - var dataset = this._key2PlotDatasetKey.get(k).dataset; - var plotMetadata = this._key2PlotDatasetKey.get(k).plotMetadata; - return _Util.Methods.min(dataset.data(), (datum: any, i: number) => { - return +valueAccessor(datum, i, dataset.metadata(), plotMetadata) + - plotMetadata.offsets.get(keyAccessor(datum, i, dataset.metadata(), plotMetadata)); - }, 0); - }, 0); - - this._stackedExtent = [Math.min(minStackExtent, 0), Math.max(0, maxStackExtent)]; - } - - /** - * Feeds the data through d3's stack layout function which will calculate - * the stack offsets and use the the function declared in .out to set the offsets on the data. - */ - public _stack(dataArray: D3.Map[]): D3.Map[] { - var outFunction = (d: StackedDatum, y0: number, y: number) => { - d.offset = y0; - }; - - d3.layout.stack() - .x((d) => d.key) - .y((d) => +d.value) - .values((d) => this._getDomainKeys().map((domainKey) => d.get(domainKey))) - .out(outFunction)(dataArray); - - return dataArray; - } - - /** - * After the stack offsets have been determined on each separate dataset, the offsets need - * to be determined correctly on the overall datasets - */ - public _setDatasetStackOffsets(positiveDataMapArray: D3.Map[], negativeDataMapArray: D3.Map[]) { - var keyAccessor = this._keyAccessor(); - var valueAccessor = this._valueAccessor(); - - this._datasetKeysInOrder.forEach((k, index) => { - var dataset = this._key2PlotDatasetKey.get(k).dataset; - var plotMetadata = this._key2PlotDatasetKey.get(k).plotMetadata; - var positiveDataMap = positiveDataMapArray[index]; - var negativeDataMap = negativeDataMapArray[index]; - var isAllNegativeValues = dataset.data().every((datum, i) => valueAccessor(datum, i, dataset.metadata(), plotMetadata) <= 0); - - dataset.data().forEach((datum: any, datumIndex: number) => { - var key = keyAccessor(datum, datumIndex, dataset.metadata(), plotMetadata); - var positiveOffset = positiveDataMap.get(key).offset; - var negativeOffset = negativeDataMap.get(key).offset; - - var value = valueAccessor(datum, datumIndex, dataset.metadata(), plotMetadata); - var offset: number; - if (!+value) { - offset = isAllNegativeValues ? negativeOffset : positiveOffset; - } else { - offset = value > 0 ? positiveOffset : negativeOffset; - } - plotMetadata.offsets.set(key, offset); - }); - }); - } - - public _getDomainKeys(): string[] { - var keyAccessor = this._keyAccessor(); - var domainKeys = d3.set(); - - this._datasetKeysInOrder.forEach((k) => { - var dataset = this._key2PlotDatasetKey.get(k).dataset; - var plotMetadata = this._key2PlotDatasetKey.get(k).plotMetadata; - dataset.data().forEach((datum, index) => { - domainKeys.add(keyAccessor(datum, index, dataset.metadata(), plotMetadata)); - }); - }); - - return domainKeys.values(); - } - - public _generateDefaultMapArray(): D3.Map[] { - var keyAccessor = this._keyAccessor(); - var valueAccessor = this._valueAccessor(); - var domainKeys = this._getDomainKeys(); - - var dataMapArray = this._datasetKeysInOrder.map(() => { - return _Util.Methods.populateMap(domainKeys, (domainKey) => { - return {key: domainKey, value: 0}; - }); - }); - - this._datasetKeysInOrder.forEach((k, datasetIndex) => { - var dataset = this._key2PlotDatasetKey.get(k).dataset; - var plotMetadata = this._key2PlotDatasetKey.get(k).plotMetadata; - dataset.data().forEach((datum, index) => { - var key = keyAccessor(datum, index, dataset.metadata(), plotMetadata); - var value = valueAccessor(datum, index, dataset.metadata(), plotMetadata); - dataMapArray[datasetIndex].set(key, {key: key, value: value}); - }); - }); - - return dataMapArray; - } - - public _updateScaleExtents() { - super._updateScaleExtents(); - var primaryScale: Scale.AbstractScale = this._isVertical ? this._yScale : this._xScale; - if (!primaryScale) { - return; - } - if (this._isAnchored && this._stackedExtent.length > 0) { - primaryScale._updateExtent(this.getID().toString(), "_PLOTTABLE_PROTECTED_FIELD_STACK_EXTENT", this._stackedExtent); - } else { - primaryScale._removeExtent(this.getID().toString(), "_PLOTTABLE_PROTECTED_FIELD_STACK_EXTENT"); - } - } - - public _normalizeDatasets(fromX: boolean): {a: A; b: B}[] { - var aAccessor = this._projections[fromX ? "x" : "y"].accessor; - var bAccessor = this._projections[fromX ? "y" : "x"].accessor; - var aStackedAccessor = (d: any, i: number, u: any, m: StackedPlotMetadata) => { - var value = aAccessor(d, i, u, m); - if (this._isVertical ? !fromX : fromX) { - value += m.offsets.get(bAccessor(d, i, u, m)); - } - return value; - }; - - var bStackedAccessor = (d: any, i: number, u: any, m: StackedPlotMetadata) => { - var value = bAccessor(d, i, u, m); - if (this._isVertical ? fromX : !fromX) { - value += m.offsets.get(aAccessor(d, i, u, m)); - } - return value; - }; - - return _Util.Methods.flatten(this._datasetKeysInOrder.map((key: string) => { - var dataset = this._key2PlotDatasetKey.get(key).dataset; - var plotMetadata = this._key2PlotDatasetKey.get(key).plotMetadata; - return dataset.data().map((d, i) => { - return { - a: aStackedAccessor(d, i, dataset.metadata(), plotMetadata), - b: bStackedAccessor(d, i, dataset.metadata(), plotMetadata) - }; - }); - })); - } - - public _keyAccessor(): _Accessor { - return this._isVertical ? this._projections["x"].accessor : this._projections["y"].accessor; - } - - public _valueAccessor(): _Accessor { - return this._isVertical ? this._projections["y"].accessor : this._projections["x"].accessor; - } - } -} -} diff --git a/src/components/plots/abstractXYPlot.ts b/src/components/plots/abstractXYPlot.ts deleted file mode 100644 index c817b1bf3d..0000000000 --- a/src/components/plots/abstractXYPlot.ts +++ /dev/null @@ -1,223 +0,0 @@ -/// - -module Plottable { -export module Plot { - export class AbstractXYPlot extends AbstractPlot { - protected _xScale: Scale.AbstractScale; - protected _yScale: Scale.AbstractScale; - private _autoAdjustXScaleDomain = false; - private _autoAdjustYScaleDomain = false; - - /** - * Constructs an XYPlot. - * - * An XYPlot is a plot from drawing 2-dimensional data. Common examples - * include Scale.Line and Scale.Bar. - * - * @constructor - * @param {any[]|Dataset} [dataset] The data or Dataset to be associated with this Renderer. - * @param {Scale} xScale The x scale to use. - * @param {Scale} yScale The y scale to use. - */ - constructor(xScale: Scale.AbstractScale, yScale: Scale.AbstractScale) { - super(); - if (xScale == null || yScale == null) { - throw new Error("XYPlots require an xScale and yScale"); - } - this.classed("xy-plot", true); - - this._xScale = xScale; - this._yScale = yScale; - this._updateXDomainer(); - xScale.broadcaster.registerListener("yDomainAdjustment" + this.getID(), () => this._adjustYDomainOnChangeFromX()); - this._updateYDomainer(); - yScale.broadcaster.registerListener("xDomainAdjustment" + this.getID(), () => this._adjustXDomainOnChangeFromY()); - } - - /** - * @param {string} attrToSet One of ["x", "y"] which determines the point's - * x and y position in the Plot. - */ - public project(attrToSet: string, accessor: any, scale?: Scale.AbstractScale) { - // We only want padding and nice-ing on scales that will correspond to axes / pixel layout. - // So when we get an "x" or "y" scale, enable autoNiceing and autoPadding. - if (attrToSet === "x" && scale) { - if (this._xScale) { - this._xScale.broadcaster.deregisterListener("yDomainAdjustment" + this.getID()); - } - this._xScale = scale; - this._updateXDomainer(); - scale.broadcaster.registerListener("yDomainAdjustment" + this.getID(), () => this._adjustYDomainOnChangeFromX()); - } - - if (attrToSet === "y" && scale) { - if (this._yScale) { - this._yScale.broadcaster.deregisterListener("xDomainAdjustment" + this.getID()); - } - this._yScale = scale; - this._updateYDomainer(); - scale.broadcaster.registerListener("xDomainAdjustment" + this.getID(), () => this._adjustXDomainOnChangeFromY()); - } - - super.project(attrToSet, accessor, scale); - - return this; - } - - public remove() { - super.remove(); - if (this._xScale) { - this._xScale.broadcaster.deregisterListener("yDomainAdjustment" + this.getID()); - } - if (this._yScale) { - this._yScale.broadcaster.deregisterListener("xDomainAdjustment" + this.getID()); - } - return this; - } - - /** - * Sets the automatic domain adjustment over visible points for y scale. - * - * If autoAdjustment is true adjustment is immediately performend. - * - * @param {boolean} autoAdjustment The new value for the automatic adjustment domain for y scale. - * @returns {AbstractXYPlot} The calling AbstractXYPlot. - */ - public automaticallyAdjustYScaleOverVisiblePoints(autoAdjustment: boolean): AbstractXYPlot { - this._autoAdjustYScaleDomain = autoAdjustment; - this._adjustYDomainOnChangeFromX(); - return this; - } - - /** - * Sets the automatic domain adjustment over visible points for x scale. - * - * If autoAdjustment is true adjustment is immediately performend. - * - * @param {boolean} autoAdjustment The new value for the automatic adjustment domain for x scale. - * @returns {AbstractXYPlot} The calling AbstractXYPlot. - */ - public automaticallyAdjustXScaleOverVisiblePoints(autoAdjustment: boolean): AbstractXYPlot { - this._autoAdjustXScaleDomain = autoAdjustment; - this._adjustXDomainOnChangeFromY(); - return this; - } - - protected _generateAttrToProjector(): AttributeToProjector { - var attrToProjector: AttributeToProjector = super._generateAttrToProjector(); - var positionXFn = attrToProjector["x"]; - var positionYFn = attrToProjector["y"]; - attrToProjector["defined"] = (d: any, i: number, u: any, m: PlotMetadata) => { - var positionX = positionXFn(d, i, u, m); - var positionY = positionYFn(d, i, u, m); - return positionX != null && positionX === positionX && - positionY != null && positionY === positionY; - }; - return attrToProjector; - } - - public _computeLayout(offeredXOrigin?: number, offeredYOffset?: number, availableWidth?: number, availableHeight?: number) { - super._computeLayout(offeredXOrigin, offeredYOffset, availableWidth, availableHeight); - this._xScale.range([0, this.width()]); - if (this._yScale instanceof Scale.Category) { - this._yScale.range([0, this.height()]); - } else { - this._yScale.range([this.height(), 0]); - } - } - - protected _updateXDomainer() { - if (this._xScale instanceof Scale.AbstractQuantitative) { - var scale = > this._xScale; - if (!scale._userSetDomainer) { - scale.domainer().pad().nice(); - } - } - } - - protected _updateYDomainer() { - if (this._yScale instanceof Scale.AbstractQuantitative) { - var scale = > this._yScale; - if (!scale._userSetDomainer) { - scale.domainer().pad().nice(); - } - } - } - - /** - * Adjusts both domains' extents to show all datasets. - * - * This call does not override auto domain adjustment behavior over visible points. - */ - public showAllData() { - this._xScale.autoDomain(); - if(!this._autoAdjustYScaleDomain) { - this._yScale.autoDomain(); - } - } - - private _adjustYDomainOnChangeFromX() { - if (!this._projectorsReady()) { return; } - if(this._autoAdjustYScaleDomain) { - this._adjustDomainToVisiblePoints(this._xScale, this._yScale, true); - } - } - private _adjustXDomainOnChangeFromY() { - if (!this._projectorsReady()) { return; } - if(this._autoAdjustXScaleDomain) { - this._adjustDomainToVisiblePoints(this._yScale, this._xScale, false); - } - } - - private _adjustDomainToVisiblePoints(fromScale: Scale.AbstractScale, - toScale: Scale.AbstractScale, - fromX: boolean) { - if (toScale instanceof Scale.AbstractQuantitative) { - var toScaleQ = > toScale; - var normalizedData = this._normalizeDatasets(fromX); - - var filterFn: (v: A) => boolean; - if (fromScale instanceof Scale.AbstractQuantitative) { - var fromDomain = fromScale.domain(); - filterFn = (a: A) => fromDomain[0] <= a && fromDomain[1] >= a; - } else { - var fromDomainSet = d3.set(fromScale.domain()); - filterFn = (a: A) => fromDomainSet.has(a); - } - - var adjustedDomain = this._adjustDomainOverVisiblePoints(normalizedData, filterFn); - if(adjustedDomain.length === 0) { - return; - } - adjustedDomain = toScaleQ.domainer().computeDomain([adjustedDomain], toScaleQ); - toScaleQ.domain(adjustedDomain); - } - } - - protected _normalizeDatasets(fromX: boolean): {a: A; b: B}[] { - var aAccessor: (d: any, i: number, u: any, m: PlotMetadata) => A = this._projections[fromX ? "x" : "y"].accessor; - var bAccessor: (d: any, i: number, u: any, m: PlotMetadata) => B = this._projections[fromX ? "y" : "x"].accessor; - return _Util.Methods.flatten(this._datasetKeysInOrder.map((key: string) => { - var dataset = this._key2PlotDatasetKey.get(key).dataset; - var plotMetadata = this._key2PlotDatasetKey.get(key).plotMetadata; - return dataset.data().map((d, i) => { - return { a: aAccessor(d, i, dataset.metadata(), plotMetadata), b: bAccessor(d, i, dataset.metadata(), plotMetadata) }; - }); - })); - } - - private _adjustDomainOverVisiblePoints(values: {a: A; b: B}[], filterFn: (v: any) => boolean): B[] { - var bVals = values.filter(v => filterFn(v.a)).map(v => v.b); - var retVal: B[] = []; - if (bVals.length !== 0) { - retVal = [_Util.Methods.min(bVals, null), _Util.Methods.max(bVals, null)]; - } - return retVal; - } - - protected _projectorsReady() { - return this._projections["x"] && this._projections["y"]; - } - } -} -} diff --git a/src/components/plots/areaPlot.ts b/src/components/plots/areaPlot.ts index eabadc2806..d54105d240 100644 --- a/src/components/plots/areaPlot.ts +++ b/src/components/plots/areaPlot.ts @@ -1,13 +1,12 @@ /// module Plottable { -export module Plot { +export module Plots { /** * An AreaPlot draws a filled region (area) between the plot's projected "y" and projected "y0" values. */ export class Area extends Line { - private _areaPath: D3.Selection; - private _defaultFillColor: string; + private static _Y0_KEY = "y0"; /** * Constructs an AreaPlot. @@ -16,63 +15,64 @@ export module Plot { * @param {QuantitativeScale} xScale The x scale to use. * @param {QuantitativeScale} yScale The y scale to use. */ - constructor(xScale: Scale.AbstractQuantitative, yScale: Scale.AbstractQuantitative) { + constructor(xScale: QuantitativeScale, yScale: QuantitativeScale) { super(xScale, yScale); this.classed("area-plot", true); - this.project("y0", 0, yScale); // default + this.y0(0, yScale); // default - this.animator("reset", new Animator.Null()); - this.animator("main", new Animator.Base() + this.animator("reset", new Animators.Null()); + this.animator("main", new Animators.Base() .duration(600) .easing("exp-in-out")); - this._defaultFillColor = new Scale.Color().range()[0]; + var defaultColor = new Scales.Color().range()[0]; + this.attr("fill-opacity", 0.25); + this.attr("fill", defaultColor); + this.attr("stroke", defaultColor); + } + + public y0(): Plots.AccessorScaleBinding; + public y0(y0: number | Accessor): Area; + public y0(y0: number | Accessor, y0Scale: Scale): Area; + public y0(y0?: number | Accessor, y0Scale?: Scale): any { + if (y0 == null) { + return this._propertyBindings.get(Area._Y0_KEY); + } + this._bindProperty(Area._Y0_KEY, y0, y0Scale); + this._updateYDomainer(); + this.renderImmediately(); + return this; } protected _onDatasetUpdate() { super._onDatasetUpdate(); - if (this._yScale != null) { + if (this.y().scale != null) { this._updateYDomainer(); } } protected _getDrawer(key: string) { - return new Plottable._Drawer.Area(key); + return new Plottable.Drawers.Area(key); } protected _updateYDomainer() { super._updateYDomainer(); - var constantBaseline: number; - var y0Projector = this._projections["y0"]; - var y0Accessor = y0Projector && y0Projector.accessor; - if (y0Accessor != null) { - var extents = this.datasets().map((d) => d._getExtent(y0Accessor, this._yScale._typeCoercer)); - var extent = _Util.Methods.flatten(extents); - var uniqExtentVals = _Util.Methods.uniq(extent); - if (uniqExtentVals.length === 1) { - constantBaseline = uniqExtentVals[0]; - } - } + var extents = this._propertyExtents.get("y0"); + var extent = Utils.Methods.flatten(extents); + var uniqExtentVals = Utils.Methods.uniq(extent); + var constantBaseline = uniqExtentVals.length === 1 ? uniqExtentVals[0] : null; - if (!this._yScale._userSetDomainer) { + var yScale = > this.y().scale; + if (!yScale._userSetDomainer) { if (constantBaseline != null) { - this._yScale.domainer().addPaddingException(constantBaseline, "AREA_PLOT+" + this.getID()); + yScale.domainer().addPaddingException(this, constantBaseline); } else { - this._yScale.domainer().removePaddingException("AREA_PLOT+" + this.getID()); + yScale.domainer().removePaddingException(this); } - // prepending "AREA_PLOT" is unnecessary but reduces likely of user accidentally creating collisions - this._yScale._autoDomainIfAutomaticMode(); + yScale._autoDomainIfAutomaticMode(); } } - public project(attrToSet: string, accessor: any, scale?: Scale.AbstractScale) { - super.project(attrToSet, accessor, scale); - if (attrToSet === "y0") { - this._updateYDomainer(); - } - return this; - } - protected _getResetYFunction() { return this._generateAttrToProjector()["y0"]; } @@ -82,14 +82,6 @@ export module Plot { wholeDatumAttributes.push("y0"); return wholeDatumAttributes; } - - protected _generateAttrToProjector() { - var attrToProjector = super._generateAttrToProjector(); - attrToProjector["fill-opacity"] = attrToProjector["fill-opacity"] || d3.functor(0.25); - attrToProjector["fill"] = attrToProjector["fill"] || d3.functor(this._defaultFillColor); - attrToProjector["stroke"] = attrToProjector["stroke"] || d3.functor(this._defaultFillColor); - return attrToProjector; - } } } } diff --git a/src/components/plots/barPlot.ts b/src/components/plots/barPlot.ts index 1622b6d341..3c06493bc1 100644 --- a/src/components/plots/barPlot.ts +++ b/src/components/plots/barPlot.ts @@ -1,8 +1,8 @@ /// module Plottable { -export module Plot { - export class Bar extends AbstractXYPlot implements Interaction.Hoverable { +export module Plots { + export class Bar extends XYPlot { protected static _BarAlignmentToFactor: {[alignment: string]: number} = {"left": 0, "center": 0.5, "right": 1}; protected static _DEFAULT_WIDTH = 10; private static _BAR_WIDTH_RATIO = 0.95; @@ -11,11 +11,9 @@ export module Plot { private _baselineValue: number; private _barAlignmentFactor = 0.5; protected _isVertical: boolean; - private _barLabelFormatter: Formatter = Formatters.identity(); - private _barLabelsEnabled = false; - private _hoverMode = "point"; + private _labelFormatter: Formatter = Formatters.identity(); + private _labelsEnabled = false; private _hideBarsIfAnyAreTooWide = true; - private _defaultFillColor: string; /** * Constructs a BarPlot. @@ -25,19 +23,20 @@ export module Plot { * @param {Scale} yScale The y scale to use. * @param {boolean} isVertical if the plot if vertical. */ - constructor(xScale: Scale.AbstractScale, yScale: Scale.AbstractScale, isVertical = true) { + constructor(xScale: Scale, yScale: Scale, isVertical = true) { super(xScale, yScale); this.classed("bar-plot", true); - this._defaultFillColor = new Scale.Color().range()[0]; - this.animator("bars-reset", new Animator.Null()); - this.animator("bars", new Animator.Base()); - this.animator("baseline", new Animator.Null()); + this.animator("bars-reset", new Animators.Null()); + this.animator("bars", new Animators.Base()); + this.animator("baseline", new Animators.Null()); this._isVertical = isVertical; this.baseline(0); + this.attr("fill", new Scales.Color().range()[0]); + this.attr("width", () => this._getBarPixelWidth()); } protected _getDrawer(key: string) { - return new Plottable._Drawer.Rect(key, this._isVertical); + return new Plottable.Drawers.Rect(key, this._isVertical); } protected _setup() { @@ -69,7 +68,7 @@ export module Plot { this._baselineValue = value; this._updateXDomainer(); this._updateYDomainer(); - this._render(); + this.render(); return this; } @@ -89,7 +88,7 @@ export module Plot { } this._barAlignmentFactor = align2factor[alignmentLC]; - this._render(); + this.render(); return this; } @@ -98,20 +97,20 @@ export module Plot { * * @returns {boolean} Whether bars should display labels or not. */ - public barLabelsEnabled(): boolean; + public labelsEnabled(): boolean; /** * Set whether bar labels are enabled. * @param {boolean} Whether bars should display labels or not. * * @returns {Bar} The calling plot. */ - public barLabelsEnabled(enabled: boolean): Bar; - public barLabelsEnabled(enabled?: boolean): any { + public labelsEnabled(enabled: boolean): Bar; + public labelsEnabled(enabled?: boolean): any { if (enabled === undefined) { - return this._barLabelsEnabled; + return this._labelsEnabled; } else { - this._barLabelsEnabled = enabled; - this._render(); + this._labelsEnabled = enabled; + this.render(); return this; } } @@ -121,20 +120,20 @@ export module Plot { * * @returns {Formatter} The formatting function for bar labels. */ - public barLabelFormatter(): Formatter; + public labelFormatter(): Formatter; /** * Change the formatting function for bar labels. * @param {Formatter} The formatting function for bar labels. * * @returns {Bar} The calling plot. */ - public barLabelFormatter(formatter: Formatter): Bar; - public barLabelFormatter(formatter?: Formatter): any { + public labelFormatter(formatter: Formatter): Bar; + public labelFormatter(formatter?: Formatter): any { if (formatter == null) { - return this._barLabelFormatter; + return this._labelFormatter; } else { - this._barLabelFormatter = formatter; - this._render(); + this._labelFormatter = formatter; + this.render(); return this; } } @@ -151,9 +150,6 @@ export module Plot { * @returns {PlotData} The PlotData closest to queryPoint */ public getClosestPlotData(queryPoint: Point): PlotData { - var chartXExtent = { min: 0, max: this.width() }; - var chartYExtent = { min: 0, max: this.height() }; - var minPrimaryDist = Infinity; var minSecondaryDist = Infinity; @@ -169,8 +165,8 @@ export module Plot { // mouse events) usually have pixel accuracy. We add a tolerance of 0.5 pixels. var tolerance = 0.5; - this.datasetOrder().forEach((key) => { - var plotData = this.getAllPlotData(key); + this.datasets().forEach((dataset) => { + var plotData = this.getAllPlotData([dataset]); plotData.pixelPoints.forEach((plotPt, index) => { var datum = plotData.data[index]; var bar = plotData.selection[0][index]; @@ -184,7 +180,7 @@ export module Plot { // if we're inside a bar, distance in both directions should stay 0 var barBBox = bar.getBBox(); - if (!_Util.Methods.intersectsBBox(queryPoint.x, queryPoint.y, barBBox, tolerance)) { + if (!Utils.Methods.intersectsBBox(queryPoint.x, queryPoint.y, barBBox, tolerance)) { var plotPtPrimary = this._isVertical ? plotPt.x : plotPt.y; primaryDist = Math.abs(queryPtPrimary - plotPtPrimary); @@ -233,7 +229,7 @@ export module Plot { var yRange = { min: 0, max: this.height() }; var barBBox = selection[0][0].getBBox(); - return Plottable._Util.Methods.intersectsBBox(xRange, yRange, barBBox); + return Plottable.Utils.Methods.intersectsBBox(xRange, yRange, barBBox); } /** @@ -262,27 +258,27 @@ export module Plot { private _getBarsFromDataset(key: string, xValOrExtent: number | Extent, yValOrExtent: number | Extent): any[] { var bars: any[] = []; - var drawer = <_Drawer.Element>this._key2PlotDatasetKey.get(key).drawer; + var drawer = this._key2PlotDatasetKey.get(key).drawer; drawer._getRenderArea().selectAll("rect").each(function(d) { - if (_Util.Methods.intersectsBBox(xValOrExtent, yValOrExtent, this.getBBox())) { + if (Utils.Methods.intersectsBBox(xValOrExtent, yValOrExtent, this.getBBox())) { bars.push(this); } }); return bars; } - protected _updateDomainer(scale: Scale.AbstractScale) { - if (scale instanceof Scale.AbstractQuantitative) { - var qscale = > scale; + protected _updateDomainer(scale: Scale) { + if (scale instanceof QuantitativeScale) { + var qscale = > scale; if (!qscale._userSetDomainer) { if (this._baselineValue != null) { qscale.domainer() - .addPaddingException(this._baselineValue, "BAR_PLOT+" + this.getID()) - .addIncludedValue(this._baselineValue, "BAR_PLOT+" + this.getID()); + .addPaddingException(this, this._baselineValue) + .addIncludedValue(this, this._baselineValue); } else { qscale.domainer() - .removePaddingException("BAR_PLOT+" + this.getID()) - .removeIncludedValue("BAR_PLOT+" + this.getID()); + .removePaddingException(this) + .removeIncludedValue(this); } qscale.domainer().pad().nice(); } @@ -293,7 +289,7 @@ export module Plot { protected _updateYDomainer() { if (this._isVertical) { - this._updateDomainer(this._yScale); + this._updateDomainer(this.y().scale); } else { super._updateYDomainer(); } @@ -301,14 +297,14 @@ export module Plot { protected _updateXDomainer() { if (!this._isVertical) { - this._updateDomainer(this._xScale); + this._updateDomainer(this.x().scale); } else { super._updateXDomainer(); } } protected _additionalPaint(time: number) { - var primaryScale: Scale.AbstractScale = this._isVertical ? this._yScale : this._xScale; + var primaryScale: Scale = this._isVertical ? this.y().scale : this.x().scale; var scaledBaseline = primaryScale.scale(this._baselineValue); var baselineAttr: any = { @@ -320,32 +316,32 @@ export module Plot { this._getAnimator("baseline").animate(this._baseline, baselineAttr); - var drawers: _Drawer.Rect[] = this._getDrawersInOrder(); - drawers.forEach((d: _Drawer.Rect) => d.removeLabels()); - if (this._barLabelsEnabled) { - _Util.Methods.setTimeout(() => this._drawLabels(), time); + var drawers: Drawers.Rect[] = this._getDrawersInOrder(); + drawers.forEach((d: Drawers.Rect) => d.removeLabels()); + if (this._labelsEnabled) { + Utils.Methods.setTimeout(() => this._drawLabels(), time); } } protected _drawLabels() { - var drawers: _Drawer.Rect[] = this._getDrawersInOrder(); + var drawers: Drawers.Rect[] = this._getDrawersInOrder(); var attrToProjector = this._generateAttrToProjector(); var dataToDraw = this._getDataToDraw(); this._datasetKeysInOrder.forEach((k, i) => drawers[i].drawText(dataToDraw.get(k), attrToProjector, - this._key2PlotDatasetKey.get(k).dataset.metadata(), + this._key2PlotDatasetKey.get(k).dataset, this._key2PlotDatasetKey.get(k).plotMetadata)); - if (this._hideBarsIfAnyAreTooWide && drawers.some((d: _Drawer.Rect) => d._getIfLabelsTooWide())) { - drawers.forEach((d: _Drawer.Rect) => d.removeLabels()); + if (this._hideBarsIfAnyAreTooWide && drawers.some((d: Drawers.Rect) => d._getIfLabelsTooWide())) { + drawers.forEach((d: Drawers.Rect) => d.removeLabels()); } } - protected _generateDrawSteps(): _Drawer.DrawStep[] { - var drawSteps: _Drawer.DrawStep[] = []; + protected _generateDrawSteps(): Drawers.DrawStep[] { + var drawSteps: Drawers.DrawStep[] = []; if (this._dataChanged && this._animate) { var resetAttrToProjector = this._generateAttrToProjector(); - var primaryScale: Scale.AbstractScale = this._isVertical ? this._yScale : this._xScale; + var primaryScale: Scale = this._isVertical ? this.y().scale : this.x().scale; var scaledBaseline = primaryScale.scale(this._baselineValue); var positionAttr = this._isVertical ? "y" : "x"; var dimensionAttr = this._isVertical ? "height" : "width"; @@ -361,50 +357,47 @@ export module Plot { // Primary scale/direction: the "length" of the bars // Secondary scale/direction: the "width" of the bars var attrToProjector = super._generateAttrToProjector(); - var primaryScale: Scale.AbstractScale = this._isVertical ? this._yScale : this._xScale; - var secondaryScale: Scale.AbstractScale = this._isVertical ? this._xScale : this._yScale; + var primaryScale: Scale = this._isVertical ? this.y().scale : this.x().scale; + var secondaryScale: Scale = this._isVertical ? this.x().scale : this.y().scale; var primaryAttr = this._isVertical ? "y" : "x"; var secondaryAttr = this._isVertical ? "x" : "y"; var scaledBaseline = primaryScale.scale(this._baselineValue); var positionF = attrToProjector[secondaryAttr]; var widthF = attrToProjector["width"]; - if (widthF == null) { widthF = () => this._getBarPixelWidth(); } var originalPositionFn = attrToProjector[primaryAttr]; - var heightF = (d: any, i: number, u: any, m: PlotMetadata) => { - return Math.abs(scaledBaseline - originalPositionFn(d, i, u, m)); + var heightF = (d: any, i: number, dataset: Dataset, m: PlotMetadata) => { + return Math.abs(scaledBaseline - originalPositionFn(d, i, dataset, m)); }; attrToProjector["width"] = this._isVertical ? widthF : heightF; attrToProjector["height"] = this._isVertical ? heightF : widthF; - if (secondaryScale instanceof Plottable.Scale.Category) { - attrToProjector[secondaryAttr] = (d: any, i: number, u: any, m: PlotMetadata) => - positionF(d, i, u, m) - widthF(d, i, u, m) / 2; + if (secondaryScale instanceof Plottable.Scales.Category) { + attrToProjector[secondaryAttr] = (d: any, i: number, dataset: Dataset, m: PlotMetadata) => + positionF(d, i, dataset, m) - widthF(d, i, dataset, m) / 2; } else { - attrToProjector[secondaryAttr] = (d: any, i: number, u: any, m: PlotMetadata) => - positionF(d, i, u, m) - widthF(d, i, u, m) * this._barAlignmentFactor; + attrToProjector[secondaryAttr] = (d: any, i: number, dataset: Dataset, m: PlotMetadata) => + positionF(d, i, dataset, m) - widthF(d, i, dataset, m) * this._barAlignmentFactor; } - attrToProjector[primaryAttr] = (d: any, i: number, u: any, m: PlotMetadata) => { - var originalPos = originalPositionFn(d, i, u, m); + attrToProjector[primaryAttr] = (d: any, i: number, dataset: Dataset, m: PlotMetadata) => { + var originalPos = originalPositionFn(d, i, dataset, m); // If it is past the baseline, it should start at the baselin then width/height // carries it over. If it's not past the baseline, leave it at original position and // then width/height carries it to baseline return (originalPos > scaledBaseline) ? scaledBaseline : originalPos; }; - var primaryAccessor = this._projections[primaryAttr].accessor; - if (this.barLabelsEnabled && this.barLabelFormatter) { - attrToProjector["label"] = (d: any, i: number, u: any, m: PlotMetadata) => { - return this._barLabelFormatter(primaryAccessor(d, i, u, m)); + var primaryAccessor = this._propertyBindings.get(primaryAttr).accessor; + if (this._labelsEnabled && this._labelFormatter) { + attrToProjector["label"] = (d: any, i: number, dataset: Dataset, m: PlotMetadata) => { + return this._labelFormatter(primaryAccessor(d, i, dataset, m)); }; - attrToProjector["positive"] = (d: any, i: number, u: any, m: PlotMetadata) => - originalPositionFn(d, i, u, m) <= scaledBaseline; + attrToProjector["positive"] = (d: any, i: number, dataset: Dataset, m: PlotMetadata) => + originalPositionFn(d, i, dataset, m) <= scaledBaseline; } - attrToProjector["fill"] = attrToProjector["fill"] || d3.functor(this._defaultFillColor); - return attrToProjector; } @@ -417,17 +410,18 @@ export module Plot { * If the position scale of the plot is a QuantitativeScale, then _getMinimumDataWidth is scaled to compute the barPixelWidth */ protected _getBarPixelWidth(): number { + if (!this._projectorsReady()) { return 0; } var barPixelWidth: number; - var barScale: Scale.AbstractScale = this._isVertical ? this._xScale : this._yScale; - if (barScale instanceof Plottable.Scale.Category) { - barPixelWidth = ( barScale).rangeBand(); + var barScale: Scale = this._isVertical ? this.x().scale : this.y().scale; + if (barScale instanceof Plottable.Scales.Category) { + barPixelWidth = ( barScale).rangeBand(); } else { - var barAccessor = this._isVertical ? this._projections["x"].accessor : this._projections["y"].accessor; + var barAccessor = this._isVertical ? this.x().accessor : this.y().accessor; - var numberBarAccessorData = d3.set(_Util.Methods.flatten(this._datasetKeysInOrder.map((k) => { + var numberBarAccessorData = d3.set(Utils.Methods.flatten(this._datasetKeysInOrder.map((k) => { var dataset = this._key2PlotDatasetKey.get(k).dataset; var plotMetadata = this._key2PlotDatasetKey.get(k).plotMetadata; - return dataset.data().map((d, i) => barAccessor(d, i, dataset.metadata(), plotMetadata).valueOf()); + return dataset.data().map((d, i) => barAccessor(d, i, dataset, plotMetadata).valueOf()); }))).values().map((value) => +value); numberBarAccessorData.sort((a, b) => a - b); @@ -435,16 +429,16 @@ export module Plot { var barAccessorDataPairs = d3.pairs(numberBarAccessorData); var barWidthDimension = this._isVertical ? this.width() : this.height(); - barPixelWidth = _Util.Methods.min(barAccessorDataPairs, (pair: any[], i: number) => { + barPixelWidth = Utils.Methods.min(barAccessorDataPairs, (pair: any[], i: number) => { return Math.abs(barScale.scale(pair[1]) - barScale.scale(pair[0])); }, barWidthDimension * Bar._SINGLE_BAR_DIMENSION_RATIO); var scaledData = numberBarAccessorData.map((datum: number) => barScale.scale(datum)); - var minScaledDatum = _Util.Methods.min(scaledData, 0); + var minScaledDatum = Utils.Methods.min(scaledData, 0); if (this._barAlignmentFactor !== 0 && minScaledDatum > 0) { barPixelWidth = Math.min(barPixelWidth, minScaledDatum / this._barAlignmentFactor); } - var maxScaledDatum = _Util.Methods.max(scaledData, 0); + var maxScaledDatum = Utils.Methods.max(scaledData, 0); if (this._barAlignmentFactor !== 1 && maxScaledDatum < barWidthDimension) { var margin = barWidthDimension - maxScaledDatum; barPixelWidth = Math.min(barPixelWidth, margin / (1 - this._barAlignmentFactor)); @@ -455,122 +449,10 @@ export module Plot { return barPixelWidth; } - /* - * Gets the current hover mode. - * - * @return {string} The current hover mode. - */ - public hoverMode(): string; - /** - * Sets the hover mode for hover interactions. There are two modes: - * - "point": Selects the bar under the mouse cursor (default). - * - "line" : Selects any bar that would be hit by a line extending - * in the same direction as the bar and passing through - * the cursor. - * - * @param {string} mode The desired hover mode. - * @return {Bar} The calling Bar Plot. - */ - public hoverMode(mode: String): Bar; - public hoverMode(mode?: String): any { - if (mode == null) { - return this._hoverMode; - } - var modeLC = mode.toLowerCase(); - if (modeLC !== "point" && modeLC !== "line") { - throw new Error(mode + " is not a valid hover mode"); - } - this._hoverMode = modeLC; - return this; - } - - private _clearHoverSelection() { - this._getDrawersInOrder().forEach((d, i) => { - d._getRenderArea().selectAll("rect").classed("not-hovered hovered", false); - }); - } - - //===== Hover logic ===== - public _hoverOverComponent(p: Point) { - // no-op - } - - public _hoverOutComponent(p: Point) { - this._clearHoverSelection(); - } - - public _doHover(p: Point): Interaction.HoverData { - var xPositionOrExtent: any = p.x; - var yPositionOrExtent: any = p.y; - if (this._hoverMode === "line") { - var maxExtent: Extent = { min: -Infinity, max: Infinity }; - if (this._isVertical) { - yPositionOrExtent = maxExtent; - } else { - xPositionOrExtent = maxExtent; - } - } - - var xExtent: Extent = _Util.Methods.parseExtent(xPositionOrExtent); - var yExtent: Extent = _Util.Methods.parseExtent(yPositionOrExtent); - - var bars: any[] = []; - var points: Point[] = []; - var projectors = this._generateAttrToProjector(); - - this._datasetKeysInOrder.forEach((key: string) => { - var dataset = this._key2PlotDatasetKey.get(key).dataset; - var plotMetadata = this._key2PlotDatasetKey.get(key).plotMetadata; - var barsFromDataset = this._getBarsFromDataset(key, xExtent, yExtent); - d3.selectAll(barsFromDataset).each((d, i) => { - if (this._isVertical) { - points.push({ - x: projectors["x"](d, i, dataset.metadata(), plotMetadata) + projectors["width"](d, i, dataset.metadata(), plotMetadata) / 2, - y: projectors["y"](d, i, dataset.metadata(), plotMetadata) + - (projectors["positive"](d, i, dataset.metadata(), plotMetadata) ? - 0 : projectors["height"](d, i, dataset.metadata(), plotMetadata)) - }); - } else { - points.push({ - x: projectors["x"](d, i, dataset.metadata(), plotMetadata) + projectors["height"](d, i, dataset.metadata(), plotMetadata) / 2, - y: projectors["y"](d, i, dataset.metadata(), plotMetadata) + - (projectors["positive"](d, i, dataset.metadata(), plotMetadata) ? - 0 : projectors["width"](d, i, dataset.metadata(), plotMetadata)) - }); - } - }); - bars = bars.concat(barsFromDataset); - }); - - var barsSelection = d3.selectAll(bars); - - if (!barsSelection.empty()) { - this._getDrawersInOrder().forEach((d, i) => { - d._getRenderArea().selectAll("rect").classed({ "hovered": false, "not-hovered": true }); - }); - barsSelection.classed({ "hovered": true, "not-hovered": false }); - } else { - this._clearHoverSelection(); - return { - data: null, - pixelPositions: null, - selection: null - }; - } - - return { - data: barsSelection.data(), - pixelPositions: points, - selection: barsSelection - }; - } - //===== /Hover logic ===== - - protected _getAllPlotData(datasetKeys: string[]): PlotData { - var plotData = super._getAllPlotData(datasetKeys); + public getAllPlotData(datasets = this.datasets()): Plots.PlotData { + var plotData = super.getAllPlotData(datasets); - var valueScale = this._isVertical ? this._yScale : this._xScale; - var scaledBaseline = (> (this._isVertical ? this._yScale : this._xScale)).scale(this.baseline()); + var scaledBaseline = (> (this._isVertical ? this.y().scale : this.x().scale)).scale(this.baseline()); var isVertical = this._isVertical; var barAlignmentFactor = this._barAlignmentFactor; diff --git a/src/components/plots/clusteredBarPlot.ts b/src/components/plots/clusteredBarPlot.ts index 122d8054fd..ef9ebfb460 100644 --- a/src/components/plots/clusteredBarPlot.ts +++ b/src/components/plots/clusteredBarPlot.ts @@ -1,7 +1,7 @@ /// module Plottable { -export module Plot { +export module Plots { export interface ClusteredPlotMetadata extends PlotMetadata { position: number; } @@ -20,7 +20,7 @@ export module Plot { * @param {Scale} yScale The y scale to use. * @param {boolean} isVertical if the plot if vertical. */ - constructor(xScale: Scale.AbstractScale, yScale: Scale.AbstractScale, isVertical = true) { + constructor(xScale: Scale, yScale: Scale, isVertical = true) { super(xScale, yScale, isVertical); } @@ -34,10 +34,10 @@ export module Plot { var xAttr = attrToProjector["x"]; var yAttr = attrToProjector["y"]; - attrToProjector["x"] = (d: any, i: number, u: any, m: ClusteredPlotMetadata) => - this._isVertical ? xAttr(d, i, u, m) + m.position : xAttr(d, u, u, m); - attrToProjector["y"] = (d: any, i: number, u: any, m: ClusteredPlotMetadata) => - this._isVertical ? yAttr(d, i, u, m) : yAttr(d, i, u, m) + m.position; + attrToProjector["x"] = (d: any, i: number, dataset: Dataset, m: ClusteredPlotMetadata) => + this._isVertical ? xAttr(d, i, dataset, m) + m.position : xAttr(d, i, dataset, m); + attrToProjector["y"] = (d: any, i: number, dataset: Dataset, m: ClusteredPlotMetadata) => + this._isVertical ? yAttr(d, i, dataset, m) : yAttr(d, i, dataset, m) + m.position; return attrToProjector; } @@ -50,16 +50,16 @@ export module Plot { }); } - private _makeInnerScale(){ - var innerScale = new Scale.Category(); + private _makeInnerScale() { + var innerScale = new Scales.Category(); innerScale.domain(this._datasetKeysInOrder); - if (!this._projections["width"]) { + if (!this._attrBindings.get("width")) { innerScale.range([0, this._getBarPixelWidth()]); } else { - var projection = this._projections["width"]; + var projection = this._attrBindings.get("width"); var accessor = projection.accessor; var scale = projection.scale; - var fn = scale ? (d: any, i: number, u: any, m: PlotMetadata) => scale.scale(accessor(d, i, u, m)) : accessor; + var fn = scale ? (d: any, i: number, dataset: Dataset, m: PlotMetadata) => scale.scale(accessor(d, i, dataset, m)) : accessor; innerScale.range([0, fn(null, 0, null, null)]); } return innerScale; diff --git a/src/components/plots/gridPlot.ts b/src/components/plots/gridPlot.ts index faaf9196a6..f610b7053c 100644 --- a/src/components/plots/gridPlot.ts +++ b/src/components/plots/gridPlot.ts @@ -1,9 +1,8 @@ /// module Plottable { -export module Plot { - export class Grid extends Rectangle { - private _colorScale: Scale.AbstractScale; +export module Plots { + export class Grid extends Rectangle { /** * Constructs a GridPlot. @@ -12,89 +11,79 @@ export module Plot { * grid, and the datum can control what color it is. * * @constructor - * @param {Scale.AbstractScale} xScale The x scale to use. - * @param {Scale.AbstractScale} yScale The y scale to use. + * @param {Scale.Scale} xScale The x scale to use. + * @param {Scale.Scale} yScale The y scale to use. * @param {Scale.Color|Scale.InterpolatedColor} colorScale The color scale * to use for each grid cell. */ - constructor(xScale: Scale.AbstractScale, yScale: Scale.AbstractScale, - colorScale: Scale.AbstractScale) { + constructor(xScale: Scale, yScale: Scale) { super(xScale, yScale); this.classed("grid-plot", true); // The x and y scales should render in bands with no padding for category scales - if (xScale instanceof Scale.Category) { - xScale.innerPadding(0).outerPadding(0); + if (xScale instanceof Scales.Category) { + ( xScale).innerPadding(0).outerPadding(0); } - if (yScale instanceof Scale.Category) { - yScale.innerPadding(0).outerPadding(0); + if (yScale instanceof Scales.Category) { + ( yScale).innerPadding(0).outerPadding(0); } - this._colorScale = colorScale; - this.animator("cells", new Animator.Null()); + this.animator("cells", new Animators.Null()); } - public addDataset(keyOrDataset: any, dataset?: any) { + public addDataset(dataset: Dataset) { if (this._datasetKeysInOrder.length === 1) { - _Util.Methods.warn("Only one dataset is supported in Grid plots"); + Utils.Methods.warn("Only one dataset is supported in Grid plots"); return this; } - super.addDataset(keyOrDataset, dataset); + super.addDataset(dataset); return this; } protected _getDrawer(key: string) { - return new _Drawer.Rect(key, true); + return new Drawers.Rect(key, true); } - /** - * @param {string} attrToSet One of ["x", "y", "x2", "y2", "fill"]. If "fill" is used, - * the data should return a valid CSS color. - */ - public project(attrToSet: string, accessor: any, scale?: Scale.AbstractScale) { - super.project(attrToSet, accessor, scale); + protected _generateDrawSteps(): Drawers.DrawStep[] { + return [{attrToProjector: this._generateAttrToProjector(), animator: this._getAnimator("cells")}]; + } - if (attrToSet === "x") { - if (scale instanceof Scale.Category) { - this.project("x1", (d: any, i: number, u: any, m: Plot.PlotMetadata) => { - return scale.scale(this._projections["x"].accessor(d, i, u, m)) - scale.rangeBand() / 2; - }); - this.project("x2", (d: any, i: number, u: any, m: Plot.PlotMetadata) => { - return scale.scale(this._projections["x"].accessor(d, i, u, m)) + scale.rangeBand() / 2; - }); - } - if (scale instanceof Scale.AbstractQuantitative) { - this.project("x1", (d: any, i: number, u: any, m: Plot.PlotMetadata) => { - return scale.scale(this._projections["x"].accessor(d, i, u, m)); - }); - } + public x(x?: number | Accessor | X | Accessor, scale?: Scale): any { + if (x == null) { + return super.x(); } - - if (attrToSet === "y") { - if (scale instanceof Scale.Category) { - this.project("y1", (d: any, i: number, u: any, m: Plot.PlotMetadata) => { - return scale.scale(this._projections["y"].accessor(d, i, u, m)) - scale.rangeBand() / 2; - }); - this.project("y2", (d: any, i: number, u: any, m: Plot.PlotMetadata) => { - return scale.scale(this._projections["y"].accessor(d, i, u, m)) + scale.rangeBand() / 2; - }); - } - if (scale instanceof Scale.AbstractQuantitative) { - this.project("y1", (d: any, i: number, u: any, m: Plot.PlotMetadata) => { - return scale.scale(this._projections["y"].accessor(d, i, u, m)); - }); + if (scale == null) { + super.x(> x); + } else { + super.x(> x, scale); + if (scale instanceof Scales.Category) { + var xCatScale = ( scale); + this.x1((d, i, dataset, m) => scale.scale(this.x().accessor(d, i, dataset, m)) - xCatScale.rangeBand() / 2); + this.x2((d, i, dataset, m) => scale.scale(this.x().accessor(d, i, dataset, m)) + xCatScale.rangeBand() / 2); + } else if (scale instanceof QuantitativeScale) { + this.x1((d, i, dataset, m) => scale.scale(this.x().accessor(d, i, dataset, m))); } } - - if (attrToSet === "fill") { - this._colorScale = this._projections["fill"].scale; - } - return this; } - protected _generateDrawSteps(): _Drawer.DrawStep[] { - return [{attrToProjector: this._generateAttrToProjector(), animator: this._getAnimator("cells")}]; + public y(y?: number | Accessor | Y | Accessor, scale?: Scale): any { + if (y == null) { + return super.y(); + } + if (scale == null) { + super.y(> y); + } else { + super.y(> y, scale); + if (scale instanceof Scales.Category) { + var yCatScale = ( scale); + this.y1((d, i, dataset, m) => scale.scale(this.y().accessor(d, i, dataset, m)) - yCatScale.rangeBand() / 2); + this.y2((d, i, dataset, m) => scale.scale(this.y().accessor(d, i, dataset, m)) + yCatScale.rangeBand() / 2); + } else if (scale instanceof QuantitativeScale) { + this.y1((d, i, dataset, m) => scale.scale(this.y().accessor(d, i, dataset, m))); + } + } + return this; } } } diff --git a/src/components/plots/linePlot.ts b/src/components/plots/linePlot.ts index 7d08815ce0..ddc09e9c5c 100644 --- a/src/components/plots/linePlot.ts +++ b/src/components/plots/linePlot.ts @@ -1,13 +1,8 @@ /// module Plottable { -export module Plot { - export class Line extends AbstractXYPlot implements Interaction.Hoverable { - private _hoverDetectionRadius = 15; - private _hoverTarget: D3.Selection; - private _defaultStrokeColor: string; - - protected _yScale: Scale.AbstractQuantitative; +export module Plots { + export class Line extends XYPlot { /** * Constructs a LinePlot. @@ -16,48 +11,36 @@ export module Plot { * @param {QuantitativeScale} xScale The x scale to use. * @param {QuantitativeScale} yScale The y scale to use. */ - constructor(xScale: Scale.AbstractQuantitative, yScale: Scale.AbstractQuantitative) { + constructor(xScale: QuantitativeScale, yScale: QuantitativeScale) { super(xScale, yScale); this.classed("line-plot", true); - this.animator("reset", new Animator.Null()); - this.animator("main", new Animator.Base() + this.animator("reset", new Animators.Null()); + this.animator("main", new Animators.Base() .duration(600) .easing("exp-in-out")); - this._defaultStrokeColor = new Scale.Color().range()[0]; - } - - protected _setup() { - super._setup(); - this._hoverTarget = this.foreground().append("circle") - .classed("hover-target", true) - .attr("r", this._hoverDetectionRadius) - .style("visibility", "hidden"); - } - - protected _rejectNullsAndNaNs(d: any, i: number, userMetdata: any, plotMetadata: any, accessor: _Accessor) { - var value = accessor(d, i, userMetdata, plotMetadata); - return value != null && value === value; + this.attr("stroke", new Scales.Color().range()[0]); + this.attr("stroke-width", "2px"); } protected _getDrawer(key: string) { - return new Plottable._Drawer.Line(key); + return new Plottable.Drawers.Line(key); } protected _getResetYFunction() { // gets the y-value generator for the animation start point - var yDomain = this._yScale.domain(); + var yDomain = this.y().scale.domain(); var domainMax = Math.max(yDomain[0], yDomain[1]); var domainMin = Math.min(yDomain[0], yDomain[1]); // start from zero, or the closest domain value to zero // avoids lines zooming on from offscreen. var startValue = (domainMax < 0 && domainMax) || (domainMin > 0 && domainMin) || 0; - var scaledStartValue = this._yScale.scale(startValue); - return (d: any, i: number, u: any, m: PlotMetadata) => scaledStartValue; + var scaledStartValue = this.y().scale.scale(startValue); + return (d: any, i: number, dataset: Dataset, m: PlotMetadata) => scaledStartValue; } - protected _generateDrawSteps(): _Drawer.DrawStep[] { - var drawSteps: _Drawer.DrawStep[] = []; + protected _generateDrawSteps(): Drawers.DrawStep[] { + var drawSteps: Drawers.DrawStep[] = []; if (this._dataChanged && this._animate) { var attrToProjector = this._generateAttrToProjector(); attrToProjector["y"] = this._getResetYFunction(); @@ -76,68 +59,23 @@ export module Plot { var singleDatumAttributes = d3.keys(attrToProjector).filter(isSingleDatumAttr); singleDatumAttributes.forEach((attribute: string) => { var projector = attrToProjector[attribute]; - attrToProjector[attribute] = (data: any[], i: number, u: any, m: any) => - data.length > 0 ? projector(data[0], i, u, m) : null; + attrToProjector[attribute] = (data: any[], i: number, dataset: Dataset, m: any) => + data.length > 0 ? projector(data[0], i, dataset, m) : null; }); - var xFunction = attrToProjector["x"]; - var yFunction = attrToProjector["y"]; - - attrToProjector["defined"] = (d: any, i: number, u: any, m: any) => - this._rejectNullsAndNaNs(d, i, u, m, xFunction) && this._rejectNullsAndNaNs(d, i, u, m, yFunction); - attrToProjector["stroke"] = attrToProjector["stroke"] || d3.functor(this._defaultStrokeColor); - attrToProjector["stroke-width"] = attrToProjector["stroke-width"] || d3.functor("2px"); - return attrToProjector; } protected _wholeDatumAttributes() { - return ["x", "y"]; - } - - protected _getClosestWithinRange(p: Point, range: number) { - var attrToProjector = this._generateAttrToProjector(); - var xProjector = attrToProjector["x"]; - var yProjector = attrToProjector["y"]; - - var getDistSq = (d: any, i: number, userMetdata: any, plotMetadata: PlotMetadata) => { - var dx = +xProjector(d, i, userMetdata, plotMetadata) - p.x; - var dy = +yProjector(d, i, userMetdata, plotMetadata) - p.y; - return (dx * dx + dy * dy); - }; - - var closestOverall: any; - var closestPoint: Point; - var closestDistSq = range * range; - - this._datasetKeysInOrder.forEach((key: string) => { - var dataset = this._key2PlotDatasetKey.get(key).dataset; - var plotMetadata = this._key2PlotDatasetKey.get(key).plotMetadata; - dataset.data().forEach((d: any, i: number) => { - var distSq = getDistSq(d, i, dataset.metadata(), plotMetadata); - if (distSq < closestDistSq) { - closestOverall = d; - closestPoint = { - x: xProjector(d, i, dataset.metadata(), plotMetadata), - y: yProjector(d, i, dataset.metadata(), plotMetadata) - }; - closestDistSq = distSq; - } - }); - }); - - return { - closestValue: closestOverall, - closestPoint: closestPoint - }; + return ["x", "y", "defined"]; } - protected _getAllPlotData(datasetKeys: string[]): PlotData { + public getAllPlotData(datasets = this.datasets()): Plots.PlotData { var data: any[] = []; var pixelPoints: Point[] = []; var allElements: EventTarget[] = []; - datasetKeys.forEach((datasetKey) => { + this._keysForDatasets(datasets).forEach((datasetKey) => { var plotDatasetKey = this._key2PlotDatasetKey.get(datasetKey); if (plotDatasetKey == null) { return; } var drawer = plotDatasetKey.drawer; @@ -176,8 +114,8 @@ export module Plot { var closestPixelPoints: Point[] = []; var closestElements: Element[] = []; - this.datasetOrder().forEach((key: string) => { - var plotData = this.getAllPlotData(key); + this.datasets().forEach((dataset) => { + var plotData = this.getAllPlotData([dataset]); plotData.pixelPoints.forEach((pixelPoint: Point, index: number) => { var datum = plotData.data[index]; var line = plotData.selection[0][0]; @@ -212,40 +150,6 @@ export module Plot { selection: d3.selectAll(closestElements) }; } - - //===== Hover logic ===== - public _hoverOverComponent(p: Point) { - // no-op - } - - public _hoverOutComponent(p: Point) { - // no-op - } - - public _doHover(p: Point): Interaction.HoverData { - var closestInfo = this._getClosestWithinRange(p, this._hoverDetectionRadius); - var closestValue = closestInfo.closestValue; - if (closestValue === undefined) { - return { - data: null, - pixelPositions: null, - selection: null - }; - } - - var closestPoint = closestInfo.closestPoint; - this._hoverTarget.attr({ - "cx": closestInfo.closestPoint.x, - "cy": closestInfo.closestPoint.y - }); - - return { - data: [closestValue], - pixelPositions: [closestPoint], - selection: this._hoverTarget - }; - } - //===== /Hover logic ===== } } } diff --git a/src/components/plots/piePlot.ts b/src/components/plots/piePlot.ts index 13a00a0dd5..7125eff9c7 100644 --- a/src/components/plots/piePlot.ts +++ b/src/components/plots/piePlot.ts @@ -1,20 +1,16 @@ /// module Plottable { -export module Plot { +export module Plots { /* * A PiePlot is a plot meant to show how much out of a total an attribute's value is. * One usecase is to show how much funding departments are given out of a total budget. - * - * Primary projection attributes: - * "fill" - Accessor determining the color of each sector - * "inner-radius" - Accessor determining the distance from the center to the inner edge of the sector - * "outer-radius" - Accessor determining the distance from the center to the outer edge of the sector - * "value" - Accessor to extract the value determining the proportion of each slice to the total */ - export class Pie extends AbstractPlot { + export class Pie extends Plot { - private _colorScale: Scale.Color; + private static _INNER_RADIUS_KEY = "inner-radius"; + private static _OUTER_RADIUS_KEY = "outer-radius"; + private static _SECTOR_VALUE_KEY = "sector-value"; /** * Constructs a PiePlot. @@ -23,41 +19,40 @@ export module Plot { */ constructor() { super(); - this._colorScale = new Scale.Color(); + this.innerRadius(0); + this.outerRadius(() => Math.min(this.width(), this.height()) / 2); this.classed("pie-plot", true); + this.attr("fill", (d, i) => String(i), new Scales.Color()); } - public _computeLayout(offeredXOrigin?: number, offeredYOrigin?: number, availableWidth?: number, availableHeight?: number) { - super._computeLayout(offeredXOrigin, offeredYOrigin, availableWidth, availableHeight); + public computeLayout(origin?: Point, availableWidth?: number, availableHeight?: number) { + super.computeLayout(origin, availableWidth, availableHeight); this._renderArea.attr("transform", "translate(" + this.width() / 2 + "," + this.height() / 2 + ")"); + var radiusLimit = Math.min(this.width(), this.height()) / 2; + if (this.innerRadius().scale != null) { + this.innerRadius().scale.range([0, radiusLimit]); + } + if (this.outerRadius().scale != null) { + this.outerRadius().scale.range([0, radiusLimit]); + } + return this; } - public addDataset(keyOrDataset: any, dataset?: any) { + public addDataset(dataset: Dataset) { if (this._datasetKeysInOrder.length === 1) { - _Util.Methods.warn("Only one dataset is supported in Pie plots"); + Utils.Methods.warn("Only one dataset is supported in Pie plots"); return this; } - super.addDataset(keyOrDataset, dataset); + super.addDataset(dataset); return this; } - protected _generateAttrToProjector(): AttributeToProjector { - var attrToProjector = super._generateAttrToProjector(); - attrToProjector["inner-radius"] = attrToProjector["inner-radius"] || d3.functor(0); - attrToProjector["outer-radius"] = attrToProjector["outer-radius"] || d3.functor(Math.min(this.width(), this.height()) / 2); - - var defaultFillFunction = (d: any, i: number) => this._colorScale.scale(String(i)); - attrToProjector["fill"] = attrToProjector["fill"] || defaultFillFunction; - - return attrToProjector; - } - - protected _getDrawer(key: string): _Drawer.AbstractDrawer { - return new Plottable._Drawer.Arc(key).setClass("arc"); + protected _getDrawer(key: string) { + return new Plottable.Drawers.Arc(key).setClass("arc"); } - public getAllPlotData(datasetKeys?: string | string[]): PlotData { - var allPlotData = super.getAllPlotData(datasetKeys); + public getAllPlotData(datasets = this.datasets()): Plots.PlotData { + var allPlotData = super.getAllPlotData(datasets); allPlotData.pixelPoints.forEach((pixelPoint: Point) => { pixelPoint.x = pixelPoint.x + this.width() / 2; @@ -66,6 +61,42 @@ export module Plot { return allPlotData; } + + public sectorValue(): AccessorScaleBinding; + public sectorValue(sectorValue: number | Accessor): Plots.Pie; + public sectorValue(sectorValue: S | Accessor, scale: Scale): Plots.Pie; + public sectorValue(sectorValue?: number | Accessor | S | Accessor, scale?: Scale): any { + if (sectorValue == null) { + return this._propertyBindings.get(Pie._SECTOR_VALUE_KEY); + } + this._bindProperty(Pie._SECTOR_VALUE_KEY, sectorValue, scale); + this.renderImmediately(); + return this; + } + + public innerRadius(): AccessorScaleBinding; + public innerRadius(innerRadius: number | Accessor): Plots.Pie; + public innerRadius(innerRadius: R | Accessor, scale: Scale): Plots.Pie; + public innerRadius(innerRadius?: number | Accessor | R | Accessor, scale?: Scale): any { + if (innerRadius == null) { + return this._propertyBindings.get(Pie._INNER_RADIUS_KEY); + } + this._bindProperty(Pie._INNER_RADIUS_KEY, innerRadius, scale); + this.renderImmediately(); + return this; + } + + public outerRadius(): AccessorScaleBinding; + public outerRadius(outerRadius: number | Accessor): Plots.Pie; + public outerRadius(outerRadius: R | Accessor, scale: Scale): Plots.Pie; + public outerRadius(outerRadius?: number | Accessor | R | Accessor, scale?: Scale): any { + if (outerRadius == null) { + return this._propertyBindings.get(Pie._OUTER_RADIUS_KEY); + } + this._bindProperty(Pie._OUTER_RADIUS_KEY, outerRadius, scale); + this.renderImmediately(); + return this; + } } } } diff --git a/src/components/plots/plot.ts b/src/components/plots/plot.ts new file mode 100644 index 0000000000..815604cd92 --- /dev/null +++ b/src/components/plots/plot.ts @@ -0,0 +1,595 @@ +/// + +module Plottable { + + export module Plots { + + /** + * A key that is also coupled with a dataset, a drawer and a metadata in Plot. + */ + export type PlotDatasetKey = { + dataset: Dataset; + drawer: Drawers.AbstractDrawer; + plotMetadata: PlotMetadata; + key: string; + } + + export interface PlotMetadata { + datasetKey: string; + } + + export type PlotData = { + data: any[]; + pixelPoints: Point[]; + selection: D3.Selection; + } + + export interface AccessorScaleBinding { + accessor: Accessor; + scale?: Scale; + } + } + + export class Plot extends Component { + protected _dataChanged = false; + protected _key2PlotDatasetKey: D3.Map; + protected _datasetKeysInOrder: string[]; + + protected _renderArea: D3.Selection; + protected _attrBindings: D3.Map<_Projection>; + protected _attrExtents: D3.Map; + private _extentsProvider: Scales.ExtentsProvider; + + protected _animate: boolean = false; + private _animators: Animators.PlotAnimatorMap = {}; + protected _animateOnNextRender = true; + private _nextSeriesIndex: number; + private _renderCallback: ScaleCallback>; + private _onDatasetUpdateCallback: DatasetCallback; + + protected _propertyExtents: D3.Map; + protected _propertyBindings: D3.Map>; + + /** + * Constructs a Plot. + * + * Plots render data. Common example include Plot.Scatter, Plot.Bar, and Plot.Line. + * + * A bare Plot has a DataSource and any number of projectors, which take + * data and "project" it onto the Plot, such as "x", "y", "fill", "r". + * + * @constructor + * @param {any[]|Dataset} [dataset] If provided, the data or Dataset to be associated with this Plot. + */ + constructor() { + super(); + this._clipPathEnabled = true; + this.classed("plot", true); + this._key2PlotDatasetKey = d3.map(); + this._attrBindings = d3.map(); + this._attrExtents = d3.map(); + this._extentsProvider = (scale: Scale) => this._extentsForScale(scale); + this._datasetKeysInOrder = []; + this._nextSeriesIndex = 0; + this._renderCallback = (scale) => this.render(); + this._onDatasetUpdateCallback = () => this._onDatasetUpdate(); + this._propertyBindings = d3.map(); + this._propertyExtents = d3.map(); + } + + public anchor(selection: D3.Selection) { + super.anchor(selection); + this._animateOnNextRender = true; + this._dataChanged = true; + this._updateExtents(); + return this; + } + + protected _setup() { + super._setup(); + this._renderArea = this._content.append("g").classed("render-area", true); + // HACKHACK on 591 + this._getDrawersInOrder().forEach((d) => d.setup(this._renderArea.append("g"))); + } + + public destroy() { + super.destroy(); + this._scales().forEach((scale) => scale.offUpdate(this._renderCallback)); + this.datasets().forEach((dataset) => this.removeDataset(dataset)); + } + + /** + * @param {Dataset} dataset + * @returns {Plot} The calling Plot. + */ + public addDataset(dataset: Dataset) { + var key = "_" + this._nextSeriesIndex++; + if (this._key2PlotDatasetKey.has(key)) { + this.removeDataset(dataset); + }; + var drawer = this._getDrawer(key); + var metadata = this._getPlotMetadataForDataset(key); + var pdk = {drawer: drawer, dataset: dataset, key: key, plotMetadata: metadata}; + this._datasetKeysInOrder.push(key); + this._key2PlotDatasetKey.set(key, pdk); + + if (this._isSetup) { + drawer.setup(this._renderArea.append("g")); + } + + dataset.onUpdate(this._onDatasetUpdateCallback); + this._onDatasetUpdate(); + return this; + } + + protected _getDrawer(key: string): Drawers.AbstractDrawer { + return new Drawers.AbstractDrawer(key); + } + + protected _getAnimator(key: string): Animators.PlotAnimator { + if (this._animate && this._animateOnNextRender) { + return this._animators[key] || new Animators.Null(); + } else { + return new Animators.Null(); + } + } + + protected _onDatasetUpdate() { + this._updateExtents(); + this._animateOnNextRender = true; + this._dataChanged = true; + this.render(); + } + + public attr(attr: string): Plots.AccessorScaleBinding; + public attr(attr: string, attrValue: number | string | Accessor | Accessor): Plot; + public attr(attr: string, attrValue: A | Accessor, scale: Scale): Plot; + public attr(attr: string, attrValue?: number | string | Accessor | Accessor | A | Accessor, + scale?: Scale): any { + if (attrValue == null) { + return this._attrBindings.get(attr); + } + this._bindAttr(attr, attrValue, scale); + this.render(); // queue a re-render upon changing projector + return this; + } + + protected _bindProperty(property: string, value: any, scale: Scale) { + this._bind(property, value, scale, this._propertyBindings, this._propertyExtents); + this._updateExtentsForProperty(property); + } + + private _bindAttr(attr: string, value: any, scale: Scale) { + this._bind(attr, value, scale, this._attrBindings, this._attrExtents); + this._updateExtentsForAttr(attr); + } + + private _bind(key: string, value: any, scale: Scale, + bindings: D3.Map>, extents: D3.Map) { + var binding = bindings.get(key); + var oldScale = binding != null ? binding.scale : null; + + if (oldScale != null) { + this._uninstallScaleForKey(oldScale, key); + } + if (scale != null) { + this._installScaleForKey(scale, key); + } + + bindings.set(key, { accessor: d3.functor(value), scale: scale }); + } + + protected _generateAttrToProjector(): AttributeToProjector { + var h: AttributeToProjector = {}; + this._attrBindings.forEach((attr, binding) => { + var accessor = binding.accessor; + var scale = binding.scale; + var fn = scale ? (d: any, i: number, dataset: Dataset, m: Plots.PlotMetadata) => scale.scale(accessor(d, i, dataset, m)) : accessor; + h[attr] = fn; + }); + var propertyProjectors = this._generatePropertyToProjectors(); + Object.keys(propertyProjectors).forEach((key) => { + if (h[key] == null) { + h[key] = propertyProjectors[key]; + } + }); + return h; + } + + /** + * Generates a dictionary mapping an attribute to a function that calculate that attribute's value + * in accordance with the given datasetKey. + * + * Note that this will return all of the data attributes, which may not perfectly align to svg attributes + * + * @param {Dataset} dataset The dataset to generate the dictionary for + * @returns {AttributeToAppliedProjector} A dictionary mapping attributes to functions + */ + public generateProjectors(dataset: Dataset): AttributeToAppliedProjector { + var attrToAppliedProjector: AttributeToAppliedProjector = {}; + var datasetKey = this._keyForDataset(dataset); + if (datasetKey != null) { + var attrToProjector = this._generateAttrToProjector(); + var plotDatasetKey = this._key2PlotDatasetKey.get(datasetKey); + var plotMetadata = plotDatasetKey.plotMetadata; + d3.entries(attrToProjector).forEach((keyValue: any) => { + attrToAppliedProjector[keyValue.key] = (datum: any, index: number) => { + return keyValue.value(datum, index, plotDatasetKey.dataset, plotMetadata); + }; + }); + } + return attrToAppliedProjector; + } + + public renderImmediately() { + if (this._isAnchored) { + this._paint(); + this._dataChanged = false; + this._animateOnNextRender = false; + } + return this; + } + + /** + * Enables or disables animation. + * + * @param {boolean} enabled Whether or not to animate. + */ + public animate(enabled: boolean) { + this._animate = enabled; + return this; + } + + public detach() { + super.detach(); + // make the domain resize + this._updateExtents(); + return this; + } + + /** + * @returns {Scale[]} A unique array of all scales currently used by the Plot. + */ + private _scales() { + var scales: Scale[] = []; + this._attrBindings.forEach((attr, binding) => { + var scale = binding.scale; + if (scale != null && scales.indexOf(scale) === -1) { + scales.push(scale); + } + }); + this._propertyBindings.forEach((property, binding) => { + var scale = binding.scale; + if (scale != null && scales.indexOf(scale) === -1) { + scales.push(scale); + } + }); + return scales; + } + + /** + * Updates the extents associated with each attribute, then autodomains all scales the Plot uses. + */ + protected _updateExtents() { + this._attrBindings.forEach((attr) => this._updateExtentsForAttr(attr)); + this._propertyExtents.forEach((property) => this._updateExtentsForProperty(property)); + this._scales().forEach((scale) => scale._autoDomainIfAutomaticMode()); + } + + private _updateExtentsForAttr(attr: string) { + // Filters should never be applied to attributes + this._updateExtentsForKey(attr, this._attrBindings, this._attrExtents, null); + } + + protected _updateExtentsForProperty(property: string) { + this._updateExtentsForKey(property, this._propertyBindings, this._propertyExtents, this._filterForProperty(property)); + } + + protected _filterForProperty(property: string): Accessor { + return null; + } + + private _updateExtentsForKey(key: string, bindings: D3.Map>, + extents: D3.Map, filter: Accessor) { + var accScaleBinding = bindings.get(key); + if (accScaleBinding.accessor == null) { return; } + extents.set(key, this._datasetKeysInOrder.map((key) => { + var plotDatasetKey = this._key2PlotDatasetKey.get(key); + var dataset = plotDatasetKey.dataset; + var plotMetadata = plotDatasetKey.plotMetadata; + return this._computeExtent(dataset, accScaleBinding.accessor, plotMetadata, filter); + })); + } + + private _computeExtent(dataset: Dataset, accessor: Accessor, plotMetadata: any, filter: Accessor): any[] { + var data = dataset.data(); + if (filter != null) { + data = data.filter((d, i) => filter(d, i, dataset, plotMetadata)); + } + var appliedAccessor = (d: any, i: number) => accessor(d, i, dataset, plotMetadata); + var mappedData = data.map(appliedAccessor); + if (mappedData.length === 0) { + return []; + } else if (typeof(mappedData[0]) === "string") { + return Utils.Methods.uniq(mappedData); + } else { + var extent = d3.extent(mappedData); + if (extent[0] == null || extent[1] == null) { + return []; + } else { + return extent; + } + } + } + + /** + * Override in subclass to add special extents, such as included values + */ + protected _extentsForProperty(property: string) { + return this._propertyExtents.get(property); + } + + private _extentsForScale(scale: Scale): D[][] { + if (!this._isAnchored) { + return []; + } + var allSetsOfExtents: D[][][] = []; + this._attrBindings.forEach((attr, binding) => { + if (binding.scale === scale) { + var extents = this._attrExtents.get(attr); + if (extents != null) { + allSetsOfExtents.push(extents); + } + } + }); + + this._propertyBindings.forEach((property, binding) => { + if (binding.scale === scale) { + var extents = this._extentsForProperty(property); + if (extents != null) { + allSetsOfExtents.push(extents); + } + } + }); + + return d3.merge(allSetsOfExtents); + } + + /** + * Get the animator associated with the specified Animator key. + * + * @return {PlotAnimator} The Animator for the specified key. + */ + public animator(animatorKey: string): Animators.PlotAnimator; + /** + * Set the animator associated with the specified Animator key. + * + * @param {string} animatorKey The key for the Animator. + * @param {PlotAnimator} animator An Animator to be assigned to + * the specified key. + * @returns {Plot} The calling Plot. + */ + public animator(animatorKey: string, animator: Animators.PlotAnimator): Plot; + public animator(animatorKey: string, animator?: Animators.PlotAnimator): any { + if (animator === undefined) { + return this._animators[animatorKey]; + } else { + this._animators[animatorKey] = animator; + return this; + } + } + + /** + * @param {Dataset} dataset + * @returns {Plot} The calling Plot. + */ + public removeDataset(dataset: Dataset): Plot { + var key = this._keyForDataset(dataset); + if (key != null && this._key2PlotDatasetKey.has(key)) { + var pdk = this._key2PlotDatasetKey.get(key); + pdk.drawer.remove(); + pdk.dataset.offUpdate(this._onDatasetUpdateCallback); + this._datasetKeysInOrder.splice(this._datasetKeysInOrder.indexOf(key), 1); + this._key2PlotDatasetKey.remove(key); + this._onDatasetUpdate(); + } + return this; + } + + /** + * Returns the internal key for the Dataset, or undefined if not found + */ + private _keyForDataset(dataset: Dataset) { + return this._datasetKeysInOrder[this.datasets().indexOf(dataset)]; + } + + /** + * Returns an array of internal keys corresponding to those Datasets actually on the plot + */ + protected _keysForDatasets(datasets: Dataset[]) { + return datasets.map((dataset) => this._keyForDataset(dataset)).filter((key) => key != null); + } + + public datasets(): Dataset[]; + public datasets(datasets: Dataset[]): Plot; + public datasets(datasets?: Dataset[]): any { + var currentDatasets = this._datasetKeysInOrder.map((k) => this._key2PlotDatasetKey.get(k).dataset); + if (datasets == null) { + return currentDatasets; + } + currentDatasets.forEach((dataset) => this.removeDataset(dataset)); + datasets.forEach((dataset) => this.addDataset(dataset)); + return this; + } + + protected _getDrawersInOrder(): Drawers.AbstractDrawer[] { + return this._datasetKeysInOrder.map((k) => this._key2PlotDatasetKey.get(k).drawer); + } + + protected _generateDrawSteps(): Drawers.DrawStep[] { + return [{attrToProjector: this._generateAttrToProjector(), animator: new Animators.Null()}]; + } + + protected _additionalPaint(time: number) { + // no-op + } + + protected _getDataToDraw() { + var datasets: D3.Map = d3.map(); + this._datasetKeysInOrder.forEach((key: string) => { + datasets.set(key, this._key2PlotDatasetKey.get(key).dataset.data()); + }); + return datasets; + } + + /** + * Gets the new plot metadata for new dataset with provided key + * + * @param {string} key The key of new dataset + */ + protected _getPlotMetadataForDataset(key: string): Plots.PlotMetadata { + return { + datasetKey: key + }; + } + + private _paint() { + var drawSteps = this._generateDrawSteps(); + var dataToDraw = this._getDataToDraw(); + var drawers = this._getDrawersInOrder(); + + var times = this._datasetKeysInOrder.map((k, i) => + drawers[i].draw( + dataToDraw.get(k), + drawSteps, + this._key2PlotDatasetKey.get(k).dataset, + this._key2PlotDatasetKey.get(k).plotMetadata + )); + var maxTime = Utils.Methods.max(times, 0); + this._additionalPaint(maxTime); + } + + /** + * Retrieves all of the Selections of this Plot for the specified Datasets. + * + * @param {Dataset[]} datasets The Datasets to retrieve the selections from. + * If not provided, all selections will be retrieved. + * @param {boolean} exclude If set to true, all Datasets will be queried excluding the keys referenced + * in the previous datasetKeys argument (default = false). + * @returns {D3.Selection} The retrieved Selections. + */ + public getAllSelections(datasets = this.datasets(), exclude = false): D3.Selection { + var datasetKeyArray = this._keysForDatasets(datasets); + + if (exclude) { + var excludedDatasetKeys = d3.set(datasetKeyArray); + datasetKeyArray = this._datasetKeysInOrder.filter((datasetKey) => !excludedDatasetKeys.has(datasetKey)); + } + + var allSelections: EventTarget[] = []; + + datasetKeyArray.forEach((datasetKey) => { + var plotDatasetKey = this._key2PlotDatasetKey.get(datasetKey); + if (plotDatasetKey == null) { return; } + var drawer = plotDatasetKey.drawer; + drawer._getRenderArea().selectAll(drawer._getSelector()).each(function () { + allSelections.push(this); + }); + }); + + return d3.selectAll(allSelections); + } + + /** + * Retrieves all of the PlotData of this plot for the specified dataset(s) + * + * @param {Dataset[]} datasets The Datasets to retrieve the PlotData from. + * If not provided, all PlotData will be retrieved. + * @returns {PlotData} The retrieved PlotData. + */ + public getAllPlotData(datasets = this.datasets()): Plots.PlotData { + var data: any[] = []; + var pixelPoints: Point[] = []; + var allElements: EventTarget[] = []; + + this._keysForDatasets(datasets).forEach((datasetKey) => { + var plotDatasetKey = this._key2PlotDatasetKey.get(datasetKey); + if (plotDatasetKey == null) { return; } + var drawer = plotDatasetKey.drawer; + plotDatasetKey.dataset.data().forEach((datum: any, index: number) => { + var pixelPoint = drawer._getPixelPoint(datum, index); + if (pixelPoint.x !== pixelPoint.x || pixelPoint.y !== pixelPoint.y) { + return; + } + data.push(datum); + pixelPoints.push(pixelPoint); + allElements.push(drawer._getSelection(index).node()); + }); + }); + + return { data: data, pixelPoints: pixelPoints, selection: d3.selectAll(allElements) }; + } + + /** + * Retrieves PlotData with the lowest distance, where distance is defined + * to be the Euclidiean norm. + * + * @param {Point} queryPoint The point to which plot data should be compared + * + * @returns {PlotData} The PlotData closest to queryPoint + */ + public getClosestPlotData(queryPoint: Point): Plots.PlotData { + var closestDistanceSquared = Infinity; + var closestIndex: number; + var plotData = this.getAllPlotData(); + plotData.pixelPoints.forEach((pixelPoint: Point, index: number) => { + var datum = plotData.data[index]; + var selection = d3.select(plotData.selection[0][index]); + + if (!this._isVisibleOnPlot(datum, pixelPoint, selection)) { + return; + } + + var distance = Utils.Methods.distanceSquared(pixelPoint, queryPoint); + if (distance < closestDistanceSquared) { + closestDistanceSquared = distance; + closestIndex = index; + } + }); + + if (closestIndex == null) { + return {data: [], pixelPoints: [], selection: d3.select()}; + } + + return {data: [plotData.data[closestIndex]], + pixelPoints: [plotData.pixelPoints[closestIndex]], + selection: d3.select(plotData.selection[0][closestIndex])}; + } + + protected _isVisibleOnPlot(datum: any, pixelPoint: Point, selection: D3.Selection): boolean { + return !(pixelPoint.x < 0 || pixelPoint.y < 0 || + pixelPoint.x > this.width() || pixelPoint.y > this.height()); + } + + protected _uninstallScaleForKey(scale: Scale, key: string) { + scale.offUpdate(this._renderCallback); + scale.removeExtentsProvider(this._extentsProvider); + scale._autoDomainIfAutomaticMode(); + } + + protected _installScaleForKey(scale: Scale, key: string) { + scale.onUpdate(this._renderCallback); + scale.addExtentsProvider(this._extentsProvider); + scale._autoDomainIfAutomaticMode(); + } + + protected _generatePropertyToProjectors(): AttributeToProjector { + var attrToProjector: AttributeToProjector = {}; + this._propertyBindings.forEach((key, binding) => { + var scaledAccessor = (d: any, i: number, dataset: Dataset, m: any) => binding.scale.scale(binding.accessor(d, i, dataset, m)); + attrToProjector[key] = binding.scale == null ? binding.accessor : scaledAccessor; + }); + return attrToProjector; + } + } +} diff --git a/src/components/plots/rectanglePlot.ts b/src/components/plots/rectanglePlot.ts index 474e917246..b35fde1867 100644 --- a/src/components/plots/rectanglePlot.ts +++ b/src/components/plots/rectanglePlot.ts @@ -1,9 +1,12 @@ /// module Plottable { -export module Plot { - export class Rectangle extends AbstractXYPlot { - private _defaultFillColor: string; +export module Plots { + export class Rectangle extends XYPlot { + private static _X1_KEY = "x1"; + private static _X2_KEY = "x2"; + private static _Y1_KEY = "y1"; + private static _Y2_KEY = "y2"; /** * Constructs a RectanglePlot. @@ -13,17 +16,17 @@ export module Plot { * as well as the bottom and top bounds (y1 and y2 respectively) * * @constructor - * @param {Scale.AbstractScale} xScale The x scale to use. - * @param {Scale.AbstractScale} yScale The y scale to use. + * @param {Scale.Scale} xScale The x scale to use. + * @param {Scale.Scale} yScale The y scale to use. */ - constructor(xScale: Scale.AbstractScale, yScale: Scale.AbstractScale) { + constructor(xScale: Scale, yScale: Scale) { super(xScale, yScale); - this._defaultFillColor = new Scale.Color().range()[0]; this.classed("rectangle-plot", true); + this.attr("fill", new Scales.Color().range()[0]); } protected _getDrawer(key: string) { - return new _Drawer.Rect(key, true); + return new Drawers.Rect(key, true); } protected _generateAttrToProjector() { @@ -36,12 +39,14 @@ export module Plot { var y2Attr = attrToProjector["y2"]; // Generate width based on difference, then adjust for the correct x origin - attrToProjector["width"] = (d, i, u, m) => Math.abs(x2Attr(d, i, u, m) - x1Attr(d, i, u, m)); - attrToProjector["x"] = (d, i, u, m) => Math.min(x1Attr(d, i, u, m), x2Attr(d, i, u, m)); + attrToProjector["width"] = (d, i, dataset, m) => Math.abs(x2Attr(d, i, dataset, m) - x1Attr(d, i, dataset, m)); + attrToProjector["x"] = (d, i, dataset, m) => Math.min(x1Attr(d, i, dataset, m), x2Attr(d, i, dataset, m)); // Generate height based on difference, then adjust for the correct y origin - attrToProjector["height"] = (d, i, u, m) => Math.abs(y2Attr(d, i, u, m) - y1Attr(d, i, u, m)); - attrToProjector["y"] = (d, i, u, m) => Math.max(y1Attr(d, i, u, m), y2Attr(d, i, u, m)) - attrToProjector["height"](d, i, u, m); + attrToProjector["height"] = (d, i, dataset, m) => Math.abs(y2Attr(d, i, dataset, m) - y1Attr(d, i, dataset, m)); + attrToProjector["y"] = (d, i, dataset, m) => { + return Math.max(y1Attr(d, i, dataset, m), y2Attr(d, i, dataset, m)) - attrToProjector["height"](d, i, dataset, m); + }; // Clean up the attributes projected onto the SVG elements delete attrToProjector["x1"]; @@ -49,14 +54,60 @@ export module Plot { delete attrToProjector["x2"]; delete attrToProjector["y2"]; - - attrToProjector["fill"] = attrToProjector["fill"] || d3.functor(this._defaultFillColor); return attrToProjector; } - protected _generateDrawSteps(): _Drawer.DrawStep[] { + protected _generateDrawSteps(): Drawers.DrawStep[] { return [{attrToProjector: this._generateAttrToProjector(), animator: this._getAnimator("rectangles")}]; } + + public x1(): AccessorScaleBinding; + public x1(x1: number | Accessor): Plots.Rectangle; + public x1(x1: X | Accessor, scale: Scale): Plots.Rectangle; + public x1(x1?: number | Accessor | X | Accessor, scale?: Scale): any { + if (x1 == null) { + return this._propertyBindings.get(Rectangle._X1_KEY); + } + this._bindProperty(Rectangle._X1_KEY, x1, scale); + this.renderImmediately(); + return this; + } + + public x2(): AccessorScaleBinding; + public x2(x2: number | Accessor): Plots.Rectangle; + public x2(x2: X | Accessor, scale: Scale): Plots.Rectangle; + public x2(x2?: number | Accessor | X | Accessor, scale?: Scale): any { + if (x2 == null) { + return this._propertyBindings.get(Rectangle._X2_KEY); + } + this._bindProperty(Rectangle._X2_KEY, x2, scale); + this.renderImmediately(); + return this; + } + + public y1(): AccessorScaleBinding; + public y1(y1: number | Accessor): Plots.Rectangle; + public y1(y1: Y | Accessor, scale: Scale): Plots.Rectangle; + public y1(y1?: number | Accessor | Y | Accessor, scale?: Scale): any { + if (y1 == null) { + return this._propertyBindings.get(Rectangle._Y1_KEY); + } + this._bindProperty(Rectangle._Y1_KEY, y1, scale); + this.renderImmediately(); + return this; + } + + public y2(): AccessorScaleBinding; + public y2(y2: number | Accessor): Plots.Rectangle; + public y2(y2: Y | Accessor, scale: Scale): Plots.Rectangle; + public y2(y2?: number | Accessor | Y | Accessor, scale?: Scale): any { + if (y2 == null) { + return this._propertyBindings.get(Rectangle._Y2_KEY); + } + this._bindProperty(Rectangle._Y2_KEY, y2, scale); + this.renderImmediately(); + return this; + } } } } diff --git a/src/components/plots/scatterPlot.ts b/src/components/plots/scatterPlot.ts index c9709e715a..d67b52e139 100644 --- a/src/components/plots/scatterPlot.ts +++ b/src/components/plots/scatterPlot.ts @@ -1,10 +1,10 @@ /// module Plottable { -export module Plot { - export class Scatter extends AbstractXYPlot implements Interaction.Hoverable { - private _closeDetectionRadius = 5; - private _defaultFillColor: string; +export module Plots { + export class Scatter extends XYPlot { + private static _SIZE_KEY = "size"; + private static _SYMBOL_KEY = "symbol"; /** * Constructs a ScatterPlot. @@ -13,33 +13,55 @@ export module Plot { * @param {Scale} xScale The x scale to use. * @param {Scale} yScale The y scale to use. */ - constructor(xScale: Scale.AbstractScale, yScale: Scale.AbstractScale) { + constructor(xScale: Scale, yScale: Scale) { super(xScale, yScale); this.classed("scatter-plot", true); - this._defaultFillColor = new Scale.Color().range()[0]; - this.animator("symbols-reset", new Animator.Null()); - this.animator("symbols", new Animator.Base() + this.animator("symbols-reset", new Animators.Null()); + this.animator("symbols", new Animators.Base() .duration(250) .delay(5)); + this.attr("opacity", 0.6); + this.attr("fill", new Scales.Color().range()[0]); } protected _getDrawer(key: string) { - return new Plottable._Drawer.Symbol(key); + return new Plottable.Drawers.Symbol(key); } protected _generateAttrToProjector() { var attrToProjector = super._generateAttrToProjector(); attrToProjector["size"] = attrToProjector["size"] || d3.functor(6); - attrToProjector["opacity"] = attrToProjector["opacity"] || d3.functor(0.6); - attrToProjector["fill"] = attrToProjector["fill"] || d3.functor(this._defaultFillColor); attrToProjector["symbol"] = attrToProjector["symbol"] || (() => SymbolFactories.circle()); return attrToProjector; } - protected _generateDrawSteps(): _Drawer.DrawStep[] { - var drawSteps: _Drawer.DrawStep[] = []; + public size(): AccessorScaleBinding; + public size(size: number | Accessor): Plots.Scatter; + public size(size: S | Accessor, scale: Scale): Plots.Scatter; + public size(size?: number | Accessor | S | Accessor, scale?: Scale): any { + if (size == null) { + return this._propertyBindings.get(Scatter._SIZE_KEY); + } + this._bindProperty(Scatter._SIZE_KEY, size, scale); + this.renderImmediately(); + return this; + } + + public symbol(): AccessorScaleBinding; + public symbol(symbol: Accessor): Plots.Scatter; + public symbol(symbol?: Accessor): any { + if (symbol == null) { + return this._propertyBindings.get(Scatter._SYMBOL_KEY); + } + this._propertyBindings.set(Scatter._SYMBOL_KEY, { accessor: symbol }); + this.renderImmediately(); + return this; + } + + protected _generateDrawSteps(): Drawers.DrawStep[] { + var drawSteps: Drawers.DrawStep[] = []; if (this._dataChanged && this._animate) { var resetAttrToProjector = this._generateAttrToProjector(); resetAttrToProjector["size"] = () => 0; @@ -50,71 +72,6 @@ export module Plot { return drawSteps; } - protected _getClosestStruckPoint(p: Point, range: number): Interaction.HoverData { - var attrToProjector = this._generateAttrToProjector(); - var xProjector = attrToProjector["x"]; - var yProjector = attrToProjector["y"]; - var getDistSq = (d: any, i: number, userMetdata: any, plotMetadata: PlotMetadata) => { - var dx = attrToProjector["x"](d, i, userMetdata, plotMetadata) - p.x; - var dy = attrToProjector["y"](d, i, userMetdata, plotMetadata) - p.y; - return (dx * dx + dy * dy); - }; - - var overAPoint = false; - var closestElement: Element; - var closestElementUserMetadata: any; - var closestElementPlotMetadata: any; - var closestIndex: number; - var minDistSq = range * range; - - this._datasetKeysInOrder.forEach((key: string) => { - var dataset = this._key2PlotDatasetKey.get(key).dataset; - var plotMetadata = this._key2PlotDatasetKey.get(key).plotMetadata; - var drawer = <_Drawer.Symbol>this._key2PlotDatasetKey.get(key).drawer; - drawer._getRenderArea().selectAll("path").each(function(d, i) { - var distSq = getDistSq(d, i, dataset.metadata(), plotMetadata); - var r = attrToProjector["size"](d, i, dataset.metadata(), plotMetadata) / 2; - - if (distSq < r * r) { // cursor is over this point - if (!overAPoint || distSq < minDistSq) { - closestElement = this; - closestIndex = i; - minDistSq = distSq; - closestElementUserMetadata = dataset.metadata(); - closestElementPlotMetadata = plotMetadata; - } - overAPoint = true; - } else if (!overAPoint && distSq < minDistSq) { - closestElement = this; - closestIndex = i; - minDistSq = distSq; - closestElementUserMetadata = dataset.metadata(); - closestElementPlotMetadata = plotMetadata; - } - }); - }); - - if (!closestElement) { - return { - selection: null, - pixelPositions: null, - data: null - }; - } - - var closestSelection = d3.select(closestElement); - var closestData = closestSelection.data(); - var closestPoint = { - x: attrToProjector["x"](closestData[0], closestIndex, closestElementUserMetadata, closestElementPlotMetadata), - y: attrToProjector["y"](closestData[0], closestIndex, closestElementUserMetadata, closestElementPlotMetadata) - }; - return { - selection: closestSelection, - pixelPositions: [closestPoint], - data: closestData - }; - } - protected _isVisibleOnPlot(datum: any, pixelPoint: Point, selection: D3.Selection): boolean { var xRange = { min: 0, max: this.width() }; var yRange = { min: 0, max: this.height() }; @@ -128,22 +85,8 @@ export module Plot { height: bbox.height }; - return Plottable._Util.Methods.intersectsBBox(xRange, yRange, translatedBbox); - } - - //===== Hover logic ===== - public _hoverOverComponent(p: Point) { - // no-op - } - - public _hoverOutComponent(p: Point) { - // no-op - } - - public _doHover(p: Point): Interaction.HoverData { - return this._getClosestStruckPoint(p, this._closeDetectionRadius); + return Utils.Methods.intersectsBBox(xRange, yRange, translatedBbox); } - //===== /Hover logic ===== } } } diff --git a/src/components/plots/stackedAreaPlot.ts b/src/components/plots/stackedAreaPlot.ts index 5c67062e05..e65c64a0ad 100644 --- a/src/components/plots/stackedAreaPlot.ts +++ b/src/components/plots/stackedAreaPlot.ts @@ -1,7 +1,7 @@ /// module Plottable { -export module Plot { +export module Plots { export class StackedArea extends Area { private _isVertical: boolean; @@ -15,18 +15,19 @@ export module Plot { * @param {QuantitativeScale} xScale The x scale to use. * @param {QuantitativeScale} yScale The y scale to use. */ - constructor(xScale: Scale.AbstractQuantitative, yScale: Scale.AbstractQuantitative) { + constructor(xScale: QuantitativeScale, yScale: QuantitativeScale) { super(xScale, yScale); this.classed("area-plot", true); this._isVertical = true; + this.attr("fill-opacity", 1); } protected _getDrawer(key: string) { - return new Plottable._Drawer.Area(key).drawLine(false); + return new Plottable.Drawers.Area(key).drawLine(false); } - public _getAnimator(key: string): Animator.PlotAnimator { - return new Animator.Null(); + public _getAnimator(key: string): Animators.PlotAnimator { + return new Animators.Null(); } protected _setup() { @@ -34,8 +35,36 @@ export module Plot { this._baseline = this._renderArea.append("line").classed("baseline", true); } + public x(x?: number | Accessor | X | Accessor, xScale?: Scale): any { + if (x == null) { + return super.x(); + } + if (xScale == null) { + super.x(> x); + Stacked.prototype.x.apply(this, [x]); + } else { + super.x(> x, xScale); + Stacked.prototype.x.apply(this, [x, xScale]); + } + return this; + } + + public y(y?: number | Accessor, yScale?: Scale): any { + if (y == null) { + return super.y(); + } + if (yScale == null) { + super.y(> y); + Stacked.prototype.y.apply(this, [y]); + } else { + super.y(> y, yScale); + Stacked.prototype.y.apply(this, [y, yScale]); + } + return this; + } + protected _additionalPaint() { - var scaledBaseline = this._yScale.scale(this._baselineValue); + var scaledBaseline = this.y().scale.scale(this._baselineValue); var baselineAttr: any = { "x1": 0, "y1": scaledBaseline, @@ -48,40 +77,30 @@ export module Plot { protected _updateYDomainer() { super._updateYDomainer(); - var scale = > this._yScale; + var scale = > this.y().scale; if (!scale._userSetDomainer) { - scale.domainer().addPaddingException(0, "STACKED_AREA_PLOT+" + this.getID()) - .addIncludedValue(0, "STACKED_AREA_PLOT+" + this.getID()); + scale.domainer().addPaddingException(this, 0) + .addIncludedValue(this, 0); // prepending "AREA_PLOT" is unnecessary but reduces likely of user accidentally creating collisions scale._autoDomainIfAutomaticMode(); } } - public project(attrToSet: string, accessor: any, scale?: Scale.AbstractScale) { - super.project(attrToSet, accessor, scale); - AbstractStacked.prototype.project.apply(this, [attrToSet, accessor, scale]); - return this; - } - protected _onDatasetUpdate() { super._onDatasetUpdate(); - AbstractStacked.prototype._onDatasetUpdate.apply(this); + Stacked.prototype._onDatasetUpdate.apply(this); return this; } protected _generateAttrToProjector() { var attrToProjector = super._generateAttrToProjector(); - if (this._projections["fill-opacity"] == null) { - attrToProjector["fill-opacity"] = d3.functor(1); - } - - var yAccessor = this._projections["y"].accessor; - var xAccessor = this._projections["x"].accessor; - attrToProjector["y"] = (d: any, i: number, u: any, m: StackedPlotMetadata) => - this._yScale.scale(+yAccessor(d, i, u, m) + m.offsets.get(xAccessor(d, i, u, m))); - attrToProjector["y0"] = (d: any, i: number, u: any, m: StackedPlotMetadata) => - this._yScale.scale(m.offsets.get(xAccessor(d, i, u, m))); + var yAccessor = this.y().accessor; + var xAccessor = this.x().accessor; + attrToProjector["y"] = (d: any, i: number, dataset: Dataset, m: StackedPlotMetadata) => + this.y().scale.scale(+yAccessor(d, i, dataset, m) + m.offsets.get(xAccessor(d, i, dataset, m))); + attrToProjector["y0"] = (d: any, i: number, dataset: Dataset, m: StackedPlotMetadata) => + this.y().scale.scale(m.offsets.get(xAccessor(d, i, dataset, m))); return attrToProjector; } @@ -90,63 +109,63 @@ export module Plot { return ["x", "y", "defined"]; } - //===== Stack logic from AbstractStackedPlot ===== + // ===== Stack logic from StackedPlot ===== public _updateStackOffsets() { if (!this._projectorsReady()) { return; } var domainKeys = this._getDomainKeys(); - var keyAccessor = this._isVertical ? this._projections["x"].accessor : this._projections["y"].accessor; + var keyAccessor = this._isVertical ? this.x().accessor : this.y().accessor; var keySets = this._datasetKeysInOrder.map((k) => { var dataset = this._key2PlotDatasetKey.get(k).dataset; var plotMetadata = this._key2PlotDatasetKey.get(k).plotMetadata; - return d3.set(dataset.data().map((datum, i) => keyAccessor(datum, i, dataset.metadata(), plotMetadata).toString())).values(); + return d3.set(dataset.data().map((datum, i) => keyAccessor(datum, i, dataset, plotMetadata).toString())).values(); }); if (keySets.some((keySet) => keySet.length !== domainKeys.length)) { - _Util.Methods.warn("the domains across the datasets are not the same. Plot may produce unintended behavior."); + Utils.Methods.warn("the domains across the datasets are not the same. Plot may produce unintended behavior."); } - AbstractStacked.prototype._updateStackOffsets.call(this); + Stacked.prototype._updateStackOffsets.call(this); } public _updateStackExtents() { - AbstractStacked.prototype._updateStackExtents.call(this); + Stacked.prototype._updateStackExtents.call(this); } public _stack(dataArray: D3.Map[]): D3.Map[] { - return AbstractStacked.prototype._stack.call(this, dataArray); + return Stacked.prototype._stack.call(this, dataArray); } public _setDatasetStackOffsets(positiveDataMapArray: D3.Map[], negativeDataMapArray: D3.Map[]) { - AbstractStacked.prototype._setDatasetStackOffsets.call(this, positiveDataMapArray, negativeDataMapArray); + Stacked.prototype._setDatasetStackOffsets.call(this, positiveDataMapArray, negativeDataMapArray); } public _getDomainKeys() { - return AbstractStacked.prototype._getDomainKeys.call(this); + return Stacked.prototype._getDomainKeys.call(this); } public _generateDefaultMapArray(): D3.Map[] { - return AbstractStacked.prototype._generateDefaultMapArray.call(this); + return Stacked.prototype._generateDefaultMapArray.call(this); } - public _updateScaleExtents() { - AbstractStacked.prototype._updateScaleExtents.call(this); + protected _extentsForProperty(attr: string) { + return ( Stacked.prototype)._extentsForProperty.call(this, attr); } - public _keyAccessor(): _Accessor { - return AbstractStacked.prototype._keyAccessor.call(this); + public _keyAccessor(): Accessor { + return Stacked.prototype._keyAccessor.call(this); } - public _valueAccessor(): _Accessor { - return AbstractStacked.prototype._valueAccessor.call(this); + public _valueAccessor(): Accessor { + return Stacked.prototype._valueAccessor.call(this); } public _getPlotMetadataForDataset(key: string): StackedPlotMetadata { - return AbstractStacked.prototype._getPlotMetadataForDataset.call(this, key); + return Stacked.prototype._getPlotMetadataForDataset.call(this, key); } - protected _normalizeDatasets(fromX: boolean): {a: A; b: B}[] { - return AbstractStacked.prototype._normalizeDatasets.call(this, fromX); + protected _updateExtentsForProperty(property: string) { + ( Stacked.prototype)._updateExtentsForProperty.call(this, property); } - //===== /Stack logic ===== + // ===== /Stack logic ===== } } } diff --git a/src/components/plots/stackedBarPlot.ts b/src/components/plots/stackedBarPlot.ts index 81279bd010..1c1bdfe27f 100644 --- a/src/components/plots/stackedBarPlot.ts +++ b/src/components/plots/stackedBarPlot.ts @@ -1,7 +1,7 @@ /// module Plottable { -export module Plot { +export module Plots { export class StackedBar extends Bar { /** @@ -13,22 +13,50 @@ export module Plot { * @param {Scale} yScale the y scale of the plot. * @param {boolean} isVertical if the plot if vertical. */ - constructor(xScale?: Scale.AbstractScale, yScale?: Scale.AbstractScale, isVertical = true) { + constructor(xScale?: Scale, yScale?: Scale, isVertical = true) { super(xScale, yScale, isVertical); } - protected _getAnimator(key: string): Animator.PlotAnimator { + protected _getAnimator(key: string): Animators.PlotAnimator { if (this._animate && this._animateOnNextRender) { if (this.animator(key)) { return this.animator(key); } else if (key === "stacked-bar") { - var primaryScale: Scale.AbstractScale = this._isVertical ? this._yScale : this._xScale; + var primaryScale: Scale = this._isVertical ? this.y().scale : this.x().scale; var scaledBaseline = primaryScale.scale(this.baseline()); - return new Animator.MovingRect(scaledBaseline, this._isVertical); + return new Animators.MovingRect(scaledBaseline, this._isVertical); } } - return new Animator.Null(); + return new Animators.Null(); + } + + public x(x?: number | Accessor | X | Accessor, xScale?: Scale): any { + if (x == null) { + return super.x(); + } + if (xScale == null) { + super.x(> x); + Stacked.prototype.x.apply(this, [x]); + } else { + super.x(> x, xScale); + Stacked.prototype.x.apply(this, [x, xScale]); + } + return this; + } + + public y(y?: number | Accessor | Y | Accessor, yScale?: Scale): any { + if (y == null) { + return super.y(); + } + if (yScale == null) { + super.y(> y); + Stacked.prototype.y.apply(this, [y]); + } else { + super.y(> y, yScale); + Stacked.prototype.y.apply(this, [y, yScale]); + } + return this; } protected _generateAttrToProjector() { @@ -36,86 +64,81 @@ export module Plot { var valueAttr = this._isVertical ? "y" : "x"; var keyAttr = this._isVertical ? "x" : "y"; - var primaryScale: Scale.AbstractScale = this._isVertical ? this._yScale : this._xScale; - var primaryAccessor = this._projections[valueAttr].accessor; - var keyAccessor = this._projections[keyAttr].accessor; - var getStart = (d: any, i: number, u: any, m: StackedPlotMetadata) => - primaryScale.scale(m.offsets.get(keyAccessor(d, i, u, m))); - var getEnd = (d: any, i: number, u: any, m: StackedPlotMetadata) => - primaryScale.scale(+primaryAccessor(d, i, u, m) + m.offsets.get(keyAccessor(d, i, u, m))); - - var heightF = (d: any, i: number, u: any, m: StackedPlotMetadata) => Math.abs(getEnd(d, i, u, m) - getStart(d, i, u, m)); - - var attrFunction = (d: any, i: number, u: any, m: StackedPlotMetadata) => - - +primaryAccessor(d, i, u, m) < 0 ? getStart(d, i, u, m) : getEnd(d, i, u, m); - attrToProjector[valueAttr] = (d: any, i: number, u: any, m: StackedPlotMetadata) => - this._isVertical ? attrFunction(d, i, u, m) : attrFunction(d, i, u, m) - heightF(d, i, u, m); + var primaryScale: Scale = this._isVertical ? this.y().scale : this.x().scale; + var primaryAccessor = this._propertyBindings.get(valueAttr).accessor; + var keyAccessor = this._propertyBindings.get(keyAttr).accessor; + var getStart = (d: any, i: number, dataset: Dataset, m: StackedPlotMetadata) => + primaryScale.scale(m.offsets.get(keyAccessor(d, i, dataset, m))); + var getEnd = (d: any, i: number, dataset: Dataset, m: StackedPlotMetadata) => + primaryScale.scale(+primaryAccessor(d, i, dataset, m) + m.offsets.get(keyAccessor(d, i, dataset, m))); + + var heightF = (d: any, i: number, dataset: Dataset, m: StackedPlotMetadata) => { + return Math.abs(getEnd(d, i, dataset, m) - getStart(d, i, dataset, m)); + }; + + var attrFunction = (d: any, i: number, dataset: Dataset, m: StackedPlotMetadata) => + +primaryAccessor(d, i, dataset, m) < 0 ? getStart(d, i, dataset, m) : getEnd(d, i, dataset, m); + attrToProjector[valueAttr] = (d: any, i: number, dataset: Dataset, m: StackedPlotMetadata) => + this._isVertical ? attrFunction(d, i, dataset, m) : attrFunction(d, i, dataset, m) - heightF(d, i, dataset, m); return attrToProjector; } - protected _generateDrawSteps(): _Drawer.DrawStep[] { + protected _generateDrawSteps(): Drawers.DrawStep[] { return [{attrToProjector: this._generateAttrToProjector(), animator: this._getAnimator("stacked-bar")}]; } - public project(attrToSet: string, accessor: any, scale?: Scale.AbstractScale) { - super.project(attrToSet, accessor, scale); - AbstractStacked.prototype.project.apply(this, [attrToSet, accessor, scale]); - return this; - } - protected _onDatasetUpdate() { super._onDatasetUpdate(); - AbstractStacked.prototype._onDatasetUpdate.apply(this); + Stacked.prototype._onDatasetUpdate.apply(this); return this; } protected _getPlotMetadataForDataset(key: string): StackedPlotMetadata { - return AbstractStacked.prototype._getPlotMetadataForDataset.call(this, key); + return Stacked.prototype._getPlotMetadataForDataset.call(this, key); } - protected _normalizeDatasets(fromX: boolean): {a: A; b: B}[] { - return AbstractStacked.prototype._normalizeDatasets.call(this, fromX); + protected _updateExtentsForProperty(property: string) { + ( Stacked.prototype)._updateExtentsForProperty.call(this, property); } - //===== Stack logic from AbstractStackedPlot ===== + // ===== Stack logic from StackedPlot ===== public _updateStackOffsets() { - AbstractStacked.prototype._updateStackOffsets.call(this); + Stacked.prototype._updateStackOffsets.call(this); } public _updateStackExtents() { - AbstractStacked.prototype._updateStackExtents.call(this); + Stacked.prototype._updateStackExtents.call(this); } public _stack(dataArray: D3.Map[]): D3.Map[] { - return AbstractStacked.prototype._stack.call(this, dataArray); + return Stacked.prototype._stack.call(this, dataArray); } public _setDatasetStackOffsets(positiveDataMapArray: D3.Map[], negativeDataMapArray: D3.Map[]) { - AbstractStacked.prototype._setDatasetStackOffsets.call(this, positiveDataMapArray, negativeDataMapArray); + Stacked.prototype._setDatasetStackOffsets.call(this, positiveDataMapArray, negativeDataMapArray); } public _getDomainKeys() { - return AbstractStacked.prototype._getDomainKeys.call(this); + return Stacked.prototype._getDomainKeys.call(this); } public _generateDefaultMapArray(): D3.Map[] { - return AbstractStacked.prototype._generateDefaultMapArray.call(this); + return Stacked.prototype._generateDefaultMapArray.call(this); } - public _updateScaleExtents() { - AbstractStacked.prototype._updateScaleExtents.call(this); + protected _extentsForProperty(attr: string) { + return ( Stacked.prototype)._extentsForProperty.call(this, attr); } - public _keyAccessor(): _Accessor { - return AbstractStacked.prototype._keyAccessor.call(this); + public _keyAccessor(): Accessor | Accessor { + return Stacked.prototype._keyAccessor.call(this); } - public _valueAccessor(): _Accessor { - return AbstractStacked.prototype._valueAccessor.call(this); + public _valueAccessor(): Accessor { + return Stacked.prototype._valueAccessor.call(this); } - //===== /Stack logic ===== + // ===== /Stack logic ===== } } } diff --git a/src/components/plots/stackedPlot.ts b/src/components/plots/stackedPlot.ts new file mode 100644 index 0000000000..030f9acf7f --- /dev/null +++ b/src/components/plots/stackedPlot.ts @@ -0,0 +1,233 @@ +/// + +module Plottable { + + export module Plots { + export interface StackedPlotMetadata extends PlotMetadata { + offsets: D3.Map; + } + + export type StackedDatum = { + key: any; + value: number; + offset?: number; + } + } + + export class Stacked extends XYPlot { + private _stackedExtent = [0, 0]; + protected _isVertical: boolean; + + public _getPlotMetadataForDataset(key: string): Plots.StackedPlotMetadata { + var metadata = super._getPlotMetadataForDataset(key); + metadata.offsets = d3.map(); + return metadata; + } + + public x(x?: number | Accessor | X | Accessor, scale?: Scale): any { + if (x == null) { + return super.x(); + } + if (scale == null) { + super.x(> x); + } else { + super.x(> x, scale); + } + if (this.x().accessor != null && this.y().accessor != null) { + this._updateStackOffsets(); + } + return this; + } + + public y(y?: number | Accessor | Y | Accessor, scale?: Scale): any { + if (y == null) { + return super.y(); + } + if (scale == null) { + super.y(> y); + } else { + super.y(> y, scale); + } + if (this.x().accessor != null && this.y().accessor != null) { + this._updateStackOffsets(); + } + return this; + } + + public _onDatasetUpdate() { + if (this._projectorsReady()) { + this._updateStackOffsets(); + } + super._onDatasetUpdate(); + } + + public _updateStackOffsets() { + var dataMapArray = this._generateDefaultMapArray(); + var domainKeys = this._getDomainKeys(); + + var positiveDataMapArray: D3.Map[] = dataMapArray.map((dataMap) => { + return Utils.Methods.populateMap(domainKeys, (domainKey) => { + return { key: domainKey, value: Math.max(0, dataMap.get(domainKey).value) || 0 }; + }); + }); + + var negativeDataMapArray: D3.Map[] = dataMapArray.map((dataMap) => { + return Utils.Methods.populateMap(domainKeys, (domainKey) => { + return { key: domainKey, value: Math.min(dataMap.get(domainKey).value, 0) || 0 }; + }); + }); + + this._setDatasetStackOffsets(this._stack(positiveDataMapArray), this._stack(negativeDataMapArray)); + this._updateStackExtents(); + } + + public _updateStackExtents() { + var valueAccessor = this._valueAccessor(); + var keyAccessor = this._keyAccessor(); + var filter = this._filterForProperty(this._isVertical ? "y" : "x"); + var maxStackExtent = Utils.Methods.max(this._datasetKeysInOrder, (k: string) => { + var dataset = this._key2PlotDatasetKey.get(k).dataset; + var plotMetadata = this._key2PlotDatasetKey.get(k).plotMetadata; + var data = dataset.data(); + if (filter != null) { + data = data.filter((d, i) => filter(d, i, dataset, plotMetadata)); + } + return Utils.Methods.max(data, (datum: any, i: number) => { + return +valueAccessor(datum, i, dataset, plotMetadata) + + plotMetadata.offsets.get(String(keyAccessor(datum, i, dataset, plotMetadata))); + }, 0); + }, 0); + + var minStackExtent = Utils.Methods.min(this._datasetKeysInOrder, (k: string) => { + var dataset = this._key2PlotDatasetKey.get(k).dataset; + var plotMetadata = this._key2PlotDatasetKey.get(k).plotMetadata; + var data = dataset.data(); + if (filter != null) { + data = data.filter((d, i) => filter(d, i, dataset, plotMetadata)); + } + return Utils.Methods.min(data, (datum: any, i: number) => { + return +valueAccessor(datum, i, dataset, plotMetadata) + + plotMetadata.offsets.get(String(keyAccessor(datum, i, dataset, plotMetadata))); + }, 0); + }, 0); + + this._stackedExtent = [Math.min(minStackExtent, 0), Math.max(0, maxStackExtent)]; + } + + /** + * Feeds the data through d3's stack layout function which will calculate + * the stack offsets and use the the function declared in .out to set the offsets on the data. + */ + public _stack(dataArray: D3.Map[]): D3.Map[] { + var outFunction = (d: Plots.StackedDatum, y0: number, y: number) => { + d.offset = y0; + }; + + d3.layout.stack() + .x((d) => d.key) + .y((d) => +d.value) + .values((d) => this._getDomainKeys().map((domainKey) => d.get(domainKey))) + .out(outFunction)(dataArray); + + return dataArray; + } + + /** + * After the stack offsets have been determined on each separate dataset, the offsets need + * to be determined correctly on the overall datasets + */ + public _setDatasetStackOffsets(positiveDataMapArray: D3.Map[], negativeDataMapArray: D3.Map[]) { + var keyAccessor = this._keyAccessor(); + var valueAccessor = this._valueAccessor(); + + this._datasetKeysInOrder.forEach((k, index) => { + var dataset = this._key2PlotDatasetKey.get(k).dataset; + var plotMetadata = this._key2PlotDatasetKey.get(k).plotMetadata; + var positiveDataMap = positiveDataMapArray[index]; + var negativeDataMap = negativeDataMapArray[index]; + var isAllNegativeValues = dataset.data().every((datum, i) => valueAccessor(datum, i, dataset, plotMetadata) <= 0); + + dataset.data().forEach((datum: any, datumIndex: number) => { + var key = String(keyAccessor(datum, datumIndex, dataset, plotMetadata)); + var positiveOffset = positiveDataMap.get(key).offset; + var negativeOffset = negativeDataMap.get(key).offset; + + var value = valueAccessor(datum, datumIndex, dataset, plotMetadata); + var offset: number; + if (!+value) { + offset = isAllNegativeValues ? negativeOffset : positiveOffset; + } else { + offset = value > 0 ? positiveOffset : negativeOffset; + } + plotMetadata.offsets.set(key, offset); + }); + }); + } + + public _getDomainKeys(): string[] { + var keyAccessor = this._keyAccessor(); + var domainKeys = d3.set(); + + this._datasetKeysInOrder.forEach((k) => { + var dataset = this._key2PlotDatasetKey.get(k).dataset; + var plotMetadata = this._key2PlotDatasetKey.get(k).plotMetadata; + dataset.data().forEach((datum, index) => { + domainKeys.add(keyAccessor(datum, index, dataset, plotMetadata)); + }); + }); + + return domainKeys.values(); + } + + public _generateDefaultMapArray(): D3.Map[] { + var keyAccessor = this._keyAccessor(); + var valueAccessor = this._valueAccessor(); + var domainKeys = this._getDomainKeys(); + + var dataMapArray = this._datasetKeysInOrder.map(() => { + return Utils.Methods.populateMap(domainKeys, (domainKey) => { + return {key: domainKey, value: 0}; + }); + }); + + this._datasetKeysInOrder.forEach((k, datasetIndex) => { + var dataset = this._key2PlotDatasetKey.get(k).dataset; + var plotMetadata = this._key2PlotDatasetKey.get(k).plotMetadata; + dataset.data().forEach((datum, index) => { + var key = String(keyAccessor(datum, index, dataset, plotMetadata)); + var value = valueAccessor(datum, index, dataset, plotMetadata); + dataMapArray[datasetIndex].set(key, {key: key, value: value}); + }); + }); + + return dataMapArray; + } + + protected _updateExtentsForProperty(property: string) { + super._updateExtentsForProperty(property); + if ((property === "x" || property === "y") && this._projectorsReady()) { + this._updateStackExtents(); + } + } + + protected _extentsForProperty(attr: string) { + var extents = super._extentsForProperty(attr); + var primaryAttr = this._isVertical ? "y" : "x"; + if (attr === primaryAttr && this._stackedExtent) { + var clonedExtents = extents.slice(); + clonedExtents.push(this._stackedExtent); + return clonedExtents; + } else { + return extents; + } + } + + public _keyAccessor(): Accessor | Accessor { + return this._isVertical ? this.x().accessor : this.y().accessor; + } + + public _valueAccessor(): Accessor { + return this._isVertical ? this.y().accessor : this.x().accessor; + } + } +} diff --git a/src/components/plots/xyPlot.ts b/src/components/plots/xyPlot.ts new file mode 100644 index 0000000000..a6a2e3e7c6 --- /dev/null +++ b/src/components/plots/xyPlot.ts @@ -0,0 +1,246 @@ +/// + +module Plottable { + export class XYPlot extends Plot { + private static _X_KEY = "x"; + private static _Y_KEY = "y"; + private _autoAdjustXScaleDomain = false; + private _autoAdjustYScaleDomain = false; + private _adjustYDomainOnChangeFromXCallback: ScaleCallback>; + private _adjustXDomainOnChangeFromYCallback: ScaleCallback>; + + /** + * Constructs an XYPlot. + * + * An XYPlot is a plot from drawing 2-dimensional data. Common examples + * include Scale.Line and Scale.Bar. + * + * @constructor + * @param {any[]|Dataset} [dataset] The data or Dataset to be associated with this Renderer. + * @param {Scale} xScale The x scale to use. + * @param {Scale} yScale The y scale to use. + */ + constructor(xScale: Scale, yScale: Scale) { + super(); + if (xScale == null || yScale == null) { + throw new Error("XYPlots require an xScale and yScale"); + } + this.classed("xy-plot", true); + + this._propertyBindings.set(XYPlot._X_KEY, { accessor: null, scale: xScale }); + this._propertyBindings.set(XYPlot._Y_KEY, { accessor: null, scale: yScale}); + + this._adjustYDomainOnChangeFromXCallback = (scale) => this._adjustYDomainOnChangeFromX(); + this._adjustXDomainOnChangeFromYCallback = (scale) => this._adjustXDomainOnChangeFromY(); + + this._updateXDomainer(); + xScale.onUpdate(this._adjustYDomainOnChangeFromXCallback); + + this._updateYDomainer(); + yScale.onUpdate(this._adjustXDomainOnChangeFromYCallback); + } + + public x(): Plots.AccessorScaleBinding; + public x(x: number | Accessor): XYPlot; + public x(x: X | Accessor, xScale: Scale): XYPlot; + public x(x?: number | Accessor | X | Accessor, xScale?: Scale): any { + if (x == null) { + return this._propertyBindings.get(XYPlot._X_KEY); + } + + this._bindProperty(XYPlot._X_KEY, x, xScale); + if (this._autoAdjustYScaleDomain) { + this._updateYExtentsAndAutodomain(); + } + this._updateXDomainer(); + this.renderImmediately(); + return this; + } + + public y(): Plots.AccessorScaleBinding; + public y(y: number | Accessor): XYPlot; + public y(y: Y | Accessor, yScale: Scale): XYPlot; + public y(y?: number | Accessor | Y | Accessor, yScale?: Scale): any { + if (y == null) { + return this._propertyBindings.get(XYPlot._Y_KEY); + } + + this._bindProperty(XYPlot._Y_KEY, y, yScale); + if (this._autoAdjustXScaleDomain) { + this._updateXExtentsAndAutodomain(); + } + this._updateYDomainer(); + this.renderImmediately(); + return this; + } + + protected _filterForProperty(property: string) { + if (property === "x" && this._autoAdjustXScaleDomain) { + return this._makeFilterByProperty("y"); + } else if (property === "y" && this._autoAdjustYScaleDomain) { + return this._makeFilterByProperty("x"); + } + return null; + } + + private _makeFilterByProperty(property: string) { + var binding = this._propertyBindings.get(property); + if (binding != null) { + var accessor = binding.accessor; + var scale = binding.scale; + if (scale != null) { + return (datum: any, index: number, dataset: Dataset, plotMetadata: Plots.PlotMetadata) => { + var range = scale.range(); + return Utils.Methods.inRange(scale.scale(accessor(datum, index, dataset, plotMetadata)), range[0], range[1]); + }; + } + } + return null; + } + + protected _uninstallScaleForKey(scale: Scale, key: string) { + super._uninstallScaleForKey(scale, key); + var adjustCallback = key === XYPlot._X_KEY ? this._adjustYDomainOnChangeFromXCallback + : this._adjustXDomainOnChangeFromYCallback; + scale.offUpdate(adjustCallback); + } + + protected _installScaleForKey(scale: Scale, key: string) { + super._installScaleForKey(scale, key); + var adjustCallback = key === XYPlot._X_KEY ? this._adjustYDomainOnChangeFromXCallback + : this._adjustXDomainOnChangeFromYCallback; + scale.onUpdate(adjustCallback); + } + + public destroy() { + super.destroy(); + if (this.x().scale) { + this.x().scale.offUpdate(this._adjustYDomainOnChangeFromXCallback); + } + if (this.y().scale) { + this.y().scale.offUpdate(this._adjustXDomainOnChangeFromYCallback); + } + return this; + } + + /** + * Sets the automatic domain adjustment over visible points for y scale. + * + * If autoAdjustment is true adjustment is immediately performend. + * + * @param {boolean} autoAdjustment The new value for the automatic adjustment domain for y scale. + * @returns {XYPlot} The calling XYPlot. + */ + public automaticallyAdjustYScaleOverVisiblePoints(autoAdjustment: boolean): XYPlot { + this._autoAdjustYScaleDomain = autoAdjustment; + this._adjustYDomainOnChangeFromX(); + return this; + } + + /** + * Sets the automatic domain adjustment over visible points for x scale. + * + * If autoAdjustment is true adjustment is immediately performend. + * + * @param {boolean} autoAdjustment The new value for the automatic adjustment domain for x scale. + * @returns {XYPlot} The calling XYPlot. + */ + public automaticallyAdjustXScaleOverVisiblePoints(autoAdjustment: boolean): XYPlot { + this._autoAdjustXScaleDomain = autoAdjustment; + this._adjustXDomainOnChangeFromY(); + return this; + } + + protected _generatePropertyToProjectors(): AttributeToProjector { + var attrToProjector = super._generatePropertyToProjectors(); + var positionXFn = attrToProjector["x"]; + var positionYFn = attrToProjector["y"]; + attrToProjector["defined"] = (d: any, i: number, dataset: Dataset, m: Plots.PlotMetadata) => { + var positionX = positionXFn(d, i, dataset, m); + var positionY = positionYFn(d, i, dataset, m); + return positionX != null && positionX === positionX && + positionY != null && positionY === positionY; + }; + return attrToProjector; + } + + public computeLayout(origin?: Point, availableWidth?: number, availableHeight?: number) { + super.computeLayout(origin, availableWidth, availableHeight); + var xScale = this.x().scale; + if (xScale != null) { + xScale.range([0, this.width()]); + } + var yScale = this.y().scale; + if (yScale != null) { + if (this.y().scale instanceof Scales.Category) { + this.y().scale.range([0, this.height()]); + } else { + this.y().scale.range([this.height(), 0]); + } + } + return this; + } + + protected _updateXDomainer() { + if (this.x().scale instanceof QuantitativeScale) { + var scale = > this.x().scale; + if (!scale._userSetDomainer) { + scale.domainer().pad().nice(); + } + } + } + + protected _updateYDomainer() { + if (this.y().scale instanceof QuantitativeScale) { + var scale = > this.y().scale; + if (!scale._userSetDomainer) { + scale.domainer().pad().nice(); + } + } + } + + private _updateXExtentsAndAutodomain() { + this._updateExtentsForProperty("x"); + var xScale = this.x().scale; + if (xScale != null) { + xScale.autoDomain(); + } + } + + private _updateYExtentsAndAutodomain() { + this._updateExtentsForProperty("y"); + var yScale = this.y().scale; + if (yScale != null) { + yScale.autoDomain(); + } + } + + /** + * Adjusts both domains' extents to show all datasets. + * + * This call does not override auto domain adjustment behavior over visible points. + */ + public showAllData() { + this._updateXExtentsAndAutodomain(); + this._updateYExtentsAndAutodomain(); + return this; + } + + private _adjustYDomainOnChangeFromX() { + if (!this._projectorsReady()) { return; } + if (this._autoAdjustYScaleDomain) { + this._updateYExtentsAndAutodomain(); + } + } + private _adjustXDomainOnChangeFromY() { + if (!this._projectorsReady()) { return; } + if (this._autoAdjustXScaleDomain) { + this._updateXExtentsAndAutodomain(); + } + } + + protected _projectorsReady() { + return this.x().accessor != null && this.y().accessor != null; + } + } +} diff --git a/src/components/selectionBoxLayer.ts b/src/components/selectionBoxLayer.ts index 4277441631..9c6eb929ae 100644 --- a/src/components/selectionBoxLayer.ts +++ b/src/components/selectionBoxLayer.ts @@ -1,8 +1,8 @@ /// module Plottable { -export module Component { - export class SelectionBoxLayer extends AbstractComponent { +export module Components { + export class SelectionBoxLayer extends Component { protected _box: D3.Selection; private _boxArea: D3.Selection; private _boxVisible = false; @@ -14,8 +14,6 @@ export module Component { constructor() { super(); this.classed("selection-box-layer", true); - this._fixedWidthFlag = true; - this._fixedHeightFlag = true; } protected _setup() { @@ -51,7 +49,7 @@ export module Component { } this._setBounds(newBounds); - this._render(); + this.render(); return this; } @@ -70,7 +68,7 @@ export module Component { }; } - public _doRender() { + public renderImmediately() { if (this._boxVisible) { var t = this._boxBounds.topLeft.y; var b = this._boxBounds.bottomRight.y; @@ -84,6 +82,7 @@ export module Component { } else { this._box.remove(); } + return this; } /** @@ -105,9 +104,17 @@ export module Component { } this._boxVisible = show; - this._render(); + this.render(); return this; } + + public fixedWidth() { + return true; + } + + public fixedHeight() { + return true; + } } } } diff --git a/src/components/table.ts b/src/components/table.ts index 8c2a7e3310..be79b3198c 100644 --- a/src/components/table.ts +++ b/src/components/table.ts @@ -1,31 +1,31 @@ /// module Plottable { -export module Component { +export module Components { type _LayoutAllocation = { - guaranteedWidths : number[]; + guaranteedWidths: number[]; guaranteedHeights: number[]; - wantsWidthArr : boolean[]; + wantsWidthArr: boolean[]; wantsHeightArr: boolean[]; } - export type _IterateLayoutResult = { + type _IterateLayoutResult = { colProportionalSpace: number[]; rowProportionalSpace: number[]; - guaranteedWidths : number[]; - guaranteedHeights : number[]; - wantsWidth : boolean; - wantsHeight : boolean; + guaranteedWidths: number[]; + guaranteedHeights: number[]; + wantsWidth: boolean; + wantsHeight: boolean; }; - export class Table extends AbstractComponentContainer { + export class Table extends ComponentContainer { private _rowPadding = 0; - private _colPadding = 0; + private _columnPadding = 0; - private _rows: AbstractComponent[][] = []; + private _rows: Component[][] = []; private _rowWeights: number[] = []; - private _colWeights: number[] = []; + private _columnWeights: number[] = []; private _nRows = 0; private _nCols = 0; @@ -46,82 +46,94 @@ export module Component { * @param {Component[][]} [rows] A 2-D array of the Components to place in the table. * null can be used if a cell is empty. (default = []) */ - constructor(rows: AbstractComponent[][] = []) { + constructor(rows: Component[][] = []) { super(); this.classed("table", true); rows.forEach((row, rowIndex) => { row.forEach((component, colIndex) => { if (component != null) { - this.addComponent(rowIndex, colIndex, component); + this.add(component, rowIndex, colIndex); } }); }); } + protected _forEach(callback: (component: Component) => any) { + for (var r = 0; r < this._nRows; r++) { + for (var c = 0; c < this._nCols; c++) { + if (this._rows[r][c] != null) { + callback(this._rows[r][c]); + } + } + } + } + /** - * Adds a Component in the specified cell. - * - * If the cell is already occupied, there are 3 cases - * - Component + Component => Group containing both components - * - Component + Group => Component is added to the group - * - Group + Component => Component is added to the group + * Checks whether the specified Component is in the Table. + */ + public has(component: Component) { + for (var r = 0; r < this._nRows; r++) { + for (var c = 0; c < this._nCols; c++) { + if (this._rows[r][c] === component) { + return true; + } + } + } + return false; + } + + /** + * Adds a Component in the specified row and column position. * * For example, instead of calling `new Table([[a, b], [null, c]])`, you * could call * ```typescript * var table = new Table(); - * table.addComponent(0, 0, a); - * table.addComponent(0, 1, b); - * table.addComponent(1, 1, c); + * table.add(a, 0, 0); + * table.add(b, 0, 1); + * table.add(c, 1, 1); * ``` * + * @param {Component} component The Component to be added. * @param {number} row The row in which to add the Component. * @param {number} col The column in which to add the Component. - * @param {Component} component The Component to be added. * @returns {Table} The calling Table. */ - public addComponent(row: number, col: number, component: AbstractComponent): Table { - + public add(component: Component, row: number, col: number) { if (component == null) { throw Error("Cannot add null to a table cell"); } - var currentComponent = this._rows[row] && this._rows[row][col]; - - if (currentComponent) { - component = component.above(currentComponent); - } - - if (this._addComponent(component)) { + if (!this.has(component)) { + var currentComponent = this._rows[row] && this._rows[row][col]; + if (currentComponent != null) { + throw new Error("cell is occupied"); + } + component.detach(); this._nRows = Math.max(row + 1, this._nRows); this._nCols = Math.max(col + 1, this._nCols); this._padTableToSize(this._nRows, this._nCols); - this._rows[row][col] = component; + + this._adoptAndAnchor(component); + this.redraw(); } return this; } - public _removeComponent(component: AbstractComponent) { - super._removeComponent(component); - var rowpos: number; - var colpos: number; - outer : for (var i = 0; i < this._nRows; i++) { - for (var j = 0; j < this._nCols; j++) { - if (this._rows[i][j] === component) { - rowpos = i; - colpos = j; - break outer; + protected _remove(component: Component) { + for (var r = 0; r < this._nRows; r++) { + for (var c = 0; c < this._nCols; c++) { + if (this._rows[r][c] === component) { + this._rows[r][c] = null; + return true; } } } - - if (rowpos !== undefined) { - this._rows[rowpos][colpos] = null; - } + return false; } - private _iterateLayout(availableWidth : number, availableHeight: number): _IterateLayoutResult { + private _iterateLayout(availableWidth: number, availableHeight: number, isFinalOffer = false): _IterateLayoutResult { /* * Given availableWidth and availableHeight, figure out how to allocate it between rows and columns using an iterative algorithm. * @@ -145,11 +157,11 @@ export module Component { */ var rows = this._rows; var cols = d3.transpose(this._rows); - var availableWidthAfterPadding = availableWidth - this._colPadding * (this._nCols - 1); + var availableWidthAfterPadding = availableWidth - this._columnPadding * (this._nCols - 1); var availableHeightAfterPadding = availableHeight - this._rowPadding * (this._nRows - 1); - var rowWeights = Table._calcComponentWeights(this._rowWeights, rows, (c: AbstractComponent) => (c == null) || c._isFixedHeight()); - var colWeights = Table._calcComponentWeights(this._colWeights, cols, (c: AbstractComponent) => (c == null) || c._isFixedWidth()); + var rowWeights = Table._calcComponentWeights(this._rowWeights, rows, (c: Component) => (c == null) || c.fixedHeight()); + var colWeights = Table._calcComponentWeights(this._columnWeights, cols, (c: Component) => (c == null) || c.fixedWidth()); // To give the table a good starting position to iterate from, we give the fixed-width components half-weight // so that they will get some initial space allocated to work with @@ -159,17 +171,17 @@ export module Component { var colProportionalSpace = Table._calcProportionalSpace(heuristicColWeights, availableWidthAfterPadding ); var rowProportionalSpace = Table._calcProportionalSpace(heuristicRowWeights, availableHeightAfterPadding); - var guaranteedWidths = _Util.Methods.createFilledArray(0, this._nCols); - var guaranteedHeights = _Util.Methods.createFilledArray(0, this._nRows); + var guaranteedWidths = Utils.Methods.createFilledArray(0, this._nCols); + var guaranteedHeights = Utils.Methods.createFilledArray(0, this._nRows); - var freeWidth : number; + var freeWidth: number; var freeHeight: number; var nIterations = 0; while (true) { - var offeredHeights = _Util.Methods.addArrays(guaranteedHeights, rowProportionalSpace); - var offeredWidths = _Util.Methods.addArrays(guaranteedWidths, colProportionalSpace); - var guarantees = this._determineGuarantees(offeredWidths, offeredHeights); + var offeredHeights = Utils.Methods.addArrays(guaranteedHeights, rowProportionalSpace); + var offeredWidths = Utils.Methods.addArrays(guaranteedWidths, colProportionalSpace); + var guarantees = this._determineGuarantees(offeredWidths, offeredHeights, isFinalOffer); guaranteedWidths = guarantees.guaranteedWidths; guaranteedHeights = guarantees.guaranteedHeights; var wantsWidth = guarantees.wantsWidthArr .some((x: boolean) => x); @@ -182,7 +194,7 @@ export module Component { var xWeights: number[]; if (wantsWidth) { // If something wants width, divide free space between components that want more width xWeights = guarantees.wantsWidthArr.map((x) => x ? 0.1 : 0); - xWeights = _Util.Methods.addArrays(xWeights, colWeights); + xWeights = Utils.Methods.addArrays(xWeights, colWeights); } else { // Otherwise, divide free space according to the weights xWeights = colWeights; } @@ -190,7 +202,7 @@ export module Component { var yWeights: number[]; if (wantsHeight) { yWeights = guarantees.wantsHeightArr.map((x) => x ? 0.1 : 0); - yWeights = _Util.Methods.addArrays(yWeights, rowWeights); + yWeights = Utils.Methods.addArrays(yWeights, rowWeights); } else { yWeights = rowWeights; } @@ -217,88 +229,133 @@ export module Component { colProportionalSpace = Table._calcProportionalSpace(colWeights, freeWidth ); rowProportionalSpace = Table._calcProportionalSpace(rowWeights, freeHeight); - return {colProportionalSpace: colProportionalSpace , - rowProportionalSpace: rowProportionalSpace , - guaranteedWidths : guarantees.guaranteedWidths , - guaranteedHeights : guarantees.guaranteedHeights, - wantsWidth : wantsWidth , - wantsHeight : wantsHeight }; + return {colProportionalSpace: colProportionalSpace, + rowProportionalSpace: rowProportionalSpace, + guaranteedWidths: guarantees.guaranteedWidths, + guaranteedHeights: guarantees.guaranteedHeights, + wantsWidth: wantsWidth, + wantsHeight: wantsHeight}; } - private _determineGuarantees(offeredWidths: number[], offeredHeights: number[]): _LayoutAllocation { - var requestedWidths = _Util.Methods.createFilledArray(0, this._nCols); - var requestedHeights = _Util.Methods.createFilledArray(0, this._nRows); - var layoutWantsWidth = _Util.Methods.createFilledArray(false, this._nCols); - var layoutWantsHeight = _Util.Methods.createFilledArray(false, this._nRows); - this._rows.forEach((row: AbstractComponent[], rowIndex: number) => { - row.forEach((component: AbstractComponent, colIndex: number) => { - var spaceRequest: _SpaceRequest; + private _determineGuarantees(offeredWidths: number[], offeredHeights: number[], isFinalOffer = false): _LayoutAllocation { + var requestedWidths = Utils.Methods.createFilledArray(0, this._nCols); + var requestedHeights = Utils.Methods.createFilledArray(0, this._nRows); + var columnNeedsWidth = Utils.Methods.createFilledArray(false, this._nCols); + var rowNeedsHeight = Utils.Methods.createFilledArray(false, this._nRows); + + this._rows.forEach((row: Component[], rowIndex: number) => { + row.forEach((component: Component, colIndex: number) => { + var spaceRequest: SpaceRequest; if (component != null) { - spaceRequest = component._requestedSpace(offeredWidths[colIndex], offeredHeights[rowIndex]); + spaceRequest = component.requestedSpace(offeredWidths[colIndex], offeredHeights[rowIndex]); } else { - spaceRequest = {width: 0, height: 0, wantsWidth: false, wantsHeight: false}; + spaceRequest = { + minWidth: 0, + minHeight: 0 + }; } - var allocatedWidth = Math.min(spaceRequest.width, offeredWidths[colIndex]); - var allocatedHeight = Math.min(spaceRequest.height, offeredHeights[rowIndex]); + var columnWidth = isFinalOffer ? Math.min(spaceRequest.minWidth, offeredWidths[colIndex]) : spaceRequest.minWidth; + requestedWidths[colIndex] = Math.max(requestedWidths[colIndex], columnWidth); + + var rowHeight = isFinalOffer ? Math.min(spaceRequest.minHeight, offeredHeights[rowIndex]) : spaceRequest.minHeight; + requestedHeights[rowIndex] = Math.max(requestedHeights[rowIndex], rowHeight); - requestedWidths [colIndex] = Math.max(requestedWidths [colIndex], allocatedWidth ); - requestedHeights[rowIndex] = Math.max(requestedHeights[rowIndex], allocatedHeight); - layoutWantsWidth [colIndex] = layoutWantsWidth [colIndex] || spaceRequest.wantsWidth; - layoutWantsHeight[rowIndex] = layoutWantsHeight[rowIndex] || spaceRequest.wantsHeight; + var componentNeedsWidth = spaceRequest.minWidth > offeredWidths[colIndex]; + columnNeedsWidth[colIndex] = columnNeedsWidth[colIndex] || componentNeedsWidth; + + var componentNeedsHeight = spaceRequest.minHeight > offeredHeights[rowIndex]; + rowNeedsHeight[rowIndex] = rowNeedsHeight[rowIndex] || componentNeedsHeight; }); }); - return {guaranteedWidths : requestedWidths , - guaranteedHeights: requestedHeights , - wantsWidthArr : layoutWantsWidth , - wantsHeightArr : layoutWantsHeight}; - } - - public _requestedSpace(offeredWidth : number, offeredHeight: number): _SpaceRequest { - this._calculatedLayout = this._iterateLayout(offeredWidth , offeredHeight); - return {width : d3.sum(this._calculatedLayout.guaranteedWidths ), - height: d3.sum(this._calculatedLayout.guaranteedHeights), - wantsWidth: this._calculatedLayout.wantsWidth, - wantsHeight: this._calculatedLayout.wantsHeight}; + return { + guaranteedWidths: requestedWidths, + guaranteedHeights: requestedHeights, + wantsWidthArr: columnNeedsWidth, + wantsHeightArr: rowNeedsHeight + }; } - public _computeLayout(offeredXOrigin?: number, offeredYOrigin?: number, availableWidth?: number, availableHeight?: number) { - super._computeLayout(offeredXOrigin, offeredYOrigin, availableWidth , availableHeight); - var layout = this._useLastCalculatedLayout() ? this._calculatedLayout : this._iterateLayout(this.width(), this.height()); + public requestedSpace(offeredWidth: number, offeredHeight: number): SpaceRequest { + this._calculatedLayout = this._iterateLayout(offeredWidth, offeredHeight); + return { + minWidth: d3.sum(this._calculatedLayout.guaranteedWidths), + minHeight: d3.sum(this._calculatedLayout.guaranteedHeights) + }; + } - this._useLastCalculatedLayout(true); + public computeLayout(origin?: Point, availableWidth?: number, availableHeight?: number) { + super.computeLayout(origin, availableWidth, availableHeight); + var lastLayoutWidth = d3.sum(this._calculatedLayout.guaranteedWidths); + var lastLayoutHeight = d3.sum(this._calculatedLayout.guaranteedHeights); + var layout = this._calculatedLayout; + if (lastLayoutWidth > this.width() || lastLayoutHeight > this.height()) { + layout = this._iterateLayout(this.width(), this.height(), true); + } var childYOrigin = 0; - var rowHeights = _Util.Methods.addArrays(layout.rowProportionalSpace, layout.guaranteedHeights); - var colWidths = _Util.Methods.addArrays(layout.colProportionalSpace, layout.guaranteedWidths ); - this._rows.forEach((row: AbstractComponent[], rowIndex: number) => { + var rowHeights = Utils.Methods.addArrays(layout.rowProportionalSpace, layout.guaranteedHeights); + var colWidths = Utils.Methods.addArrays(layout.colProportionalSpace, layout.guaranteedWidths ); + this._rows.forEach((row: Component[], rowIndex: number) => { var childXOrigin = 0; - row.forEach((component: AbstractComponent, colIndex: number) => { + row.forEach((component: Component, colIndex: number) => { // recursively compute layout if (component != null) { - component._computeLayout(childXOrigin, childYOrigin, colWidths[colIndex], rowHeights[rowIndex]); + component.computeLayout({ x: childXOrigin, y: childYOrigin }, colWidths[colIndex], rowHeights[rowIndex]); } - childXOrigin += colWidths[colIndex] + this._colPadding; + childXOrigin += colWidths[colIndex] + this._columnPadding; }); childYOrigin += rowHeights[rowIndex] + this._rowPadding; }); + return this; } /** - * Sets the row and column padding on the Table. + * Gets the row padding on the Table. + * + * @returns {number} the row padding. + */ + public rowPadding(): number; + /** + * Sets the row padding on the Table. * * @param {number} rowPadding The padding above and below each row, in pixels. - * @param {number} colPadding the padding to the left and right of each column, in pixels. * @returns {Table} The calling Table. */ - public padding(rowPadding: number, colPadding: number) { + public rowPadding(rowPadding: number): Table; + public rowPadding(rowPadding?: number): any { + if (rowPadding == null) { + return this._rowPadding; + } this._rowPadding = rowPadding; - this._colPadding = colPadding; - this._invalidateLayout(); + this.redraw(); return this; } + /** + * Gets the column padding on the Table. + * + * @returns {number} the column padding. + */ + public columnPadding(): number; + /** + * Sets the column padding on the Table. + * + * @param {number} columnPadding the padding to the left and right of each column, in pixels. + * @returns {Table} The calling Table. + */ + public columnPadding(columnPadding: number): Table; + public columnPadding(columnPadding?: number): any { + if (columnPadding == null) { + return this._columnPadding; + } + this._columnPadding = columnPadding; + this.redraw(); + return this; + } + + public rowWeight(index: number): number; /** * Sets the layout weight of a particular row. * Space is allocated to rows based on their weight. Rows with higher weights receive proportionally more space. @@ -324,12 +381,17 @@ export module Component { * @param {number} weight The weight to be set on the row. * @returns {Table} The calling Table. */ - public rowWeight(index: number, weight: number) { + public rowWeight(index: number, weight: number): Table; + public rowWeight(index: number, weight?: number): any { + if (weight == null) { + return this._rowWeights[index]; + } this._rowWeights[index] = weight; - this._invalidateLayout(); + this.redraw(); return this; } + public columnWeight(index: number): number; /** * Sets the layout weight of a particular column. * Space is allocated to columns based on their weight. Columns with higher weights receive proportionally more space. @@ -340,19 +402,23 @@ export module Component { * @param {number} weight The weight to be set on the column. * @returns {Table} The calling Table. */ - public colWeight(index: number, weight: number) { - this._colWeights[index] = weight; - this._invalidateLayout(); + public columnWeight(index: number, weight: number): Table; + public columnWeight(index: number, weight?: number): any { + if (weight == null) { + return this._columnWeights[index]; + } + this._columnWeights[index] = weight; + this.redraw(); return this; } - public _isFixedWidth(): boolean { + public fixedWidth(): boolean { var cols = d3.transpose(this._rows); - return Table._fixedSpace(cols, (c: AbstractComponent) => (c == null) || c._isFixedWidth()); + return Table._fixedSpace(cols, (c: Component) => (c == null) || c.fixedWidth()); } - public _isFixedHeight(): boolean { - return Table._fixedSpace(this._rows, (c: AbstractComponent) => (c == null) || c._isFixedHeight()); + public fixedHeight(): boolean { + return Table._fixedSpace(this._rows, (c: Component) => (c == null) || c.fixedHeight()); } private _padTableToSize(nRows: number, nCols: number) { @@ -368,15 +434,15 @@ export module Component { } } for (j = 0; j < nCols; j++) { - if (this._colWeights[j] === undefined) { - this._colWeights[j] = null; + if (this._columnWeights[j] === undefined) { + this._columnWeights[j] = null; } } } private static _calcComponentWeights(setWeights: number[], - componentGroups: AbstractComponent[][], - fixityAccessor: (c: AbstractComponent) => boolean) { + componentGroups: Component[][], + fixityAccessor: (c: Component) => boolean) { // If the row/col weight was explicitly set, then return it outright // If the weight was not explicitly set, then guess it using the heuristic that if all components are fixed-space // then weight is 0, otherwise weight is 1 @@ -393,15 +459,15 @@ export module Component { private static _calcProportionalSpace(weights: number[], freeSpace: number): number[] { var weightSum = d3.sum(weights); if (weightSum === 0) { - return _Util.Methods.createFilledArray(0, weights.length); + return Utils.Methods.createFilledArray(0, weights.length); } else { return weights.map((w) => freeSpace * w / weightSum); } } - private static _fixedSpace(componentGroup: AbstractComponent[][], fixityAccessor: (c: AbstractComponent) => boolean) { + private static _fixedSpace(componentGroup: Component[][], fixityAccessor: (c: Component) => boolean) { var all = (bools: boolean[]) => bools.reduce((a, b) => a && b, true); - var group_isFixed = (components: AbstractComponent[]) => all(components.map(fixityAccessor)); + var group_isFixed = (components: Component[]) => all(components.map(fixityAccessor)); return all(componentGroup.map(group_isFixed)); } } diff --git a/src/components/xDragBoxLayer.ts b/src/components/xDragBoxLayer.ts index 050aabcd12..35d9eb82c9 100644 --- a/src/components/xDragBoxLayer.ts +++ b/src/components/xDragBoxLayer.ts @@ -1,7 +1,7 @@ /// module Plottable { -export module Component { +export module Components { export class XDragBoxLayer extends DragBoxLayer { constructor() { super(); @@ -9,10 +9,10 @@ export module Component { this._hasCorners = false; } - public _computeLayout(offeredXOrigin?: number, offeredYOrigin?: number, - availableWidth?: number, availableHeight?: number) { - super._computeLayout(offeredXOrigin, offeredYOrigin, availableWidth, availableHeight); + public computeLayout(origin?: Point, availableWidth?: number, availableHeight?: number) { + super.computeLayout(origin, availableWidth, availableHeight); this.bounds(this.bounds()); // set correct bounds when width/height changes + return this; } protected _setBounds(newBounds: Bounds) { diff --git a/src/components/yDragBoxLayer.ts b/src/components/yDragBoxLayer.ts index 5989a09d68..2004084269 100644 --- a/src/components/yDragBoxLayer.ts +++ b/src/components/yDragBoxLayer.ts @@ -1,7 +1,7 @@ /// module Plottable { -export module Component { +export module Components { export class YDragBoxLayer extends DragBoxLayer { constructor() { super(); @@ -9,10 +9,10 @@ export module Component { this._hasCorners = false; } - public _computeLayout(offeredXOrigin?: number, offeredYOrigin?: number, - availableWidth?: number, availableHeight?: number) { - super._computeLayout(offeredXOrigin, offeredYOrigin, availableWidth, availableHeight); + public computeLayout(origin?: Point, availableWidth?: number, availableHeight?: number) { + super.computeLayout(origin, availableWidth, availableHeight); this.bounds(this.bounds()); // set correct bounds when width/height changes + return this; } protected _setBounds(newBounds: Bounds) { diff --git a/src/core/animator.ts b/src/core/animator.ts index 259744cd50..3c98df5a14 100644 --- a/src/core/animator.ts +++ b/src/core/animator.ts @@ -1,7 +1,7 @@ /// module Plottable { -export module Animator { +export module Animators { export interface PlotAnimator { /** @@ -24,7 +24,7 @@ export module Animator { getTiming(numberOfIterations: number): number; } - export type PlotAnimatorMap = { [animatorKey: string] : PlotAnimator; }; + export type PlotAnimatorMap = { [animatorKey: string]: PlotAnimator; }; } } diff --git a/src/core/broadcaster.ts b/src/core/broadcaster.ts deleted file mode 100644 index 7926653581..0000000000 --- a/src/core/broadcaster.ts +++ /dev/null @@ -1,99 +0,0 @@ -/// - -module Plottable { -export module Core { - - /** - * A callback for a Broadcaster. The callback will be called with the Broadcaster's - * "listenable" as the first argument, with subsequent optional arguments depending - * on the listenable. - */ - // HACKHACK: An interface because the "type" keyword doesn't work with generics. - export interface BroadcasterCallback { - (listenable: L, ...args: any[]): any; - } - - /** - * The Broadcaster holds a reference to a "listenable" object. - * Third parties can register and deregister listeners from the Broadcaster. - * When the broadcaster.broadcast() method is called, all registered callbacks - * are called with the Broadcaster's "listenable", along with optional - * arguments passed to the `broadcast` method. - * - * The listeners are called synchronously. - */ - export class Broadcaster extends Core.PlottableObject { - private _key2callback = new _Util.StrictEqualityAssociativeArray(); - private _listenable: L; - - /** - * Constructs a broadcaster, taking a "listenable" object to broadcast about. - * - * @constructor - * @param {L} listenable The listenable object to broadcast. - */ - constructor(listenable: L) { - super(); - this._listenable = listenable; - } - - /** - * Registers a callback to be called when the broadcast method is called. Also takes a key which - * is used to support deregistering the same callback later, by passing in the same key. - * If there is already a callback associated with that key, then the callback will be replaced. - * The callback will be passed the Broadcaster's "listenable" as the `this` context. - * - * @param key The key associated with the callback. Key uniqueness is determined by deep equality. - * @param {BroadcasterCallback} callback A callback to be called. - * @returns {Broadcaster} The calling Broadcaster - */ - public registerListener(key: any, callback: BroadcasterCallback) { - this._key2callback.set(key, callback); - return this; - } - - /** - * Call all listening callbacks, optionally with arguments passed through. - * - * @param ...args A variable number of optional arguments - * @returns {Broadcaster} The calling Broadcaster - */ - public broadcast(...args: any[]) { - args.unshift(this._listenable); - this._key2callback.values().forEach((callback) => { - callback.apply(this._listenable, args); - }); - return this; - } - - /** - * Deregisters the callback associated with a key. - * - * @param key The key to deregister. - * @returns {Broadcaster} The calling Broadcaster - */ - public deregisterListener(key: any) { - this._key2callback.delete(key); - return this; - } - - /** - * Gets the keys for all listeners attached to the Broadcaster. - * - * @returns {any[]} An array of the keys. - */ - public getListenerKeys() { - return this._key2callback.keys(); - } - - /** - * Deregisters all listeners and callbacks associated with the Broadcaster. - * - * @returns {Broadcaster} The calling Broadcaster - */ - public deregisterAllListeners() { - this._key2callback = new _Util.StrictEqualityAssociativeArray(); - } - } -} -} diff --git a/src/core/colors.ts b/src/core/colors.ts index 76ce3265a2..d1df5d12c0 100644 --- a/src/core/colors.ts +++ b/src/core/colors.ts @@ -17,7 +17,6 @@ export module Core { public static BRIGHT_SUN = "#fad419"; public static JACARTA = "#2c2b6f"; - public static PLOTTABLE_COLORS = [ Colors.INDIGO, Colors.CORAL_RED, diff --git a/src/core/config.ts b/src/core/config.ts index 286967b785..761933ba86 100644 --- a/src/core/config.ts +++ b/src/core/config.ts @@ -1,8 +1,8 @@ /// module Plottable { - export module Config { - /** + export module Configs { + /** * Specifies if Plottable should show warnings. */ export var SHOW_WARNINGS = true; diff --git a/src/core/dataset.ts b/src/core/dataset.ts index 1e843cedb9..99011cc415 100644 --- a/src/core/dataset.ts +++ b/src/core/dataset.ts @@ -1,15 +1,14 @@ /// module Plottable { - type CachedExtent = { - accessor: _Accessor; - extent: any[]; - } - export class Dataset extends Core.PlottableObject { + + export type DatasetCallback = (dataset: Dataset) => any; + + export class Dataset { private _data: any[]; private _metadata: any; - private _accessor2cachedExtent: _Util.StrictEqualityAssociativeArray; - public broadcaster: Core.Broadcaster; + private _accessor2cachedExtent: Utils.Map; + private _callbacks: Utils.CallbackSet; /** * Constructs a new set. @@ -22,11 +21,18 @@ module Plottable { * @param {any} metadata An object containing additional information (default = {}). */ constructor(data: any[] = [], metadata: any = {}) { - super(); this._data = data; this._metadata = metadata; - this._accessor2cachedExtent = new _Util.StrictEqualityAssociativeArray(); - this.broadcaster = new Core.Broadcaster(this); + this._accessor2cachedExtent = new Utils.Map(); + this._callbacks = new Utils.CallbackSet(); + } + + public onUpdate(callback: DatasetCallback) { + this._callbacks.add(callback); + } + + public offUpdate(callback: DatasetCallback) { + this._callbacks.delete(callback); } /** @@ -47,8 +53,8 @@ module Plottable { return this._data; } else { this._data = data; - this._accessor2cachedExtent = new _Util.StrictEqualityAssociativeArray(); - this.broadcaster.broadcast(); + this._accessor2cachedExtent = new Utils.Map(); + this._callbacks.callCallbacks(this); return this; } } @@ -72,36 +78,10 @@ module Plottable { return this._metadata; } else { this._metadata = metadata; - this._accessor2cachedExtent = new _Util.StrictEqualityAssociativeArray(); - this.broadcaster.broadcast(); + this._accessor2cachedExtent = new Utils.Map(); + this._callbacks.callCallbacks(this); return this; } } - - public _getExtent(accessor: _Accessor, typeCoercer: (d: any) => any, plotMetadata: any = {}): any[] { - var cachedExtent = this._accessor2cachedExtent.get(accessor); - if (cachedExtent === undefined) { - cachedExtent = this._computeExtent(accessor, typeCoercer, plotMetadata); - this._accessor2cachedExtent.set(accessor, cachedExtent); - } - return cachedExtent; - } - - private _computeExtent(accessor: _Accessor, typeCoercer: (d: any) => any, plotMetadata: any): any[] { - var appliedAccessor = (d: any, i: number) => accessor(d, i, this._metadata, plotMetadata); - var mappedData = this._data.map(appliedAccessor).map(typeCoercer); - if (mappedData.length === 0){ - return []; - } else if (typeof(mappedData[0]) === "string") { - return _Util.Methods.uniq(mappedData); - } else { - var extent = d3.extent(mappedData); - if (extent[0] == null || extent[1] == null) { - return []; - } else { - return extent; - } - } - } } } diff --git a/src/core/domainer.ts b/src/core/domainer.ts index 000c20b539..bea8c4f3dc 100644 --- a/src/core/domainer.ts +++ b/src/core/domainer.ts @@ -6,11 +6,8 @@ module Plottable { private _doNice = false; private _niceCount: number; private _padProportion = 0.0; - private _paddingExceptions: D3.Map = d3.map(); - private _unregisteredPaddingExceptions: D3.Set = d3.set(); - private _includedValues: D3.Map = d3.map(); - // _includedValues needs to be a map, even unregistered, to support getting un-stringified values back out - private _unregisteredIncludedValues: D3.Map = d3.map(); + private _paddingExceptions: Utils.Map; + private _includedValues: Utils.Map; private _combineExtents: (extents: any[][]) => any[]; private static _PADDING_FOR_IDENTICAL_DOMAIN = 1; private static _ONE_DAY = 1000 * 60 * 60 * 24; @@ -31,6 +28,8 @@ module Plottable { */ constructor(combineExtents?: (extents: any[][]) => any[]) { this._combineExtents = combineExtents; + this._paddingExceptions = new Utils.Map(); + this._includedValues = new Utils.Map(); } /** @@ -42,14 +41,14 @@ module Plottable { * @returns {any[]} The domain, as a merging of all exents, as a [min, max] * pair. */ - public computeDomain(extents: any[][], scale: Scale.AbstractQuantitative): any[] { + public computeDomain(extents: any[][], scale: QuantitativeScale): any[] { var domain: any[]; if (this._combineExtents != null) { domain = this._combineExtents(extents); } else if (extents.length === 0) { domain = scale._defaultExtent(); } else { - domain = [_Util.Methods.min(extents, (e) => e[0], 0), _Util.Methods.max(extents, (e) => e[1], 0)]; + domain = [Utils.Methods.min(extents, (e) => e[0], 0), Utils.Methods.max(extents, (e) => e[1], 0)]; } domain = this._includeDomain(domain); domain = this._padDomain(scale, domain); @@ -80,38 +79,25 @@ module Plottable { * Adds a padding exception, a value that will not be padded at either end of the domain. * * Eg, if a padding exception is added at x=0, then [0, 100] will pad to [0, 105] instead of [-2.5, 102.5]. - * If a key is provided, it will be registered under that key with standard map semantics. (Overwrite / remove by key) - * If a key is not provided, it will be added with set semantics (Can be removed by value) + * The exception will be registered under the provided with standard map semantics. (Overwrite / remove by key). * * @param {any} exception The padding exception to add. - * @param {string} key The key to register the exception under. + * @param {any} key The key to register the exception under. * @returns {Domainer} The calling domainer */ - public addPaddingException(exception: any, key?: string): Domainer { - if (key != null) { - this._paddingExceptions.set(key, exception); - } else { - this._unregisteredPaddingExceptions.add(exception); - } + public addPaddingException(key: any, exception: any) { + this._paddingExceptions.set(key, exception); return this; } - /** * Removes a padding exception, allowing the domain to pad out that value again. * - * If a string is provided, it is assumed to be a key and the exception associated with that key is removed. - * If a non-string is provdied, it is assumed to be an unkeyed exception and that exception is removed. - * - * @param {any} keyOrException The key for the value to remove, or the value to remove + * @param {any} key The key for the value to remove. * @return {Domainer} The calling domainer */ - public removePaddingException(keyOrException: any): Domainer { - if (typeof(keyOrException) === "string") { - this._paddingExceptions.remove(keyOrException); - } else { - this._unregisteredPaddingExceptions.remove(keyOrException); - } + public removePaddingException(key: any) { + this._paddingExceptions.delete(key); return this; } @@ -119,37 +105,25 @@ module Plottable { * Adds an included value, a value that must be included inside the domain. * * Eg, if a value exception is added at x=0, then [50, 100] will expand to [0, 100] rather than [50, 100]. - * If a key is provided, it will be registered under that key with standard map semantics. (Overwrite / remove by key) - * If a key is not provided, it will be added with set semantics (Can be removed by value) + * The value will be registered under that key with standard map semantics. (Overwrite / remove by key). * * @param {any} value The included value to add. - * @param {string} key The key to register the value under. + * @param {any} key The key to register the value under. * @returns {Domainer} The calling domainer */ - public addIncludedValue(value: any, key?: string): Domainer { - if (key != null) { - this._includedValues.set(key, value); - } else { - this._unregisteredIncludedValues.set(value, value); - } + public addIncludedValue(key: any, value: any) { + this._includedValues.set(key, value); return this; } /** * Remove an included value, allowing the domain to not include that value gain again. * - * If a string is provided, it is assumed to be a key and the value associated with that key is removed. - * If a non-string is provdied, it is assumed to be an unkeyed value and that value is removed. - * - * @param {any} keyOrException The key for the value to remove, or the value to remove + * @param {any} key The key for the value to remove. * @return {Domainer} The calling domainer */ - public removeIncludedValue(valueOrKey: any) { - if (typeof(valueOrKey) === "string") { - this._includedValues.remove(valueOrKey); - } else { - this._unregisteredIncludedValues.remove(valueOrKey); - } + public removeIncludedValue(key: any) { + this._includedValues.delete(key); return this; } @@ -165,7 +139,7 @@ module Plottable { return this; } - private _padDomain(scale: Scale.AbstractQuantitative, domain: any[]): any[] { + private _padDomain(scale: QuantitativeScale, domain: any[]): any[] { var min = domain[0]; var max = domain[1]; // valueOf accounts for dates properly @@ -190,7 +164,7 @@ module Plottable { (scale.scale(max) - scale.scale(min)) * p); var newMax = scale.invert(scale.scale(max) + (scale.scale(max) - scale.scale(min)) * p); - var exceptionValues = this._paddingExceptions.values().concat(this._unregisteredPaddingExceptions.values()); + var exceptionValues = this._paddingExceptions.values(); var exceptionSet = d3.set(exceptionValues); if (exceptionSet.has(min)) { newMin = min; @@ -201,7 +175,7 @@ module Plottable { return [newMin, newMax]; } - private _niceDomain(scale: Scale.AbstractQuantitative, domain: any[]): any[] { + private _niceDomain(scale: QuantitativeScale, domain: any[]): any[] { if (this._doNice) { return scale._niceDomain(domain, this._niceCount); } else { @@ -210,7 +184,7 @@ module Plottable { } private _includeDomain(domain: any[]): any[] { - var includedValues = this._includedValues.values().concat(this._unregisteredIncludedValues.values()); + var includedValues = this._includedValues.values(); return includedValues.reduce( (domain, value) => [Math.min(domain[0], value), Math.max(domain[1], value)], domain diff --git a/src/core/interfaces.ts b/src/core/interfaces.ts index 9a770a3b2d..bf31e77222 100644 --- a/src/core/interfaces.ts +++ b/src/core/interfaces.ts @@ -2,15 +2,17 @@ module Plottable { /** * Access specific datum property. */ - export type _Accessor = (datum: any, index?: number, userMetadata?: any, plotMetadata?: Plot.PlotMetadata) => any; + export interface Accessor { + (datum: any, index: number, dataset: Dataset, plotMetadata: Plots.PlotMetadata): T; + } /** * Retrieves scaled datum property. */ - export type _Projector = (datum: any, index: number, userMetadata: any, plotMetadata: Plot.PlotMetadata) => any; + export type _Projector = (datum: any, index: number, dataset: Dataset, plotMetadata: Plots.PlotMetadata) => any; /** - * Projector with applied user and plot metadata + * Projector with dataset and plot metadata */ export type AppliedProjector = (datum: any, index: number) => any; @@ -18,8 +20,8 @@ module Plottable { * Defines a way how specific attribute needs be retrieved before rendering. */ export type _Projection = { - accessor: _Accessor; - scale?: Scale.AbstractScale; + accessor: Accessor; + scale?: Scale; attribute: string; } @@ -35,21 +37,9 @@ module Plottable { export type AttributeToAppliedProjector = { [attrToSet: string]: AppliedProjector; }; - /** - * A simple bounding box. - */ - export type SelectionArea = { - xMin: number; - xMax: number; - yMin: number; - yMax: number; - } - - export type _SpaceRequest = { - width: number; - height: number; - wantsWidth: boolean; - wantsHeight: boolean; + export type SpaceRequest = { + minWidth: number; + minHeight: number; } /** diff --git a/src/core/plottableObject.ts b/src/core/plottableObject.ts deleted file mode 100644 index 8b60e68f9a..0000000000 --- a/src/core/plottableObject.ts +++ /dev/null @@ -1,18 +0,0 @@ -/// - -module Plottable { -export module Core { - /** - * A class most other Plottable classes inherit from, in order to have a - * unique ID. - */ - export class PlottableObject { - private static _nextID = 0; - private _plottableID = PlottableObject._nextID++; - - public getID() { - return this._plottableID; - } - } -} -} diff --git a/src/core/renderController.ts b/src/core/renderController.ts index 811e6754aa..d1e27a6bd6 100644 --- a/src/core/renderController.ts +++ b/src/core/renderController.ts @@ -1,8 +1,6 @@ /// module Plottable { -export module Core { - /** * The RenderController is responsible for enqueueing and synchronizing * layout and render calls for Plottable components. @@ -16,49 +14,49 @@ export module Core { * If you want to always have immediate rendering (useful for debugging), * call * ```typescript - * Plottable.Core.RenderController.setRenderPolicy( - * new Plottable.Core.RenderController.RenderPolicy.Immediate() + * Plottable.RenderController.setRenderPolicy( + * new Plottable.RenderPolicies.Immediate() * ); * ``` */ export module RenderController { - var _componentsNeedingRender: {[key: string]: Component.AbstractComponent} = {}; - var _componentsNeedingComputeLayout: {[key: string]: Component.AbstractComponent} = {}; - var _animationRequested: boolean = false; - var _isCurrentlyFlushing: boolean = false; - export var _renderPolicy: RenderPolicy.RenderPolicy = new RenderPolicy.AnimationFrame(); + var _componentsNeedingRender = new Utils.Set(); + var _componentsNeedingComputeLayout = new Utils.Set(); + var _animationRequested = false; + var _isCurrentlyFlushing = false; + export var _renderPolicy: RenderPolicies.RenderPolicy = new RenderPolicies.AnimationFrame(); - export function setRenderPolicy(policy: string | RenderPolicy.RenderPolicy): void { + export function setRenderPolicy(policy: string | RenderPolicies.RenderPolicy): void { if (typeof(policy) === "string") { switch (( policy).toLowerCase()) { case "immediate": - policy = new RenderPolicy.Immediate(); + policy = new RenderPolicies.Immediate(); break; case "animationframe": - policy = new RenderPolicy.AnimationFrame(); + policy = new RenderPolicies.AnimationFrame(); break; case "timeout": - policy = new RenderPolicy.Timeout(); + policy = new RenderPolicies.Timeout(); break; default: - _Util.Methods.warn("Unrecognized renderPolicy: " + policy); + Utils.Methods.warn("Unrecognized renderPolicy: " + policy); return; } } - _renderPolicy = policy; + _renderPolicy = policy; } /** * If the RenderController is enabled, we enqueue the component for * render. Otherwise, it is rendered immediately. * - * @param {AbstractComponent} component Any Plottable component. + * @param {Component} component Any Plottable component. */ - export function registerToRender(c: Component.AbstractComponent) { + export function registerToRender(component: Component) { if (_isCurrentlyFlushing) { - _Util.Methods.warn("Registered to render while other components are flushing: request may be ignored"); + Utils.Methods.warn("Registered to render while other components are flushing: request may be ignored"); } - _componentsNeedingRender[c.getID()] = c; + _componentsNeedingRender.add(component); requestRender(); } @@ -66,11 +64,11 @@ export module Core { * If the RenderController is enabled, we enqueue the component for * layout and render. Otherwise, it is rendered immediately. * - * @param {AbstractComponent} component Any Plottable component. + * @param {Component} component Any Plottable component. */ - export function registerToComputeLayout(c: Component.AbstractComponent) { - _componentsNeedingComputeLayout[c.getID()] = c; - _componentsNeedingRender[c.getID()] = c; + export function registerToComputeLayout(component: Component) { + _componentsNeedingComputeLayout.add(component); + _componentsNeedingRender.add(component); requestRender(); } @@ -91,40 +89,27 @@ export module Core { export function flush() { if (_animationRequested) { // Layout - var toCompute = d3.values(_componentsNeedingComputeLayout); - toCompute.forEach((c) => c._computeLayout()); + _componentsNeedingComputeLayout.values().forEach((component: Component) => component.computeLayout()); - // Top level render. - // Containers will put their children in the toRender queue - var toRender = d3.values(_componentsNeedingRender); - toRender.forEach((c) => c._render()); + // Top level render; Containers will put their children in the toRender queue + _componentsNeedingRender.values().forEach((component: Component) => component.render()); - // now we are flushing _isCurrentlyFlushing = true; - - // Finally, perform render of all components - var failed: {[key: string]: Component.AbstractComponent} = {}; - Object.keys(_componentsNeedingRender).forEach((k) => { + var failed = new Utils.Set(); + _componentsNeedingRender.values().forEach((component: Component) => { try { - _componentsNeedingRender[k]._doRender(); + component.renderImmediately(); } catch (err) { - // using setTimeout instead of console.log, we get the familiar red - // stack trace - setTimeout(() => { - throw err; - }, 0); - failed[k] = _componentsNeedingRender[k]; + // throw error with timeout to avoid interrupting further renders + window.setTimeout(() => { throw err; }, 0); + failed.add(component); } }); - - // Reset queues - _componentsNeedingComputeLayout = {}; + _componentsNeedingComputeLayout = new Utils.Set(); _componentsNeedingRender = failed; _animationRequested = false; _isCurrentlyFlushing = false; } } } - -} } diff --git a/src/core/renderPolicy.ts b/src/core/renderPolicy.ts index 42b756da0d..ec90a7f6c6 100644 --- a/src/core/renderPolicy.ts +++ b/src/core/renderPolicy.ts @@ -1,10 +1,7 @@ /// module Plottable { -export module Core { -export module RenderController { - - export module RenderPolicy { + export module RenderPolicies { /** * A policy to render components. */ @@ -28,7 +25,7 @@ export module RenderController { */ export class AnimationFrame implements RenderPolicy { public render() { - _Util.DOM.requestAnimationFramePolyfill(RenderController.flush); + Utils.DOM.requestAnimationFramePolyfill(RenderController.flush); } } @@ -38,14 +35,11 @@ export module RenderController { * it. */ export class Timeout implements RenderPolicy { - public _timeoutMsec: number = _Util.DOM.POLYFILL_TIMEOUT_MSEC; + public _timeoutMsec: number = Utils.DOM.POLYFILL_TIMEOUT_MSEC; public render() { setTimeout(RenderController.flush, this._timeoutMsec); } } } - -} -} } diff --git a/src/dispatchers/abstractDispatcher.ts b/src/dispatchers/abstractDispatcher.ts deleted file mode 100644 index ab82a258c3..0000000000 --- a/src/dispatchers/abstractDispatcher.ts +++ /dev/null @@ -1,52 +0,0 @@ -/// - -module Plottable { -export module Dispatcher { - export class AbstractDispatcher extends Core.PlottableObject { - protected _event2Callback: { [eventName: string]: (e: Event) => any; } = {}; - protected _broadcasters: Core.Broadcaster[] = []; - private _connected = false; - - private _hasNoListeners() { - return this._broadcasters.every((b) => b.getListenerKeys().length === 0); - } - - private _connect() { - if (!this._connected) { - Object.keys(this._event2Callback).forEach((event: string) => { - var callback = this._event2Callback[event]; - document.addEventListener(event, callback); - }); - this._connected = true; - } - } - - private _disconnect() { - if (this._connected && this._hasNoListeners()) { - Object.keys(this._event2Callback).forEach((event: string) => { - var callback = this._event2Callback[event]; - document.removeEventListener(event, callback); - }); - this._connected = false; - } - } - - /** - * Creates a wrapped version of the callback that can be registered to a Broadcaster - */ - protected _getWrappedCallback(callback: Function): Core.BroadcasterCallback { - return () => callback(); - } - - protected _setCallback(b: Core.Broadcaster, key: any, callback: Function) { - if (callback === null) { // remove listener if callback is null - b.deregisterListener(key); - this._disconnect(); - } else { - this._connect(); - b.registerListener(key, this._getWrappedCallback(callback)); - } - } - } -} -} diff --git a/src/dispatchers/dispatcher.ts b/src/dispatchers/dispatcher.ts new file mode 100644 index 0000000000..ce98246b91 --- /dev/null +++ b/src/dispatchers/dispatcher.ts @@ -0,0 +1,44 @@ +/// + +module Plottable { + export class Dispatcher { + protected _event2Callback: { [eventName: string]: (e: Event) => any; } = {}; + protected _callbacks: Utils.CallbackSet[] = []; + private _connected = false; + + private _hasNoListeners() { + return this._callbacks.every((cbs) => cbs.values().length === 0); + } + + private _connect() { + if (this._connected) { + return; + } + Object.keys(this._event2Callback).forEach((event: string) => { + var callback = this._event2Callback[event]; + document.addEventListener(event, callback); + }); + this._connected = true; + } + + private _disconnect() { + if (this._connected && this._hasNoListeners()) { + Object.keys(this._event2Callback).forEach((event: string) => { + var callback = this._event2Callback[event]; + document.removeEventListener(event, callback); + }); + this._connected = false; + } + } + + protected setCallback(callbackSet: Utils.CallbackSet, callback: Function) { + this._connect(); + callbackSet.add(callback); + } + + protected unsetCallback(callbackSet: Utils.CallbackSet, callback: Function) { + callbackSet.delete(callback); + this._disconnect(); + } + } +} diff --git a/src/dispatchers/keyDispatcher.ts b/src/dispatchers/keyDispatcher.ts index 4799637124..9ddff55992 100644 --- a/src/dispatchers/keyDispatcher.ts +++ b/src/dispatchers/keyDispatcher.ts @@ -1,12 +1,12 @@ /// module Plottable { -export module Dispatcher { - export type KeyCallback = (keyCode: number, e: KeyboardEvent) => any; +export module Dispatchers { + export type KeyCallback = (keyCode: number, event: KeyboardEvent) => any; - export class Key extends AbstractDispatcher { + export class Key extends Dispatcher { private static _DISPATCHER_KEY = "__Plottable_Dispatcher_Key"; - private _keydownBroadcaster: Core.Broadcaster; + private _keydownCallbacks: Utils.CallbackSet; /** * Get a Dispatcher.Key. If one already exists it will be returned; @@ -14,7 +14,7 @@ export module Dispatcher { * * @return {Dispatcher.Key} A Dispatcher.Key */ - public static getDispatcher(): Dispatcher.Key { + public static getDispatcher(): Dispatchers.Key { var dispatcher: Key = ( document)[Key._DISPATCHER_KEY]; if (dispatcher == null) { dispatcher = new Key(); @@ -34,30 +34,34 @@ export module Dispatcher { this._event2Callback["keydown"] = (e: KeyboardEvent) => this._processKeydown(e); - this._keydownBroadcaster = new Core.Broadcaster(this); - this._broadcasters = [this._keydownBroadcaster]; + this._keydownCallbacks = new Utils.CallbackSet(); + this._callbacks = [this._keydownCallbacks]; } - protected _getWrappedCallback(callback: Function): Core.BroadcasterCallback { - return (d: Dispatcher.Key, e: KeyboardEvent) => callback(e.keyCode, e); + /** + * Registers a callback to be called whenever a key is pressed. + * + * @param {KeyCallback} callback + * @return {Dispatcher.Key} The calling Dispatcher.Key. + */ + public onKeyDown(callback: KeyCallback): Key { + this.setCallback(this._keydownCallbacks, callback); + return this; } /** - * Registers a callback to be called whenever a key is pressed, - * or removes the callback if `null` is passed as the callback. + * Removes the callback to be called whenever a key is pressed. * - * @param {any} key The registration key associated with the callback. - * Registration key uniqueness is determined by deep equality. * @param {KeyCallback} callback * @return {Dispatcher.Key} The calling Dispatcher.Key. */ - public onKeyDown(key: any, callback: KeyCallback): Key { - this._setCallback(this._keydownBroadcaster, key, callback); + public offKeyDown(callback: KeyCallback): Key { + this.unsetCallback(this._keydownCallbacks, callback); return this; } - private _processKeydown(e: KeyboardEvent) { - this._keydownBroadcaster.broadcast(e); + private _processKeydown(event: KeyboardEvent) { + this._keydownCallbacks.callCallbacks(event.keyCode, event); } } } diff --git a/src/dispatchers/mouseDispatcher.ts b/src/dispatchers/mouseDispatcher.ts index 65e982e4c2..a13d3970e8 100644 --- a/src/dispatchers/mouseDispatcher.ts +++ b/src/dispatchers/mouseDispatcher.ts @@ -1,18 +1,19 @@ /// module Plottable { -export module Dispatcher { - export type MouseCallback = (p: Point, e: MouseEvent) => any; +export module Dispatchers { + export type MouseCallback = (p: Point, event: MouseEvent) => any; - export class Mouse extends AbstractDispatcher { + export class Mouse extends Dispatcher { private static _DISPATCHER_KEY = "__Plottable_Dispatcher_Mouse"; - private translator: _Util.ClientToSVGTranslator; + private translator: Utils.ClientToSVGTranslator; private _lastMousePosition: Point; - private _moveBroadcaster: Core.Broadcaster; - private _downBroadcaster: Core.Broadcaster; - private _upBroadcaster: Core.Broadcaster; - private _wheelBroadcaster: Core.Broadcaster; - private _dblClickBroadcaster: Core.Broadcaster; + + private _moveCallbacks: Utils.CallbackSet; + private _downCallbacks: Utils.CallbackSet; + private _upCallbacks: Utils.CallbackSet; + private _wheelCallbacks: Utils.CallbackSet; + private _dblClickCallbacks: Utils.CallbackSet; /** * Get a Dispatcher.Mouse for the containing elem. If one already exists @@ -21,8 +22,8 @@ export module Dispatcher { * @param {SVGElement} elem A svg DOM element. * @return {Dispatcher.Mouse} A Dispatcher.Mouse */ - public static getDispatcher(elem: SVGElement): Dispatcher.Mouse { - var svg = _Util.DOM.getBoundingSVG(elem); + public static getDispatcher(elem: SVGElement): Dispatchers.Mouse { + var svg = Utils.DOM.getBoundingSVG(elem); var dispatcher: Mouse = ( svg)[Mouse._DISPATCHER_KEY]; if (dispatcher == null) { @@ -41,125 +42,167 @@ export module Dispatcher { constructor(svg: SVGElement) { super(); - this.translator = _Util.ClientToSVGTranslator.getTranslator(svg); + this.translator = Utils.ClientToSVGTranslator.getTranslator(svg); this._lastMousePosition = { x: -1, y: -1 }; - this._moveBroadcaster = new Core.Broadcaster(this); - var processMoveCallback = (e: MouseEvent) => this._measureAndBroadcast(e, this._moveBroadcaster); + this._moveCallbacks = new Plottable.Utils.CallbackSet(); + this._downCallbacks = new Plottable.Utils.CallbackSet(); + this._upCallbacks = new Plottable.Utils.CallbackSet(); + this._wheelCallbacks = new Plottable.Utils.CallbackSet(); + this._dblClickCallbacks = new Plottable.Utils.CallbackSet(); + this._callbacks = [this._moveCallbacks, this._downCallbacks, this._upCallbacks, this._wheelCallbacks, + this._dblClickCallbacks]; + + var processMoveCallback = (e: MouseEvent) => this._measureAndDispatch(e, this._moveCallbacks); this._event2Callback["mouseover"] = processMoveCallback; this._event2Callback["mousemove"] = processMoveCallback; this._event2Callback["mouseout"] = processMoveCallback; + this._event2Callback["mousedown"] = (e: MouseEvent) => this._measureAndDispatch(e, this._downCallbacks); + this._event2Callback["mouseup"] = (e: MouseEvent) => this._measureAndDispatch(e, this._upCallbacks); + this._event2Callback["wheel"] = (e: WheelEvent) => this._measureAndDispatch(e, this._wheelCallbacks); + this._event2Callback["dblclick"] = (e: MouseEvent) => this._measureAndDispatch(e, this._dblClickCallbacks); + } - this._downBroadcaster = new Core.Broadcaster(this); - this._event2Callback["mousedown"] = (e: MouseEvent) => this._measureAndBroadcast(e, this._downBroadcaster); - - this._upBroadcaster = new Core.Broadcaster(this); - this._event2Callback["mouseup"] = (e: MouseEvent) => this._measureAndBroadcast(e, this._upBroadcaster); - - this._wheelBroadcaster = new Core.Broadcaster(this); - this._event2Callback["wheel"] = (e: WheelEvent) => this._measureAndBroadcast(e, this._wheelBroadcaster); - - this._dblClickBroadcaster = new Core.Broadcaster(this); - this._event2Callback["dblclick"] = (e: MouseEvent) => this._measureAndBroadcast(e, this._dblClickBroadcaster); + /** + * Registers a callback to be called whenever the mouse position changes, + * + * @param {(p: Point) => any} callback A callback that takes the pixel position + * in svg-coordinate-space. Pass `null` + * to remove a callback. + * @return {Dispatcher.Mouse} The calling Dispatcher.Mouse. + */ + public onMouseMove(callback: MouseCallback): Dispatchers.Mouse { + this.setCallback(this._moveCallbacks, callback); + return this; + } - this._broadcasters = [this._moveBroadcaster, this._downBroadcaster, this._upBroadcaster, this._wheelBroadcaster, - this._dblClickBroadcaster]; + /** + * Registers the callback to be called whenever the mouse position changes, + * + * @param {(p: Point) => any} callback A callback that takes the pixel position + * in svg-coordinate-space. Pass `null` + * to remove a callback. + * @return {Dispatcher.Mouse} The calling Dispatcher.Mouse. + */ + public offMouseMove(callback: MouseCallback): Dispatchers.Mouse { + this.unsetCallback(this._moveCallbacks, callback); + return this; } - protected _getWrappedCallback(callback: Function): Core.BroadcasterCallback { - return (md: Dispatcher.Mouse, p: Point, e: MouseEvent) => callback(p, e); + /** + * Registers a callback to be called whenever a mousedown occurs. + * + * @param {(p: Point) => any} callback A callback that takes the pixel position + * in svg-coordinate-space. Pass `null` + * to remove a callback. + * @return {Dispatcher.Mouse} The calling Dispatcher.Mouse. + */ + public onMouseDown(callback: MouseCallback): Dispatchers.Mouse { + this.setCallback(this._downCallbacks, callback); + return this; } /** - * Registers a callback to be called whenever the mouse position changes, - * or removes the callback if `null` is passed as the callback. + * Registers the callback to be called whenever a mousedown occurs. * - * @param {any} key The key associated with the callback. - * Key uniqueness is determined by deep equality. * @param {(p: Point) => any} callback A callback that takes the pixel position * in svg-coordinate-space. Pass `null` * to remove a callback. * @return {Dispatcher.Mouse} The calling Dispatcher.Mouse. */ - public onMouseMove(key: any, callback: MouseCallback): Dispatcher.Mouse { - this._setCallback(this._moveBroadcaster, key, callback); + public offMouseDown(callback: MouseCallback): Dispatchers.Mouse { + this.unsetCallback(this._downCallbacks, callback); return this; } /** - * Registers a callback to be called whenever a mousedown occurs, - * or removes the callback if `null` is passed as the callback. + * Registers a callback to be called whenever a mouseup occurs. * - * @param {any} key The key associated with the callback. - * Key uniqueness is determined by deep equality. * @param {(p: Point) => any} callback A callback that takes the pixel position * in svg-coordinate-space. Pass `null` * to remove a callback. * @return {Dispatcher.Mouse} The calling Dispatcher.Mouse. */ - public onMouseDown(key: any, callback: MouseCallback): Dispatcher.Mouse { - this._setCallback(this._downBroadcaster, key, callback); + public onMouseUp(callback: MouseCallback): Dispatchers.Mouse { + this.setCallback(this._upCallbacks, callback); return this; } /** - * Registers a callback to be called whenever a mouseup occurs, - * or removes the callback if `null` is passed as the callback. + * Registers the callback to be called whenever a mouseup occurs. * - * @param {any} key The key associated with the callback. - * Key uniqueness is determined by deep equality. * @param {(p: Point) => any} callback A callback that takes the pixel position * in svg-coordinate-space. Pass `null` * to remove a callback. * @return {Dispatcher.Mouse} The calling Dispatcher.Mouse. */ - public onMouseUp(key: any, callback: MouseCallback): Dispatcher.Mouse { - this._setCallback(this._upBroadcaster, key, callback); + public offMouseUp(callback: MouseCallback): Dispatchers.Mouse { + this.unsetCallback(this._upCallbacks, callback); + return this; + } + + /** + * Registers a callback to be called whenever a wheel occurs. + * + * @param {MouseCallback} callback A callback that takes the pixel position + * in svg-coordinate-space. + * Pass `null` to remove a callback. + * @return {Dispatcher.Mouse} The calling Dispatcher.Mouse. + */ + public onWheel(callback: MouseCallback): Dispatchers.Mouse { + this.setCallback(this._wheelCallbacks, callback); + return this; + } + + /** + * Registers the callback to be called whenever a wheel occurs. + * + * @param {MouseCallback} callback A callback that takes the pixel position + * in svg-coordinate-space. + * Pass `null` to remove a callback. + * @return {Dispatcher.Mouse} The calling Dispatcher.Mouse. + */ + public offWheel(callback: MouseCallback): Dispatchers.Mouse { + this.unsetCallback(this._wheelCallbacks, callback); return this; } /** - * Registers a callback to be called whenever a wheel occurs, - * or removes the callback if `null` is passed as the callback. + * Registers a callback to be called whenever a dblClick occurs. * - * @param {any} key The key associated with the callback. - * Key uniqueness is determined by deep equality. * @param {MouseCallback} callback A callback that takes the pixel position * in svg-coordinate-space. * Pass `null` to remove a callback. * @return {Dispatcher.Mouse} The calling Dispatcher.Mouse. */ - public onWheel(key: any, callback: MouseCallback): Dispatcher.Mouse { - this._setCallback(this._wheelBroadcaster, key, callback); + public onDblClick(callback: MouseCallback): Dispatchers.Mouse { + this.setCallback(this._dblClickCallbacks, callback); return this; } /** - * Registers a callback to be called whenever a dblClick occurs, - * or removes the callback if `null` is passed as the callback. + * Registers the callback to be called whenever a dblClick occurs. * - * @param {any} key The key associated with the callback. - * Key uniqueness is determined by deep equality. * @param {MouseCallback} callback A callback that takes the pixel position * in svg-coordinate-space. * Pass `null` to remove a callback. * @return {Dispatcher.Mouse} The calling Dispatcher.Mouse. */ - public onDblClick(key: any, callback: MouseCallback): Dispatcher.Mouse { - this._setCallback(this._dblClickBroadcaster, key, callback); + public offDblClick(callback: MouseCallback): Dispatchers.Mouse { + this.unsetCallback(this._dblClickCallbacks, callback); return this; } /** * Computes the mouse position from the given event, and if successful - * calls broadcast() on the supplied Broadcaster. + * calls all the callbacks in the provided callbackSet. */ - private _measureAndBroadcast(e: MouseEvent, b: Core.Broadcaster) { - var newMousePosition = this.translator.computePosition(e.clientX, e.clientY); + private _measureAndDispatch(event: MouseEvent, callbackSet: Utils.CallbackSet) { + var newMousePosition = this.translator.computePosition(event.clientX, event.clientY); if (newMousePosition != null) { this._lastMousePosition = newMousePosition; - b.broadcast(this.getLastMousePosition(), e); + callbackSet.callCallbacks(this.getLastMousePosition(), event); } } diff --git a/src/dispatchers/touchDispatcher.ts b/src/dispatchers/touchDispatcher.ts index fd73733673..6bfaa29dcd 100644 --- a/src/dispatchers/touchDispatcher.ts +++ b/src/dispatchers/touchDispatcher.ts @@ -1,10 +1,10 @@ /// module Plottable { -export module Dispatcher { - export type TouchCallback = (ids: number[], idToPoint: { [id: number]: Point; }, e: TouchEvent) => any; +export module Dispatchers { + export type TouchCallback = (ids: number[], idToPoint: { [id: number]: Point; }, event: TouchEvent) => any; - export class Touch extends AbstractDispatcher { + export class Touch extends Dispatcher { /** * Dispatcher.Touch calls callbacks when touch events occur. * It reports the (x, y) position of the first Touch relative to the @@ -12,10 +12,11 @@ export module Dispatcher { */ private static _DISPATCHER_KEY = "__Plottable_Dispatcher_Touch"; - private translator: _Util.ClientToSVGTranslator; - private _startBroadcaster: Core.Broadcaster; - private _moveBroadcaster: Core.Broadcaster; - private _endBroadcaster: Core.Broadcaster; + private translator: Utils.ClientToSVGTranslator; + private _startCallbacks: Utils.CallbackSet; + private _moveCallbacks: Utils.CallbackSet; + private _endCallbacks: Utils.CallbackSet; + private _cancelCallbacks: Utils.CallbackSet; /** * Get a Dispatcher.Touch for the containing elem. If one already exists @@ -24,8 +25,8 @@ export module Dispatcher { * @param {SVGElement} elem A svg DOM element. * @return {Dispatcher.Touch} A Dispatcher.Touch */ - public static getDispatcher(elem: SVGElement): Dispatcher.Touch { - var svg = _Util.DOM.getBoundingSVG(elem); + public static getDispatcher(elem: SVGElement): Dispatchers.Touch { + var svg = Utils.DOM.getBoundingSVG(elem); var dispatcher: Touch = ( svg)[Touch._DISPATCHER_KEY]; if (dispatcher == null) { @@ -44,78 +45,130 @@ export module Dispatcher { constructor(svg: SVGElement) { super(); - this.translator = _Util.ClientToSVGTranslator.getTranslator(svg); + this.translator = Utils.ClientToSVGTranslator.getTranslator(svg); - this._startBroadcaster = new Core.Broadcaster(this); - this._event2Callback["touchstart"] = (e: TouchEvent) => this._measureAndBroadcast(e, this._startBroadcaster); + this._startCallbacks = new Utils.CallbackSet(); + this._moveCallbacks = new Utils.CallbackSet(); + this._endCallbacks = new Utils.CallbackSet(); + this._cancelCallbacks = new Utils.CallbackSet(); + this._callbacks = [this._moveCallbacks, this._startCallbacks, this._endCallbacks, this._cancelCallbacks]; - this._moveBroadcaster = new Core.Broadcaster(this); - this._event2Callback["touchmove"] = (e: TouchEvent) => this._measureAndBroadcast(e, this._moveBroadcaster); + this._event2Callback["touchstart"] = (e: TouchEvent) => this._measureAndDispatch(e, this._startCallbacks); + this._event2Callback["touchmove"] = (e: TouchEvent) => this._measureAndDispatch(e, this._moveCallbacks); + this._event2Callback["touchend"] = (e: TouchEvent) => this._measureAndDispatch(e, this._endCallbacks); + this._event2Callback["touchcancel"] = (e: TouchEvent) => this._measureAndDispatch(e, this._cancelCallbacks); + } + + /** + * Registers a callback to be called whenever a touch starts. + * + * @param {TouchCallback} callback A callback that takes the pixel position + * in svg-coordinate-space. Pass `null` + * to remove a callback. + * @return {Dispatcher.Touch} The calling Dispatcher.Touch. + */ + public onTouchStart(callback: TouchCallback): Dispatchers.Touch { + this.setCallback(this._startCallbacks, callback); + return this; + } - this._endBroadcaster = new Core.Broadcaster(this); - this._event2Callback["touchend"] = (e: TouchEvent) => this._measureAndBroadcast(e, this._endBroadcaster); + /** + * Removes the callback to be called whenever a touch starts. + * + * @param {TouchCallback} callback A callback that takes the pixel position + * in svg-coordinate-space. Pass `null` + * to remove a callback. + * @return {Dispatcher.Touch} The calling Dispatcher.Touch. + */ + public offTouchStart(callback: TouchCallback): Dispatchers.Touch { + this.unsetCallback(this._startCallbacks, callback); + return this; + } + + /** + * Registers a callback to be called whenever the touch position changes. + * + * @param {TouchCallback} callback A callback that takes the pixel position + * in svg-coordinate-space. Pass `null` + * to remove a callback. + * @return {Dispatcher.Touch} The calling Dispatcher.Touch. + */ + public onTouchMove(callback: TouchCallback): Dispatchers.Touch { + this.setCallback(this._moveCallbacks, callback); + return this; + } - this._broadcasters = [this._moveBroadcaster, this._startBroadcaster, this._endBroadcaster]; + /** + * Removes the callback to be called whenever the touch position changes. + * + * @param {TouchCallback} callback A callback that takes the pixel position + * in svg-coordinate-space. Pass `null` + * to remove a callback. + * @return {Dispatcher.Touch} The calling Dispatcher.Touch. + */ + public offTouchMove(callback: TouchCallback): Dispatchers.Touch { + this.unsetCallback(this._moveCallbacks, callback); + return this; } - protected _getWrappedCallback(callback: Function): Core.BroadcasterCallback { - return (td: Dispatcher.Touch, ids: number[], idToPoint: { [id: number]: Point; }, e: MouseEvent) => callback(ids, idToPoint, e); + /** + * Registers a callback to be called whenever a touch ends. + * + * @param {TouchCallback} callback A callback that takes the pixel position + * in svg-coordinate-space. Pass `null` + * to remove a callback. + * @return {Dispatcher.Touch} The calling Dispatcher.Touch. + */ + public onTouchEnd(callback: TouchCallback): Dispatchers.Touch { + this.setCallback(this._endCallbacks, callback); + return this; } /** - * Registers a callback to be called whenever a touch starts, - * or removes the callback if `null` is passed as the callback. + * Removes the callback to be called whenever a touch ends. * - * @param {any} key The key associated with the callback. - * Key uniqueness is determined by deep equality. * @param {TouchCallback} callback A callback that takes the pixel position * in svg-coordinate-space. Pass `null` * to remove a callback. * @return {Dispatcher.Touch} The calling Dispatcher.Touch. */ - public onTouchStart(key: any, callback: TouchCallback): Dispatcher.Touch { - this._setCallback(this._startBroadcaster, key, callback); + public offTouchEnd(callback: TouchCallback): Dispatchers.Touch { + this.unsetCallback(this._endCallbacks, callback); return this; } /** - * Registers a callback to be called whenever the touch position changes, - * or removes the callback if `null` is passed as the callback. + * Registers a callback to be called whenever a touch is cancelled. * - * @param {any} key The key associated with the callback. - * Key uniqueness is determined by deep equality. * @param {TouchCallback} callback A callback that takes the pixel position * in svg-coordinate-space. Pass `null` * to remove a callback. * @return {Dispatcher.Touch} The calling Dispatcher.Touch. */ - public onTouchMove(key: any, callback: TouchCallback): Dispatcher.Touch { - this._setCallback(this._moveBroadcaster, key, callback); + public onTouchCancel(callback: TouchCallback): Dispatchers.Touch { + this.setCallback(this._cancelCallbacks, callback); return this; } /** - * Registers a callback to be called whenever a touch ends, - * or removes the callback if `null` is passed as the callback. + * Removes the callback to be called whenever a touch is cancelled. * - * @param {any} key The key associated with the callback. - * Key uniqueness is determined by deep equality. * @param {TouchCallback} callback A callback that takes the pixel position * in svg-coordinate-space. Pass `null` * to remove a callback. * @return {Dispatcher.Touch} The calling Dispatcher.Touch. */ - public onTouchEnd(key: any, callback: TouchCallback): Dispatcher.Touch { - this._setCallback(this._endBroadcaster, key, callback); + public offTouchCancel(callback: TouchCallback): Dispatchers.Touch { + this.unsetCallback(this._cancelCallbacks, callback); return this; } /** * Computes the Touch position from the given event, and if successful - * calls broadcast() on the supplied Broadcaster. + * calls all the callbacks in the provided callbackSet. */ - private _measureAndBroadcast(e: TouchEvent, b: Core.Broadcaster) { - var touches = e.changedTouches; + private _measureAndDispatch(event: TouchEvent, callbackSet: Utils.CallbackSet) { + var touches = event.changedTouches; var touchPositions: { [id: number]: Point; } = {}; var touchIdentifiers: number[] = []; for (var i = 0; i < touches.length; i++) { @@ -128,7 +181,7 @@ export module Dispatcher { } }; if (touchIdentifiers.length > 0) { - b.broadcast(touchIdentifiers, touchPositions, e); + callbackSet.callCallbacks(touchIdentifiers, touchPositions, event); } } } diff --git a/src/drawers/arcDrawer.ts b/src/drawers/arcDrawer.ts index e3ca318106..90be476a9f 100644 --- a/src/drawers/arcDrawer.ts +++ b/src/drawers/arcDrawer.ts @@ -1,7 +1,7 @@ /// module Plottable { -export module _Drawer { +export module Drawers { export class Arc extends Element { constructor(key: string) { @@ -24,7 +24,7 @@ export module _Drawer { } public _drawStep(step: AppliedDrawStep) { - var attrToProjector = _Util.Methods.copyMap(step.attrToProjector); + var attrToProjector = Utils.Methods.copyMap(step.attrToProjector); attrToProjector = this.retargetProjectors(attrToProjector); this._attrToProjector = this.retargetProjectors(this._attrToProjector); var innerRadiusAccessor = attrToProjector["inner-radius"]; @@ -36,23 +36,23 @@ export module _Drawer { return super._drawStep({attrToProjector: attrToProjector, animator: step.animator}); } - public draw(data: any[], drawSteps: DrawStep[], userMetadata: any, plotMetadata: Plot.PlotMetadata) { + public draw(data: any[], drawSteps: DrawStep[], dataset: Dataset, plotMetadata: Plots.PlotMetadata) { // HACKHACK Applying metadata should be done in base class - var valueAccessor = (d: any, i: number) => drawSteps[0].attrToProjector["value"](d, i, userMetadata, plotMetadata); + var valueAccessor = (d: any, i: number) => drawSteps[0].attrToProjector["sector-value"](d, i, dataset, plotMetadata); - data = data.filter(e => Plottable._Util.Methods.isValidNumber(+valueAccessor(e, null))); + data = data.filter(e => Plottable.Utils.Methods.isValidNumber(+valueAccessor(e, null))); var pie = d3.layout.pie() .sort(null) .value(valueAccessor)(data); - drawSteps.forEach(s => delete s.attrToProjector["value"]); + drawSteps.forEach(s => delete s.attrToProjector["sector-value"]); pie.forEach((slice) => { if (slice.value < 0) { - _Util.Methods.warn("Negative values will not render correctly in a pie chart."); + Utils.Methods.warn("Negative values will not render correctly in a pie chart."); } }); - return super.draw(pie, drawSteps, userMetadata, plotMetadata); + return super.draw(pie, drawSteps, dataset, plotMetadata); } public _getPixelPoint(datum: any, index: number): Point { diff --git a/src/drawers/areaDrawer.ts b/src/drawers/areaDrawer.ts index 728f32e2d2..5243a9c6fb 100644 --- a/src/drawers/areaDrawer.ts +++ b/src/drawers/areaDrawer.ts @@ -1,7 +1,7 @@ /// module Plottable { -export module _Drawer { +export module Drawers { export class Area extends Line { public static AREA_CLASS = "area"; @@ -43,7 +43,7 @@ export module _Drawer { y0Function: AppliedProjector, y1Function: AppliedProjector, definedFunction: AppliedProjector) { - if(!definedFunction) { + if (!definedFunction) { definedFunction = () => true; } @@ -61,7 +61,7 @@ export module _Drawer { // HACKHACK Forced to use anycast to access protected var ( AbstractDrawer).prototype._drawStep.call(this, step); } - var attrToProjector = _Util.Methods.copyMap(step.attrToProjector); + var attrToProjector = Utils.Methods.copyMap(step.attrToProjector); var xFunction = attrToProjector["x"]; var y0Function = attrToProjector["y0"]; var y1Function = attrToProjector["y"]; diff --git a/src/drawers/abstractDrawer.ts b/src/drawers/drawer.ts similarity index 85% rename from src/drawers/abstractDrawer.ts rename to src/drawers/drawer.ts index 487cc5e744..31cc5136bc 100644 --- a/src/drawers/abstractDrawer.ts +++ b/src/drawers/drawer.ts @@ -1,7 +1,7 @@ /// module Plottable { -export module _Drawer { +export module Drawers { /** * A step for the drawer to draw. * @@ -9,12 +9,12 @@ export module _Drawer { */ export type DrawStep = { attrToProjector: AttributeToProjector; - animator: Animator.PlotAnimator; + animator: Animators.PlotAnimator; } export type AppliedDrawStep = { attrToProjector: AttributeToAppliedProjector; - animator: Animator.PlotAnimator; + animator: Animators.PlotAnimator; } export class AbstractDrawer { @@ -79,12 +79,12 @@ export module _Drawer { } private _applyMetadata(attrToProjector: AttributeToProjector, - userMetadata: any, - plotMetadata: Plot.PlotMetadata): AttributeToAppliedProjector { + dataset: Dataset, + plotMetadata: Plots.PlotMetadata): AttributeToAppliedProjector { var modifiedAttrToProjector: AttributeToAppliedProjector = {}; d3.keys(attrToProjector).forEach((attr: string) => { modifiedAttrToProjector[attr] = - (datum: any, index: number) => attrToProjector[attr](datum, index, userMetadata, plotMetadata); + (datum: any, index: number) => attrToProjector[attr](datum, index, dataset, plotMetadata); }); return modifiedAttrToProjector; @@ -103,13 +103,13 @@ export module _Drawer { * * @param{any[]} data The data to be drawn * @param{DrawStep[]} drawSteps The list of steps, which needs to be drawn - * @param{any} userMetadata The metadata provided by user + * @param{Dataset} dataset The Dataset * @param{any} plotMetadata The metadata provided by plot */ - public draw(data: any[], drawSteps: DrawStep[], userMetadata: any, plotMetadata: Plot.PlotMetadata) { + public draw(data: any[], drawSteps: DrawStep[], dataset: Dataset, plotMetadata: Plots.PlotMetadata) { var appliedDrawSteps: AppliedDrawStep[] = drawSteps.map((dr: DrawStep) => { - var appliedAttrToProjector = this._applyMetadata(dr.attrToProjector, userMetadata, plotMetadata); - this._attrToProjector = _Util.Methods.copyMap(appliedAttrToProjector); + var appliedAttrToProjector = this._applyMetadata(dr.attrToProjector, dataset, plotMetadata); + this._attrToProjector = Utils.Methods.copyMap(appliedAttrToProjector); return { attrToProjector: appliedAttrToProjector, animator: dr.animator @@ -125,7 +125,7 @@ export module _Drawer { var delay = 0; appliedDrawSteps.forEach((drawStep, i) => { - _Util.Methods.setTimeout(() => this._drawStep(drawStep), delay); + Utils.Methods.setTimeout(() => this._drawStep(drawStep), delay); delay += drawStep.animator.getTiming(numberOfIterations); }); diff --git a/src/drawers/elementDrawer.ts b/src/drawers/elementDrawer.ts index d59cde2c56..617b044d4f 100644 --- a/src/drawers/elementDrawer.ts +++ b/src/drawers/elementDrawer.ts @@ -1,7 +1,7 @@ /// module Plottable { -export module _Drawer { +export module Drawers { export class Element extends AbstractDrawer { protected _svgElement: string; diff --git a/src/drawers/lineDrawer.ts b/src/drawers/lineDrawer.ts index fdf5def180..8328a579b0 100644 --- a/src/drawers/lineDrawer.ts +++ b/src/drawers/lineDrawer.ts @@ -1,7 +1,7 @@ /// module Plottable { -export module _Drawer { +export module Drawers { export class Line extends AbstractDrawer { public static LINE_CLASS = "line"; @@ -23,7 +23,7 @@ export module _Drawer { } private _createLine(xFunction: AppliedProjector, yFunction: AppliedProjector, definedFunction: AppliedProjector) { - if(!definedFunction) { + if (!definedFunction) { definedFunction = (d, i) => true; } @@ -38,8 +38,8 @@ export module _Drawer { } protected _drawStep(step: AppliedDrawStep) { - var baseTime = super._drawStep(step); - var attrToProjector = _Util.Methods.copyMap(step.attrToProjector); + super._drawStep(step); + var attrToProjector = Utils.Methods.copyMap(step.attrToProjector); var definedFunction = attrToProjector["defined"]; var xProjector = attrToProjector["x"]; diff --git a/src/drawers/rectDrawer.ts b/src/drawers/rectDrawer.ts index de6615bbbc..a30dc8fb0d 100644 --- a/src/drawers/rectDrawer.ts +++ b/src/drawers/rectDrawer.ts @@ -1,7 +1,7 @@ /// module Plottable { -export module _Drawer { +export module Drawers { var LABEL_VERTICAL_PADDING = 5; var LABEL_HORIZONTAL_PADDING = 5; export class Rect extends Element { @@ -33,7 +33,7 @@ export module _Drawer { return this._labelsTooWide; } - public drawText(data: any[], attrToProjector: AttributeToProjector, userMetadata: any, plotMetadata: Plot.PlotMetadata) { + public drawText(data: any[], attrToProjector: AttributeToProjector, userMetadata: any, plotMetadata: Plots.PlotMetadata) { var labelTooWide: boolean[] = data.map((d, i) => { var text = attrToProjector["label"](d, i, userMetadata, plotMetadata).toString(); var w = attrToProjector["width"](d, i, userMetadata, plotMetadata); @@ -43,7 +43,7 @@ export module _Drawer { var positive = attrToProjector["positive"](d, i, userMetadata, plotMetadata); var measurement = this._measurer.measure(text); var color = attrToProjector["fill"](d, i, userMetadata, plotMetadata); - var dark = _Util.Color.contrast("white", color) * 1.6 < _Util.Color.contrast("black", color); + var dark = Utils.Colors.contrast("white", color) * 1.6 < Utils.Colors.contrast("black", color); var primary = this._isVertical ? h : w; var primarySpace = this._isVertical ? measurement.height : measurement.width; @@ -94,9 +94,9 @@ export module _Drawer { return { x: x, y: y }; } - public draw(data: any[], drawSteps: DrawStep[], userMetadata: any, plotMetadata: Plot.PlotMetadata) { + public draw(data: any[], drawSteps: DrawStep[], userMetadata: any, plotMetadata: Plots.PlotMetadata) { var attrToProjector = drawSteps[0].attrToProjector; - var isValidNumber = Plottable._Util.Methods.isValidNumber; + var isValidNumber = Plottable.Utils.Methods.isValidNumber; data = data.filter(function(e: any, i: number) { return isValidNumber(attrToProjector["x"](e, null, userMetadata, plotMetadata)) && isValidNumber(attrToProjector["y"](e, null, userMetadata, plotMetadata)) && diff --git a/src/drawers/symbolDrawer.ts b/src/drawers/symbolDrawer.ts index 498ecd4301..3082c9fac1 100644 --- a/src/drawers/symbolDrawer.ts +++ b/src/drawers/symbolDrawer.ts @@ -1,7 +1,7 @@ /// module Plottable { -export module _Drawer { +export module Drawers { export class Symbol extends Element { constructor(key: string) { @@ -12,7 +12,7 @@ export module _Drawer { protected _drawStep(step: AppliedDrawStep) { var attrToProjector = step.attrToProjector; - this._attrToProjector = _Util.Methods.copyMap(step.attrToProjector); + this._attrToProjector = Utils.Methods.copyMap(step.attrToProjector); var xProjector = attrToProjector["x"]; var yProjector = attrToProjector["y"]; diff --git a/src/interactions/abstractInteraction.ts b/src/interactions/abstractInteraction.ts deleted file mode 100644 index 6ed7e7aa09..0000000000 --- a/src/interactions/abstractInteraction.ts +++ /dev/null @@ -1,55 +0,0 @@ -/// - -module Plottable { -export module Interaction { - export class AbstractInteraction extends Core.PlottableObject { - /** - * It maintains a 'hitBox' which is where all event listeners are - * attached. Due to cross- browser weirdness, the hitbox needs to be an - * opaque but invisible rectangle. TODO: We should give the interaction - * "foreground" and "background" elements where it can draw things, - * e.g. crosshairs. - */ - protected _hitBox: D3.Selection; - protected _componentToListenTo: Component.AbstractComponent; - - public _anchor(component: Component.AbstractComponent, hitBox: D3.Selection) { - this._componentToListenTo = component; - this._hitBox = hitBox; - } - - // HACKHACK: After all Interactions use Dispatchers, we won't need hitboxes at all (#1757) - public _requiresHitbox() { - return false; - } - - /** - * Translates an -coordinate-space point to Component-space coordinates. - * - * @param {Point} p A Point in -space coordinates. - * - * @return {Point} The same location in Component-space coordinates. - */ - protected _translateToComponentSpace(p: Point): Point { - var origin = this._componentToListenTo.originToSVG(); - return { - x: p.x - origin.x, - y: p.y - origin.y - }; - } - - /** - * Checks whether a Component-coordinate-space Point is inside the Component. - * - * @param {Point} p A Point in Coordinate-space coordinates. - * - * @return {boolean} Whether or not the point is inside the Component. - */ - protected _isInsideComponent(p: Point) { - return 0 <= p.x && 0 <= p.y - && p.x <= this._componentToListenTo.width() - && p.y <= this._componentToListenTo.height(); - } - } -} -} diff --git a/src/interactions/clickInteraction.ts b/src/interactions/clickInteraction.ts index dc888b38ef..db1911767c 100644 --- a/src/interactions/clickInteraction.ts +++ b/src/interactions/clickInteraction.ts @@ -1,26 +1,47 @@ /// module Plottable { -export module Interaction { - export class Click extends AbstractInteraction { - private _mouseDispatcher: Plottable.Dispatcher.Mouse; - private _touchDispatcher: Plottable.Dispatcher.Touch; - private _clickCallback: (p: Point) => any; +export type ClickCallback = (point: Point) => any; + +export module Interactions { + export class Click extends Interaction { + + private _mouseDispatcher: Plottable.Dispatchers.Mouse; + private _touchDispatcher: Plottable.Dispatchers.Touch; private _clickedDown = false; + private _onClickCallbacks = new Utils.CallbackSet(); + + private _mouseDownCallback = (p: Point) => this._handleClickDown(p); + private _mouseUpCallback = (p: Point) => this._handleClickUp(p); + + private _touchStartCallback = (ids: number[], idToPoint: Point[]) => this._handleClickDown(idToPoint[ids[0]]); + private _touchEndCallback = (ids: number[], idToPoint: Point[]) => this._handleClickUp(idToPoint[ids[0]]); + private _touchCancelCallback = (ids: number[], idToPoint: Point[]) => this._clickedDown = false; - public _anchor(component: Component.AbstractComponent, hitBox: D3.Selection) { - super._anchor(component, hitBox); + protected _anchor(component: Component) { + super._anchor(component); - this._mouseDispatcher = Dispatcher.Mouse.getDispatcher( component.content().node()); - this._mouseDispatcher.onMouseDown("Interaction.Click" + this.getID(), (p: Point) => this._handleClickDown(p)); - this._mouseDispatcher.onMouseUp("Interaction.Click" + this.getID(), (p: Point) => this._handleClickUp(p)); + this._mouseDispatcher = Dispatchers.Mouse.getDispatcher( component.content().node()); + this._mouseDispatcher.onMouseDown(this._mouseDownCallback); + this._mouseDispatcher.onMouseUp(this._mouseUpCallback); - this._touchDispatcher = Dispatcher.Touch.getDispatcher( component.content().node()); - this._touchDispatcher.onTouchStart("Interaction.Click" + this.getID(), (ids, idToPoint) => - this._handleClickDown(idToPoint[ids[0]])); - this._touchDispatcher.onTouchEnd("Interaction.Click" + this.getID(), (ids, idToPoint) => - this._handleClickUp(idToPoint[ids[0]])); + this._touchDispatcher = Dispatchers.Touch.getDispatcher( component.content().node()); + this._touchDispatcher.onTouchStart(this._touchStartCallback); + this._touchDispatcher.onTouchEnd(this._touchEndCallback); + this._touchDispatcher.onTouchCancel(this._touchCancelCallback); + } + + protected _unanchor() { + super._unanchor(); + this._mouseDispatcher.offMouseDown(this._mouseDownCallback); + this._mouseDispatcher.offMouseUp(this._mouseUpCallback); + this._mouseDispatcher = null; + + this._touchDispatcher.offTouchStart(this._touchStartCallback); + this._touchDispatcher.offTouchEnd(this._touchEndCallback); + this._touchDispatcher.offTouchCancel(this._touchCancelCallback); + this._touchDispatcher = null; } private _handleClickDown(p: Point) { @@ -32,33 +53,33 @@ export module Interaction { private _handleClickUp(p: Point) { var translatedPoint = this._translateToComponentSpace(p); - if (this._clickedDown && this._isInsideComponent(translatedPoint) && (this._clickCallback != null)) { - this._clickCallback(translatedPoint); + if (this._clickedDown && this._isInsideComponent(translatedPoint)) { + this._onClickCallbacks.callCallbacks(translatedPoint); } this._clickedDown = false; } /** - * Gets the callback called when the Component is clicked. + * Sets the callback called when the Component is clicked. * - * @return {(p: Point) => any} The current callback. + * @param {ClickCallback} callback The callback to set. + * @return {Interaction.Click} The calling Interaction.Click. */ - public onClick(): (p: Point) => any; + public onClick(callback: ClickCallback) { + this._onClickCallbacks.add(callback); + return this; + } + /** - * Sets the callback called when the Component is clicked. + * Removes the callback from click. * - * @param {(p: Point) => any} callback The callback to set. + * @param {ClickCallback} callback The callback to remove. * @return {Interaction.Click} The calling Interaction.Click. */ - public onClick(callback: (p: Point) => any): Interaction.Click; - public onClick(callback?: (p: Point) => any): any { - if (callback === undefined) { - return this._clickCallback; - } - this._clickCallback = callback; + public offClick(callback: ClickCallback) { + this._onClickCallbacks.delete(callback); return this; } - } } } diff --git a/src/interactions/doubleClickInteraction.ts b/src/interactions/doubleClickInteraction.ts index 1a9aff914d..9c57164d24 100644 --- a/src/interactions/doubleClickInteraction.ts +++ b/src/interactions/doubleClickInteraction.ts @@ -1,32 +1,50 @@ /// module Plottable { -export module Interaction { - +export module Interactions { enum ClickState {NotClicked, SingleClicked, DoubleClicked}; + export class DoubleClick extends Interaction { - export class DoubleClick extends AbstractInteraction { - - private _mouseDispatcher: Plottable.Dispatcher.Mouse; - private _touchDispatcher: Plottable.Dispatcher.Touch; - private _doubleClickCallback: (p: Point) => any; + private _mouseDispatcher: Plottable.Dispatchers.Mouse; + private _touchDispatcher: Plottable.Dispatchers.Touch; private _clickState = ClickState.NotClicked; private _clickedDown = false; private _clickedPoint: Point; - public _anchor(component: Component.AbstractComponent, hitBox: D3.Selection) { - super._anchor(component, hitBox); + private _onDoubleClickCallbacks = new Utils.CallbackSet(); + + private _mouseDownCallback = (p: Point) => this._handleClickDown(p); + private _mouseUpCallback = (p: Point) => this._handleClickUp(p); + private _dblClickCallback = (p: Point) => this._handleDblClick(); + private _touchStartCallback = (ids: number[], idToPoint: Point[]) => this._handleClickDown(idToPoint[ids[0]]); + private _touchEndCallback = (ids: number[], idToPoint: Point[]) => this._handleClickUp(idToPoint[ids[0]]); + private _touchCancelCallback = (ids: number[], idToPoint: Point[]) => this._handleClickCancel(); + + protected _anchor(component: Component) { + super._anchor(component); + + this._mouseDispatcher = Dispatchers.Mouse.getDispatcher( component.content().node()); + this._mouseDispatcher.onMouseDown(this._mouseDownCallback); + this._mouseDispatcher.onMouseUp(this._mouseUpCallback); + this._mouseDispatcher.onDblClick(this._dblClickCallback); - this._mouseDispatcher = Dispatcher.Mouse.getDispatcher( component.content().node()); - this._mouseDispatcher.onMouseDown("Interaction.DoubleClick" + this.getID(), (p: Point) => this._handleClickDown(p)); - this._mouseDispatcher.onMouseUp("Interaction.DoubleClick" + this.getID(), (p: Point) => this._handleClickUp(p)); - this._mouseDispatcher.onDblClick("Interaction.DoubleClick" + this.getID(), (p: Point) => this._handleDblClick()); + this._touchDispatcher = Dispatchers.Touch.getDispatcher( component.content().node()); + this._touchDispatcher.onTouchStart(this._touchStartCallback); + this._touchDispatcher.onTouchEnd(this._touchEndCallback); + this._touchDispatcher.onTouchCancel(this._touchCancelCallback); + } + + protected _unanchor() { + super._unanchor(); + this._mouseDispatcher.offMouseDown(this._mouseDownCallback); + this._mouseDispatcher.offMouseUp(this._mouseUpCallback); + this._mouseDispatcher.offDblClick(this._dblClickCallback); + this._mouseDispatcher = null; - this._touchDispatcher = Dispatcher.Touch.getDispatcher( component.content().node()); - this._touchDispatcher.onTouchStart("Interaction.DoubleClick" + this.getID(), (ids, idToPoint) => - this._handleClickDown(idToPoint[ids[0]])); - this._touchDispatcher.onTouchEnd("Interaction.DoubleClick" + this.getID(), (ids, idToPoint) => - this._handleClickUp(idToPoint[ids[0]])); + this._touchDispatcher.offTouchStart(this._touchStartCallback); + this._touchDispatcher.offTouchEnd(this._touchEndCallback); + this._touchDispatcher.offTouchCancel(this._touchCancelCallback); + this._touchDispatcher = null; } private _handleClickDown(p: Point) { @@ -52,38 +70,41 @@ export module Interaction { private _handleDblClick() { if (this._clickState === ClickState.DoubleClicked) { - if (this._doubleClickCallback) { - this._doubleClickCallback(this._clickedPoint); - } + this._onDoubleClickCallbacks.callCallbacks(this._clickedPoint); this._clickState = ClickState.NotClicked; } } + private _handleClickCancel() { + this._clickState = ClickState.NotClicked; + this._clickedDown = false; + } + private static pointsEqual(p1: Point, p2: Point) { return p1.x === p2.x && p1.y === p2.y; } /** - * Gets the callback called when the Component is double-clicked. + * Sets the callback called when the Component is double-clicked. * - * @return {(p: Point) => any} The current callback. + * @param {ClickCallback} callback The callback to set. + * @return {Interaction.DoubleClick} The calling Interaction.DoubleClick. */ - public onDoubleClick(): (p: Point) => any; + public onDoubleClick(callback: ClickCallback) { + this._onDoubleClickCallbacks.add(callback); + return this; + } + /** - * Sets the callback called when the Component is double-clicked. + * Removes the callback called when the Component is double-clicked. * - * @param {(p: Point) => any} callback The callback to set. + * @param {ClickCallback} callback The callback to remove. * @return {Interaction.DoubleClick} The calling Interaction.DoubleClick. */ - public onDoubleClick(callback: (p: Point) => any): Interaction.DoubleClick; - public onDoubleClick(callback?: (p: Point) => any): any { - if (callback === undefined) { - return this._doubleClickCallback; - } - this._doubleClickCallback = callback; + public offDoubleClick(callback: ClickCallback) { + this._onDoubleClickCallbacks.delete(callback); return this; } - } } } diff --git a/src/interactions/dragInteraction.ts b/src/interactions/dragInteraction.ts index dbc74a4d8b..ecc2c3d766 100644 --- a/src/interactions/dragInteraction.ts +++ b/src/interactions/dragInteraction.ts @@ -1,34 +1,51 @@ /// module Plottable { -export module Interaction { - export class Drag extends AbstractInteraction { + +export type DragCallback = (start: Point, end: Point) => any; + +export module Interactions { + export class Drag extends Interaction { private _dragging = false; private _constrain = true; - private _mouseDispatcher: Plottable.Dispatcher.Mouse; - private _touchDispatcher: Dispatcher.Touch; + private _mouseDispatcher: Dispatchers.Mouse; + private _touchDispatcher: Dispatchers.Touch; private _dragOrigin: Point; - private _dragStartCallback: (p: Point) => any; - private _dragCallback: (start: Point, end: Point) => any; - private _dragEndCallback: (start: Point, end: Point) => any; - - public _anchor(component: Component.AbstractComponent, hitBox: D3.Selection) { - super._anchor(component, hitBox); - this._mouseDispatcher = Dispatcher.Mouse.getDispatcher( this._componentToListenTo.content().node()); - this._mouseDispatcher.onMouseDown("Interaction.Drag" + this.getID(), - (p: Point, e: MouseEvent) => this._startDrag(p, e)); - this._mouseDispatcher.onMouseMove("Interaction.Drag" + this.getID(), - (p: Point, e: MouseEvent) => this._doDrag(p, e)); - this._mouseDispatcher.onMouseUp("Interaction.Drag" + this.getID(), - (p: Point, e: MouseEvent) => this._endDrag(p, e)); - - this._touchDispatcher = Dispatcher.Touch.getDispatcher( this._componentToListenTo.content().node()); - this._touchDispatcher.onTouchStart("Interaction.Drag" + this.getID(), - (ids, idToPoint, e) => this._startDrag(idToPoint[ids[0]], e)); - this._touchDispatcher.onTouchMove("Interaction.Drag" + this.getID(), - (ids, idToPoint, e) => this._doDrag(idToPoint[ids[0]], e)); - this._touchDispatcher.onTouchEnd("Interaction.Drag" + this.getID(), - (ids, idToPoint, e) => this._endDrag(idToPoint[ids[0]], e)); + private _dragStartCallbacks = new Utils.CallbackSet(); + private _dragCallbacks = new Utils.CallbackSet(); + private _dragEndCallbacks = new Utils.CallbackSet(); + + private _mouseDownCallback = (p: Point, e: MouseEvent) => this._startDrag(p, e); + private _mouseMoveCallback = (p: Point, e: MouseEvent) => this._doDrag(p, e); + private _mouseUpCallback = (p: Point, e: MouseEvent) => this._endDrag(p, e); + private _touchStartCallback = (ids: number[], idToPoint: Point[], e: UIEvent) => this._startDrag(idToPoint[ids[0]], e); + private _touchMoveCallback = (ids: number[], idToPoint: Point[], e: UIEvent) => this._doDrag(idToPoint[ids[0]], e); + private _touchEndCallback = (ids: number[], idToPoint: Point[], e: UIEvent) => this._endDrag(idToPoint[ids[0]], e); + + protected _anchor(component: Component) { + super._anchor(component); + this._mouseDispatcher = Dispatchers.Mouse.getDispatcher( this._componentAttachedTo.content().node()); + this._mouseDispatcher.onMouseDown(this._mouseDownCallback); + this._mouseDispatcher.onMouseMove(this._mouseMoveCallback); + this._mouseDispatcher.onMouseUp(this._mouseUpCallback); + + this._touchDispatcher = Dispatchers.Touch.getDispatcher( this._componentAttachedTo.content().node()); + this._touchDispatcher.onTouchStart(this._touchStartCallback); + this._touchDispatcher.onTouchMove(this._touchMoveCallback); + this._touchDispatcher.onTouchEnd(this._touchEndCallback); + } + + protected _unanchor() { + super._unanchor(); + this._mouseDispatcher.offMouseDown(this._mouseDownCallback); + this._mouseDispatcher.offMouseMove(this._mouseMoveCallback); + this._mouseDispatcher.offMouseUp(this._mouseUpCallback); + this._mouseDispatcher = null; + + this._touchDispatcher.offTouchStart(this._touchStartCallback); + this._touchDispatcher.offTouchMove(this._touchMoveCallback); + this._touchDispatcher.offTouchEnd(this._touchEndCallback); + this._touchDispatcher = null; } private _translateAndConstrain(p: Point) { @@ -38,61 +55,53 @@ export module Interaction { } return { - x: _Util.Methods.clamp(translatedP.x, 0, this._componentToListenTo.width()), - y: _Util.Methods.clamp(translatedP.y, 0, this._componentToListenTo.height()) + x: Utils.Methods.clamp(translatedP.x, 0, this._componentAttachedTo.width()), + y: Utils.Methods.clamp(translatedP.y, 0, this._componentAttachedTo.height()) }; } - private _startDrag(p: Point, e: UIEvent) { - if (e instanceof MouseEvent && ( e).button !== 0) { + private _startDrag(point: Point, event: UIEvent) { + if (event instanceof MouseEvent && ( event).button !== 0) { return; } - var translatedP = this._translateToComponentSpace(p); + var translatedP = this._translateToComponentSpace(point); if (this._isInsideComponent(translatedP)) { - e.preventDefault(); + event.preventDefault(); this._dragging = true; this._dragOrigin = translatedP; - if (this._dragStartCallback) { - this._dragStartCallback(this._dragOrigin); - } + this._dragStartCallbacks.callCallbacks(this._dragOrigin); } } - private _doDrag(p: Point, e: UIEvent) { + private _doDrag(point: Point, event: UIEvent) { if (this._dragging) { - if (this._dragCallback) { - var constrainedP = this._translateAndConstrain(p); - this._dragCallback(this._dragOrigin, constrainedP); - } + this._dragCallbacks.callCallbacks(this._dragOrigin, this._translateAndConstrain(point)); } } - private _endDrag(p: Point, e: UIEvent) { - if (e instanceof MouseEvent && ( e).button !== 0) { + private _endDrag(point: Point, event: UIEvent) { + if (event instanceof MouseEvent && ( event).button !== 0) { return; } if (this._dragging) { this._dragging = false; - if (this._dragEndCallback) { - var constrainedP = this._translateAndConstrain(p); - this._dragEndCallback(this._dragOrigin, constrainedP); - } + this._dragEndCallbacks.callCallbacks(this._dragOrigin, this._translateAndConstrain(point)); } } /** - * Returns whether or not this Interaction constrains Points passed to its + * Returns whether or not this Interactions constrains Points passed to its * callbacks to lie inside its Component. * * If true, when the user drags outside of the Component, the closest Point * inside the Component will be passed to the callback instead of the actual * cursor position. * - * @return {boolean} Whether or not the Interaction.Drag constrains. + * @return {boolean} Whether or not the Interactions.Drag constrains. */ public constrainToComponent(): boolean; /** - * Sets whether or not this Interaction constrains Points passed to its + * Sets whether or not this Interactions constrains Points passed to its * callbacks to lie inside its Component. * * If true, when the user drags outside of the Component, the closest Point @@ -100,7 +109,7 @@ export module Interaction { * cursor position. * * @param {boolean} constrain Whether or not to constrain Points. - * @return {Interaction.Drag} The calling Interaction.Drag. + * @return {Interactions.Drag} The calling Interactions.Drag. */ public constrainToComponent(constrain: boolean): Drag; public constrainToComponent(constrain?: boolean): any { @@ -111,70 +120,70 @@ export module Interaction { return this; } - /** - * Gets the callback that is called when dragging starts. - * - * @returns {(start: Point) => any} The callback called when dragging starts. - */ - public onDragStart(): (start: Point) => any; /** * Sets the callback to be called when dragging starts. * - * @param {(start: Point) => any} cb The callback to be called. Takes in a Point in pixels. - * @returns {Drag} The calling Interaction.Drag. + * @param {DragCallback} callback The callback to be called. Takes in a Point in pixels. + * @returns {Drag} The calling Interactions.Drag. */ - public onDragStart(cb: (start: Point) => any): Drag; - public onDragStart(cb?: (start: Point) => any): any { - if (cb === undefined) { - return this._dragStartCallback; - } else { - this._dragStartCallback = cb; - return this; - } + public onDragStart(callback: DragCallback) { + this._dragStartCallbacks.add(callback); + return this; } /** - * Gets the callback that is called during dragging. + * Removes the callback to be called when dragging starts. * - * @returns {(start: Point, end: Point) => any} The callback called during dragging. + * @param {DragCallback} callback The callback to be removed. + * @returns {Drag} The calling Interactions.Drag. */ - public onDrag(): (start: Point, end: Point) => any; + public offDragStart(callback: DragCallback) { + this._dragStartCallbacks.delete(callback); + return this; + } + /** * Adds a callback to be called during dragging. * - * @param {(start: Point, end: Point) => any} cb The callback to be called. Takes in Points in pixels. - * @returns {Drag} The calling Interaction.Drag. + * @param {DragCallback} callback The callback to be called. Takes in Points in pixels. + * @returns {Drag} The calling Interactions.Drag. */ - public onDrag(cb: (start: Point, end: Point) => any): Drag; - public onDrag(cb?: (start: Point, end: Point) => any): any { - if (cb === undefined) { - return this._dragCallback; - } else { - this._dragCallback = cb; - return this; - } + public onDrag(callback: DragCallback) { + this._dragCallbacks.add(callback); + return this; } /** - * Gets the callback that is called when dragging ends. + * Removes a callback to be called during dragging. * - * @returns {(start: Point, end: Point) => any} The callback called when dragging ends. + * @param {DragCallback} callback The callback to be removed. + * @returns {Drag} The calling Interactions.Drag. */ - public onDragEnd(): (start: Point, end: Point) => any; + public offDrag(callback: DragCallback) { + this._dragCallbacks.delete(callback); + return this; + } + /** * Adds a callback to be called when the dragging ends. * - * @param {(start: Point, end: Point) => any} cb The callback to be called. Takes in Points in pixels. - * @returns {Drag} The calling Interaction.Drag. + * @param {DragCallback} callback The callback to be called. Takes in Points in pixels. + * @returns {Drag} The calling Interactions.Drag. */ - public onDragEnd(cb: (start: Point, end: Point) => any): Drag; - public onDragEnd(cb?: (start: Point, end: Point) => any): any { - if (cb === undefined) { - return this._dragEndCallback; - } else { - this._dragEndCallback = cb; - return this; - } + public onDragEnd(callback: DragCallback) { + this._dragEndCallbacks.add(callback); + return this; + } + + /** + * Removes a callback to be called when the dragging ends. + * + * @param {DragCallback} callback The callback to be removed + * @returns {Drag} The calling Interactions.Drag. + */ + public offDragEnd(callback: DragCallback) { + this._dragEndCallbacks.delete(callback); + return this; } } } diff --git a/src/interactions/hoverInteraction.ts b/src/interactions/hoverInteraction.ts deleted file mode 100644 index b85b82ecf8..0000000000 --- a/src/interactions/hoverInteraction.ts +++ /dev/null @@ -1,184 +0,0 @@ -/// - -module Plottable { -export module Interaction { - export type HoverData = { - data: any[]; - pixelPositions: Point[]; - selection: D3.Selection; - } - - export interface Hoverable extends Component.AbstractComponent { - /** - * Called when the user first mouses over the Component. - * - * @param {Point} The cursor's position relative to the Component's origin. - */ - _hoverOverComponent(p: Point): void; - /** - * Called when the user mouses out of the Component. - * - * @param {Point} The cursor's position relative to the Component's origin. - */ - _hoverOutComponent(p: Point): void; - /** - * Returns the HoverData associated with the given position, and performs - * any visual changes associated with hovering inside a Component. - * - * @param {Point} The cursor's position relative to the Component's origin. - * @return {HoverData} The HoverData associated with the given position. - */ - _doHover(p: Point): HoverData; - } - - export class Hover extends Interaction.AbstractInteraction { - - private static warned = false; - - public _componentToListenTo: Hoverable; - private _mouseDispatcher: Dispatcher.Mouse; - private _touchDispatcher: Dispatcher.Touch; - private _hoverOverCallback: (hoverData: HoverData) => any; - private _hoverOutCallback: (hoverData: HoverData) => any; - private _overComponent = false; - - constructor() { - super(); - if (!Hover.warned) { - Hover.warned = true; - _Util.Methods.warn("Interaction.Hover is deprecated; use Interaction.Pointer in conjunction with getClosestPlotData() instead."); - } - } - - private _currentHoverData: HoverData = { - data: null, - pixelPositions: null, - selection: null - }; - - public _anchor(component: Hoverable, hitBox: D3.Selection) { - super._anchor(component, hitBox); - this._mouseDispatcher = Dispatcher.Mouse.getDispatcher( ( this._componentToListenTo)._element.node()); - this._mouseDispatcher.onMouseMove("hover" + this.getID(), (p: Point) => this._handlePointerEvent(p)); - - this._touchDispatcher = Dispatcher.Touch.getDispatcher( ( this._componentToListenTo)._element.node()); - - this._touchDispatcher.onTouchStart("hover" + this.getID(), (ids, idToPoint) => - this._handlePointerEvent(idToPoint[ids[0]])); - } - - private _handlePointerEvent(p: Point) { - p = this._translateToComponentSpace(p); - if (this._isInsideComponent(p)) { - if (!this._overComponent) { - this._componentToListenTo._hoverOverComponent(p); - } - this.handleHoverOver(p); - this._overComponent = true; - } else { - this._componentToListenTo._hoverOutComponent(p); - this.safeHoverOut(this._currentHoverData); - this._currentHoverData = { - data: null, - pixelPositions: null, - selection: null - }; - this._overComponent = false; - } - } - - /** - * Returns a HoverData consisting of all data and selections in a but not in b. - */ - private static diffHoverData(a: HoverData, b: HoverData): HoverData { - if (a.data == null || b.data == null) { - return a; - } - - var diffData: any[] = []; - var diffPoints: Point[] = []; - var diffElements: Element[] = []; - a.data.forEach((d: any, i: number) => { - if (b.data.indexOf(d) === -1) { - diffData.push(d); - diffPoints.push(a.pixelPositions[i]); - diffElements.push(a.selection[0][i]); - } - }); - - if (diffData.length === 0) { - return { - data: null, - pixelPositions: null, - selection: null - }; - } - - return { - data: diffData, - pixelPositions: diffPoints, - selection: d3.selectAll(diffElements) - }; - } - - private handleHoverOver(p: Point) { - var lastHoverData = this._currentHoverData; - var newHoverData = this._componentToListenTo._doHover(p); - - this._currentHoverData = newHoverData; - - var outData = Hover.diffHoverData(lastHoverData, newHoverData); - this.safeHoverOut(outData); - - var overData = Hover.diffHoverData(newHoverData, lastHoverData); - this.safeHoverOver(overData); - } - - private safeHoverOut(outData: HoverData) { - if (this._hoverOutCallback && outData.data) { - this._hoverOutCallback(outData); - } - } - - private safeHoverOver(overData: HoverData) { - if (this._hoverOverCallback && overData.data) { - this._hoverOverCallback(overData); - } - } - - /** - * Attaches an callback to be called when the user mouses over an element. - * - * @param {(hoverData: HoverData) => any} callback The callback to be called. - * The callback will be passed data for newly hovered-over elements. - * @return {Interaction.Hover} The calling Interaction.Hover. - */ - public onHoverOver(callback: (hoverData: HoverData) => any) { - this._hoverOverCallback = callback; - return this; - } - - /** - * Attaches a callback to be called when the user mouses off of an element. - * - * @param {(hoverData: HoverData) => any} callback The callback to be called. - * The callback will be passed data from the hovered-out elements. - * @return {Interaction.Hover} The calling Interaction.Hover. - */ - public onHoverOut(callback: (hoverData: HoverData) => any) { - this._hoverOutCallback = callback; - return this; - } - - /** - * Retrieves the HoverData associated with the elements the user is currently hovering over. - * - * @return {HoverData} The data and selection corresponding to the elements - * the user is currently hovering over. - */ - public getCurrentHoverData(): HoverData { - return this._currentHoverData; - } - } -} -} diff --git a/src/interactions/interaction.ts b/src/interactions/interaction.ts new file mode 100644 index 0000000000..e0a68fcb84 --- /dev/null +++ b/src/interactions/interaction.ts @@ -0,0 +1,84 @@ +/// + +module Plottable { + export class Interaction { + protected _componentAttachedTo: Component; + + private _anchorCallback = (component: Component) => this._anchor(component); + + private _isAnchored: boolean; + + protected _anchor(component: Component) { + this._isAnchored = true; + } + + protected _unanchor() { + this._isAnchored = false; + } + + /** + * Attaches this interaction to a Component. + * If the interaction was already attached to a Component, it first detaches itself from the old Component. + * + * @param {Component} component The component to which to attach the interaction. + * + * @return {Interaction} + */ + public attachTo(component: Component) { + if (this._componentAttachedTo) { + this.detachFrom(this._componentAttachedTo); + } + + this._componentAttachedTo = component; + component.onAnchor(this._anchorCallback); + + return this; + } + + /** + * Detaches this interaction from the Component. + * This interaction can be reused. + * + * @param {Component} component The component from which to detach the interaction. + * + * @return {Interaction} + */ + public detachFrom(component: Component) { + if (this._isAnchored) { + this._unanchor(); + } + this._componentAttachedTo = null; + component.offAnchor(this._anchorCallback); + + return this; + } + + /** + * Translates an -coordinate-space point to Component-space coordinates. + * + * @param {Point} p A Point in -space coordinates. + * + * @return {Point} The same location in Component-space coordinates. + */ + protected _translateToComponentSpace(p: Point): Point { + var origin = this._componentAttachedTo.originToSVG(); + return { + x: p.x - origin.x, + y: p.y - origin.y + }; + } + + /** + * Checks whether a Component-coordinate-space Point is inside the Component. + * + * @param {Point} p A Point in Coordinate-space coordinates. + * + * @return {boolean} Whether or not the point is inside the Component. + */ + protected _isInsideComponent(p: Point) { + return 0 <= p.x && 0 <= p.y + && p.x <= this._componentAttachedTo.width() + && p.y <= this._componentAttachedTo.height(); + } + } +} diff --git a/src/interactions/keyInteraction.ts b/src/interactions/keyInteraction.ts index a721b9907f..5eb2ab9c8b 100644 --- a/src/interactions/keyInteraction.ts +++ b/src/interactions/keyInteraction.ts @@ -1,32 +1,44 @@ /// module Plottable { -export module Interaction { - export class Key extends AbstractInteraction { +export type KeyCallback = (keyCode: number) => void; +export module Interactions { + export class Key extends Interaction { /** * KeyInteraction listens to key events that occur while the Component is * moused over. */ - private _positionDispatcher: Plottable.Dispatcher.Mouse; - private _keyDispatcher: Plottable.Dispatcher.Key; - private _keyCode2Callback: { [keyCode: string]: () => void; } = {}; + private _positionDispatcher: Plottable.Dispatchers.Mouse; + private _keyDispatcher: Plottable.Dispatchers.Key; + private _keyCodeCallbacks: { [keyCode: string]: Utils.CallbackSet } = {}; - public _anchor(component: Component.AbstractComponent, hitBox: D3.Selection) { - super._anchor(component, hitBox); + private _mouseMoveCallback = (point: Point) => false; // HACKHACK: registering a listener + private _keyDownCallback = (keyCode: number) => this._handleKeyEvent(keyCode); - this._positionDispatcher = Dispatcher.Mouse.getDispatcher( - ( this._componentToListenTo)._element.node() + protected _anchor(component: Component) { + super._anchor(component); + this._positionDispatcher = Dispatchers.Mouse.getDispatcher( + ( this._componentAttachedTo)._element.node() ); - this._positionDispatcher.onMouseMove("Interaction.Key" + this.getID(), (p: Point) => null); // HACKHACK: registering a listener + this._positionDispatcher.onMouseMove(this._mouseMoveCallback); - this._keyDispatcher = Dispatcher.Key.getDispatcher(); - this._keyDispatcher.onKeyDown("Interaction.Key" + this.getID(), (keyCode: number) => this._handleKeyEvent(keyCode)); + this._keyDispatcher = Dispatchers.Key.getDispatcher(); + this._keyDispatcher.onKeyDown(this._keyDownCallback); + } + + protected _unanchor() { + super._unanchor(); + this._positionDispatcher.offMouseMove(this._mouseMoveCallback); + this._positionDispatcher = null; + + this._keyDispatcher.offKeyDown(this._keyDownCallback); + this._keyDispatcher = null; } private _handleKeyEvent(keyCode: number) { var p = this._translateToComponentSpace(this._positionDispatcher.getLastMousePosition()); - if (this._isInsideComponent(p) && this._keyCode2Callback[keyCode]) { - this._keyCode2Callback[keyCode](); + if (this._isInsideComponent(p) && this._keyCodeCallbacks[keyCode]) { + this._keyCodeCallbacks[keyCode].callCallbacks(keyCode); } } @@ -35,11 +47,30 @@ export module Interaction { * pressed and the user is moused over the Component. * * @param {number} keyCode The key code associated with the key. - * @param {() => void} callback Callback to be called. + * @param {KeyCallback} callback Callback to be set. + * @returns The calling Interaction.Key. + */ + public onKey(keyCode: number, callback: KeyCallback) { + if (!this._keyCodeCallbacks[keyCode]) { + this._keyCodeCallbacks[keyCode] = new Utils.CallbackSet(); + } + this._keyCodeCallbacks[keyCode].add(callback); + return this; + } + + /** + * Removes the callback to be called when the key with the given keyCode is + * pressed and the user is moused over the Component. + * + * @param {number} keyCode The key code associated with the key. + * @param {KeyCallback} callback Callback to be removed. * @returns The calling Interaction.Key. */ - public on(keyCode: number, callback: () => void): Key { - this._keyCode2Callback[keyCode] = callback; + public offKey(keyCode: number, callback: KeyCallback) { + this._keyCodeCallbacks[keyCode].delete(callback); + if (this._keyCodeCallbacks[keyCode].values().length === 0) { + delete this._keyCodeCallbacks[keyCode]; + } return this; } } diff --git a/src/interactions/panZoomInteraction.ts b/src/interactions/panZoomInteraction.ts index b6cb0992b3..ca7ddd3eef 100644 --- a/src/interactions/panZoomInteraction.ts +++ b/src/interactions/panZoomInteraction.ts @@ -1,12 +1,27 @@ /// module Plottable { -export module Interaction { - export class PanZoom extends AbstractInteraction { +export module Interactions { + export class PanZoom extends Interaction { - private _zoom: D3.Behavior.Zoom; - private _xScale: Scale.AbstractQuantitative; - private _yScale: Scale.AbstractQuantitative; + /** + * The number of pixels occupied in a line. + */ + public static PIXELS_PER_LINE = 120; + + private _xScale: QuantitativeScale; + private _yScale: QuantitativeScale; + private _dragInteraction: Interactions.Drag; + private _mouseDispatcher: Dispatchers.Mouse; + private _touchDispatcher: Dispatchers.Touch; + + private _touchIds: D3.Map; + + private _wheelCallback = (p: Point, e: WheelEvent) => this._handleWheelEvent(p, e); + private _touchStartCallback = (ids: number[], idToPoint: Point[], e: TouchEvent) => this._handleTouchStart(ids, idToPoint, e); + private _touchMoveCallback = (ids: number[], idToPoint: Point[], e: TouchEvent) => this._handlePinch(ids, idToPoint, e); + private _touchEndCallback = (ids: number[], idToPoint: Point[], e: TouchEvent) => this._handleTouchEnd(ids, idToPoint, e); + private _touchCancelCallback = (ids: number[], idToPoint: Point[], e: TouchEvent) => this._handleTouchEnd(ids, idToPoint, e); /** * Creates a PanZoomInteraction. @@ -18,58 +33,157 @@ export module Interaction { * @param {QuantitativeScale} [xScale] The X scale to update on panning/zooming. * @param {QuantitativeScale} [yScale] The Y scale to update on panning/zooming. */ - constructor(xScale?: Scale.AbstractQuantitative, yScale?: Scale.AbstractQuantitative) { + constructor(xScale?: QuantitativeScale, yScale?: QuantitativeScale) { super(); - if (xScale) { - this._xScale = xScale; - // HACKHACK #1388: self-register for resetZoom() - this._xScale.broadcaster.registerListener("pziX" + this.getID(), () => this.resetZoom()); - } - if (yScale) { - this._yScale = yScale; - // HACKHACK #1388: self-register for resetZoom() - this._yScale.broadcaster.registerListener("pziY" + this.getID(), () => this.resetZoom()); + this._xScale = xScale; + this._yScale = yScale; + + this._dragInteraction = new Interactions.Drag(); + this._setupDragInteraction(); + this._touchIds = d3.map(); + } + + protected _anchor(component: Component) { + super._anchor(component); + this._dragInteraction.attachTo(component); + + this._mouseDispatcher = Dispatchers.Mouse.getDispatcher( this._componentAttachedTo.content().node()); + this._mouseDispatcher.onWheel(this._wheelCallback); + + this._touchDispatcher = Dispatchers.Touch.getDispatcher( this._componentAttachedTo.content().node()); + this._touchDispatcher.onTouchStart(this._touchStartCallback); + this._touchDispatcher.onTouchMove(this._touchMoveCallback); + this._touchDispatcher.onTouchEnd(this._touchEndCallback); + this._touchDispatcher.onTouchCancel(this._touchCancelCallback); + } + + protected _unanchor() { + super._unanchor(); + this._mouseDispatcher.offWheel(this._wheelCallback); + this._mouseDispatcher = null; + + this._touchDispatcher.offTouchStart(this._touchStartCallback); + this._touchDispatcher.offTouchMove(this._touchMoveCallback); + this._touchDispatcher.offTouchEnd(this._touchEndCallback); + this._touchDispatcher.offTouchCancel(this._touchCancelCallback); + this._touchDispatcher = null; + + this._dragInteraction.detachFrom(this._componentAttachedTo); + } + + private _handleTouchStart(ids: number[], idToPoint: { [id: number]: Point; }, e: TouchEvent) { + for (var i = 0; i < ids.length && this._touchIds.size() < 2; i++) { + var id = ids[i]; + this._touchIds.set(id.toString(), this._translateToComponentSpace(idToPoint[id])); } } - /** - * Sets the scales back to their original domains. - */ - public resetZoom() { - // HACKHACK #254 - this._zoom = d3.behavior.zoom(); - if (this._xScale) { - this._zoom.x(( this._xScale)._d3Scale); + private _handlePinch(ids: number[], idToPoint: { [id: number]: Point; }, e: TouchEvent) { + if (this._touchIds.size() < 2) { + return; } - if (this._yScale) { - this._zoom.y(( this._yScale)._d3Scale); + + var oldCenterPoint = this.centerPoint(); + var oldCornerDistance = this.cornerDistance(); + + ids.forEach((id) => { + if (this._touchIds.has(id.toString())) { + this._touchIds.set(id.toString(), this._translateToComponentSpace(idToPoint[id])); + } + }); + + var newCenterPoint = this.centerPoint(); + var newCornerDistance = this.cornerDistance(); + + if (this._xScale != null && newCornerDistance !== 0 && oldCornerDistance !== 0) { + PanZoom.magnifyScale(this._xScale, oldCornerDistance / newCornerDistance, oldCenterPoint.x); + PanZoom.translateScale(this._xScale, oldCenterPoint.x - newCenterPoint.x); + } + if (this._yScale != null && newCornerDistance !== 0 && oldCornerDistance !== 0) { + PanZoom.magnifyScale(this._yScale, oldCornerDistance / newCornerDistance, oldCenterPoint.y); + PanZoom.translateScale(this._yScale, oldCenterPoint.y - newCenterPoint.y); } - this._zoom.on("zoom", () => this._rerenderZoomed()); - this._zoom(this._hitBox); } - public _anchor(component: Component.AbstractComponent, hitBox: D3.Selection) { - super._anchor(component, hitBox); - this.resetZoom(); + private centerPoint() { + var points = this._touchIds.values(); + var firstTouchPoint = points[0]; + var secondTouchPoint = points[1]; + + var leftX = Math.min(firstTouchPoint.x, secondTouchPoint.x); + var rightX = Math.max(firstTouchPoint.x, secondTouchPoint.x); + var topY = Math.min(firstTouchPoint.y, secondTouchPoint.y); + var bottomY = Math.max(firstTouchPoint.y, secondTouchPoint.y); + + return {x: (leftX + rightX) / 2, y: (bottomY + topY) / 2}; } - public _requiresHitbox() { - return true; + private cornerDistance() { + var points = this._touchIds.values(); + var firstTouchPoint = points[0]; + var secondTouchPoint = points[1]; + + var leftX = Math.min(firstTouchPoint.x, secondTouchPoint.x); + var rightX = Math.max(firstTouchPoint.x, secondTouchPoint.x); + var topY = Math.min(firstTouchPoint.y, secondTouchPoint.y); + var bottomY = Math.max(firstTouchPoint.y, secondTouchPoint.y); + + return Math.sqrt(Math.pow(rightX - leftX, 2) + Math.pow(bottomY - topY, 2)); } - private _rerenderZoomed() { - // HACKHACK since the d3.zoom.x modifies d3 scales and not our TS scales, and the TS scales have the - // event listener machinery, let's grab the domain out of the d3 scale and pipe it back into the TS scale - if (this._xScale) { - var xDomain = ( this._xScale)._d3Scale.domain(); - this._xScale.domain(xDomain); - } + private _handleTouchEnd(ids: number[], idToPoint: { [id: number]: Point; }, e: TouchEvent) { + ids.forEach((id) => { + this._touchIds.remove(id.toString()); + }); + } - if (this._yScale) { - var yDomain = ( this._yScale)._d3Scale.domain(); - this._yScale.domain(yDomain); + private static magnifyScale(scale: QuantitativeScale, magnifyAmount: number, centerValue: number) { + var magnifyTransform = (rangeValue: number) => scale.invert(centerValue - (centerValue - rangeValue) * magnifyAmount); + scale.domain(scale.range().map(magnifyTransform)); + } + + private static translateScale(scale: QuantitativeScale, translateAmount: number) { + var translateTransform = (rangeValue: number) => scale.invert(rangeValue + translateAmount); + scale.domain(scale.range().map(translateTransform)); + } + + private _handleWheelEvent(p: Point, e: WheelEvent) { + var translatedP = this._translateToComponentSpace(p); + if (this._isInsideComponent(translatedP)) { + e.preventDefault(); + + var deltaPixelAmount = e.deltaY * (e.deltaMode ? PanZoom.PIXELS_PER_LINE : 1); + var zoomAmount = Math.pow(2, deltaPixelAmount * .002); + if (this._xScale != null) { + PanZoom.magnifyScale(this._xScale, zoomAmount, translatedP.x); + } + if (this._yScale != null) { + PanZoom.magnifyScale(this._yScale, zoomAmount, translatedP.y); + } } } + + private _setupDragInteraction() { + this._dragInteraction.constrainToComponent(false); + + var lastDragPoint: Point; + this._dragInteraction.onDragStart(() => lastDragPoint = null); + this._dragInteraction.onDrag((startPoint, endPoint) => { + if (this._touchIds.size() >= 2) { + return; + } + if (this._xScale != null) { + var dragAmountX = endPoint.x - (lastDragPoint == null ? startPoint.x : lastDragPoint.x); + PanZoom.translateScale(this._xScale, -dragAmountX); + } + if (this._yScale != null) { + var dragAmountY = endPoint.y - (lastDragPoint == null ? startPoint.y : lastDragPoint.y); + PanZoom.translateScale(this._yScale, -dragAmountY); + } + lastDragPoint = endPoint; + }); + } + } } } diff --git a/src/interactions/pointerInteraction.ts b/src/interactions/pointerInteraction.ts index 4d5716d4e3..e040d2f370 100644 --- a/src/interactions/pointerInteraction.ts +++ b/src/interactions/pointerInteraction.ts @@ -1,24 +1,36 @@ /// module Plottable { -export module Interaction { - export class Pointer extends Interaction.AbstractInteraction { - private _mouseDispatcher: Dispatcher.Mouse; - private _touchDispatcher: Dispatcher.Touch; + +export type PointerCallback = (point: Point) => any; +export module Interactions { + export class Pointer extends Interaction { + private _mouseDispatcher: Dispatchers.Mouse; + private _touchDispatcher: Dispatchers.Touch; private _overComponent = false; - private _pointerEnterCallback: (p: Point) => any; - private _pointerMoveCallback: (p: Point) => any; - private _pointerExitCallback: (p: Point) => any; + private _pointerEnterCallbacks = new Utils.CallbackSet(); + private _pointerMoveCallbacks = new Utils.CallbackSet(); + private _pointerExitCallbacks = new Utils.CallbackSet(); + + private _mouseMoveCallback = (p: Point) => this._handlePointerEvent(p); + private _touchStartCallback = (ids: number[], idToPoint: Point[]) => this._handlePointerEvent(idToPoint[ids[0]]); - public _anchor(component: Component.AbstractComponent, hitBox: D3.Selection) { - super._anchor(component, hitBox); - this._mouseDispatcher = Dispatcher.Mouse.getDispatcher( this._componentToListenTo.content().node()); - this._mouseDispatcher.onMouseMove("Interaction.Pointer" + this.getID(), (p: Point) => this._handlePointerEvent(p)); + protected _anchor(component: Component) { + super._anchor(component); + this._mouseDispatcher = Dispatchers.Mouse.getDispatcher( this._componentAttachedTo.content().node()); + this._mouseDispatcher.onMouseMove(this._mouseMoveCallback); + + this._touchDispatcher = Dispatchers.Touch.getDispatcher( this._componentAttachedTo.content().node()); + this._touchDispatcher.onTouchStart(this._touchStartCallback); + } - this._touchDispatcher = Dispatcher.Touch.getDispatcher( this._componentToListenTo.content().node()); + protected _unanchor() { + super._unanchor(); + this._mouseDispatcher.offMouseMove(this._mouseMoveCallback); + this._mouseDispatcher = null; - this._touchDispatcher.onTouchStart("Interaction.Pointer" + this.getID(), (ids, idToPoint) => - this._handlePointerEvent(idToPoint[ids[0]])); + this._touchDispatcher.offTouchStart(this._touchStartCallback); + this._touchDispatcher = null; } private _handlePointerEvent(p: Point) { @@ -26,80 +38,79 @@ export module Interaction { if (this._isInsideComponent(translatedP)) { var wasOverComponent = this._overComponent; this._overComponent = true; - if (!wasOverComponent && this._pointerEnterCallback) { - this._pointerEnterCallback(translatedP); - } - if (this._pointerMoveCallback) { - this._pointerMoveCallback(translatedP); + if (!wasOverComponent) { + this._pointerEnterCallbacks.callCallbacks(translatedP); } + this._pointerMoveCallbacks.callCallbacks(translatedP); } else if (this._overComponent) { this._overComponent = false; - if (this._pointerExitCallback) { - this._pointerExitCallback(translatedP); - } + this._pointerExitCallbacks.callCallbacks(translatedP); } } - /** - * Gets the callback called when the pointer enters the Component. - * - * @return {(p: Point) => any} The current callback. - */ - public onPointerEnter(): (p: Point) => any; /** * Sets the callback called when the pointer enters the Component. * - * @param {(p: Point) => any} callback The callback to set. + * @param {PointerCallback} callback The callback to set. * @return {Interaction.Pointer} The calling Interaction.Pointer. */ - public onPointerEnter(callback: (p: Point) => any): Interaction.Pointer; - public onPointerEnter(callback?: (p: Point) => any): any { - if (callback === undefined) { - return this._pointerEnterCallback; - } - this._pointerEnterCallback = callback; + public onPointerEnter(callback: PointerCallback) { + this._pointerEnterCallbacks.add(callback); return this; } /** - * Gets the callback called when the pointer moves. + * Removes a callback called when the pointer enters the Component. * - * @return {(p: Point) => any} The current callback. + * @param {PointerCallback} callback The callback to remove. + * @return {Interaction.Pointer} The calling Interaction.Pointer. */ - public onPointerMove(): (p: Point) => any; + public offPointerEnter(callback: PointerCallback) { + this._pointerEnterCallbacks.delete(callback); + return this; + } + /** * Sets the callback called when the pointer moves. * - * @param {(p: Point) => any} callback The callback to set. + * @param {PointerCallback} callback The callback to set. * @return {Interaction.Pointer} The calling Interaction.Pointer. */ - public onPointerMove(callback: (p: Point) => any): Interaction.Pointer; - public onPointerMove(callback?: (p: Point) => any): any { - if (callback === undefined) { - return this._pointerMoveCallback; - } - this._pointerMoveCallback = callback; + public onPointerMove(callback: PointerCallback) { + this._pointerMoveCallbacks.add(callback); return this; } /** - * Gets the callback called when the pointer exits the Component. + * Removes a callback called when the pointer moves. * - * @return {(p: Point) => any} The current callback. + * @param {PointerCallback} callback The callback to remove. + * @return {Interaction.Pointer} The calling Interaction.Pointer. */ - public onPointerExit(): (p: Point) => any; + public offPointerMove(callback: PointerCallback) { + this._pointerMoveCallbacks.delete(callback); + return this; + } + /** * Sets the callback called when the pointer exits the Component. * - * @param {(p: Point) => any} callback The callback to set. + * @param {PointerCallback} callback The callback to set. * @return {Interaction.Pointer} The calling Interaction.Pointer. */ - public onPointerExit(callback: (p: Point) => any): Interaction.Pointer; - public onPointerExit(callback?: (p: Point) => any): any { - if (callback === undefined) { - return this._pointerExitCallback; - } - this._pointerExitCallback = callback; + public onPointerExit(callback: PointerCallback) { + this._pointerExitCallbacks.add(callback); + return this; + } + + /** + * Removes a callback called when the pointer exits the Component. + * + * @param {PointerCallback} callback The callback to remove. + * @return {Interaction.Pointer} The calling Interaction.Pointer. + */ + public offPointerExit(callback: PointerCallback) { + this._pointerExitCallbacks.delete(callback); return this; } } diff --git a/src/reference.ts b/src/reference.ts index 223303771e..b8455f519e 100644 --- a/src/reference.ts +++ b/src/reference.ts @@ -1,10 +1,12 @@ +/// /// /// -/// -/// +/// +/// /// /// +/// /// /// @@ -14,8 +16,6 @@ /// /// /// -/// -/// /// /// @@ -23,19 +23,17 @@ /// /// -/// +/// /// /// -/// /// /// /// /// /// -/// /// -/// +/// /// /// /// @@ -43,11 +41,11 @@ /// /// -/// -/// -/// +/// +/// +/// -/// +/// /// /// /// @@ -59,9 +57,9 @@ /// /// -/// +/// /// -/// +/// /// /// /// @@ -69,7 +67,7 @@ /// /// /// -/// +/// /// /// @@ -79,19 +77,18 @@ /// /// -/// +/// /// /// /// -/// +/// /// /// /// /// /// /// -/// /// /// diff --git a/src/scales/categoryScale.ts b/src/scales/categoryScale.ts index 47bcab1a0a..4008340a69 100644 --- a/src/scales/categoryScale.ts +++ b/src/scales/categoryScale.ts @@ -1,14 +1,13 @@ /// module Plottable { -export module Scale { - export class Category extends AbstractScale { +export module Scales { + export class Category extends Scale { protected _d3Scale: D3.Scale.OrdinalScale; private _range = [0, 1]; private _innerPadding: number; private _outerPadding: number; - public _typeCoercer: (d: any) => any = (d: any) => d != null && d.toString ? d.toString() : d; /** * Creates a CategoryScale. @@ -28,7 +27,7 @@ export module Scale { protected _getExtent(): string[] { var extents: string[][] = this._getAllExtents(); - return _Util.Methods.uniq(_Util.Methods.flatten(extents)); + return Utils.Methods.uniq(Utils.Methods.flatten(extents)); } public domain(): string[]; @@ -69,7 +68,7 @@ export module Scale { * * @returns {number} The range band width */ - public rangeBand() : number { + public rangeBand(): number { return this._d3Scale.rangeBand(); } @@ -109,7 +108,7 @@ export module Scale { } this._innerPadding = innerPadding; this.range(this.range()); - this.broadcaster.broadcast(); + this._dispatchUpdate(); return this; } @@ -137,16 +136,12 @@ export module Scale { } this._outerPadding = outerPadding; this.range(this.range()); - this.broadcaster.broadcast(); + this._dispatchUpdate(); return this; } - public copy(): Category { - return new Category(this._d3Scale.copy()); - } - public scale(value: string): number { - //scale it to the middle + // scale it to the middle return super.scale(value) + this.rangeBand() / 2; } } diff --git a/src/scales/colorScale.ts b/src/scales/colorScale.ts index 93a6824b6b..03fd61c589 100644 --- a/src/scales/colorScale.ts +++ b/src/scales/colorScale.ts @@ -1,12 +1,11 @@ /// module Plottable { -export module Scale { - export class Color extends AbstractScale { +export module Scales { + export class Color extends Scale { - private static HEX_SCALE_FACTOR = 20; private static LOOP_LIGHTEN_FACTOR = 1.6; - //The maximum number of colors we are getting from CSS stylesheets + // The maximum number of colors we are getting from CSS stylesheets private static MAXIMUM_COLORS_FROM_CSS = 256; /** @@ -57,17 +56,17 @@ export module Scale { extents.forEach((e) => { concatenatedExtents = concatenatedExtents.concat(e); }); - return _Util.Methods.uniq(concatenatedExtents); + return Utils.Methods.uniq(concatenatedExtents); } private static _getPlottableColors(): string[] { var plottableDefaultColors: string[] = []; var colorTester = d3.select("body").append("plottable-color-tester"); - var defaultColorHex: string = _Util.Methods.colorTest(colorTester, ""); + var defaultColorHex: string = Utils.Methods.colorTest(colorTester, ""); var i = 0; var colorHex: string; - while ((colorHex = _Util.Methods.colorTest(colorTester, "plottable-colors-" + i)) !== null && + while ((colorHex = Utils.Methods.colorTest(colorTester, "plottable-colors-" + i)) !== null && i < this.MAXIMUM_COLORS_FROM_CSS) { if (colorHex === defaultColorHex && colorHex === plottableDefaultColors[plottableDefaultColors.length - 1]) { break; @@ -86,7 +85,7 @@ export module Scale { var index = this.domain().indexOf(value); var numLooped = Math.floor(index / this.range().length); var modifyFactor = Math.log(numLooped * Color.LOOP_LIGHTEN_FACTOR + 1); - return _Util.Methods.lightenColor(color, modifyFactor); + return Utils.Methods.lightenColor(color, modifyFactor); } } } diff --git a/src/scales/interpolatedColorScale.ts b/src/scales/interpolatedColorScale.ts index eb7851e47d..bdffab8f89 100644 --- a/src/scales/interpolatedColorScale.ts +++ b/src/scales/interpolatedColorScale.ts @@ -1,8 +1,7 @@ /// module Plottable { -export module Scale { - type ColorGroups = { [key: string]: string[]; }; +export module Scales { /** * This class implements a color scale that takes quantitive input and @@ -11,99 +10,103 @@ export module Scale { * * By default it generates a linear scale internally. */ - export class InterpolatedColor extends AbstractScale { - private static _COLOR_SCALES: ColorGroups = { - reds : [ - "#FFFFFF", // white - "#FFF6E1", - "#FEF4C0", - "#FED976", - "#FEB24C", - "#FD8D3C", - "#FC4E2A", - "#E31A1C", - "#B10026" // red - ], - blues : [ - "#FFFFFF", // white - "#CCFFFF", - "#A5FFFD", - "#85F7FB", - "#6ED3EF", - "#55A7E0", - "#417FD0", - "#2545D3", - "#0B02E1" // blue - ], - posneg : [ - "#0B02E1", // blue - "#2545D3", - "#417FD0", - "#55A7E0", - "#6ED3EF", - "#85F7FB", - "#A5FFFD", - "#CCFFFF", - "#FFFFFF", // white - "#FFF6E1", - "#FEF4C0", - "#FED976", - "#FEB24C", - "#FD8D3C", - "#FC4E2A", - "#E31A1C", - "#B10026" // red - ] - }; + export class InterpolatedColor extends Scale { + public static REDS = [ + "#FFFFFF", // white + "#FFF6E1", + "#FEF4C0", + "#FED976", + "#FEB24C", + "#FD8D3C", + "#FC4E2A", + "#E31A1C", + "#B10026" // red + ]; + public static BLUES = [ + "#FFFFFF", // white + "#CCFFFF", + "#A5FFFD", + "#85F7FB", + "#6ED3EF", + "#55A7E0", + "#417FD0", + "#2545D3", + "#0B02E1" // blue + ]; + public static POSNEG = [ + "#0B02E1", // blue + "#2545D3", + "#417FD0", + "#55A7E0", + "#6ED3EF", + "#85F7FB", + "#A5FFFD", + "#CCFFFF", + "#FFFFFF", // white + "#FFF6E1", + "#FEF4C0", + "#FED976", + "#FEB24C", + "#FD8D3C", + "#FC4E2A", + "#E31A1C", + "#B10026" // red + ]; + private _colorRange: string[]; + private _colorScale: D3.Scale.QuantitativeScale; /** - * Converts the string array into a d3 scale. - * - * @param {string[]} colors an array of strings representing color - * values in hex ("#FFFFFF") or keywords ("white"). + * An InterpolatedColorScale maps numbers to color strings. + * + * @param {string[]} colors an array of strings representing color values in hex + * ("#FFFFFF") or keywords ("white"). Defaults to InterpolatedColor.REDS * @param {string} scaleType a string representing the underlying scale - * type ("linear"/"log"/"sqrt"/"pow") - * @returns {D3.Scale.QuantitativeScale} The converted Quantitative d3 scale. + * type ("linear"/"log"/"sqrt"/"pow"). Defaults to "linear" + * @returns {D3.Scale.QuantitativeScale} The converted QuantitativeScale d3 scale. */ - private static _getD3InterpolatedScale(colors: string[], scaleType: string): D3.Scale.QuantitativeScale { - var scale: D3.Scale.QuantitativeScale; - switch(scaleType){ + constructor(colorRange = InterpolatedColor.REDS, scaleType = "linear") { + this._colorRange = colorRange; + switch (scaleType) { case "linear": - scale = d3.scale.linear(); + this._colorScale = d3.scale.linear(); break; case "log": - scale = d3.scale.log(); + this._colorScale = d3.scale.log(); break; case "sqrt": - scale = d3.scale.sqrt(); + this._colorScale = d3.scale.sqrt(); break; case "pow": - scale = d3.scale.pow(); + this._colorScale = d3.scale.pow(); break; } - if (scale == null){ - throw new Error("unknown Quantitative scale type " + scaleType); + if (this._colorScale == null) { + throw new Error("unknown QuantitativeScale scale type " + scaleType); } - return scale - .range([0, 1]) - .interpolate(InterpolatedColor._interpolateColors(colors)); + super(this._D3InterpolatedScale()); } /** - * Creates a d3 interpolator given the color array. - * - * This class implements a scale that maps numbers to strings. - * - * @param {string[]} colors an array of strings representing color - * values in hex ("#FFFFFF") or keywords ("white"). - * @returns {D3.Transition.Interpolate} The d3 interpolator for colors. + * Generates the converted QuantitativeScale. + * + * @returns {D3.Scale.QuantitativeScale} The converted d3 QuantitativeScale + */ + private _D3InterpolatedScale(): D3.Scale.QuantitativeScale { + return this._colorScale.range([0, 1]).interpolate(this._interpolateColors()); + } + + /** + * Generates the d3 interpolator for colors. + * + * @return {D3.Transition.Interpolate} The d3 interpolator for colors. */ - private static _interpolateColors(colors: string[]): D3.Transition.Interpolate { + private _interpolateColors(): D3.Transition.Interpolate { + var colors = this._colorRange; if (colors.length < 2) { throw new Error("Color scale arrays must have at least two elements."); }; - return (ignored: any): any => { - return (t: any): any => { + return (ignored: any) => { + return (t: number) => { // Clamp t parameter to [0,1] t = Math.max(0, Math.min(1, t)); @@ -119,28 +122,6 @@ export module Scale { }; } - private _colorRange: string[]; - private _scaleType: string; - - /** - * Constructs an InterpolatedColorScale. - * - * An InterpolatedColorScale maps numbers evenly to color strings. - * - * @constructor - * @param {string|string[]} colorRange the type of color scale to - * create. Default is "reds". @see {@link colorRange} for further - * options. - * @param {string} scaleType the type of underlying scale to use - * (linear/pow/log/sqrt). Default is "linear". @see {@link scaleType} - * for further options. - */ - constructor(colorRange: any = "reds", scaleType: string = "linear") { - this._colorRange = this._resolveColorValues(colorRange); - this._scaleType = scaleType; - super(InterpolatedColor._getD3InterpolatedScale(this._colorRange, this._scaleType)); - } - /** * Gets the color range. * @@ -150,66 +131,34 @@ export module Scale { /** * Sets the color range. * - * @param {string|string[]} [colorRange]. If provided and if colorRange is one of + * @param {string[]} [colorRange]. If provided and if colorRange is one of * (reds/blues/posneg), uses the built-in color groups. If colorRange is an * array of strings with at least 2 values (e.g. ["#FF00FF", "red", * "dodgerblue"], the resulting scale will interpolate between the color * values across the domain. * @returns {InterpolatedColor} The calling InterpolatedColor. */ - public colorRange(colorRange: string | string[]): InterpolatedColor; - public colorRange(colorRange?: string | string[]): any { + public colorRange(colorRange: string[]): InterpolatedColor; + public colorRange(colorRange?: string[]): any { if (colorRange == null) { return this._colorRange; } - this._colorRange = this._resolveColorValues(colorRange); - this._resetScale(); - return this; - } - - /** - * Gets the internal scale type. - * - * @returns {string} The current scale type. - */ - public scaleType(): string; - /** - * Sets the internal scale type. - * - * @param {string} scaleType If provided, the type of d3 scale to use internally. (linear/log/sqrt/pow). - * @returns {InterpolatedColor} The calling InterpolatedColor. - */ - public scaleType(scaleType: string): InterpolatedColor; - public scaleType(scaleType?: string): any { - if (scaleType == null){ - return this._scaleType; - } - this._scaleType = scaleType; + this._colorRange = colorRange; this._resetScale(); return this; } private _resetScale(): any { - this._d3Scale = InterpolatedColor._getD3InterpolatedScale(this._colorRange, this._scaleType); + this._d3Scale = this._D3InterpolatedScale(); this._autoDomainIfAutomaticMode(); - this.broadcaster.broadcast(); - } - - private _resolveColorValues(colorRange: string | string[]): string[] { - if (typeof(colorRange) === "object") { - return colorRange; - } else if (InterpolatedColor._COLOR_SCALES[ colorRange] != null) { - return InterpolatedColor._COLOR_SCALES[ colorRange]; - } else { - return InterpolatedColor._COLOR_SCALES["reds"]; - } + this._dispatchUpdate(); } public autoDomain() { // unlike other QuantitativeScales, interpolatedColorScale ignores its domainer var extents = this._getAllExtents(); if (extents.length > 0) { - this._setDomain([_Util.Methods.min(extents, (x) => x[0], 0), _Util.Methods.max(extents, (x) => x[1], 0)]); + this._setDomain([Utils.Methods.min(extents, (x) => x[0], 0), Utils.Methods.max(extents, (x) => x[1], 0)]); } return this; } diff --git a/src/scales/linearScale.ts b/src/scales/linearScale.ts index d798d327b7..a44a04f33c 100644 --- a/src/scales/linearScale.ts +++ b/src/scales/linearScale.ts @@ -1,8 +1,8 @@ /// module Plottable { -export module Scale { - export class Linear extends AbstractQuantitative { +export module Scales { + export class Linear extends QuantitativeScale { /** * Constructs a new LinearScale. @@ -19,14 +19,8 @@ export module Scale { super(scale == null ? d3.scale.linear() : scale); } - /** - * Constructs a copy of the LinearScale with the same domain and range but - * without any registered listeners. - * - * @returns {Linear} A copy of the calling LinearScale. - */ - public copy(): Linear { - return new Linear(this._d3Scale.copy()); + public _defaultExtent(): number[] { + return [0, 1]; } } } diff --git a/src/scales/logScale.ts b/src/scales/logScale.ts deleted file mode 100644 index 6e4a23aff1..0000000000 --- a/src/scales/logScale.ts +++ /dev/null @@ -1,45 +0,0 @@ -/// - -module Plottable { -export module Scale { - export class Log extends AbstractQuantitative { - - private static warned = false; - - /** - * Constructs a new Scale.Log. - * - * Warning: Log is deprecated; if possible, use ModifiedLog. Log scales are - * very unstable due to the fact that they can't handle 0 or negative - * numbers. The only time when you would want to use a Log scale over a - * ModifiedLog scale is if you're plotting very small data, such as all - * data < 1. - * - * @constructor - * @param {D3.Scale.LogScale} [scale] The D3 Scale.Log backing the Scale.Log. If not supplied, uses a default scale. - */ - constructor(); - constructor(scale: D3.Scale.LogScale); - constructor(scale?: any) { - super(scale == null ? d3.scale.log() : scale); - if (!Log.warned) { - Log.warned = true; - _Util.Methods.warn("Plottable.Scale.Log is deprecated. If possible, use Plottable.Scale.ModifiedLog instead."); - } - } - - /** - * Creates a copy of the Scale.Log with the same domain and range but without any registered listeners. - * - * @returns {Log} A copy of the calling Log. - */ - public copy(): Log { - return new Log(this._d3Scale.copy()); - } - - public _defaultExtent(): number[] { - return [1, 10]; - } - } -} -} diff --git a/src/scales/modifiedLogScale.ts b/src/scales/modifiedLogScale.ts index 651b3b4210..ad34f2de90 100644 --- a/src/scales/modifiedLogScale.ts +++ b/src/scales/modifiedLogScale.ts @@ -1,11 +1,11 @@ /// module Plottable { -export module Scale { - export class ModifiedLog extends AbstractQuantitative { - private base: number; - private pivot: number; - private untransformedDomain: number[]; +export module Scales { + export class ModifiedLog extends QuantitativeScale { + private _base: number; + private _pivot: number; + private _untransformedDomain: number[]; private _showIntermediateTicks = false; /** @@ -35,10 +35,9 @@ export module Scale { */ constructor(base = 10) { super(d3.scale.linear()); - this.base = base; - this.pivot = this.base; - this.untransformedDomain = this._defaultExtent(); - this.numTicks(10); + this._base = base; + this._pivot = this._base; + this._setDomain(this._defaultExtent()); if (base <= 1) { throw new Error("ModifiedLogScale: The base must be > 1"); } @@ -56,11 +55,11 @@ export module Scale { var negationFactor = x < 0 ? -1 : 1; x *= negationFactor; - if (x < this.pivot) { - x += (this.pivot - x) / this.pivot; + if (x < this._pivot) { + x += (this._pivot - x) / this._pivot; } - x = Math.log(x) / Math.log(this.base); + x = Math.log(x) / Math.log(this._base); x *= negationFactor; return x; @@ -70,10 +69,10 @@ export module Scale { var negationFactor = x < 0 ? -1 : 1; x *= negationFactor; - x = Math.pow(this.base, x); + x = Math.pow(this._base, x); - if (x < this.pivot) { - x = (this.pivot * (x - 1)) / (this.pivot - 1); + if (x < this._pivot) { + x = (this._pivot * (x - 1)) / (this._pivot - 1); } x *= negationFactor; @@ -89,39 +88,38 @@ export module Scale { } protected _getDomain() { - return this.untransformedDomain; + return this._untransformedDomain; } protected _setDomain(values: number[]) { - this.untransformedDomain = values; + this._untransformedDomain = values; var transformedDomain = [this.adjustedLog(values[0]), this.adjustedLog(values[1])]; - this._d3Scale.domain(transformedDomain); - this.broadcaster.broadcast(); + super._setDomain(transformedDomain); } - public ticks(count = this.numTicks()): number[] { + public ticks(): number[] { // Say your domain is [-100, 100] and your pivot is 10. // then we're going to draw negative log ticks from -100 to -10, // linear ticks from -10 to 10, and positive log ticks from 10 to 100. var middle = (x: number, y: number, z: number) => [x, y, z].sort((a, b) => a - b)[1]; - var min = _Util.Methods.min(this.untransformedDomain, 0); - var max = _Util.Methods.max(this.untransformedDomain, 0); + var min = Utils.Methods.min(this._untransformedDomain, 0); + var max = Utils.Methods.max(this._untransformedDomain, 0); var negativeLower = min; - var negativeUpper = middle(min, max, -this.pivot); - var positiveLower = middle(min, max, this.pivot); + var negativeUpper = middle(min, max, -this._pivot); + var positiveLower = middle(min, max, this._pivot); var positiveUpper = max; var negativeLogTicks = this.logTicks(-negativeUpper, -negativeLower).map((x) => -x).reverse(); var positiveLogTicks = this.logTicks(positiveLower, positiveUpper); var linearTicks = this._showIntermediateTicks ? d3.scale.linear().domain([negativeUpper, positiveLower]) - .ticks(this.howManyTicks(negativeUpper, positiveLower)) : - [-this.pivot, 0, this.pivot].filter((x) => min <= x && x <= max); + .ticks(this._howManyTicks(negativeUpper, positiveLower)) : + [-this._pivot, 0, this._pivot].filter((x) => min <= x && x <= max); var ticks = negativeLogTicks.concat(linearTicks).concat(positiveLogTicks); // If you only have 1 tick, you can't tell how big the scale is. if (ticks.length <= 1) { - ticks = d3.scale.linear().domain([min, max]).ticks(count); + ticks = d3.scale.linear().domain([min, max]).ticks(ModifiedLog._DEFAULT_NUM_TICKS); } return ticks; } @@ -140,18 +138,18 @@ export module Scale { * drastically exceeding its number of ticks. */ private logTicks(lower: number, upper: number): number[] { - var nTicks = this.howManyTicks(lower, upper); + var nTicks = this._howManyTicks(lower, upper); if (nTicks === 0) { return []; } - var startLogged = Math.floor(Math.log(lower) / Math.log(this.base)); - var endLogged = Math.ceil(Math.log(upper) / Math.log(this.base)); + var startLogged = Math.floor(Math.log(lower) / Math.log(this._base)); + var endLogged = Math.ceil(Math.log(upper) / Math.log(this._base)); var bases = d3.range(endLogged, startLogged, -Math.ceil((endLogged - startLogged) / nTicks)); var nMultiples = this._showIntermediateTicks ? Math.floor(nTicks / bases.length) : 1; - var multiples = d3.range(this.base, 1, -(this.base - 1) / nMultiples).map(Math.floor); - var uniqMultiples = _Util.Methods.uniq(multiples); - var clusters = bases.map((b) => uniqMultiples.map((x) => Math.pow(this.base, b - 1) * x)); - var flattened = _Util.Methods.flatten(clusters); + var multiples = d3.range(this._base, 1, -(this._base - 1) / nMultiples).map(Math.floor); + var uniqMultiples = Utils.Methods.uniq(multiples); + var clusters = bases.map((b) => uniqMultiples.map((x) => Math.pow(this._base, b - 1) * x)); + var flattened = Utils.Methods.flatten(clusters); var filtered = flattened.filter((x) => lower <= x && x <= upper); var sorted = filtered.sort((x, y) => x - y); return sorted; @@ -160,25 +158,21 @@ export module Scale { /** * How many ticks does the range [lower, upper] deserve? * - * e.g. if your domain was [10, 1000] and I asked howManyTicks(10, 100), + * e.g. if your domain was [10, 1000] and I asked _howManyTicks(10, 100), * I would get 1/2 of the ticks. The range 10, 100 takes up 1/2 of the * distance when plotted. */ - private howManyTicks(lower: number, upper: number): number { - var adjustedMin = this.adjustedLog(_Util.Methods.min(this.untransformedDomain, 0)); - var adjustedMax = this.adjustedLog(_Util.Methods.max(this.untransformedDomain, 0)); + private _howManyTicks(lower: number, upper: number): number { + var adjustedMin = this.adjustedLog(Utils.Methods.min(this._untransformedDomain, 0)); + var adjustedMax = this.adjustedLog(Utils.Methods.max(this._untransformedDomain, 0)); var adjustedLower = this.adjustedLog(lower); var adjustedUpper = this.adjustedLog(upper); var proportion = (adjustedUpper - adjustedLower) / (adjustedMax - adjustedMin); - var ticks = Math.ceil(proportion * this.numTicks()); + var ticks = Math.ceil(proportion * ModifiedLog._DEFAULT_NUM_TICKS); return ticks; } - public copy(): ModifiedLog { - return new ModifiedLog(this.base); - } - - public _niceDomain(domain: any[], count?: number): any[] { + public _niceDomain(domain: number[], count?: number): number[] { return domain; } @@ -206,6 +200,10 @@ export module Scale { } } + public _defaultExtent(): number[] { + return [0, this._base]; + } + } } } diff --git a/src/scales/quantitativeScale.ts b/src/scales/quantitativeScale.ts index d5b2d554df..54875330b8 100644 --- a/src/scales/quantitativeScale.ts +++ b/src/scales/quantitativeScale.ts @@ -1,16 +1,12 @@ /// module Plottable { -export module Scale { - export class AbstractQuantitative extends AbstractScale { + export class QuantitativeScale extends Scale { + protected static _DEFAULT_NUM_TICKS = 10; protected _d3Scale: D3.Scale.QuantitativeScale; - private _numTicks = 10; - private _PADDING_FOR_IDENTICAL_DOMAIN = 1; - public _userSetDomainer: boolean = false; + public _userSetDomainer = false; private _domainer: Domainer = new Domainer(); - public _typeCoercer = (d: any) => +d; - private _tickGenerator: TickGenerators.TickGenerator = (scale: Plottable.Scale.AbstractQuantitative) => scale.getDefaultTicks(); - + private _tickGenerator: Scales.TickGenerators.TickGenerator = (scale: Plottable.QuantitativeScale) => scale.getDefaultTicks(); /** * Constructs a new QuantitativeScale. @@ -40,119 +36,42 @@ export module Scale { return this._d3Scale.invert(value); } - /** - * Creates a copy of the QuantitativeScale with the same domain and range but without any registered list. - * - * @returns {AbstractQuantitative} A copy of the calling QuantitativeScale. - */ - public copy(): AbstractQuantitative { - return new AbstractQuantitative(this._d3Scale.copy()); - } - public domain(): D[]; - public domain(values: D[]): AbstractQuantitative; + public domain(values: D[]): QuantitativeScale; public domain(values?: D[]): any { - return super.domain(values); // need to override type sig to enable method chaining :/ + return super.domain(values); // need to override type sig to enable method chaining:/ } protected _setDomain(values: D[]) { var isNaNOrInfinity = (x: any) => x !== x || x === Infinity || x === -Infinity; if (isNaNOrInfinity(values[0]) || isNaNOrInfinity(values[1])) { - _Util.Methods.warn("Warning: QuantitativeScales cannot take NaN or Infinity as a domain value. Ignoring."); + Utils.Methods.warn("Warning: QuantitativeScales cannot take NaN or Infinity as a domain value. Ignoring."); return; } super._setDomain(values); } - /** - * Sets or gets the QuantitativeScale's output interpolator - * - * @param {D3.Transition.Interpolate} [factory] The output interpolator to use. - * @returns {D3.Transition.Interpolate|AbstractQuantitative} The current output interpolator, or the calling QuantitativeScale. - */ - public interpolate(): D3.Transition.Interpolate; - public interpolate(factory: D3.Transition.Interpolate): AbstractQuantitative; - public interpolate(factory?: D3.Transition.Interpolate): any { - if (factory == null) { - return this._d3Scale.interpolate(); - } - this._d3Scale.interpolate(factory); - return this; - } - - /** - * Sets the range of the QuantitativeScale and sets the interpolator to d3.interpolateRound. - * - * @param {number[]} values The new range value for the range. - */ - public rangeRound(values: number[]) { - this._d3Scale.rangeRound(values); - return this; - } - /** * Gets ticks generated by the default algorithm. */ public getDefaultTicks(): D[] { - return this._d3Scale.ticks(this.numTicks()); - } - - /** - * Gets the clamp status of the QuantitativeScale (whether to cut off values outside the ouput range). - * - * @returns {boolean} The current clamp status. - */ - public clamp(): boolean; - /** - * Sets the clamp status of the QuantitativeScale (whether to cut off values outside the ouput range). - * - * @param {boolean} clamp Whether or not to clamp the QuantitativeScale. - * @returns {AbstractQuantitative} The calling QuantitativeScale. - */ - public clamp(clamp: boolean): AbstractQuantitative; - public clamp(clamp?: boolean): any { - if (clamp == null) { - return this._d3Scale.clamp(); - } - this._d3Scale.clamp(clamp); - return this; + return this._d3Scale.ticks(QuantitativeScale._DEFAULT_NUM_TICKS); } /** * Gets a set of tick values spanning the domain. * - * @returns {any[]} The generated ticks. + * @returns {D[]} The generated ticks. */ - public ticks(): any[] { + public ticks(): D[] { return this._tickGenerator(this); } - /** - * Gets the default number of ticks. - * - * @returns {number} The default number of ticks. - */ - public numTicks(): number; - /** - * Sets the default number of ticks to generate. - * - * @param {number} count The new default number of ticks. - * @returns {Quantitative} The calling QuantitativeScale. - */ - public numTicks(count: number): AbstractQuantitative; - public numTicks(count?: number): any { - if (count == null) { - return this._numTicks; - } - this._numTicks = count; - return this; - } - /** * Given a domain, expands its domain onto "nice" values, e.g. whole * numbers. */ - public _niceDomain(domain: any[], count?: number): any[] { + public _niceDomain(domain: D[], count?: number): D[] { return this._d3Scale.copy().domain(domain).nice(count).domain(); } @@ -172,9 +91,9 @@ export module Scale { * includes 0, etc., will be the responsability of the new domainer. * * @param {Domainer} domainer If provided, the new domainer. - * @return {AbstractQuantitative} The calling QuantitativeScale. + * @return {QuantitativeScale} The calling QuantitativeScale. */ - public domainer(domainer: Domainer): AbstractQuantitative; + public domainer(domainer: Domainer): QuantitativeScale; public domainer(domainer?: Domainer): any { if (domainer == null) { return this._domainer; @@ -186,25 +105,25 @@ export module Scale { } } - public _defaultExtent(): any[] { - return [0, 1]; + public _defaultExtent(): D[] { + throw Error("The quantitative scale itself does not have a default extent"); } /** - * Gets the tick generator of the AbstractQuantitative. + * Gets the tick generator of the QuantitativeScale. * * @returns {TickGenerator} The current tick generator. */ - public tickGenerator(): TickGenerators.TickGenerator; + public tickGenerator(): Scales.TickGenerators.TickGenerator; /** * Sets a tick generator * * @param {TickGenerator} generator, the new tick generator. - * @return {AbstractQuantitative} The calling AbstractQuantitative. + * @return {QuantitativeScale} The calling QuantitativeScale. */ - public tickGenerator(generator: TickGenerators.TickGenerator): AbstractQuantitative; - public tickGenerator(generator?: TickGenerators.TickGenerator): any { - if(generator == null) { + public tickGenerator(generator: Scales.TickGenerators.TickGenerator): QuantitativeScale; + public tickGenerator(generator?: Scales.TickGenerators.TickGenerator): any { + if (generator == null) { return this._tickGenerator; } else { this._tickGenerator = generator; @@ -213,4 +132,3 @@ export module Scale { } } } -} diff --git a/src/scales/abstractScale.ts b/src/scales/scale.ts similarity index 66% rename from src/scales/abstractScale.ts rename to src/scales/scale.ts index 1db7ae59b1..4dd7f90bd5 100644 --- a/src/scales/abstractScale.ts +++ b/src/scales/scale.ts @@ -1,14 +1,25 @@ /// module Plottable { -export module Scale { - export class AbstractScale extends Core.PlottableObject { + + export interface ScaleCallback> { + (scale: S): any; + } + + export module Scales { + export interface ExtentsProvider { + (scale: Scale): D[][]; + } + } + + export class Scale { protected _d3Scale: D3.Scale.Scale; + + private _callbacks: Utils.CallbackSet>>; private _autoDomainAutomatically = true; - public broadcaster: Core.Broadcaster>; - private _rendererAttrID2Extent: {[rendererAttrID: string]: D[]} = {}; - public _typeCoercer: (d: any) => any = (d: any) => d; - private _domainModificationInProgress: boolean = false; + private _domainModificationInProgress = false; + private _extentsProviders: Utils.Set>; + /** * Constructs a new Scale. * @@ -20,19 +31,33 @@ export module Scale { * @param {D3.Scale.Scale} scale The D3 scale backing the Scale. */ constructor(scale: D3.Scale.Scale) { - super(); this._d3Scale = scale; - this.broadcaster = new Core.Broadcaster(this); + this._callbacks = new Utils.CallbackSet>>(); + this._extentsProviders = new Utils.Set>(); } protected _getAllExtents(): D[][] { - return d3.values(this._rendererAttrID2Extent); + return d3.merge(this._extentsProviders.values().map((provider) => provider(this))); } protected _getExtent(): D[] { return []; // this should be overwritten } + public onUpdate(callback: ScaleCallback>) { + this._callbacks.add(callback); + return this; + } + + public offUpdate(callback: ScaleCallback>) { + this._callbacks.delete(callback); + return this; + } + + protected _dispatchUpdate() { + this._callbacks.callCallbacks(this); + } + /** * Modifies the domain on the scale so that it includes the extent of all * perspectives it depends on. This will normally happen automatically, but @@ -40,7 +65,7 @@ export module Scale { * call this function if you want the domain to neccessarily include all * the data. * - * Extent: The [min, max] pair for a Scale.Quantitative, all covered + * Extent: The [min, max] pair for a Scale.QuantitativeScale, all covered * strings for a Scale.Category. * * Perspective: A combination of a Dataset and an Accessor that @@ -86,7 +111,7 @@ export module Scale { * input values. * @returns {Scale} The calling Scale. */ - public domain(values: D[]): AbstractScale; + public domain(values: D[]): Scale; public domain(values?: D[]): any { if (values == null) { return this._getDomain(); @@ -102,10 +127,10 @@ export module Scale { } protected _setDomain(values: D[]) { - if(!this._domainModificationInProgress) { + if (!this._domainModificationInProgress) { this._domainModificationInProgress = true; this._d3Scale.domain(values); - this.broadcaster.broadcast(); + this._dispatchUpdate(); this._domainModificationInProgress = false; } } @@ -130,7 +155,7 @@ export module Scale { * @param {R[]} values If provided, the new values for the range. * @returns {Scale} The calling Scale. */ - public range(values: R[]): AbstractScale; + public range(values: R[]): Scale; public range(values?: R[]): any { if (values == null) { return this._d3Scale.range(); @@ -140,37 +165,14 @@ export module Scale { } } - /** - * Constructs a copy of the Scale with the same domain and range but without - * any registered listeners. - * - * @returns {Scale} A copy of the calling Scale. - */ - public copy(): AbstractScale { - return new AbstractScale(this._d3Scale.copy()); - } - - /** - * When a renderer determines that the extent of a projector has changed, - * it will call this function. This function should ensure that - * the scale has a domain at least large enough to include extent. - * - * @param {number} rendererID A unique indentifier of the renderer sending - * the new extent. - * @param {string} attr The attribute being projected, e.g. "x", "y0", "r" - * @param {D[]} extent The new extent to be included in the scale. - */ - public _updateExtent(plotProvidedKey: string, attr: string, extent: D[]) { - this._rendererAttrID2Extent[plotProvidedKey + attr] = extent; - this._autoDomainIfAutomaticMode(); + public addExtentsProvider(provider: Scales.ExtentsProvider) { + this._extentsProviders.add(provider); return this; } - public _removeExtent(plotProvidedKey: string, attr: string) { - delete this._rendererAttrID2Extent[plotProvidedKey + attr]; - this._autoDomainIfAutomaticMode(); + public removeExtentsProvider(provider: Scales.ExtentsProvider) { + this._extentsProviders.delete(provider); return this; } } } -} diff --git a/src/scales/scaleDomainCoordinator.ts b/src/scales/scaleDomainCoordinator.ts deleted file mode 100644 index 1bfff3b80a..0000000000 --- a/src/scales/scaleDomainCoordinator.ts +++ /dev/null @@ -1,36 +0,0 @@ -/// - -module Plottable { -export module _Util { - export class ScaleDomainCoordinator { - /* This class is responsible for maintaining coordination between linked scales. - It registers event listeners for when one of its scales changes its domain. When the scale - does change its domain, it re-propogates the change to every linked scale. - */ - private _rescaleInProgress = false; - private _scales: Scale.AbstractScale[]; - - /** - * Constructs a ScaleDomainCoordinator. - * - * @constructor - * @param {Scale[]} scales A list of scales whose domains should be linked. - */ - constructor(scales: Scale.AbstractScale[]) { - if (scales == null) { throw new Error("ScaleDomainCoordinator requires scales to coordinate"); } - this._scales = scales; - this._scales.forEach((s) => s.broadcaster.registerListener(this, (sx: Scale.AbstractScale) => this.rescale(sx))); - } - - public rescale(scale: Scale.AbstractScale) { - if (this._rescaleInProgress) { - return; - } - this._rescaleInProgress = true; - var newDomain = scale.domain(); - this._scales.forEach((s) => s.domain(newDomain)); - this._rescaleInProgress = false; - } - } -} -} diff --git a/src/scales/tickGenerators.ts b/src/scales/tickGenerators.ts index 169315413a..9a0d111a5a 100644 --- a/src/scales/tickGenerators.ts +++ b/src/scales/tickGenerators.ts @@ -1,12 +1,12 @@ /// module Plottable { - export module Scale { + export module Scales { export module TickGenerators { // HACKHACK: Generic types in type definition fails compilation // https://github.com/Microsoft/TypeScript/issues/1616 export interface TickGenerator { - (scale: Plottable.Scale.AbstractQuantitative): D[] + (scale: Plottable.QuantitativeScale): D[]; } /** * Creates a tick generator using the specified interval. @@ -17,12 +17,12 @@ module Plottable { * * @returns {TickGenerator} A tick generator using the specified interval. */ - export function intervalTickGenerator(interval: number) : TickGenerator { - if(interval <= 0) { + export function intervalTickGenerator(interval: number): TickGenerator { + if (interval <= 0) { throw new Error("interval must be positive number"); } - return function(s: Scale.AbstractQuantitative) { + return function(s: QuantitativeScale) { var domain = s.domain(); var low = Math.min(domain[0], domain[1]); var high = Math.max(domain[0], domain[1]); @@ -30,7 +30,7 @@ module Plottable { var numTicks = Math.floor((high - firstTick) / interval) + 1; var lowTicks = low % interval === 0 ? [] : [low]; - var middleTicks = _Util.Methods.range(0, numTicks).map(t => firstTick + t * interval); + var middleTicks = Utils.Methods.range(0, numTicks).map(t => firstTick + t * interval); var highTicks = high % interval === 0 ? [] : [high]; return lowTicks.concat(middleTicks).concat(highTicks); @@ -45,7 +45,7 @@ module Plottable { * @returns {TickGenerator} A tick generator returning only integer ticks. */ export function integerTickGenerator(): TickGenerator { - return function(s: Scale.AbstractQuantitative) { + return function(s: QuantitativeScale) { var defaultTicks = s.getDefaultTicks(); return defaultTicks.filter((tick, i) => (tick % 1 === 0) || (i === 0) || (i === defaultTicks.length - 1)); }; diff --git a/src/scales/timeScale.ts b/src/scales/timeScale.ts index 79a8bcb162..59dad7961e 100644 --- a/src/scales/timeScale.ts +++ b/src/scales/timeScale.ts @@ -1,10 +1,8 @@ /// module Plottable { -export module Scale { - export class Time extends AbstractQuantitative { - public _typeCoercer = (d: any) => d && d._isAMomentObject || d instanceof Date ? d : new Date(d); - +export module Scales { + export class Time extends QuantitativeScale { /** * Constructs a TimeScale. * @@ -15,36 +13,39 @@ export module Scale { */ constructor(); constructor(scale: D3.Scale.LinearScale); - constructor(scale?: any) { - // need to cast since d3 time scales do not descend from Quantitative scales + constructor(scale?: D3.Scale.LinearScale) { + // need to cast since d3 time scales do not descend from QuantitativeScale scales super(scale == null ? (d3.time.scale()) : scale); } - public tickInterval(interval: D3.Time.Interval, step?: number): any[] { + /** + * Specifies the interval between ticks + * + * @param {string} interval TimeInterval string specifying the interval unit measure + * @param {number?} step? The distance between adjacent ticks (using the interval unit measure) + * + * @return {Date[]} + */ + public tickInterval(interval: string, step?: number): Date[] { // temporarily creats a time scale from our linear scale into a time scale so we can get access to its api var tempScale = d3.time.scale(); + var d3Interval = Formatters.timeIntervalToD3Time(interval); tempScale.domain(this.domain()); tempScale.range(this.range()); - return tempScale.ticks(interval.range, step); + return tempScale.ticks(d3Interval.range, step); } - protected _setDomain(values: any[]) { - // attempt to parse dates - values = values.map(this._typeCoercer); + protected _setDomain(values: Date[]) { if (values[1] < values[0]) { throw new Error("Scale.Time domain values must be in chronological order"); } return super._setDomain(values); } - public copy(): Time { - return new Time(this._d3Scale.copy()); - } - - public _defaultExtent(): any[] { - var endTime = new Date().valueOf(); - var startTime = endTime - Plottable.MILLISECONDS_IN_ONE_DAY; - return [startTime, endTime]; + public _defaultExtent(): Date[] { + var endTimeValue = new Date().valueOf(); + var startTimeValue = endTimeValue - Plottable.MILLISECONDS_IN_ONE_DAY; + return [new Date(startTimeValue), new Date(endTimeValue)]; } } } diff --git a/src/utils/callbackSet.ts b/src/utils/callbackSet.ts new file mode 100644 index 0000000000..4cf9f7ad4c --- /dev/null +++ b/src/utils/callbackSet.ts @@ -0,0 +1,19 @@ +/// + +module Plottable { + export module Utils { + /** + * A set of callbacks which can be all invoked at once. + * Each callback exists at most once in the set (based on reference equality). + * All callbacks should have the same signature. + */ + export class CallbackSet extends Set { + public callCallbacks(...args: any[]) { + this.values().forEach((callback) => { + callback.apply(this, args); + }); + return this; + } + } + } +} diff --git a/src/utils/clientToSVGTranslator.ts b/src/utils/clientToSVGTranslator.ts index 553937d4f8..83d0ce5c0f 100644 --- a/src/utils/clientToSVGTranslator.ts +++ b/src/utils/clientToSVGTranslator.ts @@ -1,14 +1,14 @@ /// module Plottable { -export module _Util { +export module Utils { export class ClientToSVGTranslator { - private static _TRANSLATOR_KEY = "__Plottable_ClientToSVGTranslator" + private static _TRANSLATOR_KEY = "__Plottable_ClientToSVGTranslator"; private _svg: SVGElement; private _measureRect: SVGElement; public static getTranslator(elem: SVGElement): ClientToSVGTranslator { - var svg = _Util.DOM.getBoundingSVG(elem); + var svg = Utils.DOM.getBoundingSVG(elem); var translator: ClientToSVGTranslator = ( svg)[ClientToSVGTranslator._TRANSLATOR_KEY]; if (translator == null) { diff --git a/src/utils/color.ts b/src/utils/color.ts index e43532be33..17a033c995 100644 --- a/src/utils/color.ts +++ b/src/utils/color.ts @@ -1,8 +1,8 @@ /// module Plottable { -export module _Util { - export module Color { +export module Utils { + export module Colors { /** * Return relative luminance (defined here: http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef) * Based on implementation from chroma.js by Gregor Aisch (gka) (licensed under BSD) diff --git a/src/utils/domUtils.ts b/src/utils/domUtils.ts index da0895cdb2..a8fb67fa30 100644 --- a/src/utils/domUtils.ts +++ b/src/utils/domUtils.ts @@ -1,6 +1,6 @@ module Plottable { -export module _Util { +export module Utils { export module DOM { /** * Gets the bounding box of an element. @@ -46,7 +46,7 @@ export module _Util { return (n == null); } - export function getElementWidth(elem: HTMLScriptElement): number{ + export function getElementWidth(elem: HTMLScriptElement): number { var style: CSSStyleDeclaration = window.getComputedStyle(elem); return getParsedStyleValue(style, "width") + getParsedStyleValue(style, "padding-left") @@ -55,7 +55,7 @@ export module _Util { + getParsedStyleValue(style, "border-right-width"); } - export function getElementHeight(elem: HTMLScriptElement): number{ + export function getElementHeight(elem: HTMLScriptElement): number { var style: CSSStyleDeclaration = window.getComputedStyle(elem); return getParsedStyleValue(style, "height") + getParsedStyleValue(style, "padding-top") @@ -127,6 +127,11 @@ export module _Util { } return null; // not in the DOM } + + var _latestClipPathId = 0; + export function getUniqueClipPathId() { + return "plottableClipPath" + ++_latestClipPathId; + } } } } diff --git a/src/utils/formatters.ts b/src/utils/formatters.ts index 3f46b7d486..5f220d3a3a 100644 --- a/src/utils/formatters.ts +++ b/src/utils/formatters.ts @@ -189,6 +189,31 @@ module Plottable { return d3.time.format(specifier); } + /** + * Transforms the Plottable TimeInterval string into a d3 time interval equivalent. + * If the provided TimeInterval is incorrect, the default is d3.time.year + */ + export function timeIntervalToD3Time(timeInterval: string) { + switch (timeInterval) { + case TimeInterval.second: + return d3.time.second; + case TimeInterval.minute: + return d3.time.minute; + case TimeInterval.hour: + return d3.time.hour; + case TimeInterval.day: + return d3.time.day; + case TimeInterval.week: + return d3.time.week; + case TimeInterval.month: + return d3.time.month; + case TimeInterval.year: + return d3.time.year; + default: + throw Error("TimeInterval specified does not exist: " + timeInterval); + } + } + /** * Creates a formatter for relative dates. * diff --git a/src/utils/strictEqualityAssociativeArray.ts b/src/utils/map.ts similarity index 72% rename from src/utils/strictEqualityAssociativeArray.ts rename to src/utils/map.ts index 691ddfad40..d40f83b7aa 100644 --- a/src/utils/strictEqualityAssociativeArray.ts +++ b/src/utils/map.ts @@ -1,25 +1,25 @@ /// module Plottable { -export module _Util { +export module Utils { /** * An associative array that can be keyed by anything (inc objects). * Uses pointer equality checks which is why this works. * This power has a price: everything is linear time since it is actually backed by an array... */ - export class StrictEqualityAssociativeArray { + export class Map { private _keyValuePairs: any[][] = []; /** * Set a new key/value pair in the store. * - * @param {any} key Key to set in the store - * @param {any} value Value to set in the store + * @param {K} key Key to set in the store + * @param {V} value Value to set in the store * @return {boolean} True if key already in store, false otherwise */ - public set(key: any, value: any) { + public set(key: K, value: V) { if (key !== key) { - throw new Error("NaN may not be used as a key to the StrictEqualityAssociativeArray"); + throw new Error("NaN may not be used as a key to the Map"); } for (var i = 0; i < this._keyValuePairs.length; i++) { if (this._keyValuePairs[i][0] === key) { @@ -34,10 +34,10 @@ export module _Util { /** * Get a value from the store, given a key. * - * @param {any} key Key associated with value to retrieve - * @return {any} Value if found, undefined otherwise + * @param {K} key Key associated with value to retrieve + * @return {V} Value if found, undefined otherwise */ - public get(key: any): any { + public get(key: K) { for (var i = 0; i < this._keyValuePairs.length; i++) { if (this._keyValuePairs[i][0] === key) { return this._keyValuePairs[i][1]; @@ -52,10 +52,10 @@ export module _Util { * Will return true if there is a key/value entry, * even if the value is explicitly `undefined`. * - * @param {any} key Key to test for presence of an entry + * @param {K} key Key to test for presence of an entry * @return {boolean} Whether there was a matching entry for that key */ - public has(key: any): boolean { + public has(key: K) { for (var i = 0; i < this._keyValuePairs.length; i++) { if (this._keyValuePairs[i][0] === key) { return true; @@ -67,28 +67,28 @@ export module _Util { /** * Return an array of the values in the key-value store * - * @return {any[]} The values in the store + * @return {V[]} The values in the store */ - public values(): any[] { + public values() { return this._keyValuePairs.map((x) => x[1]); } /** * Return an array of keys in the key-value store * - * @return {any[]} The keys in the store + * @return {K[]} The keys in the store */ - public keys(): any[] { + public keys() { return this._keyValuePairs.map((x) => x[0]); } /** * Execute a callback for each entry in the array. * - * @param {(key: any, val?: any, index?: number) => any} callback The callback to eecute + * @param {(key: K, val?: V, index?: number) => any} callback The callback to execute * @return {any[]} The results of mapping the callback over the entries */ - public map(cb: (key?: any, val?: any, index?: number) => any) { + public map(cb: (key?: K, val?: V, index?: number) => any) { return this._keyValuePairs.map((kv: any[], index: number) => { return cb(kv[0], kv[1], index); }); @@ -97,10 +97,10 @@ export module _Util { /** * Delete a key from the key-value store. Return whether the key was present. * - * @param {any} The key to remove + * @param {K} The key to remove * @return {boolean} Whether a matching entry was found and removed */ - public delete(key: any): boolean { + public delete(key: K) { for (var i = 0; i < this._keyValuePairs.length; i++) { if (this._keyValuePairs[i][0] === key) { this._keyValuePairs.splice(i, 1); diff --git a/src/utils/osUtils.ts b/src/utils/osUtils.ts deleted file mode 100644 index 2724b7ebca..0000000000 --- a/src/utils/osUtils.ts +++ /dev/null @@ -1,64 +0,0 @@ -/// - -// This file contains open source utilities, along with their copyright notices - -module Plottable { -export module _Util { - export module OpenSource { - /** - * Returns the sortedIndex for inserting a value into an array. - * Takes a number and an array of numbers OR an array of objects and an accessor that returns a number. - * @param {number} value: The numerical value to insert - * @param {any[]} arr: Array to find insertion index, can be number[] or any[] (if accessor provided) - * @param {_Accessor} accessor: If provided, this function is called on members of arr to determine insertion index - * @returns {number} The insertion index. - * The behavior is undefined for arrays that are unsorted - * If there are multiple valid insertion indices that maintain sorted order (e.g. addign 1 to [1,1,1,1,1]) then - * the behavior must satisfy that the array is sorted post-insertion, but is otherwise unspecified. - * This is a modified version of Underscore.js's implementation of sortedIndex. - * Underscore.js is released under the MIT License: - * Copyright (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative - * Reporters & Editors - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - export function sortedIndex(val: number, arr: number[]): number; - export function sortedIndex(val: number, arr: any[], accessor: _Accessor): number; - export function sortedIndex(val: number, arr: any[], accessor?: _Accessor): number { - var low = 0; - var high = arr.length; - while (low < high) { - /* tslint:disable:no-bitwise */ - var mid = (low + high) >>> 1; - /* tslint:enable:no-bitwise */ - var x = accessor == null ? arr[mid] : accessor(arr[mid]); - if (x < val) { - low = mid + 1; - } else { - high = mid; - } - } - return low; - }; - } -} -} diff --git a/src/utils/set.ts b/src/utils/set.ts new file mode 100644 index 0000000000..338ff99917 --- /dev/null +++ b/src/utils/set.ts @@ -0,0 +1,41 @@ +/// + +module Plottable { + export module Utils { + /** + * Shim for ES6 set. + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set + */ + export class Set { + private _values: T[]; + + constructor() { + this._values = []; + } + + public add(value: T) { + if (!this.has(value)) { + this._values.push(value); + } + return this; + } + + public delete(value: T) { + var index = this._values.indexOf(value); + if (index !== -1) { + this._values.splice(index, 1); + return true; + } + return false; + } + + public has(value: T) { + return this._values.indexOf(value) !== -1; + } + + public values() { + return this._values; + } + } + } +} diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 13f412d0bd..d30044313a 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,7 +1,7 @@ /// module Plottable { -export module _Util { +export module Utils { export module Methods { /** @@ -33,7 +33,7 @@ export module _Util { * @param {string} The warnings to print */ export function warn(warning: string) { - if (!Config.SHOW_WARNINGS) { + if (!Configs.SHOW_WARNINGS) { return; } /* tslint:disable:no-console */ @@ -73,27 +73,13 @@ export module _Util { export function intersection(set1: D3.Set, set2: D3.Set): D3.Set { var set: D3.Set = d3.set(); set1.forEach((v) => { - if(set2.has( v)) { // checking a string is always appropriate due to d3.set implementation + if (set2.has( v)) { // checking a string is always appropriate due to d3.set implementation set.add(v); } }); return set; } - /** - * Take an accessor object (may be a string to be made into a key, or a value, or a color code) - * and "activate" it by turning it into a function in (datum, index, metadata) - */ - export function accessorize(accessor: any): _Accessor { - if (typeof(accessor) === "function") { - return (<_Accessor> accessor); - } else if (typeof(accessor) === "string" && accessor[0] !== "#") { - return (d: any, i: number, s: any) => d[accessor]; - } else { - return (d: any, i: number, s: any) => accessor; - }; - } - /** * Takes two sets and returns the union * @@ -206,45 +192,33 @@ export module _Util { } /** - * Computes the max value from the array. - * - * If type is not comparable then t will be converted to a comparable before computing max. + * Applies the accessor, if provided, to each element of `array` and returns the maximum value. + * If no maximum value can be computed, returns defaultValue. */ - export function max(arr: C[], default_val: C): C; - export function max(arr: T[], acc: (x?: T, i?: number) => C, default_val: C): C; - export function max(arr: any[], one: any, two?: any): any { - if (arr.length === 0) { - if (typeof(one) !== "function") { - return one; - } else { - return two; - } - } + export function max(array: C[], defaultValue: C): C; + export function max(array: T[], accessor: (t?: T, i?: number) => C, defaultValue: C): C; + export function max(array: any[], firstArg: any, secondArg?: any): any { + var accessor = typeof(firstArg) === "function" ? firstArg : null; + var defaultValue = accessor == null ? firstArg : secondArg; /* tslint:disable:ban */ - var acc = typeof(one) === "function" ? one : typeof(two) === "function" ? two : undefined; - return acc === undefined ? d3.max(arr) : d3.max(arr, acc); + var maxValue = accessor == null ? d3.max(array) : d3.max(array, accessor); /* tslint:enable:ban */ + return maxValue !== undefined ? maxValue : defaultValue; } /** - * Computes the min value from the array. - * - * If type is not comparable then t will be converted to a comparable before computing min. + * Applies the accessor, if provided, to each element of `array` and returns the minimum value. + * If no minimum value can be computed, returns defaultValue. */ - export function min(arr: C[], default_val: C): C; - export function min(arr: T[], acc: (x?: T, i?: number) => C, default_val: C): C; - export function min(arr: any[], one: any, two?: any): any { - if (arr.length === 0) { - if (typeof(one) !== "function") { - return one; - } else { - return two; - } - } + export function min(array: C[], defaultValue: C): C; + export function min(array: T[], accessor: (t?: T, i?: number) => C, defaultValue: C): C; + export function min(array: any[], firstArg: any, secondArg?: any): any { + var accessor = typeof(firstArg) === "function" ? firstArg : null; + var defaultValue = accessor == null ? firstArg : secondArg; /* tslint:disable:ban */ - var acc = typeof(one) === "function" ? one : typeof(two) === "function" ? two : undefined; - return acc === undefined ? d3.min(arr) : d3.min(arr, acc); + var minValue = accessor == null ? d3.min(array) : d3.min(array, accessor); /* tslint:enable:ban */ + return minValue !== undefined ? minValue : defaultValue; } /** @@ -259,7 +233,7 @@ export module _Util { * Numbers represented as strings do not pass this function */ export function isValidNumber(n: any) { - return typeof n === "number" && !Plottable._Util.Methods.isNaN(n) && isFinite(n); + return typeof n === "number" && !Plottable.Utils.Methods.isNaN(n) && isFinite(n); } /** @@ -275,7 +249,7 @@ export module _Util { } export function range(start: number, stop: number, step = 1): number[] { - if(step === 0) { + if (step === 0) { throw new Error("step cannot be 0"); } var length = Math.max(Math.ceil((stop - start) / step), 0); diff --git a/test/components/baseAxisTests.ts b/test/components/baseAxisTests.ts index a1221e0697..89ad852729 100644 --- a/test/components/baseAxisTests.ts +++ b/test/components/baseAxisTests.ts @@ -1,23 +1,22 @@ /// - var assert = chai.assert; describe("BaseAxis", () => { it("orientation", () => { - var scale = new Plottable.Scale.Linear(); - assert.throws(() => new Plottable.Axis.AbstractAxis(scale, "blargh"), "unsupported"); + var scale = new Plottable.Scales.Linear(); + assert.throws(() => new Plottable.Axis(scale, "blargh"), "unsupported"); }); it("tickLabelPadding() rejects negative values", () => { - var scale = new Plottable.Scale.Linear(); - var baseAxis = new Plottable.Axis.AbstractAxis(scale, "bottom"); + var scale = new Plottable.Scales.Linear(); + var baseAxis = new Plottable.Axis(scale, "bottom"); assert.throws(() => baseAxis.tickLabelPadding(-1), "must be positive"); }); it("gutter() rejects negative values", () => { - var scale = new Plottable.Scale.Linear(); - var axis = new Plottable.Axis.AbstractAxis(scale, "right"); + var scale = new Plottable.Scales.Linear(); + var axis = new Plottable.Axis(scale, "right"); assert.throws(() => axis.gutter(-1), "must be positive"); }); @@ -25,9 +24,9 @@ describe("BaseAxis", () => { it("width() + gutter()", () => { var SVG_WIDTH = 100; var SVG_HEIGHT = 500; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var scale = new Plottable.Scale.Linear(); - var verticalAxis = new Plottable.Axis.AbstractAxis(scale, "right"); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var scale = new Plottable.Scales.Linear(); + var verticalAxis = new Plottable.Axis(scale, "right"); verticalAxis.renderTo(svg); var expectedWidth = verticalAxis.tickLength() + verticalAxis.gutter(); // tick length and gutter by default @@ -43,9 +42,9 @@ describe("BaseAxis", () => { it("height() + gutter()", () => { var SVG_WIDTH = 500; var SVG_HEIGHT = 100; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var scale = new Plottable.Scale.Linear(); - var horizontalAxis = new Plottable.Axis.AbstractAxis(scale, "bottom"); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var scale = new Plottable.Scales.Linear(); + var horizontalAxis = new Plottable.Axis(scale, "bottom"); horizontalAxis.renderTo(svg); var expectedHeight = horizontalAxis.tickLength() + horizontalAxis.gutter(); // tick length and gutter by default @@ -61,16 +60,16 @@ describe("BaseAxis", () => { it("draws ticks and baseline (horizontal)", () => { var SVG_WIDTH = 500; var SVG_HEIGHT = 100; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var scale = new Plottable.Scale.Linear(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var scale = new Plottable.Scales.Linear(); scale.domain([0, 10]); scale.range([0, SVG_WIDTH]); - var baseAxis = new Plottable.Axis.AbstractAxis(scale, "bottom"); + var baseAxis = new Plottable.Axis(scale, "bottom"); var tickValues = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; ( baseAxis)._getTickValues = function() { return tickValues; }; baseAxis.renderTo(svg); - var tickMarks = svg.selectAll("." + Plottable.Axis.AbstractAxis.TICK_MARK_CLASS); + var tickMarks = svg.selectAll("." + Plottable.Axis.TICK_MARK_CLASS); assert.strictEqual(tickMarks[0].length, tickValues.length, "A tick mark was created for each value"); var baseline = svg.select(".baseline"); @@ -80,7 +79,7 @@ describe("BaseAxis", () => { assert.strictEqual(baseline.attr("y1"), "0"); assert.strictEqual(baseline.attr("y2"), "0"); - baseAxis.orient("top"); + baseAxis.orientation("top"); assert.isNotNull(baseline.node(), "baseline was drawn"); assert.strictEqual(baseline.attr("x1"), "0"); assert.strictEqual(baseline.attr("x2"), String(SVG_WIDTH)); @@ -93,16 +92,16 @@ describe("BaseAxis", () => { it("draws ticks and baseline (vertical)", () => { var SVG_WIDTH = 100; var SVG_HEIGHT = 500; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var scale = new Plottable.Scale.Linear(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var scale = new Plottable.Scales.Linear(); scale.domain([0, 10]); scale.range([0, SVG_HEIGHT]); - var baseAxis = new Plottable.Axis.AbstractAxis(scale, "left"); + var baseAxis = new Plottable.Axis(scale, "left"); var tickValues = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; ( baseAxis)._getTickValues = function() { return tickValues; }; baseAxis.renderTo(svg); - var tickMarks = svg.selectAll("." + Plottable.Axis.AbstractAxis.TICK_MARK_CLASS); + var tickMarks = svg.selectAll("." + Plottable.Axis.TICK_MARK_CLASS); assert.strictEqual(tickMarks[0].length, tickValues.length, "A tick mark was created for each value"); var baseline = svg.select(".baseline"); @@ -112,7 +111,7 @@ describe("BaseAxis", () => { assert.strictEqual(baseline.attr("y1"), "0"); assert.strictEqual(baseline.attr("y2"), String(SVG_HEIGHT)); - baseAxis.orient("right"); + baseAxis.orientation("right"); assert.isNotNull(baseline.node(), "baseline was drawn"); assert.strictEqual(baseline.attr("x1"), "0"); assert.strictEqual(baseline.attr("x2"), "0"); @@ -125,15 +124,15 @@ describe("BaseAxis", () => { it("tickLength()", () => { var SVG_WIDTH = 500; var SVG_HEIGHT = 100; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var scale = new Plottable.Scale.Linear(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var scale = new Plottable.Scales.Linear(); scale.domain([0, 10]); scale.range([0, SVG_WIDTH]); - var baseAxis = new Plottable.Axis.AbstractAxis(scale, "bottom"); + var baseAxis = new Plottable.Axis(scale, "bottom"); var tickValues = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; ( baseAxis)._getTickValues = function() { return tickValues; }; baseAxis.renderTo(svg); - var secondTickMark = svg.selectAll("." + Plottable.Axis.AbstractAxis.TICK_MARK_CLASS + ":nth-child(2)"); + var secondTickMark = svg.selectAll("." + Plottable.Axis.TICK_MARK_CLASS + ":nth-child(2)"); assert.strictEqual(secondTickMark.attr("x1"), "50"); assert.strictEqual(secondTickMark.attr("x2"), "50"); assert.strictEqual(secondTickMark.attr("y1"), "0"); @@ -150,16 +149,16 @@ describe("BaseAxis", () => { it("endTickLength()", () => { var SVG_WIDTH = 500; var SVG_HEIGHT = 100; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var scale = new Plottable.Scale.Linear(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var scale = new Plottable.Scales.Linear(); scale.domain([0, 10]); scale.range([0, SVG_WIDTH]); - var baseAxis = new Plottable.Axis.AbstractAxis(scale, "bottom"); + var baseAxis = new Plottable.Axis(scale, "bottom"); var tickValues = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; ( baseAxis)._getTickValues = () => tickValues; baseAxis.renderTo(svg); - var firstTickMark = svg.selectAll("." + Plottable.Axis.AbstractAxis.END_TICK_MARK_CLASS); + var firstTickMark = svg.selectAll("." + Plottable.Axis.END_TICK_MARK_CLASS); assert.strictEqual(firstTickMark.attr("x1"), "0"); assert.strictEqual(firstTickMark.attr("x2"), "0"); assert.strictEqual(firstTickMark.attr("y1"), "0"); @@ -176,9 +175,9 @@ describe("BaseAxis", () => { it("height is adjusted to greater of tickLength or endTickLength", () => { var SVG_WIDTH = 500; var SVG_HEIGHT = 100; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var scale = new Plottable.Scale.Linear(); - var baseAxis = new Plottable.Axis.AbstractAxis(scale, "bottom"); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var scale = new Plottable.Scales.Linear(); + var baseAxis = new Plottable.Axis(scale, "bottom"); baseAxis.showEndTickLabels(true); baseAxis.renderTo(svg); @@ -198,14 +197,14 @@ describe("BaseAxis", () => { }); it("default alignment based on orientation", () => { - var scale = new Plottable.Scale.Linear(); - var baseAxis = new Plottable.Axis.AbstractAxis(scale, "bottom"); - assert.equal(( baseAxis)._yAlignProportion, 0, "yAlignProportion defaults to 0 for bottom axis"); - baseAxis = new Plottable.Axis.AbstractAxis(scale, "top"); - assert.equal(( baseAxis)._yAlignProportion, 1, "yAlignProportion defaults to 1 for top axis"); - baseAxis = new Plottable.Axis.AbstractAxis(scale, "left"); - assert.equal(( baseAxis)._xAlignProportion, 1, "xAlignProportion defaults to 1 for left axis"); - baseAxis = new Plottable.Axis.AbstractAxis(scale, "right"); - assert.equal(( baseAxis)._xAlignProportion, 0, "xAlignProportion defaults to 0 for right axis"); + var scale = new Plottable.Scales.Linear(); + var baseAxis = new Plottable.Axis(scale, "bottom"); + assert.strictEqual(baseAxis.yAlignment(), "top", "y alignment defaults to \"top\" for bottom axis"); + baseAxis = new Plottable.Axis(scale, "top"); + assert.strictEqual(baseAxis.yAlignment(), "bottom", "y alignment defaults to \"bottom\" for top axis"); + baseAxis = new Plottable.Axis(scale, "left"); + assert.strictEqual(baseAxis.xAlignment(), "right", "x alignment defaults to \"right\" for left axis"); + baseAxis = new Plottable.Axis(scale, "right"); + assert.strictEqual(baseAxis.xAlignment(), "left", "x alignment defaults to \"left\" for right axis"); }); }); diff --git a/test/components/categoryAxisTests.ts b/test/components/categoryAxisTests.ts index f3d992fe87..1f2939690e 100644 --- a/test/components/categoryAxisTests.ts +++ b/test/components/categoryAxisTests.ts @@ -3,9 +3,9 @@ var assert = chai.assert; describe("Category Axes", () => { it("re-renders appropriately when data is changed", () => { - var svg = generateSVG(400, 400); - var xScale = new Plottable.Scale.Category().domain(["foo", "bar", "baz"]).range([400, 0]); - var ca = new Plottable.Axis.Category(xScale, "left"); + var svg = TestMethods.generateSVG(400, 400); + var xScale = new Plottable.Scales.Category().domain(["foo", "bar", "baz"]).range([400, 0]); + var ca = new Plottable.Axes.Category(xScale, "left"); ca.renderTo(svg); assert.deepEqual(( ca)._tickLabelContainer.selectAll(".tick-label").data(), xScale.domain(), "tick labels render domain"); assert.doesNotThrow(() => xScale.domain(["bar", "baz", "bam"])); @@ -14,23 +14,21 @@ describe("Category Axes", () => { }); it("requests appropriate space when the scale has no domain", () => { - var svg = generateSVG(400, 400); - var scale = new Plottable.Scale.Category(); - var ca = new Plottable.Axis.Category(scale); - ca._anchor(svg); - var s = ca._requestedSpace(400, 400); - assert.operator(s.width, ">=", 0, "it requested 0 or more width"); - assert.operator(s.height, ">=", 0, "it requested 0 or more height"); - assert.isFalse(s.wantsWidth, "it doesn't want width"); - assert.isFalse(s.wantsHeight, "it doesn't want height"); + var svg = TestMethods.generateSVG(400, 400); + var scale = new Plottable.Scales.Category(); + var ca = new Plottable.Axes.Category(scale); + ca.anchor(svg); + var s = ca.requestedSpace(400, 400); + assert.operator(s.minWidth, ">=", 0, "it requested 0 or more width"); + assert.operator(s.minHeight, ">=", 0, "it requested 0 or more height"); svg.remove(); }); it("doesnt blow up for non-string data", () => { - var svg = generateSVG(1000, 400); + var svg = TestMethods.generateSVG(1000, 400); var domain: any[] = [null, undefined, true, 2, "foo"]; - var scale = new Plottable.Scale.Category().domain(domain); - var axis = new Plottable.Axis.Category(scale); + var scale = new Plottable.Scales.Category().domain(domain); + var axis = new Plottable.Axes.Category(scale); axis.renderTo(svg); var texts = svg.selectAll("text")[0].map((s: any) => d3.select(s).text()); assert.deepEqual(texts, ["null", "undefined", "true", "2", "foo"]); @@ -38,10 +36,10 @@ describe("Category Axes", () => { }); it("uses the formatter if supplied", () => { - var svg = generateSVG(400, 400); + var svg = TestMethods.generateSVG(400, 400); var domain = ["Air", "Bi", "Sea"]; - var scale = new Plottable.Scale.Category().domain(domain); - var axis = new Plottable.Axis.Category(scale, "bottom"); + var scale = new Plottable.Scales.Category().domain(domain); + var axis = new Plottable.Axes.Category(scale, "bottom"); var addPlane = (l: string) => l + "plane"; axis.formatter(addPlane); axis.renderTo(svg); @@ -54,9 +52,9 @@ describe("Category Axes", () => { }); it("width accounts for gutter. ticklength, and padding on vertical axes", () => { - var svg = generateSVG(400, 400); - var xScale = new Plottable.Scale.Category().domain(["foo", "bar", "baz"]).range([400, 0]); - var ca = new Plottable.Axis.Category(xScale, "left"); + var svg = TestMethods.generateSVG(400, 400); + var xScale = new Plottable.Scales.Category().domain(["foo", "bar", "baz"]).range([400, 0]); + var ca = new Plottable.Axes.Category(xScale, "left"); ca.renderTo(svg); var axisWidth = ca.width(); @@ -75,9 +73,9 @@ describe("Category Axes", () => { }); it("height accounts for gutter. ticklength, and padding on horizontal axes", () => { - var svg = generateSVG(400, 400); - var xScale = new Plottable.Scale.Category().domain(["foo", "bar", "baz"]).range([400, 0]); - var ca = new Plottable.Axis.Category(xScale, "bottom"); + var svg = TestMethods.generateSVG(400, 400); + var xScale = new Plottable.Scales.Category().domain(["foo", "bar", "baz"]).range([400, 0]); + var ca = new Plottable.Axes.Category(xScale, "bottom"); ca.renderTo(svg); var axisHeight = ca.height(); @@ -97,10 +95,10 @@ describe("Category Axes", () => { it("vertically aligns short words properly", () => { var SVG_WIDTH = 400; - var svg = generateSVG(SVG_WIDTH, 100); + var svg = TestMethods.generateSVG(SVG_WIDTH, 100); var years = ["2000", "2001", "2002", "2003"]; - var scale = new Plottable.Scale.Category().domain(years).range([0, SVG_WIDTH]); - var axis = new Plottable.Axis.Category(scale, "bottom"); + var scale = new Plottable.Scales.Category().domain(years).range([0, SVG_WIDTH]); + var axis = new Plottable.Axes.Category(scale, "bottom"); axis.renderTo(svg); var ticks = ( axis)._content.selectAll("text"); @@ -126,17 +124,17 @@ describe("Category Axes", () => { }); it("axis should request more space if there's not enough space to fit the text", () => { - var svg = generateSVG(300, 300); + var svg = TestMethods.generateSVG(300, 300); var years = ["2000", "2001", "2002", "2003"]; - var scale = new Plottable.Scale.Category().domain(years); - var axis = new Plottable.Axis.Category(scale, "bottom"); + var scale = new Plottable.Scales.Category().domain(years); + var axis = new Plottable.Axes.Category(scale, "bottom"); axis.renderTo(svg); - var requestedSpace = axis._requestedSpace(300, 10); - assert.isTrue(requestedSpace.wantsHeight, "axis should ask for more space (horizontal orientation)"); - axis.orient("left"); - requestedSpace = axis._requestedSpace(10, 300); - assert.isTrue(requestedSpace.wantsWidth, "axis should ask for more space (vertical orientation)"); - + var smallDimension = 10; + var spaceRequest = axis.requestedSpace(300, smallDimension); + assert.operator(spaceRequest.minHeight, ">", smallDimension, "horizontal axis requested more height if constrained"); + axis.orientation("left"); + spaceRequest = axis.requestedSpace(smallDimension, 300); + assert.operator(spaceRequest.minWidth, ">", smallDimension, "vertical axis requested more width if constrained"); svg.remove(); }); @@ -146,37 +144,37 @@ describe("Category Axes", () => { for (var i = 0; i < tickLabels[0].length; i++) { var tickLabelBox = tickLabels[0][i].getBoundingClientRect(); var tickMarkBox = tickMarks[0][i].getBoundingClientRect(); - assert.isFalse(Plottable._Util.DOM.boxesOverlap(tickLabelBox, tickMarkBox), "tick label and box do not overlap"); + assert.isFalse(Plottable.Utils.DOM.boxesOverlap(tickLabelBox, tickMarkBox), "tick label and box do not overlap"); } } - var svg = generateSVG(400, 300); - var yScale = new Plottable.Scale.Category(); - var axis = new Plottable.Axis.Category(yScale, "left"); + var svg = TestMethods.generateSVG(400, 300); + var yScale = new Plottable.Scales.Category(); + var axis = new Plottable.Axes.Category(yScale, "left"); yScale.domain(["A", "B", "C"]); axis.renderTo(svg); var tickLabels = ( axis)._content.selectAll(".tick-label"); var tickMarks = ( axis)._content.selectAll(".tick-mark"); verifyTickLabelOverlaps(tickLabels, tickMarks); - axis.orient("right"); + axis.orientation("right"); verifyTickLabelOverlaps(tickLabels, tickMarks); svg.remove(); }); it("axis should request more space when rotated than not rotated", () => { - var svg = generateSVG(300, 300); + var svg = TestMethods.generateSVG(300, 300); var labels = ["label1", "label2", "label100"]; - var scale = new Plottable.Scale.Category().domain(labels); - var axis = new Plottable.Axis.Category(scale, "bottom"); + var scale = new Plottable.Scales.Category().domain(labels); + var axis = new Plottable.Axes.Category(scale, "bottom"); axis.renderTo(svg); - var requestedSpace = axis._requestedSpace(300, 50); - var flatHeight = requestedSpace.height; + var requestedSpace = axis.requestedSpace(300, 50); + var flatHeight = requestedSpace.minHeight; axis.tickLabelAngle(-90); - requestedSpace = axis._requestedSpace(300, 50); - assert.isTrue(flatHeight < requestedSpace.height, "axis should request more height when tick labels are rotated"); + requestedSpace = axis.requestedSpace(300, 50); + assert.isTrue(flatHeight < requestedSpace.minHeight, "axis should request more height when tick labels are rotated"); svg.remove(); }); diff --git a/test/components/interactive/dragBoxLayerTests.ts b/test/components/dragBoxLayerTests.ts similarity index 63% rename from test/components/interactive/dragBoxLayerTests.ts rename to test/components/dragBoxLayerTests.ts index ab5a8ac33d..cdf699d0f6 100644 --- a/test/components/interactive/dragBoxLayerTests.ts +++ b/test/components/dragBoxLayerTests.ts @@ -1,4 +1,4 @@ -/// +/// var assert = chai.assert; @@ -8,8 +8,8 @@ describe("Interactive Components", () => { var SVG_HEIGHT = 400; it("correctly draws box on drag", () => { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var dbl = new Plottable.Component.DragBoxLayer(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var dbl = new Plottable.Components.DragBoxLayer(); dbl.renderTo(svg); assert.isFalse(dbl.boxVisible(), "box is hidden initially"); @@ -23,7 +23,7 @@ describe("Interactive Components", () => { }; var target = dbl.background(); - triggerFakeDragSequence(target, startPoint, endPoint); + TestMethods.triggerFakeDragSequence(target, startPoint, endPoint); assert.isTrue(dbl.boxVisible(), "box is drawn on drag"); var bounds = dbl.bounds(); assert.deepEqual(bounds.topLeft, startPoint, "top-left point was set correctly"); @@ -33,8 +33,8 @@ describe("Interactive Components", () => { }); it("dismisses on click", () => { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var dbl = new Plottable.Component.DragBoxLayer(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var dbl = new Plottable.Components.DragBoxLayer(); dbl.renderTo(svg); var targetPoint = { @@ -43,7 +43,7 @@ describe("Interactive Components", () => { }; var target = dbl.background(); - triggerFakeDragSequence(target, targetPoint, targetPoint); + TestMethods.triggerFakeDragSequence(target, targetPoint, targetPoint); assert.isFalse(dbl.boxVisible(), "box is hidden on click"); @@ -51,16 +51,22 @@ describe("Interactive Components", () => { }); it("clipPath enabled", () => { - var dbl = new Plottable.Component.DragBoxLayer(); - assert.isTrue(dbl.clipPathEnabled, "uses clipPath (to hide detection edges)"); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var dbl = new Plottable.Components.DragBoxLayer(); + dbl.renderTo(svg); + TestMethods.verifyClipPath(dbl); + var clipRect = ( dbl)._boxContainer.select(".clip-rect"); + assert.strictEqual(TestMethods.numAttr(clipRect, "width"), SVG_WIDTH, "the clipRect has an appropriate width"); + assert.strictEqual(TestMethods.numAttr(clipRect, "height"), SVG_HEIGHT, "the clipRect has an appropriate height"); + svg.remove(); }); it("detectionRadius()", () => { - var dbl = new Plottable.Component.DragBoxLayer(); + var dbl = new Plottable.Components.DragBoxLayer(); assert.doesNotThrow(() => dbl.detectionRadius(3), Error, "can set detection radius before anchoring"); - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); dbl.renderTo("svg"); var radius = 5; @@ -84,8 +90,8 @@ describe("Interactive Components", () => { }); it("onDragStart()", () => { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var dbl = new Plottable.Component.DragBoxLayer(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var dbl = new Plottable.Components.DragBoxLayer(); dbl.renderTo(svg); var startPoint = { @@ -98,26 +104,32 @@ describe("Interactive Components", () => { }; var receivedBounds: Plottable.Bounds; + var callbackCalled = false; var callback = (b: Plottable.Bounds) => { receivedBounds = b; + callbackCalled = true; }; dbl.onDragStart(callback); var target = dbl.background(); - triggerFakeDragSequence(target, startPoint, endPoint); + TestMethods.triggerFakeDragSequence(target, startPoint, endPoint); + assert.isTrue(callbackCalled, "the callback was called"); assert.deepEqual(receivedBounds.topLeft, startPoint, "top-left point was set correctly"); assert.deepEqual(receivedBounds.bottomRight, startPoint, "bottom-right point was set correctly"); - assert.strictEqual(dbl.onDragStart(), callback, "can retrieve callback by calling with no args"); - dbl.onDragStart(null); - assert.isNull(dbl.onDragStart(), "can blank callback by passing null"); + + dbl.offDragStart(callback); + + callbackCalled = false; + TestMethods.triggerFakeDragSequence(target, startPoint, endPoint); + assert.isFalse(callbackCalled, "the callback was detached from the dragBoxLayer and not called"); svg.remove(); }); it("onDrag()", () => { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var dbl = new Plottable.Component.DragBoxLayer(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var dbl = new Plottable.Components.DragBoxLayer(); dbl.renderTo(svg); var startPoint = { @@ -130,26 +142,32 @@ describe("Interactive Components", () => { }; var receivedBounds: Plottable.Bounds; + var callbackCalled = false; var callback = (b: Plottable.Bounds) => { receivedBounds = b; + callbackCalled = true; }; dbl.onDrag(callback); var target = dbl.background(); - triggerFakeDragSequence(target, startPoint, endPoint); + TestMethods.triggerFakeDragSequence(target, startPoint, endPoint); + assert.isTrue(callbackCalled, "the callback was called"); assert.deepEqual(receivedBounds.topLeft, startPoint, "top-left point was set correctly"); assert.deepEqual(receivedBounds.bottomRight, endPoint, "bottom-right point was set correctly"); - assert.strictEqual(dbl.onDrag(), callback, "can retrieve callback by calling with no args"); - dbl.onDrag(null); - assert.isNull(dbl.onDrag(), "can blank callback by passing null"); + + callbackCalled = false; + dbl.offDrag(callback); + + TestMethods.triggerFakeDragSequence(target, startPoint, endPoint); + assert.isFalse(callbackCalled, "the callback was detached from the dragoBoxLayer and not called"); svg.remove(); }); it("onDragEnd()", () => { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var dbl = new Plottable.Component.DragBoxLayer(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var dbl = new Plottable.Components.DragBoxLayer(); dbl.renderTo(svg); var startPoint = { @@ -162,25 +180,98 @@ describe("Interactive Components", () => { }; var receivedBounds: Plottable.Bounds; + var callbackCalled = false; var callback = (b: Plottable.Bounds) => { receivedBounds = b; + callbackCalled = true; }; dbl.onDragEnd(callback); var target = dbl.background(); - triggerFakeDragSequence(target, startPoint, endPoint); + TestMethods.triggerFakeDragSequence(target, startPoint, endPoint); + assert.isTrue(callbackCalled, "the callback was called"); assert.deepEqual(receivedBounds.topLeft, startPoint, "top-left point was set correctly"); assert.deepEqual(receivedBounds.bottomRight, endPoint, "bottom-right point was set correctly"); - assert.strictEqual(dbl.onDragEnd(), callback, "can retrieve callback by calling with no args"); - dbl.onDragEnd(null); - assert.isNull(dbl.onDragEnd(), "can blank callback by passing null"); + dbl.offDragEnd(callback); + callbackCalled = false; + + TestMethods.triggerFakeDragSequence(target, startPoint, endPoint); + assert.isFalse(callbackCalled, "the callback was detached from the dragoBoxLayer and not called"); + + svg.remove(); + }); + + it("multiple drag interaction callbacks", () => { + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var dbl = new Plottable.Components.DragBoxLayer(); + dbl.renderTo(svg); + + var startPoint = { + x: SVG_WIDTH / 4, + y: SVG_HEIGHT / 4 + }; + var endPoint = { + x: SVG_WIDTH / 2, + y: SVG_HEIGHT / 2 + }; + + var callbackDragStart1Called = false; + var callbackDragStart2Called = false; + var callbackDrag1Called = false; + var callbackDrag2Called = false; + var callbackDragEnd1Called = false; + var callbackDragEnd2Called = false; + + var callbackDragStart1 = () => callbackDragStart1Called = true; + var callbackDragStart2 = () => callbackDragStart2Called = true; + var callbackDrag1 = () => callbackDrag1Called = true; + var callbackDrag2 = () => callbackDrag2Called = true; + var callbackDragEnd1 = () => callbackDragEnd1Called = true; + var callbackDragEnd2 = () => callbackDragEnd2Called = true; + + dbl.onDragStart(callbackDragStart1); + dbl.onDragStart(callbackDragStart2); + dbl.onDrag(callbackDrag1); + dbl.onDrag(callbackDrag2); + dbl.onDragEnd(callbackDragEnd1); + dbl.onDragEnd(callbackDragEnd2); + + var target = dbl.background(); + TestMethods.triggerFakeDragSequence(target, startPoint, endPoint); + + assert.isTrue(callbackDragStart1Called, "the callback 1 for drag start was called"); + assert.isTrue(callbackDragStart2Called, "the callback 2 for drag start was called"); + assert.isTrue(callbackDrag1Called, "the callback 1 for drag was called"); + assert.isTrue(callbackDrag2Called, "the callback 2 for drag was called"); + assert.isTrue(callbackDragEnd1Called, "the callback 1 for drag end was called"); + assert.isTrue(callbackDragEnd2Called, "the callback 2 for drag end was called"); + + dbl.offDragStart(callbackDragStart1); + dbl.offDrag(callbackDrag1); + dbl.offDragEnd(callbackDragEnd1); + + callbackDragStart1Called = false; + callbackDragStart2Called = false; + callbackDrag1Called = false; + callbackDrag2Called = false; + callbackDragEnd1Called = false; + callbackDragEnd2Called = false; + + TestMethods.triggerFakeDragSequence(target, startPoint, endPoint); + assert.isFalse(callbackDragStart1Called, "the callback 1 for drag start was disconnected"); + assert.isTrue(callbackDragStart2Called, "the callback 2 for drag start is still connected"); + assert.isFalse(callbackDrag1Called, "the callback 1 for drag was called disconnected"); + assert.isTrue(callbackDrag2Called, "the callback 2 for drag is still connected"); + assert.isFalse(callbackDragEnd1Called, "the callback 1 for drag end was disconnected"); + assert.isTrue(callbackDragEnd2Called, "the callback 2 for drag end is still connected"); svg.remove(); }); + describe("resizing", () => { var svg: D3.Selection; - var dbl: Plottable.Component.DragBoxLayer; + var dbl: Plottable.Components.DragBoxLayer; var target: D3.Selection; var midPoint = { x: SVG_WIDTH / 2, @@ -193,7 +284,7 @@ describe("Interactive Components", () => { topLeft: { x: 0, y: 0 }, bottomRight: { x: 0, y: 0} }); - triggerFakeDragSequence(target, + TestMethods.triggerFakeDragSequence(target, { x: SVG_WIDTH / 4, y: SVG_HEIGHT / 4}, { x: SVG_WIDTH * 3 / 4, y: SVG_HEIGHT * 3 / 4} ); @@ -201,8 +292,8 @@ describe("Interactive Components", () => { } beforeEach(() => { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - dbl = new Plottable.Component.DragBoxLayer(); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + dbl = new Plottable.Components.DragBoxLayer(); dbl.renderTo(svg); target = dbl.background(); @@ -216,7 +307,7 @@ describe("Interactive Components", () => { it("resize from top edge", () => { dbl.resizable(true); - triggerFakeDragSequence(target, + TestMethods.triggerFakeDragSequence(target, { x: midPoint.x, y: initialBounds.topLeft.y }, { x: midPoint.x, y: 0 } ); @@ -227,7 +318,7 @@ describe("Interactive Components", () => { assert.strictEqual(bounds.bottomRight.x, initialBounds.bottomRight.x, "right edge was not moved"); resetBox(); - triggerFakeDragSequence(target, + TestMethods.triggerFakeDragSequence(target, { x: midPoint.x, y: initialBounds.topLeft.y }, { x: midPoint.x, y: SVG_HEIGHT } ); @@ -238,7 +329,7 @@ describe("Interactive Components", () => { it("resize from bottom edge", () => { dbl.resizable(true); - triggerFakeDragSequence(target, + TestMethods.triggerFakeDragSequence(target, { x: midPoint.x, y: initialBounds.bottomRight.y }, { x: midPoint.x, y: SVG_HEIGHT } ); @@ -249,7 +340,7 @@ describe("Interactive Components", () => { assert.strictEqual(bounds.bottomRight.x, initialBounds.bottomRight.x, "right edge was not moved"); resetBox(); - triggerFakeDragSequence(target, + TestMethods.triggerFakeDragSequence(target, { x: midPoint.x, y: initialBounds.bottomRight.y }, { x: midPoint.x, y: 0 } ); @@ -260,7 +351,7 @@ describe("Interactive Components", () => { it("resize from left edge", () => { dbl.resizable(true); - triggerFakeDragSequence(target, + TestMethods.triggerFakeDragSequence(target, { x: initialBounds.topLeft.x, y: midPoint.y }, { x: 0, y: midPoint.y } ); @@ -271,7 +362,7 @@ describe("Interactive Components", () => { assert.strictEqual(bounds.bottomRight.x, initialBounds.bottomRight.x, "right edge was not moved"); resetBox(); - triggerFakeDragSequence(target, + TestMethods.triggerFakeDragSequence(target, { x: initialBounds.topLeft.x, y: midPoint.y }, { x: SVG_WIDTH, y: midPoint.y } ); @@ -282,7 +373,7 @@ describe("Interactive Components", () => { it("resize from right edge", () => { dbl.resizable(true); - triggerFakeDragSequence(target, + TestMethods.triggerFakeDragSequence(target, { x: initialBounds.bottomRight.x, y: midPoint.y }, { x: SVG_WIDTH, y: midPoint.y } ); @@ -293,7 +384,7 @@ describe("Interactive Components", () => { assert.strictEqual(bounds.bottomRight.x, SVG_WIDTH, "right edge was repositioned"); resetBox(); - triggerFakeDragSequence(target, + TestMethods.triggerFakeDragSequence(target, { x: initialBounds.bottomRight.x, y: midPoint.y }, { x: 0, y: midPoint.y } ); @@ -305,7 +396,7 @@ describe("Interactive Components", () => { it("resizes if grabbed within detectionRadius()", () => { dbl.resizable(true); var detectionRadius = dbl.detectionRadius(); - triggerFakeDragSequence(target, + TestMethods.triggerFakeDragSequence(target, { x: midPoint.x, y: initialBounds.bottomRight.y + detectionRadius - 1 }, { x: midPoint.x, y: SVG_HEIGHT } ); @@ -317,7 +408,7 @@ describe("Interactive Components", () => { resetBox(); var startYOutside = initialBounds.bottomRight.y + detectionRadius + 1; - triggerFakeDragSequence(target, + TestMethods.triggerFakeDragSequence(target, { x: midPoint.x, y: startYOutside }, { x: midPoint.x, y: SVG_HEIGHT } ); @@ -329,11 +420,10 @@ describe("Interactive Components", () => { it("doesn't dismiss on no-op resize", () => { dbl.resizable(true); - triggerFakeDragSequence(target, + TestMethods.triggerFakeDragSequence(target, { x: midPoint.x, y: initialBounds.topLeft.y }, { x: midPoint.x, y: initialBounds.topLeft.y } ); - var bounds = dbl.bounds(); assert.isTrue(dbl.boxVisible(), "box was not dismissed"); svg.remove(); }); @@ -341,7 +431,7 @@ describe("Interactive Components", () => { it("can't resize if hidden", () => { dbl.resizable(true); dbl.boxVisible(false); - triggerFakeDragSequence(target, + TestMethods.triggerFakeDragSequence(target, { x: midPoint.x, y: initialBounds.bottomRight.y }, { x: midPoint.x, y: SVG_HEIGHT } ); diff --git a/test/components/gridlinesTests.ts b/test/components/gridlinesTests.ts index 389eed3a84..6e94c2b57a 100644 --- a/test/components/gridlinesTests.ts +++ b/test/components/gridlinesTests.ts @@ -4,38 +4,38 @@ var assert = chai.assert; describe("Gridlines", () => { it("Gridlines and axis tick marks align", () => { - var svg = generateSVG(640, 480); - var xScale = new Plottable.Scale.Linear(); + var svg = TestMethods.generateSVG(640, 480); + var xScale = new Plottable.Scales.Linear(); xScale.domain([0, 10]); // manually set domain since we won't have a renderer - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); + var xAxis = new Plottable.Axes.Numeric(xScale, "bottom"); - var yScale = new Plottable.Scale.Linear(); + var yScale = new Plottable.Scales.Linear(); yScale.domain([0, 10]); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); + var yAxis = new Plottable.Axes.Numeric(yScale, "left"); - var gridlines = new Plottable.Component.Gridlines(xScale, yScale); - var basicTable = new Plottable.Component.Table().addComponent(0, 0, yAxis) - .addComponent(0, 1, gridlines) - .addComponent(1, 1, xAxis); + var gridlines = new Plottable.Components.Gridlines(xScale, yScale); + var basicTable = new Plottable.Components.Table().add(yAxis, 0, 0) + .add(gridlines, 0, 1) + .add(xAxis, 1, 1); - basicTable._anchor(svg); - basicTable._computeLayout(); + basicTable.anchor(svg); + basicTable.computeLayout(); xScale.range([0, xAxis.width() ]); // manually set range since we don't have a renderer yScale.range([yAxis.height(), 0]); - basicTable._render(); + basicTable.render(); - var xAxisTickMarks = ( xAxis)._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_MARK_CLASS)[0]; + var xAxisTickMarks = ( xAxis)._element.selectAll("." + Plottable.Axis.TICK_MARK_CLASS)[0]; var xGridlines = ( gridlines)._element.select(".x-gridlines").selectAll("line")[0]; - assert.equal(xAxisTickMarks.length, xGridlines.length, "There is an x gridline for each x tick"); + assert.strictEqual(xAxisTickMarks.length, xGridlines.length, "There is an x gridline for each x tick"); for (var i = 0; i < xAxisTickMarks.length; i++) { var xTickMarkRect = xAxisTickMarks[i].getBoundingClientRect(); var xGridlineRect = xGridlines[i].getBoundingClientRect(); assert.closeTo(xTickMarkRect.left, xGridlineRect.left, 1, "x tick and gridline align"); } - var yAxisTickMarks = ( yAxis)._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_MARK_CLASS)[0]; + var yAxisTickMarks = ( yAxis)._element.selectAll("." + Plottable.Axis.TICK_MARK_CLASS)[0]; var yGridlines = ( gridlines)._element.select(".y-gridlines").selectAll("line")[0]; - assert.equal(yAxisTickMarks.length, yGridlines.length, "There is an x gridline for each x tick"); + assert.strictEqual(yAxisTickMarks.length, yGridlines.length, "There is an x gridline for each x tick"); for (var j = 0; j < yAxisTickMarks.length; j++) { var yTickMarkRect = yAxisTickMarks[j].getBoundingClientRect(); var yGridlineRect = yGridlines[j].getBoundingClientRect(); @@ -44,11 +44,4 @@ describe("Gridlines", () => { svg.remove(); }); - - it("Unanchored Gridlines don't throw an error when scale updates", () => { - var xScale = new Plottable.Scale.Linear(); - var gridlines = new Plottable.Component.Gridlines(xScale, null); - xScale.domain([0, 1]); - // test passes if error is not thrown. - }); }); diff --git a/test/components/interpolatedColorLegendTests.ts b/test/components/interpolatedColorLegendTests.ts index a070f01e5a..32ecb1cf35 100644 --- a/test/components/interpolatedColorLegendTests.ts +++ b/test/components/interpolatedColorLegendTests.ts @@ -4,14 +4,14 @@ var assert = chai.assert; describe("InterpolatedColorLegend", () => { var svg: D3.Selection; - var colorScale: Plottable.Scale.InterpolatedColor; + var colorScale: Plottable.Scales.InterpolatedColor; beforeEach(() => { - svg = generateSVG(400, 400); - colorScale = new Plottable.Scale.InterpolatedColor(); + svg = TestMethods.generateSVG(400, 400); + colorScale = new Plottable.Scales.InterpolatedColor(); }); - function assertBasicRendering(legend: Plottable.Component.InterpolatedColorLegend) { + function assertBasicRendering(legend: Plottable.Components.InterpolatedColorLegend) { var scaleDomain = colorScale.domain(); var legendElement: D3.Selection = ( legend)._element; @@ -27,11 +27,11 @@ describe("InterpolatedColorLegend", () => { var swatchContainerBCR = swatchContainer.node().getBoundingClientRect(); var swatchBoundingBox = legendElement.select(".swatch-bounding-box"); var boundingBoxBCR = swatchBoundingBox.node().getBoundingClientRect(); - assert.isTrue(Plottable._Util.DOM.boxIsInside(swatchContainerBCR, boundingBoxBCR), + assert.isTrue(Plottable.Utils.DOM.boxIsInside(swatchContainerBCR, boundingBoxBCR), "bounding box contains all swatches"); var elementBCR = legendElement.node().getBoundingClientRect(); - assert.isTrue(Plottable._Util.DOM.boxIsInside(swatchContainerBCR, elementBCR), + assert.isTrue(Plottable.Utils.DOM.boxIsInside(swatchContainerBCR, elementBCR), "swatches are drawn within the legend's element"); var formattedDomainValues = scaleDomain.map(( legend)._formatter); @@ -41,7 +41,7 @@ describe("InterpolatedColorLegend", () => { } it("renders correctly (orientation: horizontal)", () => { - var legend = new Plottable.Component.InterpolatedColorLegend(colorScale, "horizontal"); + var legend = new Plottable.Components.InterpolatedColorLegend(colorScale, "horizontal"); legend.renderTo(svg); assertBasicRendering(legend); @@ -60,7 +60,7 @@ describe("InterpolatedColorLegend", () => { }); it("renders correctly (orientation: right)", () => { - var legend = new Plottable.Component.InterpolatedColorLegend(colorScale, "right"); + var legend = new Plottable.Components.InterpolatedColorLegend(colorScale, "right"); legend.renderTo(svg); assertBasicRendering(legend); @@ -80,7 +80,7 @@ describe("InterpolatedColorLegend", () => { }); it("renders correctly (orientation: left)", () => { - var legend = new Plottable.Component.InterpolatedColorLegend(colorScale, "left"); + var legend = new Plottable.Components.InterpolatedColorLegend(colorScale, "left"); legend.renderTo(svg); assertBasicRendering(legend); @@ -100,7 +100,7 @@ describe("InterpolatedColorLegend", () => { }); it("re-renders when scale domain updates", () => { - var legend = new Plottable.Component.InterpolatedColorLegend(colorScale, "horizontal"); + var legend = new Plottable.Components.InterpolatedColorLegend(colorScale, "horizontal"); legend.renderTo(svg); colorScale.domain([0, 85]); @@ -109,25 +109,25 @@ describe("InterpolatedColorLegend", () => { svg.remove(); }); - it("orient() input-checking", () => { - var legend = new Plottable.Component.InterpolatedColorLegend(colorScale, "horizontal"); + it("orientation() input-checking", () => { + var legend = new Plottable.Components.InterpolatedColorLegend(colorScale, "horizontal"); - legend.orient("horizontal"); // should work - legend.orient("right"); // should work - legend.orient("left"); // should work + legend.orientation("horizontal"); // should work + legend.orientation("right"); // should work + legend.orientation("left"); // should work - assert.throws(() => legend.orient("blargh"), "not a valid orientation"); + assert.throws(() => legend.orientation("blargh"), "not a valid orientation"); svg.remove(); }); it("orient() triggers layout computation", () => { - var legend = new Plottable.Component.InterpolatedColorLegend(colorScale, "horizontal"); + var legend = new Plottable.Components.InterpolatedColorLegend(colorScale, "horizontal"); legend.renderTo(svg); var widthBefore = legend.width(); var heightBefore = legend.height(); - legend.orient("right"); + legend.orientation("right"); assert.notEqual(legend.width(), widthBefore, "proportions changed (width)"); assert.notEqual(legend.height(), heightBefore, "proportions changed (height)"); svg.remove(); @@ -135,7 +135,7 @@ describe("InterpolatedColorLegend", () => { it("renders correctly when width is constrained (orientation: horizontal)", () => { svg.attr("width", 100); - var legend = new Plottable.Component.InterpolatedColorLegend(colorScale, "horizontal"); + var legend = new Plottable.Components.InterpolatedColorLegend(colorScale, "horizontal"); legend.renderTo(svg); assertBasicRendering(legend); svg.remove(); @@ -143,7 +143,7 @@ describe("InterpolatedColorLegend", () => { it("renders correctly when height is constrained (orientation: horizontal)", () => { svg.attr("height", 20); - var legend = new Plottable.Component.InterpolatedColorLegend(colorScale, "horizontal"); + var legend = new Plottable.Components.InterpolatedColorLegend(colorScale, "horizontal"); legend.renderTo(svg); assertBasicRendering(legend); svg.remove(); @@ -151,7 +151,7 @@ describe("InterpolatedColorLegend", () => { it("renders correctly when width is constrained (orientation: right)", () => { svg.attr("width", 30); - var legend = new Plottable.Component.InterpolatedColorLegend(colorScale, "right"); + var legend = new Plottable.Components.InterpolatedColorLegend(colorScale, "right"); legend.renderTo(svg); assertBasicRendering(legend); svg.remove(); @@ -159,7 +159,7 @@ describe("InterpolatedColorLegend", () => { it("renders correctly when height is constrained (orientation: right)", () => { svg.attr("height", 100); - var legend = new Plottable.Component.InterpolatedColorLegend(colorScale, "right"); + var legend = new Plottable.Components.InterpolatedColorLegend(colorScale, "right"); legend.renderTo(svg); assertBasicRendering(legend); svg.remove(); @@ -167,7 +167,7 @@ describe("InterpolatedColorLegend", () => { it("renders correctly when width is constrained (orientation: left)", () => { svg.attr("width", 30); - var legend = new Plottable.Component.InterpolatedColorLegend(colorScale, "left"); + var legend = new Plottable.Components.InterpolatedColorLegend(colorScale, "left"); legend.renderTo(svg); assertBasicRendering(legend); svg.remove(); @@ -175,7 +175,7 @@ describe("InterpolatedColorLegend", () => { it("renders correctly when height is constrained (orientation: left)", () => { svg.attr("height", 100); - var legend = new Plottable.Component.InterpolatedColorLegend(colorScale, "left"); + var legend = new Plottable.Components.InterpolatedColorLegend(colorScale, "left"); legend.renderTo(svg); assertBasicRendering(legend); svg.remove(); diff --git a/test/components/labelTests.ts b/test/components/labelTests.ts index f6d1b99200..7b14e21646 100644 --- a/test/components/labelTests.ts +++ b/test/components/labelTests.ts @@ -2,12 +2,12 @@ var assert = chai.assert; - describe("Labels", () => { it("Standard text title label generates properly", () => { - var svg = generateSVG(400, 80); - var label = new Plottable.Component.TitleLabel("A CHART TITLE"); + var svg = TestMethods.generateSVG(400, 80); + var label = new Plottable.Components.Label("A CHART TITLE"); + label.classed(Plottable.Components.Label.TITLE_LABEL_CLASS, true); label.renderTo(svg); var content = ( label)._content; @@ -17,47 +17,50 @@ describe("Labels", () => { assert.lengthOf(textChildren, 1, "There is one text node in the parent element"); var text = content.select("text"); - var bbox = Plottable._Util.DOM.getBBox(text); + var bbox = Plottable.Utils.DOM.getBBox(text); assert.closeTo(bbox.height, label.height(), 0.5, "text height === label.minimumHeight()"); - assert.equal(text.node().textContent, "A CHART TITLE", "node's text content is as expected"); + assert.strictEqual(text.node().textContent, "A CHART TITLE", "node's text content is as expected"); svg.remove(); }); // Skipping due to FF odd client bounding rect computation - #1470. it.skip("Left-rotated text is handled properly", () => { - var svg = generateSVG(100, 400); - var label = new Plottable.Component.AxisLabel("LEFT-ROTATED LABEL", "left"); + var svg = TestMethods.generateSVG(100, 400); + var label = new Plottable.Components.Label("LEFT-ROTATED LABEL", "left"); + label.classed(Plottable.Components.Label.AXIS_LABEL_CLASS, true); label.renderTo(svg); var content = ( label)._content; var text = content.select("text"); - var textBBox = Plottable._Util.DOM.getBBox(text); - assertBBoxInclusion(( label)._element.select(".bounding-box"), text); + var textBBox = Plottable.Utils.DOM.getBBox(text); + TestMethods.assertBBoxInclusion(( label)._element.select(".bounding-box"), text); assert.closeTo(textBBox.height, label.width(), window.Pixel_CloseTo_Requirement, "text height"); svg.remove(); }); // Skipping due to FF odd client bounding rect computation - #1470. it.skip("Right-rotated text is handled properly", () => { - var svg = generateSVG(100, 400); - var label = new Plottable.Component.AxisLabel("RIGHT-ROTATED LABEL", "right"); + var svg = TestMethods.generateSVG(100, 400); + var label = new Plottable.Components.Label("RIGHT-ROTATED LABEL", "right"); + label.classed(Plottable.Components.Label.AXIS_LABEL_CLASS, true); label.renderTo(svg); var content = ( label)._content; var text = content.select("text"); - var textBBox = Plottable._Util.DOM.getBBox(text); - assertBBoxInclusion(( label)._element.select(".bounding-box"), text); + var textBBox = Plottable.Utils.DOM.getBBox(text); + TestMethods.assertBBoxInclusion(( label)._element.select(".bounding-box"), text); assert.closeTo(textBBox.height, label.width(), window.Pixel_CloseTo_Requirement, "text height"); svg.remove(); }); it("Label text can be changed after label is created", () => { - var svg = generateSVG(400, 80); - var label = new Plottable.Component.TitleLabel("a"); + var svg = TestMethods.generateSVG(400, 80); + var label = new Plottable.Components.Label("a"); + label.classed(Plottable.Components.Label.TITLE_LABEL_CLASS, true); label.renderTo(svg); - assert.equal(( label)._content.select("text").text(), "a", "the text starts at the specified string"); + assert.strictEqual(( label)._content.select("text").text(), "a", "the text starts at the specified string"); assert.operator(label.height(), ">", 0, "rowMin is > 0 for non-empty string"); label.text("hello world"); label.renderTo(svg); - assert.equal(( label)._content.select("text").text(), "hello world", "the label text updated properly"); + assert.strictEqual(( label)._content.select("text").text(), "hello world", "the label text updated properly"); assert.operator(label.height(), ">", 0, "rowMin is > 0 for non-empty string"); svg.remove(); }); @@ -65,99 +68,102 @@ describe("Labels", () => { // skipping because Dan is rewriting labels and the height test fails it.skip("Superlong text is handled in a sane fashion", () => { var svgWidth = 400; - var svg = generateSVG(svgWidth, 80); - var label = new Plottable.Component.TitleLabel("THIS LABEL IS SO LONG WHOEVER WROTE IT WAS PROBABLY DERANGED"); + var svg = TestMethods.generateSVG(svgWidth, 80); + var label = new Plottable.Components.Label("THIS LABEL IS SO LONG WHOEVER WROTE IT WAS PROBABLY DERANGED"); + label.classed(Plottable.Components.Label.TITLE_LABEL_CLASS, true); label.renderTo(svg); var content = ( label)._content; var text = content.select("text"); - var bbox = Plottable._Util.DOM.getBBox(text); - assert.equal(bbox.height, label.height(), "text height === label.minimumHeight()"); + var bbox = Plottable.Utils.DOM.getBBox(text); + assert.strictEqual(bbox.height, label.height(), "text height === label.minimumHeight()"); assert.operator(bbox.width, "<=", svgWidth, "the text is not wider than the SVG width"); svg.remove(); }); it("text in a tiny box is truncated to empty string", () => { - var svg = generateSVG(10, 10); - var label = new Plottable.Component.TitleLabel("Yeah, not gonna fit..."); + var svg = TestMethods.generateSVG(10, 10); + var label = new Plottable.Components.Label("Yeah, not gonna fit..."); + label.classed(Plottable.Components.Label.TITLE_LABEL_CLASS, true); label.renderTo(svg); var text = ( label)._content.select("text"); - assert.equal(text.text(), "", "text was truncated to empty string"); + assert.strictEqual(text.text(), "", "text was truncated to empty string"); svg.remove(); }); it("centered text in a table is positioned properly", () => { - var svg = generateSVG(400, 400); - var label = new Plottable.Component.TitleLabel("X"); - var t = new Plottable.Component.Table().addComponent(0, 0, label) - .addComponent(1, 0, new Plottable.Component.AbstractComponent()); + var svg = TestMethods.generateSVG(400, 400); + var label = new Plottable.Components.Label("X"); + var t = new Plottable.Components.Table().add(label, 0, 0) + .add(new Plottable.Component(), 1, 0); t.renderTo(svg); var textTranslate = d3.transform(( label)._content.select("g").attr("transform")).translate; var eleTranslate = d3.transform(( label)._element.attr("transform")).translate; - var textWidth = Plottable._Util.DOM.getBBox(( label)._content.select("text")).width; + var textWidth = Plottable.Utils.DOM.getBBox(( label)._content.select("text")).width; assert.closeTo(eleTranslate[0] + textTranslate[0] + textWidth / 2, 200, 5, "label is centered"); svg.remove(); }); it("if a label text is changed to empty string, width updates to 0", () => { - var svg = generateSVG(400, 400); - var label = new Plottable.Component.TitleLabel("foo"); + var svg = TestMethods.generateSVG(400, 400); + var label = new Plottable.Components.Label("foo"); label.renderTo(svg); label.text(""); - assert.equal(label.width(), 0, "width updated to 0"); + assert.strictEqual(label.width(), 0, "width updated to 0"); svg.remove(); }); it("unsupported alignments and orientations are unsupported", () => { - assert.throws(() => new Plottable.Component.Label("foo", "bar"), Error, "not a valid orientation"); + assert.throws(() => new Plottable.Components.Label("foo", "bar"), Error, "not a valid orientation"); }); // Skipping due to FF odd client bounding rect computation - #1470. it.skip("Label orientation can be changed after label is created", () => { - var svg = generateSVG(400, 400); - var label = new Plottable.Component.AxisLabel("CHANGING ORIENTATION"); + var svg = TestMethods.generateSVG(400, 400); + var label = new Plottable.Components.Label("CHANGING ORIENTATION"); + label.classed(Plottable.Components.Label.AXIS_LABEL_CLASS, true); label.renderTo(svg); var content = ( label)._content; var text = content.select("text"); - var bbox = Plottable._Util.DOM.getBBox(text); + var bbox = Plottable.Utils.DOM.getBBox(text); assert.closeTo(bbox.height, label.height(), 1, "label is in horizontal position"); - label.orient("right"); + label.orientation("right"); text = content.select("text"); - bbox = Plottable._Util.DOM.getBBox(text); - assertBBoxInclusion(( label)._element.select(".bounding-box"), text); + bbox = Plottable.Utils.DOM.getBBox(text); + TestMethods.assertBBoxInclusion(( label)._element.select(".bounding-box"), text); assert.closeTo(bbox.height, label.width(), window.Pixel_CloseTo_Requirement, "label is in vertical position"); svg.remove(); }); it("padding reacts well under align", () => { - var svg = generateSVG(400, 200); - var testLabel = new Plottable.Component.Label("testing label").padding(30).xAlign("left"); - var longLabel = new Plottable.Component.Label("LONG LABELLLLLLLLLLLLLLLLL").xAlign("left"); - var topLabel = new Plottable.Component.Label("label").yAlign("bottom"); - new Plottable.Component.Table([[topLabel], [testLabel], [longLabel]]).renderTo(svg); + var svg = TestMethods.generateSVG(400, 200); + var testLabel = new Plottable.Components.Label("testing label").padding(30).xAlignment("left"); + var longLabel = new Plottable.Components.Label("LONG LABELLLLLLLLLLLLLLLLL").xAlignment("left"); + var topLabel = new Plottable.Components.Label("label").yAlignment("bottom"); + new Plottable.Components.Table([[topLabel], [testLabel], [longLabel]]).renderTo(svg); var testTextRect = ( testLabel)._element.select("text").node().getBoundingClientRect(); var longTextRect = ( longLabel)._element.select("text").node().getBoundingClientRect(); assert.closeTo(testTextRect.left, longTextRect.left + 30, 2, "left difference by padding amount"); - testLabel.xAlign("right"); + testLabel.xAlignment("right"); testTextRect = ( testLabel)._element.select("text").node().getBoundingClientRect(); longTextRect = ( longLabel)._element.select("text").node().getBoundingClientRect(); assert.closeTo(testTextRect.right, longTextRect.right - 30, 2, "right difference by padding amount"); - testLabel.yAlign("bottom"); + testLabel.yAlignment("bottom"); testTextRect = ( testLabel)._element.select("text").node().getBoundingClientRect(); longTextRect = ( longLabel)._element.select("text").node().getBoundingClientRect(); assert.closeTo(testTextRect.bottom, longTextRect.top - 30, 2, "vertical difference by padding amount"); - testLabel.yAlign("top"); + testLabel.yAlignment("top"); testTextRect = ( testLabel)._element.select("text").node().getBoundingClientRect(); var topTextRect = ( topLabel)._element.select("text").node().getBoundingClientRect(); @@ -167,8 +173,8 @@ describe("Labels", () => { }); it("padding puts space around the label", () => { - var svg = generateSVG(400, 200); - var testLabel = new Plottable.Component.Label("testing label").padding(30); + var svg = TestMethods.generateSVG(400, 200); + var testLabel = new Plottable.Components.Label("testing label").padding(30); testLabel.renderTo(svg); var measurer = new SVGTypewriter.Measurers.Measurer(svg); @@ -181,7 +187,7 @@ describe("Labels", () => { }); it("negative padding throws an error", () => { - var testLabel = new Plottable.Component.Label("testing label"); + var testLabel = new Plottable.Components.Label("testing label"); assert.throws(() => testLabel.padding(-10), Error, "Cannot be less than 0"); }); }); diff --git a/test/components/legendTests.ts b/test/components/legendTests.ts index 5700151a01..45423dbeac 100644 --- a/test/components/legendTests.ts +++ b/test/components/legendTests.ts @@ -2,19 +2,18 @@ var assert = chai.assert; - describe("Legend", () => { var svg: D3.Selection; - var color: Plottable.Scale.Color; - var legend: Plottable.Component.Legend; + var color: Plottable.Scales.Color; + var legend: Plottable.Components.Legend; - var entrySelector = "." + Plottable.Component.Legend.LEGEND_ENTRY_CLASS; - var rowSelector = "." + Plottable.Component.Legend.LEGEND_ROW_CLASS; + var entrySelector = "." + Plottable.Components.Legend.LEGEND_ENTRY_CLASS; + var rowSelector = "." + Plottable.Components.Legend.LEGEND_ROW_CLASS; beforeEach(() => { - svg = generateSVG(400, 400); - color = new Plottable.Scale.Color(); - legend = new Plottable.Component.Legend(color); + svg = TestMethods.generateSVG(400, 400); + color = new Plottable.Scales.Color(); + legend = new Plottable.Components.Legend(color); }); it("a basic legend renders", () => { @@ -24,12 +23,12 @@ describe("Legend", () => { assert.lengthOf(rows[0], color.domain().length, "one entry is created for each item in the domain"); rows.each(function(d: any, i: number) { - assert.equal(d, color.domain()[i], "the data is set properly"); + assert.strictEqual(d, color.domain()[i], "the data is set properly"); var d3this = d3.select(this); var text = d3this.select("text").text(); - assert.equal(text, d, "the text node has correct text"); - var symbol = d3this.select("." + Plottable.Component.Legend.LEGEND_SYMBOL_CLASS); - assert.equal(symbol.attr("fill"), color.scale(d), "the symbol's fill is set properly"); + assert.strictEqual(text, d, "the text node has correct text"); + var symbol = d3this.select("." + Plottable.Components.Legend.LEGEND_SYMBOL_CLASS); + assert.strictEqual(symbol.attr("fill"), color.scale(d), "the symbol's fill is set properly"); }); svg.remove(); }); @@ -37,17 +36,17 @@ describe("Legend", () => { it("legend domain can be updated after initialization, and height updates as well", () => { legend.renderTo(svg); legend.scale(color); - assert.equal(legend._requestedSpace(200, 200).height, 10, "there is a padding requested height when domain is empty"); + assert.strictEqual(legend.requestedSpace(200, 200).minHeight, 10, "there is a padding requested height when domain is empty"); color.domain(["foo", "bar"]); - var height1 = legend._requestedSpace(400, 400).height; + var height1 = legend.requestedSpace(400, 400).minHeight; var actualHeight1 = legend.height(); assert.operator(height1, ">", 0, "changing the domain gives a positive height"); color.domain(["foo", "bar", "baz"]); - assert.operator(legend._requestedSpace(400, 400).height, ">", height1, "adding to the domain increases the height requested"); + assert.operator(legend.requestedSpace(400, 400).minHeight, ">", height1, "adding to the domain increases the height requested"); var actualHeight2 = legend.height(); assert.operator(actualHeight1, "<", actualHeight2, "Changing the domain caused the legend to re-layout with more height"); var numRows = ( legend)._content.selectAll(rowSelector)[0].length; - assert.equal(numRows, 3, "there are 3 rows"); + assert.strictEqual(numRows, 3, "there are 3 rows"); svg.remove(); }); @@ -55,9 +54,9 @@ describe("Legend", () => { color.domain(["alpha", "beta", "gamma", "delta", "omega", "omicron", "persei", "eight"]); legend.renderTo(svg); - var contentBBox = Plottable._Util.DOM.getBBox(( legend)._content); + var contentBBox = Plottable.Utils.DOM.getBBox(( legend)._content); var contentBottomEdge = contentBBox.y + contentBBox.height; - var bboxBBox = Plottable._Util.DOM.getBBox(( legend)._element.select(".bounding-box")); + var bboxBBox = Plottable.Utils.DOM.getBBox(( legend)._element.select(".bounding-box")); var bboxBottomEdge = bboxBBox.y + bboxBBox.height; assert.operator(contentBottomEdge, "<=", bboxBottomEdge, "content does not extend past bounding box"); @@ -82,10 +81,10 @@ describe("Legend", () => { color.domain(["foo", "bar", "baz"]); legend.renderTo(svg); var numRows = ( legend)._content.selectAll(rowSelector)[0].length; - assert.equal(numRows, 3, "there are 3 legend rows initially"); - legend._render(); + assert.strictEqual(numRows, 3, "there are 3 legend rows initially"); + legend.render(); numRows = ( legend)._content.selectAll(rowSelector)[0].length; - assert.equal(numRows, 3, "there are 3 legend rows after second render"); + assert.strictEqual(numRows, 3, "there are 3 legend rows after second render"); svg.remove(); }); @@ -96,11 +95,11 @@ describe("Legend", () => { color.domain(newDomain); ( legend)._content.selectAll(entrySelector).each(function(d: any, i: number) { - assert.equal(d, newDomain[i], "the data is set correctly"); + assert.strictEqual(d, newDomain[i], "the data is set correctly"); var text = d3.select(this).select("text").text(); - assert.equal(text, d, "the text was set properly"); - var fill = d3.select(this).select("." + Plottable.Component.Legend.LEGEND_SYMBOL_CLASS).attr("fill"); - assert.equal(fill, color.scale(d), "the fill was set properly"); + assert.strictEqual(text, d, "the text was set properly"); + var fill = d3.select(this).select("." + Plottable.Components.Legend.LEGEND_SYMBOL_CLASS).attr("fill"); + assert.strictEqual(fill, color.scale(d), "the fill was set properly"); }); assert.lengthOf(( legend)._content.selectAll(rowSelector)[0], 5, "there are the right number of legend elements"); svg.remove(); @@ -111,17 +110,16 @@ describe("Legend", () => { legend.renderTo(svg); var newDomain = ["a", "b", "c"]; - var newColorScale = new Plottable.Scale.Color("20"); + var newColorScale = new Plottable.Scales.Color("20"); newColorScale.domain(newDomain); legend.scale(newColorScale); - ( legend)._content.selectAll(entrySelector).each(function(d: any, i: number) { - assert.equal(d, newDomain[i], "the data is set correctly"); + assert.strictEqual(d, newDomain[i], "the data is set correctly"); var text = d3.select(this).select("text").text(); - assert.equal(text, d, "the text was set properly"); - var fill = d3.select(this).select("." + Plottable.Component.Legend.LEGEND_SYMBOL_CLASS).attr("fill"); - assert.equal(fill, newColorScale.scale(d), "the fill was set properly"); + assert.strictEqual(text, d, "the text was set properly"); + var fill = d3.select(this).select("." + Plottable.Components.Legend.LEGEND_SYMBOL_CLASS).attr("fill"); + assert.strictEqual(fill, newColorScale.scale(d), "the fill was set properly"); }); svg.remove(); @@ -132,18 +130,18 @@ describe("Legend", () => { legend.renderTo(svg); var tempDomain = ["a", "b", "c"]; - var newColorScale = new Plottable.Scale.Color("20"); + var newColorScale = new Plottable.Scales.Color("20"); newColorScale.domain(tempDomain); legend.scale(newColorScale); var newDomain = ["a", "foo", "d"]; newColorScale.domain(newDomain); ( legend)._content.selectAll(entrySelector).each(function(d: any, i: number) { - assert.equal(d, newDomain[i], "the data is set correctly"); + assert.strictEqual(d, newDomain[i], "the data is set correctly"); var text = d3.select(this).select("text").text(); - assert.equal(text, d, "the text was set properly"); - var fill = d3.select(this).select("." + Plottable.Component.Legend.LEGEND_SYMBOL_CLASS).attr("fill"); - assert.equal(fill, newColorScale.scale(d), "the fill was set properly"); + assert.strictEqual(text, d, "the text was set properly"); + var fill = d3.select(this).select("." + Plottable.Components.Legend.LEGEND_SYMBOL_CLASS).attr("fill"); + assert.strictEqual(fill, newColorScale.scale(d), "the fill was set properly"); }); svg.remove(); }); @@ -156,8 +154,8 @@ describe("Legend", () => { function verifySymbolHeight() { var text = ( legend)._content.select("text"); - var icon = ( legend)._content.select("." + Plottable.Component.Legend.LEGEND_SYMBOL_CLASS); - var textHeight = Plottable._Util.DOM.getBBox(text).height; + var icon = ( legend)._content.select("." + Plottable.Components.Legend.LEGEND_SYMBOL_CLASS); + var textHeight = Plottable.Utils.DOM.getBBox(text).height; var symbolHeight = icon.node().getBoundingClientRect().height; assert.operator(symbolHeight, "<", textHeight, "icons too small: symbolHeight < textHeight"); assert.operator(symbolHeight, ">", textHeight / 2, "icons too big: textHeight / 2 > symbolHeight"); @@ -166,13 +164,13 @@ describe("Legend", () => { verifySymbolHeight(); style.text(".plottable .legend text { font-size: 60px; }"); - legend._computeLayout(); - legend._render(); + legend.computeLayout(); + legend.render(); verifySymbolHeight(); style.text(".plottable .legend text { font-size: 10px; }"); - legend._computeLayout(); - legend._render(); + legend.computeLayout(); + legend.render(); verifySymbolHeight(); svg.remove(); @@ -210,13 +208,26 @@ describe("Legend", () => { legend.detach(); svg.remove(); - svg = generateSVG(100, 100); + svg = TestMethods.generateSVG(100, 100); legend.renderTo(svg); rows = ( legend)._element.selectAll(rowSelector); assert.lengthOf(rows[0], 3, "Wrapped text on to three rows when further constrained"); svg.remove(); }); + it("requests more width if entries would be truncated", () => { + color.domain(["George Waaaaaashington", "John Adaaaams", "Thomaaaaas Jefferson"]); + + legend.renderTo(svg); // have to be in DOM to measure + + var idealSpaceRequest = legend.requestedSpace(Infinity, Infinity); + var constrainedRequest = legend.requestedSpace(idealSpaceRequest.minWidth * 0.9, Infinity); + + assert.strictEqual(idealSpaceRequest.minWidth, constrainedRequest.minWidth, + "won't settle for less width if entries would be truncated"); + svg.remove(); + }); + it("getEntry() retrieves the correct entry for vertical legends", () => { color.domain(["AA", "BB", "CC"]); legend.maxEntriesPerRow(1); @@ -259,24 +270,24 @@ describe("Legend", () => { it("truncates and hides entries if space is constrained for a horizontal legend", () => { svg.remove(); - svg = generateSVG(70, 400); + svg = TestMethods.generateSVG(70, 400); legend.maxEntriesPerRow(Infinity); legend.renderTo(svg); var textEls = ( legend)._element.selectAll("text"); textEls.each(function(d: any) { var textEl = d3.select(this); - assertBBoxInclusion(( legend)._element, textEl); + TestMethods.assertBBoxInclusion(( legend)._element, textEl); }); legend.detach(); svg.remove(); - svg = generateSVG(100, 50); + svg = TestMethods.generateSVG(100, 50); legend.renderTo(svg); textEls = ( legend)._element.selectAll("text"); textEls.each(function(d: any) { var textEl = d3.select(this); - assertBBoxInclusion(( legend)._element, textEl); + TestMethods.assertBBoxInclusion(( legend)._element, textEl); }); svg.remove(); diff --git a/test/components/numericAxisTests.ts b/test/components/numericAxisTests.ts index 47356092e9..bb05215eea 100644 --- a/test/components/numericAxisTests.ts +++ b/test/components/numericAxisTests.ts @@ -3,14 +3,6 @@ var assert = chai.assert; describe("NumericAxis", () => { - function boxesOverlap(boxA: ClientRect, boxB: ClientRect) { - if (boxA.right < boxB.left) { return false; } - if (boxA.left > boxB.right) { return false; } - if (boxA.bottom < boxB.top) { return false; } - if (boxA.top > boxB.bottom) { return false; } - return true; - } - function boxIsInside(inner: ClientRect, outer: ClientRect, epsilon = 0) { if (inner.left < outer.left - epsilon) { return false; } if (inner.right > outer.right + epsilon) { return false; } @@ -27,12 +19,12 @@ describe("NumericAxis", () => { } it("tickLabelPosition() input validation", () => { - var scale = new Plottable.Scale.Linear(); - var horizontalAxis = new Plottable.Axis.Numeric(scale, "bottom"); + var scale = new Plottable.Scales.Linear(); + var horizontalAxis = new Plottable.Axes.Numeric(scale, "bottom"); assert.throws(() => horizontalAxis.tickLabelPosition("top"), "horizontal"); assert.throws(() => horizontalAxis.tickLabelPosition("bottom"), "horizontal"); - var verticalAxis = new Plottable.Axis.Numeric(scale, "left"); + var verticalAxis = new Plottable.Axes.Numeric(scale, "left"); assert.throws(() => verticalAxis.tickLabelPosition("left"), "vertical"); assert.throws(() => verticalAxis.tickLabelPosition("right"), "vertical"); }); @@ -40,15 +32,15 @@ describe("NumericAxis", () => { it("draws tick labels correctly (horizontal)", () => { var SVG_WIDTH = 500; var SVG_HEIGHT = 100; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var scale = new Plottable.Scale.Linear(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var scale = new Plottable.Scales.Linear(); scale.range([0, SVG_WIDTH]); - var numericAxis = new Plottable.Axis.Numeric(scale, "bottom"); + var numericAxis = new Plottable.Axes.Numeric(scale, "bottom"); numericAxis.renderTo(svg); - var tickLabels = ( numericAxis)._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_LABEL_CLASS); + var tickLabels = ( numericAxis)._element.selectAll("." + Plottable.Axis.TICK_LABEL_CLASS); assert.operator(tickLabels[0].length, ">=", 2, "at least two tick labels were drawn"); - var tickMarks = ( numericAxis)._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_MARK_CLASS); + var tickMarks = ( numericAxis)._element.selectAll("." + Plottable.Axis.TICK_MARK_CLASS); assert.strictEqual( tickLabels[0].length, tickMarks[0].length, "there is one label per mark"); var i: number; @@ -64,8 +56,8 @@ describe("NumericAxis", () => { // labels to left numericAxis.tickLabelPosition("left"); - tickLabels = ( numericAxis)._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_LABEL_CLASS); - tickMarks = ( numericAxis)._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_MARK_CLASS); + tickLabels = ( numericAxis)._element.selectAll("." + Plottable.Axis.TICK_LABEL_CLASS); + tickMarks = ( numericAxis)._element.selectAll("." + Plottable.Axis.TICK_MARK_CLASS); for (i = 0; i < tickLabels[0].length; i++) { markBB = tickMarks[0][i].getBoundingClientRect(); labelBB = tickLabels[0][i].getBoundingClientRect(); @@ -74,8 +66,8 @@ describe("NumericAxis", () => { // labels to right numericAxis.tickLabelPosition("right"); - tickLabels = ( numericAxis)._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_LABEL_CLASS); - tickMarks = ( numericAxis)._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_MARK_CLASS); + tickLabels = ( numericAxis)._element.selectAll("." + Plottable.Axis.TICK_LABEL_CLASS); + tickMarks = ( numericAxis)._element.selectAll("." + Plottable.Axis.TICK_MARK_CLASS); for (i = 0; i < tickLabels[0].length; i++) { markBB = tickMarks[0][i].getBoundingClientRect(); labelBB = tickLabels[0][i].getBoundingClientRect(); @@ -88,15 +80,15 @@ describe("NumericAxis", () => { it("draws ticks correctly (vertical)", () => { var SVG_WIDTH = 100; var SVG_HEIGHT = 500; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var scale = new Plottable.Scale.Linear(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var scale = new Plottable.Scales.Linear(); scale.range([0, SVG_HEIGHT]); - var numericAxis = new Plottable.Axis.Numeric(scale, "left"); + var numericAxis = new Plottable.Axes.Numeric(scale, "left"); numericAxis.renderTo(svg); - var tickLabels = ( numericAxis)._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_LABEL_CLASS); + var tickLabels = ( numericAxis)._element.selectAll("." + Plottable.Axis.TICK_LABEL_CLASS); assert.operator(tickLabels[0].length, ">=", 2, "at least two tick labels were drawn"); - var tickMarks = ( numericAxis)._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_MARK_CLASS); + var tickMarks = ( numericAxis)._element.selectAll("." + Plottable.Axis.TICK_MARK_CLASS); assert.strictEqual(tickLabels[0].length, tickMarks[0].length, "there is one label per mark"); var i: number; @@ -112,8 +104,8 @@ describe("NumericAxis", () => { // labels to top numericAxis.tickLabelPosition("top"); - tickLabels = ( numericAxis)._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_LABEL_CLASS); - tickMarks = ( numericAxis)._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_MARK_CLASS); + tickLabels = ( numericAxis)._element.selectAll("." + Plottable.Axis.TICK_LABEL_CLASS); + tickMarks = ( numericAxis)._element.selectAll("." + Plottable.Axis.TICK_MARK_CLASS); for (i = 0; i < tickLabels[0].length; i++) { markBB = tickMarks[0][i].getBoundingClientRect(); labelBB = tickLabels[0][i].getBoundingClientRect(); @@ -122,8 +114,8 @@ describe("NumericAxis", () => { // labels to bottom numericAxis.tickLabelPosition("bottom"); - tickLabels = ( numericAxis)._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_LABEL_CLASS); - tickMarks = ( numericAxis)._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_MARK_CLASS); + tickLabels = ( numericAxis)._element.selectAll("." + Plottable.Axis.TICK_LABEL_CLASS); + tickMarks = ( numericAxis)._element.selectAll("." + Plottable.Axis.TICK_MARK_CLASS); for (i = 0; i < tickLabels[0].length; i++) { markBB = tickMarks[0][i].getBoundingClientRect(); labelBB = tickLabels[0][i].getBoundingClientRect(); @@ -136,16 +128,16 @@ describe("NumericAxis", () => { it("uses the supplied Formatter", () => { var SVG_WIDTH = 100; var SVG_HEIGHT = 500; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var scale = new Plottable.Scale.Linear(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var scale = new Plottable.Scales.Linear(); scale.range([0, SVG_HEIGHT]); var formatter = Plottable.Formatters.fixed(2); - var numericAxis = new Plottable.Axis.Numeric(scale, "left", formatter); + var numericAxis = new Plottable.Axes.Numeric(scale, "left", formatter); numericAxis.renderTo(svg); - var tickLabels = ( numericAxis)._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_LABEL_CLASS); + var tickLabels = ( numericAxis)._element.selectAll("." + Plottable.Axis.TICK_LABEL_CLASS); tickLabels.each(function(d: any, i: number) { var labelText = d3.select(this).text(); var formattedValue = formatter(d); @@ -158,10 +150,10 @@ describe("NumericAxis", () => { it("can hide tick labels that don't fit", () => { var SVG_WIDTH = 500; var SVG_HEIGHT = 100; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var scale = new Plottable.Scale.Linear(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var scale = new Plottable.Scales.Linear(); scale.range([0, SVG_WIDTH]); - var numericAxis = new Plottable.Axis.Numeric(scale, "bottom"); + var numericAxis = new Plottable.Axes.Numeric(scale, "bottom"); numericAxis.showEndTickLabel("left", false); assert.isFalse(numericAxis.showEndTickLabel("left"), "retrieve showEndTickLabel setting"); @@ -172,7 +164,7 @@ describe("NumericAxis", () => { numericAxis.renderTo(svg); - var tickLabels = ( numericAxis)._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_LABEL_CLASS); + var tickLabels = ( numericAxis)._element.selectAll("." + Plottable.Axis.TICK_LABEL_CLASS); var firstLabel = d3.select(tickLabels[0][0]); assert.strictEqual(firstLabel.style("visibility"), "hidden", "first label is hidden"); var lastLabel = d3.select(tickLabels[0][tickLabels[0].length - 1]); @@ -181,19 +173,18 @@ describe("NumericAxis", () => { svg.remove(); }); - it("tick labels don't overlap in a constrained space", () => { var SVG_WIDTH = 100; var SVG_HEIGHT = 100; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var scale = new Plottable.Scale.Linear(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var scale = new Plottable.Scales.Linear(); scale.range([0, SVG_WIDTH]); - var numericAxis = new Plottable.Axis.Numeric(scale, "bottom"); + var numericAxis = new Plottable.Axes.Numeric(scale, "bottom"); numericAxis.showEndTickLabel("left", false).showEndTickLabel("right", false); numericAxis.renderTo(svg); var visibleTickLabels = ( numericAxis)._element - .selectAll("." + Plottable.Axis.AbstractAxis.TICK_LABEL_CLASS) + .selectAll("." + Plottable.Axis.TICK_LABEL_CLASS) .filter(function(d: any, i: number) { return d3.select(this).style("visibility") === "visible"; }); @@ -205,13 +196,13 @@ describe("NumericAxis", () => { box1 = visibleTickLabels[0][i].getBoundingClientRect(); box2 = visibleTickLabels[0][j].getBoundingClientRect(); - assert.isFalse(Plottable._Util.DOM.boxesOverlap(box1, box2), "tick labels don't overlap"); + assert.isFalse(Plottable.Utils.DOM.boxesOverlap(box1, box2), "tick labels don't overlap"); } } - numericAxis.orient("bottom"); + numericAxis.orientation("bottom"); visibleTickLabels = ( numericAxis)._element - .selectAll("." + Plottable.Axis.AbstractAxis.TICK_LABEL_CLASS) + .selectAll("." + Plottable.Axis.TICK_LABEL_CLASS) .filter(function(d: any, i: number) { return d3.select(this).style("visibility") === "visible"; }); @@ -221,7 +212,7 @@ describe("NumericAxis", () => { box1 = visibleTickLabels[0][i].getBoundingClientRect(); box2 = visibleTickLabels[0][j].getBoundingClientRect(); - assert.isFalse(Plottable._Util.DOM.boxesOverlap(box1, box2), "tick labels don't overlap"); + assert.isFalse(Plottable.Utils.DOM.boxesOverlap(box1, box2), "tick labels don't overlap"); } } @@ -231,8 +222,8 @@ describe("NumericAxis", () => { it("allocates enough width to show all tick labels when vertical", () => { var SVG_WIDTH = 150; var SVG_HEIGHT = 500; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var scale = new Plottable.Scale.Linear(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var scale = new Plottable.Scales.Linear(); scale.domain([5, -5]); scale.range([0, SVG_HEIGHT]); @@ -243,11 +234,11 @@ describe("NumericAxis", () => { return String(d); }; - var numericAxis = new Plottable.Axis.Numeric(scale, "left", formatter); + var numericAxis = new Plottable.Axes.Numeric(scale, "left", formatter); numericAxis.renderTo(svg); var visibleTickLabels = ( numericAxis)._element - .selectAll("." + Plottable.Axis.AbstractAxis.TICK_LABEL_CLASS) + .selectAll("." + Plottable.Axis.TICK_LABEL_CLASS) .filter(function(d: any, i: number) { return d3.select(this).style("visibility") === "visible"; }); @@ -260,7 +251,7 @@ describe("NumericAxis", () => { scale.domain([50000000000, -50000000000]); visibleTickLabels = ( numericAxis)._element - .selectAll("." + Plottable.Axis.AbstractAxis.TICK_LABEL_CLASS) + .selectAll("." + Plottable.Axis.TICK_LABEL_CLASS) .filter(function(d: any, i: number) { return d3.select(this).style("visibility") === "visible"; }); @@ -270,25 +261,24 @@ describe("NumericAxis", () => { assertBoxInside(labelBox, boundingBox, 0, "long tick " + label.textContent + " is inside the bounding box"); }); - svg.remove(); }); it("allocates enough height to show all tick labels when horizontal", () => { var SVG_WIDTH = 500; var SVG_HEIGHT = 100; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var scale = new Plottable.Scale.Linear(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var scale = new Plottable.Scales.Linear(); scale.domain([5, -5]); scale.range([0, SVG_WIDTH]); var formatter = Plottable.Formatters.fixed(2); - var numericAxis = new Plottable.Axis.Numeric(scale, "bottom", formatter); + var numericAxis = new Plottable.Axes.Numeric(scale, "bottom", formatter); numericAxis.renderTo(svg); var visibleTickLabels = ( numericAxis)._element - .selectAll("." + Plottable.Axis.AbstractAxis.TICK_LABEL_CLASS) + .selectAll("." + Plottable.Axis.TICK_LABEL_CLASS) .filter(function(d: any, i: number) { return d3.select(this).style("visibility") === "visible"; }); @@ -303,31 +293,32 @@ describe("NumericAxis", () => { }); it("truncates long labels", () => { - var data = [ + var dataset = new Plottable.Dataset([ { x: "A", y: 500000000 }, { x: "B", y: 400000000 } - ]; + ]); var SVG_WIDTH = 120; var SVG_HEIGHT = 300; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Linear(); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - var yLabel = new Plottable.Component.AxisLabel("LABEL", "left"); - var barPlot = new Plottable.Plot.Bar(xScale, yScale); - barPlot.project("x", "x", xScale); - barPlot.project("y", "y", yScale); - barPlot.addDataset(data); - - var chart = new Plottable.Component.Table([ + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Linear(); + var yAxis = new Plottable.Axes.Numeric(yScale, "left"); + var yLabel = new Plottable.Components.Label("LABEL", "left"); + yLabel.classed(Plottable.Components.Label.AXIS_LABEL_CLASS, true); + var barPlot = new Plottable.Plots.Bar(xScale, yScale); + barPlot.x((d) => d.x, xScale); + barPlot.y((d) => d.y, yScale); + barPlot.addDataset(dataset); + + var chart = new Plottable.Components.Table([ [ yLabel, yAxis, barPlot ] ]); chart.renderTo(svg); var labelContainer = d3.select(".tick-label-container"); d3.selectAll(".tick-label").each(function() { - assertBBoxInclusion(labelContainer, d3.select(this)); + TestMethods.assertBBoxInclusion(labelContainer, d3.select(this)); }); svg.remove(); }); @@ -335,16 +326,16 @@ describe("NumericAxis", () => { it("confines labels to the bounding box for the axis", () => { var SVG_WIDTH = 500; var SVG_HEIGHT = 100; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var scale = new Plottable.Scale.Linear(); - var axis = new Plottable.Axis.Numeric(scale, "bottom"); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var scale = new Plottable.Scales.Linear(); + var axis = new Plottable.Axes.Numeric(scale, "bottom"); axis.formatter((d: any) => "longstringsareverylong"); axis.renderTo(svg); var boundingBox = d3.select(".x-axis .bounding-box"); d3.selectAll(".x-axis .tick-label").each(function() { var tickLabel = d3.select(this); if (tickLabel.style("visibility") === "inherit") { - assertBBoxInclusion(boundingBox, tickLabel); + TestMethods.assertBBoxInclusion(boundingBox, tickLabel); } }); svg.remove(); @@ -357,12 +348,12 @@ describe("NumericAxis", () => { it("tick labels follow a sensible interval", () => { var SVG_WIDTH = 500; var SVG_HEIGHT = 100; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); - var scale = new Plottable.Scale.Linear(); + var scale = new Plottable.Scales.Linear(); scale.domain([-2500000, 2500000]); - var baseAxis = new Plottable.Axis.Numeric(scale, "bottom"); + var baseAxis = new Plottable.Axes.Numeric(scale, "bottom"); baseAxis.renderTo(svg); var visibleTickLabels = ( baseAxis)._element.selectAll(".tick-label") @@ -384,13 +375,13 @@ describe("NumericAxis", () => { it("does not draw ticks marks outside of the svg", () => { var SVG_WIDTH = 300; var SVG_HEIGHT = 100; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var scale = new Plottable.Scale.Linear(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var scale = new Plottable.Scales.Linear(); scale.domain([0, 3]); scale.tickGenerator(function(s) { return [0, 1, 2, 3, 4]; }); - var baseAxis = new Plottable.Axis.Numeric(scale, "bottom"); + var baseAxis = new Plottable.Axes.Numeric(scale, "bottom"); baseAxis.renderTo(svg); var tickMarks = ( baseAxis)._element.selectAll(".tick-mark"); tickMarks.each(function() { @@ -404,12 +395,12 @@ describe("NumericAxis", () => { it("renders tick labels properly when the domain is reversed", () => { var SVG_WIDTH = 300; var SVG_HEIGHT = 100; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); - var scale = new Plottable.Scale.Linear(); + var scale = new Plottable.Scales.Linear(); scale.domain([3, 0]); - var baseAxis = new Plottable.Axis.Numeric(scale, "bottom"); + var baseAxis = new Plottable.Axes.Numeric(scale, "bottom"); baseAxis.renderTo(svg); var tickLabels = ( baseAxis)._element.selectAll(".tick-label") @@ -429,40 +420,33 @@ describe("NumericAxis", () => { }); it("constrained tick labels do not overlap tick marks", () => { + var svg = TestMethods.generateSVG(100, 50); - var svg = generateSVG(300, 400); - - var yScale = new Plottable.Scale.Linear().numTicks(100); + var yScale = new Plottable.Scales.Linear(); yScale.domain([175, 185]); - var yAxis = new Plottable.Axis.Numeric(yScale, "left") + var yAxis = new Plottable.Axes.Numeric(yScale, "left") .tickLabelPosition("top") .tickLength(50); + yAxis.renderTo(svg); - var chartTable = new Plottable.Component.Table([ - [yAxis], - ]); - - chartTable.renderTo(svg); - - var tickLabels = ( yAxis)._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_LABEL_CLASS) + var tickLabels = ( yAxis)._element.selectAll("." + Plottable.Axis.TICK_LABEL_CLASS) .filter(function(d: any, i: number) { var visibility = d3.select(this).style("visibility"); return (visibility === "visible") || (visibility === "inherit"); }); - var tickMarks = ( yAxis)._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_MARK_CLASS) + var tickMarks = ( yAxis)._element.selectAll("." + Plottable.Axis.TICK_MARK_CLASS) .filter(function(d: any, i: number) { var visibility = d3.select(this).style("visibility"); return (visibility === "visible") || (visibility === "inherit"); }); - tickLabels.each(function() { var tickLabelBox = this.getBoundingClientRect(); tickMarks.each(function() { var tickMarkBox = this.getBoundingClientRect(); - assert.isFalse(Plottable._Util.DOM.boxesOverlap(tickLabelBox, tickMarkBox), + assert.isFalse(Plottable.Utils.DOM.boxesOverlap(tickLabelBox, tickMarkBox), "tickMarks and tickLabels should not overlap when top/bottom/left/right position is used for the tickLabel"); }); }); @@ -470,6 +454,4 @@ describe("NumericAxis", () => { svg.remove(); }); - - }); diff --git a/test/components/plots/areaPlotTests.ts b/test/components/plots/areaPlotTests.ts index f1cb9ef67a..fad9cd7e74 100644 --- a/test/components/plots/areaPlotTests.ts +++ b/test/components/plots/areaPlotTests.ts @@ -6,12 +6,12 @@ describe("Plots", () => { describe("AreaPlot", () => { // HACKHACK #1798: beforeEach being used below it("renders correctly with no data", () => { - var svg = generateSVG(400, 400); - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - var plot = new Plottable.Plot.Area(xScale, yScale); - plot.project("x", (d: any) => d.x, xScale); - plot.project("y", (d: any) => d.y, yScale); + var svg = TestMethods.generateSVG(400, 400); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); + var plot = new Plottable.Plots.Area(xScale, yScale); + plot.x((d) => d.x, xScale); + plot.y((d) => d.y, yScale); assert.doesNotThrow(() => plot.renderTo(svg), Error); assert.strictEqual(plot.width(), 400, "was allocated width"); assert.strictEqual(plot.height(), 400, "was allocated height"); @@ -21,8 +21,8 @@ describe("Plots", () => { describe("AreaPlot", () => { var svg: D3.Selection; - var xScale: Plottable.Scale.Linear; - var yScale: Plottable.Scale.Linear; + var xScale: Plottable.Scales.Linear; + var yScale: Plottable.Scales.Linear; var xAccessor: any; var yAccessor: any; var y0Accessor: any; @@ -30,12 +30,12 @@ describe("Plots", () => { var fillAccessor: any; var twoPointData = [{foo: 0, bar: 0}, {foo: 1, bar: 1}]; var simpleDataset: Plottable.Dataset; - var areaPlot: Plottable.Plot.Area; + var areaPlot: Plottable.Plots.Area; var renderArea: D3.Selection; before(() => { - xScale = new Plottable.Scale.Linear().domain([0, 1]); - yScale = new Plottable.Scale.Linear().domain([0, 1]); + xScale = new Plottable.Scales.Linear().domain([0, 1]); + yScale = new Plottable.Scales.Linear().domain([0, 1]); xAccessor = (d: any) => d.foo; yAccessor = (d: any) => d.bar; y0Accessor = () => 0; @@ -44,28 +44,28 @@ describe("Plots", () => { }); beforeEach(() => { - svg = generateSVG(500, 500); + svg = TestMethods.generateSVG(500, 500); simpleDataset = new Plottable.Dataset(twoPointData); - areaPlot = new Plottable.Plot.Area(xScale, yScale); - areaPlot.addDataset("sd", simpleDataset) - .project("x", xAccessor, xScale) - .project("y", yAccessor, yScale) - .project("y0", y0Accessor, yScale) - .project("fill", fillAccessor) - .project("stroke", colorAccessor) + areaPlot = new Plottable.Plots.Area(xScale, yScale); + areaPlot.addDataset(simpleDataset); + areaPlot.x(xAccessor, xScale) + .y(yAccessor, yScale); + areaPlot.y0(y0Accessor, yScale) + .attr("fill", fillAccessor) + .attr("stroke", colorAccessor) .renderTo(svg); renderArea = ( areaPlot)._renderArea; }); it("draws area and line correctly", () => { var areaPath = renderArea.select(".area"); - assert.strictEqual(normalizePath(areaPath.attr("d")), "M0,500L500,0L500,500L0,500Z", "area d was set correctly"); + assert.strictEqual(TestMethods.normalizePath(areaPath.attr("d")), "M0,500L500,0L500,500L0,500Z", "area d was set correctly"); assert.strictEqual(areaPath.attr("fill"), "steelblue", "area fill was set correctly"); var areaComputedStyle = window.getComputedStyle(areaPath.node()); assert.strictEqual(areaComputedStyle.stroke, "none", "area stroke renders as \"none\""); var linePath = renderArea.select(".line"); - assert.strictEqual(normalizePath(linePath.attr("d")), "M0,500L500,0", "line d was set correctly"); + assert.strictEqual(TestMethods.normalizePath(linePath.attr("d")), "M0,500L500,0", "line d was set correctly"); assert.strictEqual(linePath.attr("stroke"), "#000000", "line stroke was set correctly"); var lineComputedStyle = window.getComputedStyle(linePath.node()); assert.strictEqual(lineComputedStyle.fill, "none", "line fill renders as \"none\""); @@ -73,11 +73,11 @@ describe("Plots", () => { }); it("area fill works for non-zero floor values appropriately, e.g. half the height of the line", () => { - areaPlot.project("y0", (d: any) => d.bar / 2, yScale); + areaPlot.y0((d) => d.bar / 2, yScale); areaPlot.renderTo(svg); renderArea = ( areaPlot)._renderArea; var areaPath = renderArea.select(".area"); - assert.equal(normalizePath(areaPath.attr("d")), "M0,500L500,0L500,250L0,500Z"); + assert.strictEqual(TestMethods.normalizePath(areaPath.attr("d")), "M0,500L500,0L500,250L0,500Z"); svg.remove(); }); @@ -104,27 +104,27 @@ describe("Plots", () => { dataWithNaN[2] = { foo: 0.4, bar: NaN }; simpleDataset.data(dataWithNaN); - var areaPathString = normalizePath(areaPath.attr("d")); - assertAreaPathCloseTo(areaPathString, expectedPath, 0.1, "area d was set correctly (y=NaN case)"); + var areaPathString = TestMethods.normalizePath(areaPath.attr("d")); + TestMethods.assertAreaPathCloseTo(areaPathString, expectedPath, 0.1, "area d was set correctly (y=NaN case)"); dataWithNaN[2] = { foo: NaN, bar: 0.4 }; simpleDataset.data(dataWithNaN); - areaPathString = normalizePath(areaPath.attr("d")); - assertAreaPathCloseTo(areaPathString, expectedPath, 0.1, "area d was set correctly (x=NaN case)"); + areaPathString = TestMethods.normalizePath(areaPath.attr("d")); + TestMethods.assertAreaPathCloseTo(areaPathString, expectedPath, 0.1, "area d was set correctly (x=NaN case)"); var dataWithUndefined = areaData.slice(); dataWithUndefined[2] = { foo: 0.4, bar: undefined }; simpleDataset.data(dataWithUndefined); - areaPathString = normalizePath(areaPath.attr("d")); - assertAreaPathCloseTo(areaPathString, expectedPath, 0.1, "area d was set correctly (y=undefined case)"); + areaPathString = TestMethods.normalizePath(areaPath.attr("d")); + TestMethods.assertAreaPathCloseTo(areaPathString, expectedPath, 0.1, "area d was set correctly (y=undefined case)"); dataWithUndefined[2] = { foo: undefined, bar: 0.4 }; simpleDataset.data(dataWithUndefined); - areaPathString = normalizePath(areaPath.attr("d")); - assertAreaPathCloseTo(areaPathString, expectedPath, 0.1, "area d was set correctly (x=undefined case)"); + areaPathString = TestMethods.normalizePath(areaPath.attr("d")); + TestMethods.assertAreaPathCloseTo(areaPathString, expectedPath, 0.1, "area d was set correctly (x=undefined case)"); svg.remove(); }); @@ -133,46 +133,33 @@ describe("Plots", () => { it("retrieves all selections with no args", () => { var newTwoPointData = [{ foo: 2, bar: 1 }, { foo: 3, bar: 2 }]; - areaPlot.addDataset("newTwo", new Plottable.Dataset(newTwoPointData)); + areaPlot.addDataset(new Plottable.Dataset(newTwoPointData)); var allAreas = areaPlot.getAllSelections(); - var allAreas2 = areaPlot.getAllSelections(( areaPlot)._datasetKeysInOrder); - assert.deepEqual(allAreas, allAreas2, "all areas/lines retrieved"); - assert.strictEqual(allAreas.filter(".line").size(), 2, "2 lines retrieved"); assert.strictEqual(allAreas.filter(".area").size(), 2, "2 areas retrieved"); svg.remove(); }); - it("retrieves correct selections (string arg)", () => { - var newTwoPointData = [{ foo: 2, bar: 1 }, { foo: 3, bar: 2 }]; - areaPlot.addDataset("newTwo", new Plottable.Dataset(newTwoPointData)); - var allAreas = areaPlot.getAllSelections("newTwo"); - assert.strictEqual(allAreas.size(), 2, "areas/lines retrieved"); - var selectionData = allAreas.data(); - assert.include(selectionData, newTwoPointData, "new dataset data in selection data"); - - svg.remove(); - }); - - it("retrieves correct selections (array arg)", () => { - var newTwoPointData = [{ foo: 2, bar: 1 }, { foo: 3, bar: 2 }]; - areaPlot.addDataset("newTwo", new Plottable.Dataset(newTwoPointData)); - var allAreas = areaPlot.getAllSelections(["newTwo"]); + it("retrieves correct selections", () => { + var twoPointDataset = new Plottable.Dataset([{ foo: 2, bar: 1 }, { foo: 3, bar: 2 }]); + areaPlot.addDataset(twoPointDataset); + var allAreas = areaPlot.getAllSelections([twoPointDataset]); assert.strictEqual(allAreas.size(), 2, "areas/lines retrieved"); var selectionData = allAreas.data(); - assert.include(selectionData, newTwoPointData, "new dataset data in selection data"); + assert.include(selectionData, twoPointDataset.data(), "new dataset data in selection data"); svg.remove(); }); - it("skips invalid keys", () => { - var newTwoPointData = [{ foo: 2, bar: 1 }, { foo: 3, bar: 2 }]; - areaPlot.addDataset("newTwo", new Plottable.Dataset(newTwoPointData)); - var allAreas = areaPlot.getAllSelections(["newTwo", "test"]); + it("skips invalid Datasets", () => { + var twoPointDataset = new Plottable.Dataset([{ foo: 2, bar: 1 }, { foo: 3, bar: 2 }]); + areaPlot.addDataset(twoPointDataset); + var dummyDataset = new Plottable.Dataset([]); + var allAreas = areaPlot.getAllSelections([twoPointDataset, dummyDataset]); assert.strictEqual(allAreas.size(), 2, "areas/lines retrieved"); var selectionData = allAreas.data(); - assert.include(selectionData, newTwoPointData, "new dataset data in selection data"); + assert.include(selectionData, twoPointDataset.data(), "new dataset data in selection data"); svg.remove(); }); @@ -180,11 +167,11 @@ describe("Plots", () => { it("retains original classes when class is projected", () => { var newClassProjector = () => "pink"; - areaPlot.project("class", newClassProjector); + areaPlot.attr("class", newClassProjector); areaPlot.renderTo(svg); - var areaPath = renderArea.select("." + Plottable._Drawer.Area.AREA_CLASS); + var areaPath = renderArea.select("." + Plottable.Drawers.Area.AREA_CLASS); assert.isTrue(areaPath.classed("pink")); - assert.isTrue(areaPath.classed(Plottable._Drawer.Area.AREA_CLASS)); + assert.isTrue(areaPath.classed(Plottable.Drawers.Area.AREA_CLASS)); svg.remove(); }); }); diff --git a/test/components/plots/barPlotTests.ts b/test/components/plots/barPlotTests.ts index 608e24608a..74739fd50d 100644 --- a/test/components/plots/barPlotTests.ts +++ b/test/components/plots/barPlotTests.ts @@ -7,19 +7,19 @@ describe("Plots", () => { // HACKHACK #1798: beforeEach being used below it("renders correctly with no data", () => { - var svg = generateSVG(400, 400); - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - var plot = new Plottable.Plot.Bar(xScale, yScale); - plot.project("x", (d: any) => d.x, xScale); - plot.project("y", (d: any) => d.y, yScale); + var svg = TestMethods.generateSVG(400, 400); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); + var plot = new Plottable.Plots.Bar(xScale, yScale); + plot.x((d) => d.x, xScale); + plot.y((d) => d.y, yScale); assert.doesNotThrow(() => plot.renderTo(svg), Error); assert.strictEqual(plot.width(), 400, "was allocated width"); assert.strictEqual(plot.height(), 400, "was allocated height"); svg.remove(); }); - function assertPlotDataEqual(expected: Plottable.Plot.PlotData, actual: Plottable.Plot.PlotData, + function assertPlotDataEqual(expected: Plottable.Plots.PlotData, actual: Plottable.Plots.PlotData, msg: string) { assert.deepEqual(expected.data, actual.data, msg); assert.closeTo(expected.pixelPoints[0].x, actual.pixelPoints[0].x, 0.01, msg); @@ -30,29 +30,29 @@ describe("Plots", () => { describe("Vertical Bar Plot", () => { var svg: D3.Selection; var dataset: Plottable.Dataset; - var xScale: Plottable.Scale.Category; - var yScale: Plottable.Scale.Linear; - var barPlot: Plottable.Plot.Bar; + var xScale: Plottable.Scales.Category; + var yScale: Plottable.Scales.Linear; + var barPlot: Plottable.Plots.Bar; var SVG_WIDTH = 600; var SVG_HEIGHT = 400; beforeEach(() => { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - xScale = new Plottable.Scale.Category().domain(["A", "B"]); - yScale = new Plottable.Scale.Linear(); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + xScale = new Plottable.Scales.Category().domain(["A", "B"]); + yScale = new Plottable.Scales.Linear(); var data = [ {x: "A", y: 1}, {x: "B", y: -1.5}, {x: "B", y: 1} // duplicate X-value ]; dataset = new Plottable.Dataset(data); - barPlot = new Plottable.Plot.Bar(xScale, yScale); + barPlot = new Plottable.Plots.Bar(xScale, yScale); barPlot.addDataset(dataset); barPlot.animate(false); barPlot.baseline(0); yScale.domain([-2, 2]); - barPlot.project("x", "x", xScale); - barPlot.project("y", "y", yScale); + barPlot.x((d) => d.x, xScale); + barPlot.y((d) => d.y, yScale); barPlot.renderTo(svg); }); @@ -62,20 +62,20 @@ describe("Plots", () => { assert.lengthOf(bars[0], 3, "One bar was created per data point"); var bar0 = d3.select(bars[0][0]); var bar1 = d3.select(bars[0][1]); - assert.closeTo(numAttr(bar0, "width"), xScale.rangeBand(), 1, "bar0 width is correct"); - assert.closeTo(numAttr(bar1, "width"), xScale.rangeBand(), 1, "bar1 width is correct"); - assert.equal(bar0.attr("height"), "100", "bar0 height is correct"); - assert.equal(bar1.attr("height"), "150", "bar1 height is correct"); - assert.closeTo(numAttr(bar0, "x"), 111, 1, "bar0 x is correct"); - assert.closeTo(numAttr(bar1, "x"), 333, 1, "bar1 x is correct"); - assert.equal(bar0.attr("y"), "100", "bar0 y is correct"); - assert.equal(bar1.attr("y"), "200", "bar1 y is correct"); + assert.closeTo(TestMethods.numAttr(bar0, "width"), xScale.rangeBand(), 1, "bar0 width is correct"); + assert.closeTo(TestMethods.numAttr(bar1, "width"), xScale.rangeBand(), 1, "bar1 width is correct"); + assert.strictEqual(bar0.attr("height"), "100", "bar0 height is correct"); + assert.strictEqual(bar1.attr("height"), "150", "bar1 height is correct"); + assert.closeTo(TestMethods.numAttr(bar0, "x"), 111, 1, "bar0 x is correct"); + assert.closeTo(TestMethods.numAttr(bar1, "x"), 333, 1, "bar1 x is correct"); + assert.strictEqual(bar0.attr("y"), "100", "bar0 y is correct"); + assert.strictEqual(bar1.attr("y"), "200", "bar1 y is correct"); var baseline = renderArea.select(".baseline"); - assert.equal(baseline.attr("y1"), "200", "the baseline is in the correct vertical position"); - assert.equal(baseline.attr("y2"), "200", "the baseline is in the correct vertical position"); - assert.equal(baseline.attr("x1"), "0", "the baseline starts at the edge of the chart"); - assert.equal(baseline.attr("x2"), SVG_WIDTH, "the baseline ends at the edge of the chart"); + assert.strictEqual(baseline.attr("y1"), "200", "the baseline is in the correct vertical position"); + assert.strictEqual(baseline.attr("y2"), "200", "the baseline is in the correct vertical position"); + assert.strictEqual(baseline.attr("x1"), "0", "the baseline starts at the edge of the chart"); + assert.strictEqual(baseline.attr("x2"), String(SVG_WIDTH), "the baseline ends at the edge of the chart"); svg.remove(); }); @@ -86,24 +86,24 @@ describe("Plots", () => { var bars = renderArea.selectAll("rect"); var bar0 = d3.select(bars[0][0]); var bar1 = d3.select(bars[0][1]); - assert.equal(bar0.attr("height"), "200", "bar0 height is correct"); - assert.equal(bar1.attr("height"), "50", "bar1 height is correct"); - assert.equal(bar0.attr("y"), "100", "bar0 y is correct"); - assert.equal(bar1.attr("y"), "300", "bar1 y is correct"); + assert.strictEqual(bar0.attr("height"), "200", "bar0 height is correct"); + assert.strictEqual(bar1.attr("height"), "50", "bar1 height is correct"); + assert.strictEqual(bar0.attr("y"), "100", "bar0 y is correct"); + assert.strictEqual(bar1.attr("y"), "300", "bar1 y is correct"); var baseline = renderArea.select(".baseline"); - assert.equal(baseline.attr("y1"), "300", "the baseline is in the correct vertical position"); - assert.equal(baseline.attr("y2"), "300", "the baseline is in the correct vertical position"); - assert.equal(baseline.attr("x1"), "0", "the baseline starts at the edge of the chart"); - assert.equal(baseline.attr("x2"), SVG_WIDTH, "the baseline ends at the edge of the chart"); + assert.strictEqual(baseline.attr("y1"), "300", "the baseline is in the correct vertical position"); + assert.strictEqual(baseline.attr("y2"), "300", "the baseline is in the correct vertical position"); + assert.strictEqual(baseline.attr("x1"), "0", "the baseline starts at the edge of the chart"); + assert.strictEqual(baseline.attr("x2"), String(SVG_WIDTH), "the baseline ends at the edge of the chart"); svg.remove(); }); - it("getBar()", () => { + it("getBars()", () => { var bar: D3.Selection = barPlot.getBars(155, 150); // in the middle of bar 0 assert.lengthOf(bar[0], 1, "getBar returns a bar"); - assert.equal(bar.data()[0], dataset.data()[0], "the data in the bar matches the datasource"); + assert.strictEqual(bar.data()[0], dataset.data()[0], "the data in the bar matches the datasource"); bar = barPlot.getBars(-1, -1); // no bars here assert.isTrue(bar.empty(), "returns empty selection if no bar was selected"); @@ -119,14 +119,14 @@ describe("Plots", () => { bar = barPlot.getBars({min: 155, max: 455}, {min: 150, max: 150}); assert.lengthOf(bar.data(), 2, "selected 2 bars (not the negative one)"); - assert.equal(bar.data()[0], dataset.data()[0], "the data in bar 0 matches the datasource"); - assert.equal(bar.data()[1], dataset.data()[2], "the data in bar 1 matches the datasource"); + assert.strictEqual(bar.data()[0], dataset.data()[0], "the data in bar 0 matches the datasource"); + assert.strictEqual(bar.data()[1], dataset.data()[2], "the data in bar 1 matches the datasource"); bar = barPlot.getBars({min: 155, max: 455}, {min: 150, max: 350}); assert.lengthOf(bar.data(), 3, "selected all the bars"); - assert.equal(bar.data()[0], dataset.data()[0], "the data in bar 0 matches the datasource"); - assert.equal(bar.data()[1], dataset.data()[1], "the data in bar 1 matches the datasource"); - assert.equal(bar.data()[2], dataset.data()[2], "the data in bar 2 matches the datasource"); + assert.strictEqual(bar.data()[0], dataset.data()[0], "the data in bar 0 matches the datasource"); + assert.strictEqual(bar.data()[1], dataset.data()[1], "the data in bar 1 matches the datasource"); + assert.strictEqual(bar.data()[2], dataset.data()[2], "the data in bar 2 matches the datasource"); svg.remove(); }); @@ -260,7 +260,7 @@ describe("Plots", () => { }); it("handles empty plots gracefully", () => { - barPlot = new Plottable.Plot.Bar(xScale, yScale); + barPlot = new Plottable.Plots.Bar(xScale, yScale); var closest = barPlot.getClosestPlotData({ x: d0Px.x, y: d0Px.y }); assert.lengthOf(closest.data, 0, "empty plots return empty data"); @@ -275,29 +275,29 @@ describe("Plots", () => { describe("Vertical Bar Plot modified log scale", () => { var svg: D3.Selection; var dataset: Plottable.Dataset; - var xScale: Plottable.Scale.ModifiedLog; - var yScale: Plottable.Scale.Linear; - var barPlot: Plottable.Plot.Bar; + var xScale: Plottable.Scales.ModifiedLog; + var yScale: Plottable.Scales.Linear; + var barPlot: Plottable.Plots.Bar; var SVG_WIDTH = 600; var SVG_HEIGHT = 400; beforeEach(() => { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - xScale = new Plottable.Scale.ModifiedLog(); - yScale = new Plottable.Scale.Linear(); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + xScale = new Plottable.Scales.ModifiedLog(); + yScale = new Plottable.Scales.Linear(); var data = [ {x: 2, y: 1}, {x: 10, y: -1.5}, {x: 100, y: 1} ]; dataset = new Plottable.Dataset(data); - barPlot = new Plottable.Plot.Bar(xScale, yScale); + barPlot = new Plottable.Plots.Bar(xScale, yScale); barPlot.addDataset(dataset); barPlot.animate(false); barPlot.baseline(0); yScale.domain([-2, 2]); - barPlot.project("x", "x", xScale); - barPlot.project("y", "y", yScale); + barPlot.x((d) => d.x, xScale); + barPlot.y((d) => d.y, yScale); barPlot.renderTo(svg); }); @@ -315,9 +315,9 @@ describe("Plots", () => { var bar0 = d3.select(bars[0][0]); var bar1 = d3.select(bars[0][1]); var bar2 = d3.select(bars[0][2]); - assert.closeTo(numAttr(bar0, "width"), barPixelWidth, 0.1, "bar0 width is correct"); - assert.closeTo(numAttr(bar1, "width"), barPixelWidth, 0.1, "bar1 width is correct"); - assert.closeTo(numAttr(bar2, "width"), barPixelWidth, 0.1, "bar2 width is correct"); + assert.closeTo(TestMethods.numAttr(bar0, "width"), barPixelWidth, 0.1, "bar0 width is correct"); + assert.closeTo(TestMethods.numAttr(bar1, "width"), barPixelWidth, 0.1, "bar1 width is correct"); + assert.closeTo(TestMethods.numAttr(bar2, "width"), barPixelWidth, 0.1, "bar2 width is correct"); svg.remove(); }); }); @@ -325,27 +325,27 @@ describe("Plots", () => { describe("Vertical Bar Plot linear scale", () => { var svg: D3.Selection; var dataset: Plottable.Dataset; - var xScale: Plottable.Scale.Linear; - var yScale: Plottable.Scale.Linear; - var barPlot: Plottable.Plot.Bar; + var xScale: Plottable.Scales.Linear; + var yScale: Plottable.Scales.Linear; + var barPlot: Plottable.Plots.Bar; var SVG_WIDTH = 600; var SVG_HEIGHT = 400; beforeEach(() => { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - xScale = new Plottable.Scale.Linear(); - yScale = new Plottable.Scale.Linear(); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + xScale = new Plottable.Scales.Linear(); + yScale = new Plottable.Scales.Linear(); var data = [ {x: 2, y: 1}, {x: 10, y: -1.5}, {x: 100, y: 1} ]; + barPlot = new Plottable.Plots.Bar(xScale, yScale); dataset = new Plottable.Dataset(data); - barPlot = new Plottable.Plot.Bar(xScale, yScale); barPlot.addDataset(dataset); barPlot.baseline(0); - barPlot.project("x", "x", xScale); - barPlot.project("y", "y", yScale); + barPlot.x((d) => d.x, xScale); + barPlot.y((d) => d.y, yScale); barPlot.renderTo(svg); }); @@ -363,29 +363,29 @@ describe("Plots", () => { var bar0 = d3.select(bars[0][0]); var bar1 = d3.select(bars[0][1]); var bar2 = d3.select(bars[0][2]); - assert.closeTo(numAttr(bar0, "width"), barPixelWidth, 0.1, "bar0 width is correct"); - assert.closeTo(numAttr(bar1, "width"), barPixelWidth, 0.1, "bar1 width is correct"); - assert.closeTo(numAttr(bar2, "width"), barPixelWidth, 0.1, "bar2 width is correct"); + assert.closeTo(TestMethods.numAttr(bar0, "width"), barPixelWidth, 0.1, "bar0 width is correct"); + assert.closeTo(TestMethods.numAttr(bar1, "width"), barPixelWidth, 0.1, "bar1 width is correct"); + assert.closeTo(TestMethods.numAttr(bar2, "width"), barPixelWidth, 0.1, "bar2 width is correct"); svg.remove(); }); it("sensible bar width one datum", () => { barPlot.removeDataset(dataset); - barPlot.addDataset([{x: 10, y: 2}]); + barPlot.addDataset(new Plottable.Dataset([{x: 10, y: 2}])); assert.closeTo(( barPlot)._getBarPixelWidth(), 228, 0.1, "sensible bar width for only one datum"); svg.remove(); }); it("sensible bar width same datum", () => { barPlot.removeDataset(dataset); - barPlot.addDataset([{x: 10, y: 2}, {x: 10, y: 2}]); + barPlot.addDataset(new Plottable.Dataset([{x: 10, y: 2}, {x: 10, y: 2}])); assert.closeTo(( barPlot)._getBarPixelWidth(), 228, 0.1, "uses the width sensible for one datum"); svg.remove(); }); it("sensible bar width unsorted data", () => { barPlot.removeDataset(dataset); - barPlot.addDataset([{x: 2, y: 2}, {x: 20, y: 2}, {x: 5, y: 2}]); + barPlot.addDataset(new Plottable.Dataset([{x: 2, y: 2}, {x: 20, y: 2}, {x: 5, y: 2}])); var expectedBarPixelWidth = (xScale.scale(5) - xScale.scale(2)) * 0.95; assert.closeTo(( barPlot)._getBarPixelWidth(), expectedBarPixelWidth, 0.1, "bar width uses closest sorted x values"); svg.remove(); @@ -394,23 +394,23 @@ describe("Plots", () => { describe("Vertical Bar Plot time scale", () => { var svg: D3.Selection; - var barPlot: Plottable.Plot.Bar; - var xScale: Plottable.Scale.Time; + var barPlot: Plottable.Plots.Bar; + var xScale: Plottable.Scales.Time; beforeEach(() => { - svg = generateSVG(600, 400); + svg = TestMethods.generateSVG(600, 400); var data = [{ x: "12/01/92", y: 0, type: "a" }, { x: "12/01/93", y: 1, type: "a" }, { x: "12/01/94", y: 1, type: "a" }, { x: "12/01/95", y: 2, type: "a" }, { x: "12/01/96", y: 2, type: "a" }, { x: "12/01/97", y: 2, type: "a" }]; - xScale = new Plottable.Scale.Time(); - var yScale = new Plottable.Scale.Linear(); - barPlot = new Plottable.Plot.Bar(xScale, yScale); - barPlot.addDataset(data) - .project("x", (d: any) => d3.time.format("%m/%d/%y").parse(d.x), xScale) - .project("y", "y", yScale) + xScale = new Plottable.Scales.Time(); + var yScale = new Plottable.Scales.Linear(); + barPlot = new Plottable.Plots.Bar(xScale, yScale); + barPlot.addDataset(new Plottable.Dataset(data)); + barPlot.x((d: any) => d3.time.format("%m/%d/%y").parse(d.x), xScale) + .y((d) => d.y, yScale) .renderTo(svg); }); @@ -426,15 +426,15 @@ describe("Plots", () => { describe("Horizontal Bar Plot", () => { var svg: D3.Selection; var dataset: Plottable.Dataset; - var yScale: Plottable.Scale.Category; - var xScale: Plottable.Scale.Linear; - var barPlot: Plottable.Plot.Bar; + var yScale: Plottable.Scales.Category; + var xScale: Plottable.Scales.Linear; + var barPlot: Plottable.Plots.Bar; var SVG_WIDTH = 600; var SVG_HEIGHT = 400; beforeEach(() => { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - yScale = new Plottable.Scale.Category().domain(["A", "B"]); - xScale = new Plottable.Scale.Linear(); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + yScale = new Plottable.Scales.Category().domain(["A", "B"]); + xScale = new Plottable.Scales.Linear(); xScale.domain([-3, 3]); var data = [ @@ -443,13 +443,12 @@ describe("Plots", () => { {y: "B", x: 1} // duplicate Y-value ]; dataset = new Plottable.Dataset(data); - - barPlot = new Plottable.Plot.Bar(xScale, yScale, false); + barPlot = new Plottable.Plots.Bar(xScale, yScale, false); barPlot.addDataset(dataset); barPlot.animate(false); barPlot.baseline(0); - barPlot.project("x", "x", xScale); - barPlot.project("y", "y", yScale); + barPlot.x((d) => d.x, xScale); + barPlot.y((d) => d.y, yScale); barPlot.renderTo(svg); }); @@ -459,20 +458,20 @@ describe("Plots", () => { assert.lengthOf(bars[0], 3, "One bar was created per data point"); var bar0 = d3.select(bars[0][0]); var bar1 = d3.select(bars[0][1]); - assert.closeTo(numAttr(bar0, "height"), yScale.rangeBand(), 1, "bar0 height is correct"); - assert.closeTo(numAttr(bar1, "height"), yScale.rangeBand(), 1, "bar1 height is correct"); - assert.equal(bar0.attr("width"), "100", "bar0 width is correct"); - assert.equal(bar1.attr("width"), "150", "bar1 width is correct"); - assert.closeTo(numAttr(bar0, "y"), 74, 1, "bar0 y is correct"); - assert.closeTo(numAttr(bar1, "y"), 222, 1, "bar1 y is correct"); - assert.equal(bar0.attr("x"), "300", "bar0 x is correct"); - assert.equal(bar1.attr("x"), "150", "bar1 x is correct"); + assert.closeTo(TestMethods.numAttr(bar0, "height"), yScale.rangeBand(), 1, "bar0 height is correct"); + assert.closeTo(TestMethods.numAttr(bar1, "height"), yScale.rangeBand(), 1, "bar1 height is correct"); + assert.strictEqual(bar0.attr("width"), "100", "bar0 width is correct"); + assert.strictEqual(bar1.attr("width"), "150", "bar1 width is correct"); + assert.closeTo(TestMethods.numAttr(bar0, "y"), 74, 1, "bar0 y is correct"); + assert.closeTo(TestMethods.numAttr(bar1, "y"), 222, 1, "bar1 y is correct"); + assert.strictEqual(bar0.attr("x"), "300", "bar0 x is correct"); + assert.strictEqual(bar1.attr("x"), "150", "bar1 x is correct"); var baseline = renderArea.select(".baseline"); - assert.equal(baseline.attr("x1"), "300", "the baseline is in the correct horizontal position"); - assert.equal(baseline.attr("x2"), "300", "the baseline is in the correct horizontal position"); - assert.equal(baseline.attr("y1"), "0", "the baseline starts at the top of the chart"); - assert.equal(baseline.attr("y2"), SVG_HEIGHT, "the baseline ends at the bottom of the chart"); + assert.strictEqual(baseline.attr("x1"), "300", "the baseline is in the correct horizontal position"); + assert.strictEqual(baseline.attr("x2"), "300", "the baseline is in the correct horizontal position"); + assert.strictEqual(baseline.attr("y1"), "0", "the baseline starts at the top of the chart"); + assert.strictEqual(baseline.attr("y2"), String(SVG_HEIGHT), "the baseline ends at the bottom of the chart"); svg.remove(); }); @@ -483,16 +482,16 @@ describe("Plots", () => { var bars = renderArea.selectAll("rect"); var bar0 = d3.select(bars[0][0]); var bar1 = d3.select(bars[0][1]); - assert.equal(bar0.attr("width"), "200", "bar0 width is correct"); - assert.equal(bar1.attr("width"), "50", "bar1 width is correct"); - assert.equal(bar0.attr("x"), "200", "bar0 x is correct"); - assert.equal(bar1.attr("x"), "150", "bar1 x is correct"); + assert.strictEqual(bar0.attr("width"), "200", "bar0 width is correct"); + assert.strictEqual(bar1.attr("width"), "50", "bar1 width is correct"); + assert.strictEqual(bar0.attr("x"), "200", "bar0 x is correct"); + assert.strictEqual(bar1.attr("x"), "150", "bar1 x is correct"); var baseline = renderArea.select(".baseline"); - assert.equal(baseline.attr("x1"), "200", "the baseline is in the correct horizontal position"); - assert.equal(baseline.attr("x2"), "200", "the baseline is in the correct horizontal position"); - assert.equal(baseline.attr("y1"), "0", "the baseline starts at the top of the chart"); - assert.equal(baseline.attr("y2"), SVG_HEIGHT, "the baseline ends at the bottom of the chart"); + assert.strictEqual(baseline.attr("x1"), "200", "the baseline is in the correct horizontal position"); + assert.strictEqual(baseline.attr("x2"), "200", "the baseline is in the correct horizontal position"); + assert.strictEqual(baseline.attr("y1"), "0", "the baseline starts at the top of the chart"); + assert.strictEqual(baseline.attr("y2"), String(SVG_HEIGHT), "the baseline ends at the bottom of the chart"); svg.remove(); }); @@ -502,13 +501,13 @@ describe("Plots", () => { var bar1 = d3.select(bars[0][1]); var bar0y = bar0.data()[0].y; var bar1y = bar1.data()[0].y; - barPlot.project("width", 10); - assert.closeTo(numAttr(bar0, "height"), 10, 0.01, "bar0 height"); - assert.closeTo(numAttr(bar1, "height"), 10, 0.01, "bar1 height"); - assert.closeTo(numAttr(bar0, "width"), 100, 0.01, "bar0 width"); - assert.closeTo(numAttr(bar1, "width"), 150, 0.01, "bar1 width"); - assert.closeTo(numAttr(bar0, "y"), yScale.scale(bar0y) - numAttr(bar0, "height") / 2, 0.01, "bar0 ypos"); - assert.closeTo(numAttr(bar1, "y"), yScale.scale(bar1y) - numAttr(bar1, "height") / 2, 0.01, "bar1 ypos"); + barPlot.attr("width", 10); + assert.closeTo(TestMethods.numAttr(bar0, "height"), 10, 0.01, "bar0 height"); + assert.closeTo(TestMethods.numAttr(bar1, "height"), 10, 0.01, "bar1 height"); + assert.closeTo(TestMethods.numAttr(bar0, "width"), 100, 0.01, "bar0 width"); + assert.closeTo(TestMethods.numAttr(bar1, "width"), 150, 0.01, "bar1 width"); + assert.closeTo(TestMethods.numAttr(bar0, "y"), yScale.scale(bar0y) - TestMethods.numAttr(bar0, "height") / 2, 0.01, "bar0 ypos"); + assert.closeTo(TestMethods.numAttr(bar1, "y"), yScale.scale(bar1y) - TestMethods.numAttr(bar1, "height") / 2, 0.01, "bar1 ypos"); svg.remove(); }); @@ -637,23 +636,23 @@ describe("Plots", () => { }); describe("Vertical Bar Plot With Bar Labels", () => { - var plot: Plottable.Plot.Bar; + var plot: Plottable.Plots.Bar; var data: any[]; var dataset: Plottable.Dataset; - var xScale: Plottable.Scale.Category; - var yScale: Plottable.Scale.Linear; + var xScale: Plottable.Scales.Category; + var yScale: Plottable.Scales.Linear; var svg: D3.Selection; beforeEach(() => { - svg = generateSVG(); + svg = TestMethods.generateSVG(); data = [{x: "foo", y: 5}, {x: "bar", y: 640}, {x: "zoo", y: 12345}]; + xScale = new Plottable.Scales.Category(); + yScale = new Plottable.Scales.Linear(); dataset = new Plottable.Dataset(data); - xScale = new Plottable.Scale.Category(); - yScale = new Plottable.Scale.Linear(); - plot = new Plottable.Plot.Bar(xScale, yScale); + plot = new Plottable.Plots.Bar(xScale, yScale); plot.addDataset(dataset); - plot.project("x", "x", xScale); - plot.project("y", "y", yScale); + plot.x((d) => d.x, xScale); + plot.y((d) => d.y, yScale); }); it("bar labels disabled by default", () => { @@ -663,39 +662,38 @@ describe("Plots", () => { svg.remove(); }); - it("bar labels render properly", () => { plot.renderTo(svg); - plot.barLabelsEnabled(true); + plot.labelsEnabled(true); var texts = svg.selectAll("text")[0].map((n: any) => d3.select(n).text()); assert.lengthOf(texts, 2, "both texts drawn"); - assert.equal(texts[0], "640", "first label is 640"); - assert.equal(texts[1], "12345", "first label is 12345"); + assert.strictEqual(texts[0], "640", "first label is 640"); + assert.strictEqual(texts[1], "12345", "first label is 12345"); svg.remove(); }); it("bar labels hide if bars too skinny", () => { - plot.barLabelsEnabled(true); + plot.labelsEnabled(true); plot.renderTo(svg); - plot.barLabelFormatter((n: number) => n.toString() + (n === 12345 ? "looong" : "")); + plot.labelFormatter((n: number) => n.toString() + (n === 12345 ? "looong" : "")); var texts = svg.selectAll("text")[0].map((n: any) => d3.select(n).text()); assert.lengthOf(texts, 0, "no text drawn"); svg.remove(); }); it("formatters are used properly", () => { - plot.barLabelsEnabled(true); - plot.barLabelFormatter((n: number) => n.toString() + "%"); + plot.labelsEnabled(true); + plot.labelFormatter((n: number) => n.toString() + "%"); plot.renderTo(svg); var texts = svg.selectAll("text")[0].map((n: any) => d3.select(n).text()); assert.lengthOf(texts, 2, "both texts drawn"); - assert.equal(texts[0], "640%", "first label is 640%"); - assert.equal(texts[1], "12345%", "first label is 12345%"); + assert.strictEqual(texts[0], "640%", "first label is 640%"); + assert.strictEqual(texts[1], "12345%", "first label is 12345%"); svg.remove(); }); it("bar labels are removed instantly on dataset change", (done) => { - plot.barLabelsEnabled(true); + plot.labelsEnabled(true); plot.renderTo(svg); var texts = svg.selectAll("text")[0].map((n: any) => d3.select(n).text()); assert.lengthOf(texts, 2, "both texts drawn"); @@ -718,76 +716,56 @@ describe("Plots", () => { }); describe("getAllSelections", () => { - var verticalBarPlot: Plottable.Plot.Bar; + var verticalBarPlot: Plottable.Plots.Bar; var dataset: Plottable.Dataset; var svg: D3.Selection; beforeEach(() => { - svg = generateSVG(); + svg = TestMethods.generateSVG(); dataset = new Plottable.Dataset(); - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Linear(); - verticalBarPlot = new Plottable.Plot.Bar(xScale, yScale); - verticalBarPlot.project("x", "x", xScale); - verticalBarPlot.project("y", "y", yScale); + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Linear(); + verticalBarPlot = new Plottable.Plots.Bar(xScale, yScale); + verticalBarPlot.x((d) => d.x, xScale); + verticalBarPlot.y((d) => d.y, yScale); }); it("retrieves all dataset selections with no args", () => { var barData = [{ x: "foo", y: 5 }, { x: "bar", y: 640 }, { x: "zoo", y: 12345 }]; - var barData2 = [{ x: "one", y: 5 }, { x: "two", y: 640 }, { x: "three", y: 12345 }]; - verticalBarPlot.addDataset("a", barData); - verticalBarPlot.addDataset("b", barData2); + verticalBarPlot.addDataset(new Plottable.Dataset(barData)); verticalBarPlot.renderTo(svg); var allBars = verticalBarPlot.getAllSelections(); - var allBars2 = verticalBarPlot.getAllSelections(( verticalBarPlot)._datasetKeysInOrder); - assert.deepEqual(allBars, allBars2, "both ways of getting all selections work"); + assert.strictEqual(allBars.size(), 3, "retrieved all bars"); svg.remove(); }); - it("retrieves correct selections (string arg)", () => { - var barData = [{ x: "foo", y: 5 }, { x: "bar", y: 640 }, { x: "zoo", y: 12345 }]; - var barData2 = [{ x: "one", y: 5 }, { x: "two", y: 640 }, { x: "three", y: 12345 }]; - verticalBarPlot.addDataset("a", barData); - verticalBarPlot.addDataset(barData2); + it("retrieves correct selections for supplied Datasets", () => { + var dataset1 = new Plottable.Dataset([{ x: "foo", y: 5 }, { x: "bar", y: 640 }, { x: "zoo", y: 12345 }]); + var dataset2 = new Plottable.Dataset([{ x: "one", y: 5 }, { x: "two", y: 640 }, { x: "three", y: 12345 }]); + verticalBarPlot.addDataset(dataset1); + verticalBarPlot.addDataset(dataset2); verticalBarPlot.renderTo(svg); - var allBars = verticalBarPlot.getAllSelections("a"); + var allBars = verticalBarPlot.getAllSelections([dataset1]); assert.strictEqual(allBars.size(), 3, "all bars retrieved"); var selectionData = allBars.data(); - assert.includeMembers(selectionData, barData, "first dataset data in selection data"); - - svg.remove(); - }); - - it("retrieves correct selections (array arg)", () => { - var barData = [{ x: "foo", y: 5 }, { x: "bar", y: 640 }, { x: "zoo", y: 12345 }]; - var barData2 = [{ x: "one", y: 5 }, { x: "two", y: 640 }, { x: "three", y: 12345 }]; - verticalBarPlot.addDataset("a", barData); - verticalBarPlot.addDataset("b", barData2); - verticalBarPlot.renderTo(svg); - - var allBars = verticalBarPlot.getAllSelections(["a", "b"]); - assert.strictEqual(allBars.size(), 6, "all bars retrieved"); - var selectionData = allBars.data(); - assert.includeMembers(selectionData, barData, "first dataset data in selection data"); - assert.includeMembers(selectionData, barData2, "second dataset data in selection data"); + assert.includeMembers(selectionData, dataset1.data(), "first dataset data in selection data"); svg.remove(); }); - it("skips invalid keys", () => { - var barData = [{ x: "foo", y: 5 }, { x: "bar", y: 640 }, { x: "zoo", y: 12345 }]; - var barData2 = [{ x: "one", y: 5 }, { x: "two", y: 640 }, { x: "three", y: 12345 }]; - verticalBarPlot.addDataset("a", barData); - verticalBarPlot.addDataset("b", barData2); + it("skips invalid Datasets", () => { + var dataset1 = new Plottable.Dataset([{ x: "foo", y: 5 }, { x: "bar", y: 640 }, { x: "zoo", y: 12345 }]); + var notAddedDataset = new Plottable.Dataset([{ x: "one", y: 5 }, { x: "two", y: 640 }, { x: "three", y: 12345 }]); + verticalBarPlot.addDataset(dataset1); verticalBarPlot.renderTo(svg); - var allBars = verticalBarPlot.getAllSelections(["a", "c"]); + var allBars = verticalBarPlot.getAllSelections([dataset1, notAddedDataset]); assert.strictEqual(allBars.size(), 3, "all bars retrieved"); var selectionData = allBars.data(); - assert.includeMembers(selectionData, barData, "first dataset data in selection data"); + assert.includeMembers(selectionData, dataset1.data(), "first dataset data in selection data"); svg.remove(); }); @@ -795,16 +773,16 @@ describe("Plots", () => { }); it("plot auto domain scale to visible points on Category scale", () => { - var svg = generateSVG(500, 500); - var xAccessor = (d: any, i: number, u: any) => d.a; - var yAccessor = (d: any, i: number, u: any) => d.b + u.foo; + var svg = TestMethods.generateSVG(500, 500); + var xAccessor = (d: any, i: number, dataset: Plottable.Dataset) => d.a; + var yAccessor = (d: any, i: number, dataset: Plottable.Dataset) => d.b + dataset.metadata().foo; var simpleDataset = new Plottable.Dataset([{a: "a", b: 6}, {a: "b", b: 2}, {a: "c", b: -2}, {a: "d", b: -6}], {foo: 0}); - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Linear(); - var plot = new Plottable.Plot.Bar(xScale, yScale); - plot.addDataset(simpleDataset) - .project("x", xAccessor, xScale) - .project("y", yAccessor, yScale) + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Linear(); + var plot = new Plottable.Plots.Bar(xScale, yScale); + plot.addDataset(simpleDataset); + plot.x(xAccessor, xScale) + .y(yAccessor, yScale) .renderTo(svg); xScale.domain(["b", "c"]); assert.deepEqual(yScale.domain(), [-7, 7], "domain has not been adjusted to visible points"); diff --git a/test/components/plots/clusteredBarPlotTests.ts b/test/components/plots/clusteredBarPlotTests.ts index bc72156ca9..0728b66357 100644 --- a/test/components/plots/clusteredBarPlotTests.ts +++ b/test/components/plots/clusteredBarPlotTests.ts @@ -7,9 +7,9 @@ describe("Plots", () => { var svg: D3.Selection; var dataset1: Plottable.Dataset; var dataset2: Plottable.Dataset; - var xScale: Plottable.Scale.Category; - var yScale: Plottable.Scale.Linear; - var renderer: Plottable.Plot.ClusteredBar; + var xScale: Plottable.Scales.Category; + var yScale: Plottable.Scales.Linear; + var renderer: Plottable.Plots.ClusteredBar; var SVG_WIDTH = 600; var SVG_HEIGHT = 400; var axisHeight = 0; @@ -18,9 +18,9 @@ describe("Plots", () => { var originalData2: any[]; beforeEach(() => { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - xScale = new Plottable.Scale.Category(); - yScale = new Plottable.Scale.Linear().domain([0, 2]); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + xScale = new Plottable.Scales.Category(); + yScale = new Plottable.Scales.Linear().domain([0, 2]); originalData1 = [ {x: "A", y: 1}, @@ -43,14 +43,14 @@ describe("Plots", () => { dataset1 = new Plottable.Dataset(data1); dataset2 = new Plottable.Dataset(data2); - renderer = new Plottable.Plot.ClusteredBar(xScale, yScale); + renderer = new Plottable.Plots.ClusteredBar(xScale, yScale); renderer.addDataset(dataset1); renderer.addDataset(dataset2); renderer.baseline(0); - renderer.project("x", "x", xScale); - renderer.project("y", "y", yScale); - var xAxis = new Plottable.Axis.Category(xScale, "bottom"); - var table = new Plottable.Component.Table([[renderer], [xAxis]]).renderTo(svg); + renderer.x((d) => d.x, xScale); + renderer.y((d) => d.y, yScale); + var xAxis = new Plottable.Axes.Category(xScale, "bottom"); + new Plottable.Components.Table([[renderer], [xAxis]]).renderTo(svg); axisHeight = xAxis.height(); bandWidth = xScale.rangeBand(); }); @@ -67,27 +67,28 @@ describe("Plots", () => { var bar3X = bar3.data()[0].x; // check widths - assert.closeTo(numAttr(bar0, "width"), 40, 2); - assert.closeTo(numAttr(bar1, "width"), 40, 2); - assert.closeTo(numAttr(bar2, "width"), 40, 2); - assert.closeTo(numAttr(bar3, "width"), 40, 2); + assert.closeTo(TestMethods.numAttr(bar0, "width"), 40, 2); + assert.closeTo(TestMethods.numAttr(bar1, "width"), 40, 2); + assert.closeTo(TestMethods.numAttr(bar2, "width"), 40, 2); + assert.closeTo(TestMethods.numAttr(bar3, "width"), 40, 2); // check heights - assert.closeTo(numAttr(bar0, "height"), (400 - axisHeight) / 2, 0.01, "height is correct for bar0"); - assert.closeTo(numAttr(bar1, "height"), (400 - axisHeight), 0.01, "height is correct for bar1"); - assert.closeTo(numAttr(bar2, "height"), (400 - axisHeight), 0.01, "height is correct for bar2"); - assert.closeTo(numAttr(bar3, "height"), (400 - axisHeight) / 2, 0.01, "height is correct for bar3"); + assert.closeTo(TestMethods.numAttr(bar0, "height"), (400 - axisHeight) / 2, 0.01, "height is correct for bar0"); + assert.closeTo(TestMethods.numAttr(bar1, "height"), (400 - axisHeight), 0.01, "height is correct for bar1"); + assert.closeTo(TestMethods.numAttr(bar2, "height"), (400 - axisHeight), 0.01, "height is correct for bar2"); + assert.closeTo(TestMethods.numAttr(bar3, "height"), (400 - axisHeight) / 2, 0.01, "height is correct for bar3"); // check that clustering is correct var innerScale = (renderer)._makeInnerScale(); var off = innerScale.scale("_0"); - assert.closeTo(numAttr(bar0, "x") + numAttr(bar0, "width") / 2, xScale.scale(bar0X) - xScale.rangeBand() / 2 + off, 0.01 + var width = xScale.rangeBand() / 2; + assert.closeTo(TestMethods.numAttr(bar0, "x") + TestMethods.numAttr(bar0, "width") / 2, xScale.scale(bar0X) - width + off, 0.01 , "x pos correct for bar0"); - assert.closeTo(numAttr(bar1, "x") + numAttr(bar1, "width") / 2, xScale.scale(bar1X) - xScale.rangeBand() / 2 + off, 0.01 + assert.closeTo(TestMethods.numAttr(bar1, "x") + TestMethods.numAttr(bar1, "width") / 2, xScale.scale(bar1X) - width + off, 0.01 , "x pos correct for bar1"); - assert.closeTo(numAttr(bar2, "x") + numAttr(bar2, "width") / 2, xScale.scale(bar2X) + xScale.rangeBand() / 2 - off, 0.01 + assert.closeTo(TestMethods.numAttr(bar2, "x") + TestMethods.numAttr(bar2, "width") / 2, xScale.scale(bar2X) + width - off, 0.01 , "x pos correct for bar2"); - assert.closeTo(numAttr(bar3, "x") + numAttr(bar3, "width") / 2, xScale.scale(bar3X) + xScale.rangeBand() / 2 - off, 0.01 + assert.closeTo(TestMethods.numAttr(bar3, "x") + TestMethods.numAttr(bar3, "width") / 2, xScale.scale(bar3X) + width - off, 0.01 , "x pos correct for bar3"); assert.deepEqual(dataset1.data(), originalData1, "underlying data is not modified"); @@ -100,18 +101,18 @@ describe("Plots", () => { var svg: D3.Selection; var dataset1: Plottable.Dataset; var dataset2: Plottable.Dataset; - var yScale: Plottable.Scale.Category; - var xScale: Plottable.Scale.Linear; - var renderer: Plottable.Plot.ClusteredBar; + var yScale: Plottable.Scales.Category; + var xScale: Plottable.Scales.Linear; + var renderer: Plottable.Plots.ClusteredBar; var SVG_WIDTH = 600; var SVG_HEIGHT = 400; var rendererWidth: number; var bandWidth = 0; beforeEach(() => { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - yScale = new Plottable.Scale.Category(); - xScale = new Plottable.Scale.Linear().domain([0, 2]); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + yScale = new Plottable.Scales.Category(); + xScale = new Plottable.Scales.Linear().domain([0, 2]); var data1 = [ {y: "A", x: 1}, @@ -124,14 +125,14 @@ describe("Plots", () => { dataset1 = new Plottable.Dataset(data1); dataset2 = new Plottable.Dataset(data2); - renderer = new Plottable.Plot.ClusteredBar(xScale, yScale, false); - renderer.addDataset(data1); - renderer.addDataset(data2); + renderer = new Plottable.Plots.ClusteredBar(xScale, yScale, false); + renderer.addDataset(new Plottable.Dataset(data1)); + renderer.addDataset(new Plottable.Dataset(data2)); renderer.baseline(0); - renderer.project("x", "x", xScale); - renderer.project("y", "y", yScale); - var yAxis = new Plottable.Axis.Category(yScale, "left"); - var table = new Plottable.Component.Table([[yAxis, renderer]]).renderTo(svg); + renderer.x((d) => d.x, xScale); + renderer.y((d) => d.y, yScale); + var yAxis = new Plottable.Axes.Category(yScale, "left"); + new Plottable.Components.Table([[yAxis, renderer]]).renderTo(svg); rendererWidth = renderer.width(); bandWidth = yScale.rangeBand(); }); @@ -144,16 +145,16 @@ describe("Plots", () => { var bar3 = d3.select(bars[0][3]); // check widths - assert.closeTo(numAttr(bar0, "height"), 26, 2, "height is correct for bar0"); - assert.closeTo(numAttr(bar1, "height"), 26, 2, "height is correct for bar1"); - assert.closeTo(numAttr(bar2, "height"), 26, 2, "height is correct for bar2"); - assert.closeTo(numAttr(bar3, "height"), 26, 2, "height is correct for bar3"); + assert.closeTo(TestMethods.numAttr(bar0, "height"), 26, 2, "height is correct for bar0"); + assert.closeTo(TestMethods.numAttr(bar1, "height"), 26, 2, "height is correct for bar1"); + assert.closeTo(TestMethods.numAttr(bar2, "height"), 26, 2, "height is correct for bar2"); + assert.closeTo(TestMethods.numAttr(bar3, "height"), 26, 2, "height is correct for bar3"); // check heights - assert.closeTo(numAttr(bar0, "width"), rendererWidth / 2, 0.01, "width is correct for bar0"); - assert.closeTo(numAttr(bar1, "width"), rendererWidth, 0.01, "width is correct for bar1"); - assert.closeTo(numAttr(bar2, "width"), rendererWidth, 0.01, "width is correct for bar2"); - assert.closeTo(numAttr(bar3, "width"), rendererWidth / 2, 0.01, "width is correct for bar3"); + assert.closeTo(TestMethods.numAttr(bar0, "width"), rendererWidth / 2, 0.01, "width is correct for bar0"); + assert.closeTo(TestMethods.numAttr(bar1, "width"), rendererWidth, 0.01, "width is correct for bar1"); + assert.closeTo(TestMethods.numAttr(bar2, "width"), rendererWidth, 0.01, "width is correct for bar2"); + assert.closeTo(TestMethods.numAttr(bar3, "width"), rendererWidth / 2, 0.01, "width is correct for bar3"); var bar0Y = bar0.data()[0].y; var bar1Y = bar1.data()[0].y; @@ -163,13 +164,14 @@ describe("Plots", () => { // check that clustering is correct var innerScale = (renderer)._makeInnerScale(); var off = innerScale.scale("_0"); - assert.closeTo(numAttr(bar0, "y") + numAttr(bar0, "height") / 2, yScale.scale(bar0Y) - yScale.rangeBand() / 2 + off, 0.01 + var width = yScale.rangeBand() / 2; + assert.closeTo(TestMethods.numAttr(bar0, "y") + TestMethods.numAttr(bar0, "height") / 2, yScale.scale(bar0Y) - width + off, 0.01 , "y pos correct for bar0"); - assert.closeTo(numAttr(bar1, "y") + numAttr(bar1, "height") / 2, yScale.scale(bar1Y) - yScale.rangeBand() / 2 + off, 0.01 + assert.closeTo(TestMethods.numAttr(bar1, "y") + TestMethods.numAttr(bar1, "height") / 2, yScale.scale(bar1Y) - width + off, 0.01 , "y pos correct for bar1"); - assert.closeTo(numAttr(bar2, "y") + numAttr(bar2, "height") / 2, yScale.scale(bar2Y) + yScale.rangeBand() / 2 - off, 0.01 + assert.closeTo(TestMethods.numAttr(bar2, "y") + TestMethods.numAttr(bar2, "height") / 2, yScale.scale(bar2Y) + width - off, 0.01 , "y pos correct for bar2"); - assert.closeTo(numAttr(bar3, "y") + numAttr(bar3, "height") / 2, yScale.scale(bar3Y) + yScale.rangeBand() / 2 - off, 0.01 + assert.closeTo(TestMethods.numAttr(bar3, "y") + TestMethods.numAttr(bar3, "height") / 2, yScale.scale(bar3Y) + width - off, 0.01 , "y pos correct for bar3"); svg.remove(); }); @@ -177,30 +179,28 @@ describe("Plots", () => { describe("Clustered Bar Plot Missing Values", () => { var svg: D3.Selection; - var plot: Plottable.Plot.ClusteredBar; - - var numAttr = (s: D3.Selection, a: string) => parseFloat(s.attr(a)); + var plot: Plottable.Plots.ClusteredBar; beforeEach(() => { var SVG_WIDTH = 600; var SVG_HEIGHT = 400; - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Linear(); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Linear(); var data1 = [{x: "A", y: 1}, {x: "B", y: 2}, {x: "C", y: 1}]; var data2 = [{x: "A", y: 2}, {x: "B", y: 4}]; var data3 = [{x: "B", y: 15}, {x: "C", y: 15}]; - plot = new Plottable.Plot.ClusteredBar(xScale, yScale); - plot.addDataset(data1); - plot.addDataset(data2); - plot.addDataset(data3); + plot = new Plottable.Plots.ClusteredBar(xScale, yScale); + plot.addDataset(new Plottable.Dataset(data1)); + plot.addDataset(new Plottable.Dataset(data2)); + plot.addDataset(new Plottable.Dataset(data3)); plot.baseline(0); - plot.project("x", "x", xScale); - plot.project("y", "y", yScale); - var xAxis = new Plottable.Axis.Category(xScale, "bottom"); - new Plottable.Component.Table([[plot], [xAxis]]).renderTo(svg); + plot.x((d) => d.x, xScale); + plot.y((d) => d.y, yScale); + var xAxis = new Plottable.Axes.Category(xScale, "bottom"); + new Plottable.Components.Table([[plot], [xAxis]]).renderTo(svg); }); it("renders correctly", () => { @@ -219,20 +219,20 @@ describe("Plots", () => { var cBar1 = d3.select(bars[0][6]); // check bars are in domain order - assert.operator(numAttr(aBar0, "x"), "<", numAttr(bBar0, "x"), "first dataset bars ordered correctly"); - assert.operator(numAttr(bBar0, "x"), "<", numAttr(cBar0, "x"), "first dataset bars ordered correctly"); + assert.operator(TestMethods.numAttr(aBar0, "x"), "<", TestMethods.numAttr(bBar0, "x"), "first dataset bars ordered correctly"); + assert.operator(TestMethods.numAttr(bBar0, "x"), "<", TestMethods.numAttr(cBar0, "x"), "first dataset bars ordered correctly"); - assert.operator(numAttr(aBar1, "x"), "<", numAttr(bBar1, "x"), "second dataset bars ordered correctly"); + assert.operator(TestMethods.numAttr(aBar1, "x"), "<", TestMethods.numAttr(bBar1, "x"), "second dataset bars ordered correctly"); - assert.operator(numAttr(bBar2, "x"), "<", numAttr(cBar1, "x"), "third dataset bars ordered correctly"); + assert.operator(TestMethods.numAttr(bBar2, "x"), "<", TestMethods.numAttr(cBar1, "x"), "third dataset bars ordered correctly"); // check that clustering is correct - assert.operator(numAttr(aBar0, "x"), "<", numAttr(aBar1, "x"), "A bars clustered in dataset order"); + assert.operator(TestMethods.numAttr(aBar0, "x"), "<", TestMethods.numAttr(aBar1, "x"), "A bars clustered in dataset order"); - assert.operator(numAttr(bBar0, "x"), "<", numAttr(bBar1, "x"), "B bars clustered in dataset order"); - assert.operator(numAttr(bBar1, "x"), "<", numAttr(bBar2, "x"), "B bars clustered in dataset order"); + assert.operator(TestMethods.numAttr(bBar0, "x"), "<", TestMethods.numAttr(bBar1, "x"), "B bars clustered in dataset order"); + assert.operator(TestMethods.numAttr(bBar1, "x"), "<", TestMethods.numAttr(bBar2, "x"), "B bars clustered in dataset order"); - assert.operator(numAttr(cBar0, "x"), "<", numAttr(cBar1, "x"), "C bars clustered in dataset order"); + assert.operator(TestMethods.numAttr(cBar0, "x"), "<", TestMethods.numAttr(cBar1, "x"), "C bars clustered in dataset order"); svg.remove(); }); @@ -240,25 +240,25 @@ describe("Plots", () => { describe("Horizontal Clustered Bar Plot Missing Values", () => { var svg: D3.Selection; - var plot: Plottable.Plot.ClusteredBar; + var plot: Plottable.Plots.ClusteredBar; beforeEach(() => { var SVG_WIDTH = 600; var SVG_HEIGHT = 400; - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Category(); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Category(); var data1 = [{y: "A", x: 1}, {y: "B", x: 2}, {y: "C", x: 1}]; var data2 = [{y: "A", x: 2}, {y: "B", x: 4}]; var data3 = [{y: "B", x: 15}, {y: "C", x: 15}]; - plot = new Plottable.Plot.ClusteredBar(xScale, yScale, false); - plot.addDataset(data1); - plot.addDataset(data2); - plot.addDataset(data3); - plot.project("x", "x", xScale); - plot.project("y", "y", yScale); + plot = new Plottable.Plots.ClusteredBar(xScale, yScale, false); + plot.addDataset(new Plottable.Dataset(data1)); + plot.addDataset(new Plottable.Dataset(data2)); + plot.addDataset(new Plottable.Dataset(data3)); + plot.x((d) => d.x, xScale); + plot.y((d) => d.y, yScale); plot.renderTo(svg); }); @@ -278,20 +278,20 @@ describe("Plots", () => { var cBar1 = d3.select(bars[0][6]); // check bars are in domain order - assert.operator(numAttr(aBar0, "y"), "<", numAttr(bBar0, "y"), "first dataset bars ordered correctly"); - assert.operator(numAttr(bBar0, "y"), "<", numAttr(cBar0, "y"), "first dataset bars ordered correctly"); + assert.operator(TestMethods.numAttr(aBar0, "y"), "<", TestMethods.numAttr(bBar0, "y"), "first dataset bars ordered correctly"); + assert.operator(TestMethods.numAttr(bBar0, "y"), "<", TestMethods.numAttr(cBar0, "y"), "first dataset bars ordered correctly"); - assert.operator(numAttr(aBar1, "y"), "<", numAttr(bBar1, "y"), "second dataset bars ordered correctly"); + assert.operator(TestMethods.numAttr(aBar1, "y"), "<", TestMethods.numAttr(bBar1, "y"), "second dataset bars ordered correctly"); - assert.operator(numAttr(bBar2, "y"), "<", numAttr(cBar1, "y"), "third dataset bars ordered correctly"); + assert.operator(TestMethods.numAttr(bBar2, "y"), "<", TestMethods.numAttr(cBar1, "y"), "third dataset bars ordered correctly"); // check that clustering is correct - assert.operator(numAttr(aBar0, "y"), "<", numAttr(aBar1, "y"), "A bars clustered in dataset order"); + assert.operator(TestMethods.numAttr(aBar0, "y"), "<", TestMethods.numAttr(aBar1, "y"), "A bars clustered in dataset order"); - assert.operator(numAttr(bBar0, "y"), "<", numAttr(bBar1, "y"), "B bars clustered in dataset order"); - assert.operator(numAttr(bBar1, "y"), "<", numAttr(bBar2, "y"), "B bars clustered in dataset order"); + assert.operator(TestMethods.numAttr(bBar0, "y"), "<", TestMethods.numAttr(bBar1, "y"), "B bars clustered in dataset order"); + assert.operator(TestMethods.numAttr(bBar1, "y"), "<", TestMethods.numAttr(bBar2, "y"), "B bars clustered in dataset order"); - assert.operator(numAttr(cBar0, "y"), "<", numAttr(cBar1, "y"), "C bars clustered in dataset order"); + assert.operator(TestMethods.numAttr(cBar0, "y"), "<", TestMethods.numAttr(cBar1, "y"), "C bars clustered in dataset order"); svg.remove(); }); diff --git a/test/components/plots/gridPlotTests.ts b/test/components/plots/gridPlotTests.ts index 5f6d4dc870..063e36a18e 100644 --- a/test/components/plots/gridPlotTests.ts +++ b/test/components/plots/gridPlotTests.ts @@ -14,65 +14,64 @@ describe("Plots", () => { ]; var VERIFY_CELLS = (cells: any[]) => { - assert.equal(cells.length, 4); + assert.strictEqual(cells.length, 4); var cellAU = d3.select(cells[0]); var cellBU = d3.select(cells[1]); var cellAV = d3.select(cells[2]); var cellBV = d3.select(cells[3]); - assert.equal(cellAU.attr("height"), "100", "cell 'AU' height is correct"); - assert.equal(cellAU.attr("width"), "200", "cell 'AU' width is correct"); - assert.equal(cellAU.attr("x"), "0", "cell 'AU' x coord is correct"); - assert.equal(cellAU.attr("y"), "0", "cell 'AU' y coord is correct"); - assert.equal(cellAU.attr("fill"), "#000000", "cell 'AU' color is correct"); - - assert.equal(cellBU.attr("height"), "100", "cell 'BU' height is correct"); - assert.equal(cellBU.attr("width"), "200", "cell 'BU' width is correct"); - assert.equal(cellBU.attr("x"), "200", "cell 'BU' x coord is correct"); - assert.equal(cellBU.attr("y"), "0", "cell 'BU' y coord is correct"); - assert.equal(cellBU.attr("fill"), "#212121", "cell 'BU' color is correct"); - - assert.equal(cellAV.attr("height"), "100", "cell 'AV' height is correct"); - assert.equal(cellAV.attr("width"), "200", "cell 'AV' width is correct"); - assert.equal(cellAV.attr("x"), "0", "cell 'AV' x coord is correct"); - assert.equal(cellAV.attr("y"), "100", "cell 'AV' y coord is correct"); - assert.equal(cellAV.attr("fill"), "#ffffff", "cell 'AV' color is correct"); - - assert.equal(cellBV.attr("height"), "100", "cell 'BV' height is correct"); - assert.equal(cellBV.attr("width"), "200", "cell 'BV' width is correct"); - assert.equal(cellBV.attr("x"), "200", "cell 'BV' x coord is correct"); - assert.equal(cellBV.attr("y"), "100", "cell 'BV' y coord is correct"); - assert.equal(cellBV.attr("fill"), "#777777", "cell 'BV' color is correct"); + assert.strictEqual(cellAU.attr("height"), "100", "cell 'AU' height is correct"); + assert.strictEqual(cellAU.attr("width"), "200", "cell 'AU' width is correct"); + assert.strictEqual(cellAU.attr("x"), "0", "cell 'AU' x coord is correct"); + assert.strictEqual(cellAU.attr("y"), "0", "cell 'AU' y coord is correct"); + assert.strictEqual(cellAU.attr("fill"), "#000000", "cell 'AU' color is correct"); + + assert.strictEqual(cellBU.attr("height"), "100", "cell 'BU' height is correct"); + assert.strictEqual(cellBU.attr("width"), "200", "cell 'BU' width is correct"); + assert.strictEqual(cellBU.attr("x"), "200", "cell 'BU' x coord is correct"); + assert.strictEqual(cellBU.attr("y"), "0", "cell 'BU' y coord is correct"); + assert.strictEqual(cellBU.attr("fill"), "#212121", "cell 'BU' color is correct"); + + assert.strictEqual(cellAV.attr("height"), "100", "cell 'AV' height is correct"); + assert.strictEqual(cellAV.attr("width"), "200", "cell 'AV' width is correct"); + assert.strictEqual(cellAV.attr("x"), "0", "cell 'AV' x coord is correct"); + assert.strictEqual(cellAV.attr("y"), "100", "cell 'AV' y coord is correct"); + assert.strictEqual(cellAV.attr("fill"), "#ffffff", "cell 'AV' color is correct"); + + assert.strictEqual(cellBV.attr("height"), "100", "cell 'BV' height is correct"); + assert.strictEqual(cellBV.attr("width"), "200", "cell 'BV' width is correct"); + assert.strictEqual(cellBV.attr("x"), "200", "cell 'BV' x coord is correct"); + assert.strictEqual(cellBV.attr("y"), "100", "cell 'BV' y coord is correct"); + assert.strictEqual(cellBV.attr("fill"), "#777777", "cell 'BV' color is correct"); }; it("renders correctly", () => { - var xScale: Plottable.Scale.Category = new Plottable.Scale.Category(); - var yScale: Plottable.Scale.Category = new Plottable.Scale.Category(); - var colorScale: Plottable.Scale.InterpolatedColor = new Plottable.Scale.InterpolatedColor(["black", "white"]); - var svg: D3.Selection = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var gridPlot: Plottable.Plot.Grid = new Plottable.Plot.Grid(xScale, yScale, colorScale); - gridPlot.addDataset(DATA) - .project("fill", "magnitude", colorScale) - .project("x", "x", xScale) - .project("y", "y", yScale); + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Category(); + var colorScale = new Plottable.Scales.InterpolatedColor(["black", "white"]); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var gridPlot = new Plottable.Plots.Grid(xScale, yScale); + gridPlot.addDataset(new Plottable.Dataset(DATA)) + .attr("fill", (d) => d.magnitude, colorScale); + gridPlot.x((d: any) => d.x, xScale) + .y((d: any) => d.y, yScale); gridPlot.renderTo(svg); VERIFY_CELLS(( gridPlot)._renderArea.selectAll("rect")[0]); svg.remove(); }); - it("renders correctly when data is set after construction", () => { - var xScale: Plottable.Scale.Category = new Plottable.Scale.Category(); - var yScale: Plottable.Scale.Category = new Plottable.Scale.Category(); - var colorScale: Plottable.Scale.InterpolatedColor = new Plottable.Scale.InterpolatedColor(["black", "white"]); - var svg: D3.Selection = generateSVG(SVG_WIDTH, SVG_HEIGHT); + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Category(); + var colorScale = new Plottable.Scales.InterpolatedColor(["black", "white"]); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); var dataset = new Plottable.Dataset(); - var gridPlot: Plottable.Plot.Grid = new Plottable.Plot.Grid(xScale, yScale, colorScale); + var gridPlot = new Plottable.Plots.Grid(xScale, yScale); gridPlot.addDataset(dataset) - .project("fill", "magnitude", colorScale) - .project("x", "x", xScale) - .project("y", "y", yScale) + .attr("fill", (d) => d.magnitude, colorScale); + gridPlot.x((d: any) => d.x, xScale) + .y((d: any) => d.y, yScale) .renderTo(svg); dataset.data(DATA); VERIFY_CELLS(( gridPlot)._renderArea.selectAll("rect")[0]); @@ -82,16 +81,16 @@ describe("Plots", () => { it("renders correctly when there isn't data for every spot", () => { var CELL_HEIGHT = 50; var CELL_WIDTH = 100; - var xScale: Plottable.Scale.Category = new Plottable.Scale.Category(); - var yScale: Plottable.Scale.Category = new Plottable.Scale.Category(); - var colorScale: Plottable.Scale.InterpolatedColor = new Plottable.Scale.InterpolatedColor(["black", "white"]); - var svg: D3.Selection = generateSVG(SVG_WIDTH, SVG_HEIGHT); + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Category(); + var colorScale = new Plottable.Scales.InterpolatedColor(["black", "white"]); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); var dataset = new Plottable.Dataset(); - var gridPlot: Plottable.Plot.Grid = new Plottable.Plot.Grid(xScale, yScale, colorScale); + var gridPlot = new Plottable.Plots.Grid(xScale, yScale); gridPlot.addDataset(dataset) - .project("fill", "magnitude", colorScale) - .project("x", "x", xScale) - .project("y", "y", yScale) + .attr("fill", (d) => d.magnitude, colorScale); + gridPlot.x((d: any) => d.x, xScale) + .y((d: any) => d.y, yScale) .renderTo(svg); var data = [ {x: "A", y: "W", magnitude: 0}, @@ -101,27 +100,27 @@ describe("Plots", () => { ]; dataset.data(data); var cells = ( gridPlot)._renderArea.selectAll("rect")[0]; - assert.equal(cells.length, data.length); + assert.strictEqual(cells.length, data.length); for (var i = 0; i < cells.length; i++) { var cell = d3.select(cells[i]); - assert.equal(cell.attr("x"), i * CELL_WIDTH, "Cell x coord is correct"); - assert.equal(cell.attr("y"), i * CELL_HEIGHT, "Cell y coord is correct"); - assert.equal(cell.attr("width"), CELL_WIDTH, "Cell width is correct"); - assert.equal(cell.attr("height"), CELL_HEIGHT, "Cell height is correct"); + assert.strictEqual(cell.attr("x"), String(i * CELL_WIDTH), "Cell x coord is correct"); + assert.strictEqual(cell.attr("y"), String(i * CELL_HEIGHT), "Cell y coord is correct"); + assert.strictEqual(cell.attr("width"), String(CELL_WIDTH), "Cell width is correct"); + assert.strictEqual(cell.attr("height"), String(CELL_HEIGHT), "Cell height is correct"); } svg.remove(); }); it("can invert y axis correctly", () => { - var xScale: Plottable.Scale.Category = new Plottable.Scale.Category(); - var yScale: Plottable.Scale.Category = new Plottable.Scale.Category(); - var colorScale: Plottable.Scale.InterpolatedColor = new Plottable.Scale.InterpolatedColor(["black", "white"]); - var svg: D3.Selection = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var gridPlot: Plottable.Plot.Grid = new Plottable.Plot.Grid(xScale, yScale, colorScale); - gridPlot.addDataset(DATA) - .project("fill", "magnitude") - .project("x", "x", xScale) - .project("y", "y", yScale) + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Category(); + var colorScale = new Plottable.Scales.InterpolatedColor(["black", "white"]); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var gridPlot = new Plottable.Plots.Grid(xScale, yScale); + gridPlot.addDataset(new Plottable.Dataset(DATA)) + .attr("fill", (d) => d.magnitude, colorScale); + gridPlot.x((d: any) => d.x, xScale) + .y((d: any) => d.y, yScale) .renderTo(svg); yScale.domain(["U", "V"]); @@ -155,57 +154,38 @@ describe("Plots", () => { describe("getAllSelections()", () => { it("retrieves all selections with no args", () => { - var xScale: Plottable.Scale.Category = new Plottable.Scale.Category(); - var yScale: Plottable.Scale.Category = new Plottable.Scale.Category(); - var colorScale: Plottable.Scale.InterpolatedColor = new Plottable.Scale.InterpolatedColor(["black", "white"]); - var svg: D3.Selection = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var gridPlot: Plottable.Plot.Grid = new Plottable.Plot.Grid(xScale, yScale, colorScale); - gridPlot.addDataset("a", DATA) - .project("fill", "magnitude", colorScale) - .project("x", "x", xScale) - .project("y", "y", yScale); + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Category(); + var colorScale = new Plottable.Scales.InterpolatedColor(["black", "white"]); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var gridPlot = new Plottable.Plots.Grid(xScale, yScale); + var dataset = new Plottable.Dataset(DATA); + gridPlot.addDataset(dataset) + .attr("fill", (d) => d.magnitude, colorScale); + gridPlot.x((d: any) => d.x, xScale) + .y((d: any) => d.y, yScale); gridPlot.renderTo(svg); var allCells = gridPlot.getAllSelections(); - var allCells2 = gridPlot.getAllSelections(( gridPlot)._datasetKeysInOrder); - assert.deepEqual(allCells, allCells2, "all cells retrieved"); - - svg.remove(); - }); - - it("retrieves correct selections (string arg)", () => { - var xScale: Plottable.Scale.Category = new Plottable.Scale.Category(); - var yScale: Plottable.Scale.Category = new Plottable.Scale.Category(); - var colorScale: Plottable.Scale.InterpolatedColor = new Plottable.Scale.InterpolatedColor(["black", "white"]); - var svg: D3.Selection = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var gridPlot: Plottable.Plot.Grid = new Plottable.Plot.Grid(xScale, yScale, colorScale); - gridPlot.addDataset("a", DATA) - .project("fill", "magnitude", colorScale) - .project("x", "x", xScale) - .project("y", "y", yScale); - gridPlot.renderTo(svg); - - var allCells = gridPlot.getAllSelections("a"); assert.strictEqual(allCells.size(), 4, "all cells retrieved"); - var selectionData = allCells.data(); - assert.includeMembers(selectionData, DATA, "data in selection data"); svg.remove(); }); - it("retrieves correct selections (array arg)", () => { - var xScale: Plottable.Scale.Category = new Plottable.Scale.Category(); - var yScale: Plottable.Scale.Category = new Plottable.Scale.Category(); - var colorScale: Plottable.Scale.InterpolatedColor = new Plottable.Scale.InterpolatedColor(["black", "white"]); - var svg: D3.Selection = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var gridPlot: Plottable.Plot.Grid = new Plottable.Plot.Grid(xScale, yScale, colorScale); - gridPlot.addDataset("a", DATA) - .project("fill", "magnitude", colorScale) - .project("x", "x", xScale) - .project("y", "y", yScale); + it("retrieves correct selections", () => { + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Category(); + var colorScale = new Plottable.Scales.InterpolatedColor(["black", "white"]); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var gridPlot = new Plottable.Plots.Grid(xScale, yScale); + var dataset = new Plottable.Dataset(DATA); + gridPlot.addDataset(dataset) + .attr("fill", (d) => d.magnitude, colorScale); + gridPlot.x((d: any) => d.x, xScale) + .y((d: any) => d.y, yScale); gridPlot.renderTo(svg); - var allCells = gridPlot.getAllSelections(["a"]); + var allCells = gridPlot.getAllSelections([dataset]); assert.strictEqual(allCells.size(), 4, "all cells retrieved"); var selectionData = allCells.data(); assert.includeMembers(selectionData, DATA, "data in selection data"); @@ -213,19 +193,21 @@ describe("Plots", () => { svg.remove(); }); - it("skips invalid keys", () => { - var xScale: Plottable.Scale.Category = new Plottable.Scale.Category(); - var yScale: Plottable.Scale.Category = new Plottable.Scale.Category(); - var colorScale: Plottable.Scale.InterpolatedColor = new Plottable.Scale.InterpolatedColor(["black", "white"]); - var svg: D3.Selection = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var gridPlot: Plottable.Plot.Grid = new Plottable.Plot.Grid(xScale, yScale, colorScale); - gridPlot.addDataset("a", DATA) - .project("fill", "magnitude", colorScale) - .project("x", "x", xScale) - .project("y", "y", yScale); + it("skips invalid Datasets", () => { + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Category(); + var colorScale = new Plottable.Scales.InterpolatedColor(["black", "white"]); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var gridPlot = new Plottable.Plots.Grid(xScale, yScale); + var dataset = new Plottable.Dataset(DATA); + gridPlot.addDataset(dataset) + .attr("fill", (d) => d.magnitude, colorScale); + gridPlot.x((d: any) => d.x, xScale) + .y((d: any) => d.y, yScale); gridPlot.renderTo(svg); - var allCells = gridPlot.getAllSelections(["a", "b"]); + var dummyDataset = new Plottable.Dataset([]); + var allCells = gridPlot.getAllSelections([dataset, dummyDataset]); assert.strictEqual(allCells.size(), 4, "all cells retrieved"); var selectionData = allCells.data(); assert.includeMembers(selectionData, DATA, "data in selection data"); diff --git a/test/components/plots/linePlotTests.ts b/test/components/plots/linePlotTests.ts index 42e251bf77..7f639f7f6a 100644 --- a/test/components/plots/linePlotTests.ts +++ b/test/components/plots/linePlotTests.ts @@ -6,7 +6,7 @@ describe("Plots", () => { // HACKHACK #1798: beforeEach being used below describe("LinePlot", () => { it("getAllPlotData with NaNs", () => { - var svg = generateSVG(500, 500); + var svg = TestMethods.generateSVG(500, 500); var dataWithNaN = [ { foo: 0.0, bar: 0.0 }, { foo: 0.2, bar: 0.2 }, @@ -15,13 +15,13 @@ describe("Plots", () => { { foo: 0.8, bar: 0.8 } ]; - var xScale = new Plottable.Scale.Linear().domain([0, 1]); - var yScale = new Plottable.Scale.Linear().domain([0, 1]); + var xScale = new Plottable.Scales.Linear().domain([0, 1]); + var yScale = new Plottable.Scales.Linear().domain([0, 1]); - var linePlot = new Plottable.Plot.Line(xScale, yScale); - linePlot.addDataset(dataWithNaN); - linePlot.project("x", (d: any) => d.foo, xScale); - linePlot.project("y", (d: any) => d.bar, yScale); + var linePlot = new Plottable.Plots.Line(xScale, yScale); + linePlot.addDataset(new Plottable.Dataset(dataWithNaN)); + linePlot.x((d: any) => d.foo, xScale); + linePlot.y((d: any) => d.bar, yScale); linePlot.renderTo(svg); var apd = linePlot.getAllPlotData(); @@ -37,12 +37,12 @@ describe("Plots", () => { describe("LinePlot", () => { // HACKHACK #1798: beforeEach being used below it("renders correctly with no data", () => { - var svg = generateSVG(400, 400); - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - var plot = new Plottable.Plot.Line(xScale, yScale); - plot.project("x", (d: any) => d.x, xScale); - plot.project("y", (d: any) => d.y, yScale); + var svg = TestMethods.generateSVG(400, 400); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); + var plot = new Plottable.Plots.Line(xScale, yScale); + plot.x((d: any) => d.x, xScale); + plot.y((d: any) => d.y, yScale); assert.doesNotThrow(() => plot.renderTo(svg), Error); assert.strictEqual(plot.width(), 400, "was allocated width"); assert.strictEqual(plot.height(), 400, "was allocated height"); @@ -52,40 +52,39 @@ describe("Plots", () => { describe("LinePlot", () => { var svg: D3.Selection; - var xScale: Plottable.Scale.Linear; - var yScale: Plottable.Scale.Linear; + var xScale: Plottable.Scales.Linear; + var yScale: Plottable.Scales.Linear; var xAccessor: any; var yAccessor: any; var colorAccessor: any; var twoPointData = [{foo: 0, bar: 0}, {foo: 1, bar: 1}]; var simpleDataset: Plottable.Dataset; - var linePlot: Plottable.Plot.Line; + var linePlot: Plottable.Plots.Line; var renderArea: D3.Selection; before(() => { - xScale = new Plottable.Scale.Linear().domain([0, 1]); - yScale = new Plottable.Scale.Linear().domain([0, 1]); + xScale = new Plottable.Scales.Linear().domain([0, 1]); + yScale = new Plottable.Scales.Linear().domain([0, 1]); xAccessor = (d: any) => d.foo; yAccessor = (d: any) => d.bar; colorAccessor = (d: any, i: number, m: any) => d3.rgb(d.foo, d.bar, i).toString(); }); beforeEach(() => { - svg = generateSVG(500, 500); + svg = TestMethods.generateSVG(500, 500); simpleDataset = new Plottable.Dataset(twoPointData); - linePlot = new Plottable.Plot.Line(xScale, yScale); - linePlot.addDataset("s1", simpleDataset) - .project("x", xAccessor, xScale) - .project("y", yAccessor, yScale) - .project("stroke", colorAccessor) - .addDataset("s2", simpleDataset) + linePlot = new Plottable.Plots.Line(xScale, yScale); + linePlot.addDataset(simpleDataset); + linePlot.x(xAccessor, xScale) + .y(yAccessor, yScale) + .attr("stroke", colorAccessor) .renderTo(svg); renderArea = ( linePlot)._renderArea; }); it("draws a line correctly", () => { var linePath = renderArea.select(".line"); - assert.strictEqual(normalizePath(linePath.attr("d")), "M0,500L500,0", "line d was set correctly"); + assert.strictEqual(TestMethods.normalizePath(linePath.attr("d")), "M0,500L500,0", "line d was set correctly"); var lineComputedStyle = window.getComputedStyle(linePath.node()); assert.strictEqual(lineComputedStyle.fill, "none", "line fill renders as \"none\""); svg.remove(); @@ -93,16 +92,16 @@ describe("Plots", () => { it("attributes set appropriately from accessor", () => { var areaPath = renderArea.select(".line"); - assert.equal(areaPath.attr("stroke"), "#000000", "stroke set correctly"); + assert.strictEqual(areaPath.attr("stroke"), "#000000", "stroke set correctly"); svg.remove(); }); it("attributes can be changed by projecting new accessor and re-render appropriately", () => { var newColorAccessor = () => "pink"; - linePlot.project("stroke", newColorAccessor); + linePlot.attr("stroke", newColorAccessor); linePlot.renderTo(svg); var linePath = renderArea.select(".line"); - assert.equal(linePath.attr("stroke"), "pink", "stroke changed correctly"); + assert.strictEqual(linePath.attr("stroke"), "pink", "stroke changed correctly"); svg.remove(); }); @@ -110,13 +109,13 @@ describe("Plots", () => { var data = JSON.parse(JSON.stringify(twoPointData)); // deep copy to not affect other tests data.forEach(function(d: any) { d.stroke = "pink"; }); simpleDataset.data(data); - linePlot.project("stroke", "stroke"); + linePlot.attr("stroke", (d) => d.stroke); var areaPath = renderArea.select(".line"); - assert.equal(areaPath.attr("stroke"), "pink", "stroke set to uniform stroke color"); + assert.strictEqual(areaPath.attr("stroke"), "pink", "stroke set to uniform stroke color"); data[0].stroke = "green"; simpleDataset.data(data); - assert.equal(areaPath.attr("stroke"), "green", "stroke set to first datum stroke color"); + assert.strictEqual(areaPath.attr("stroke"), "green", "stroke set to first datum stroke color"); svg.remove(); }); @@ -130,16 +129,16 @@ describe("Plots", () => { ]; simpleDataset.data(lineData); var linePath = renderArea.select(".line"); - var d_original = normalizePath(linePath.attr("d")); + var d_original = TestMethods.normalizePath(linePath.attr("d")); function assertCorrectPathSplitting(msgPrefix: string) { - var d = normalizePath(linePath.attr("d")); + var d = TestMethods.normalizePath(linePath.attr("d")); var pathSegements = d.split("M").filter((segment) => segment !== ""); assert.lengthOf(pathSegements, 2, msgPrefix + " split path into two segments"); var firstSegmentContained = d_original.indexOf(pathSegements[0]) >= 0; assert.isTrue(firstSegmentContained, "first path segment is a subpath of the original path"); var secondSegmentContained = d_original.indexOf(pathSegements[1]) >= 0; - assert.isTrue(firstSegmentContained, "second path segment is a subpath of the original path"); + assert.isTrue(secondSegmentContained, "second path segment is a subpath of the original path"); } var dataWithNaN = lineData.slice(); @@ -161,112 +160,47 @@ describe("Plots", () => { svg.remove(); }); - it("_getClosestWithinRange", () => { - var dataset2 = [ - { foo: 0, bar: 1 }, - { foo: 1, bar: 0.95 } - ]; - linePlot.addDataset(dataset2); - - var closestData = ( linePlot)._getClosestWithinRange({ x: 500, y: 0 }, 5); - assert.strictEqual(closestData.closestValue, twoPointData[1], "got closest point from first dataset"); - - closestData = ( linePlot)._getClosestWithinRange({ x: 500, y: 25 }, 5); - assert.strictEqual(closestData.closestValue, dataset2[1], "got closest point from second dataset"); - - closestData = ( linePlot)._getClosestWithinRange({ x: 500, y: 10 }, 5); - assert.isUndefined(closestData.closestValue, "returns nothing if no points are within range"); - - closestData = ( linePlot)._getClosestWithinRange({ x: 500, y: 10 }, 25); - assert.strictEqual(closestData.closestValue, twoPointData[1], "returns the closest point within range"); - - closestData = ( linePlot)._getClosestWithinRange({ x: 500, y: 20 }, 25); - assert.strictEqual(closestData.closestValue, dataset2[1], "returns the closest point within range"); - - svg.remove(); - }); - - it("_doHover()", () => { - var dataset2 = [ - { foo: 0, bar: 1 }, - { foo: 1, bar: 0.95 } - ]; - linePlot.addDataset(dataset2); - - var hoverData = linePlot._doHover({ x: 495, y: 0 }); - var expectedDatum = twoPointData[1]; - assert.strictEqual(hoverData.data[0], expectedDatum, "returned the closest point within range"); - var hoverTarget = hoverData.selection; - assert.strictEqual(parseFloat(hoverTarget.attr("cx")), xScale.scale(expectedDatum.foo), "hover target was positioned correctly (x)"); - assert.strictEqual(parseFloat(hoverTarget.attr("cy")), yScale.scale(expectedDatum.bar), "hover target was positioned correctly (y)"); - - hoverData = linePlot._doHover({ x: 0, y: 0 }); - expectedDatum = dataset2[0]; - assert.strictEqual(hoverData.data[0], expectedDatum, "returned the closest point within range"); - hoverTarget = hoverData.selection; - assert.strictEqual(parseFloat(hoverTarget.attr("cx")), xScale.scale(expectedDatum.foo), "hover target was positioned correctly (x)"); - assert.strictEqual(parseFloat(hoverTarget.attr("cy")), yScale.scale(expectedDatum.bar), "hover target was positioned correctly (y)"); - - svg.remove(); - }); - describe("getAllSelections()", () => { - it("retrieves all dataset selections with no args", () => { - var dataset3 = [ + var dataset3 = new Plottable.Dataset([ { foo: 0, bar: 1 }, { foo: 1, bar: 0.95 } - ]; - linePlot.addDataset("d3", dataset3); + ]); + linePlot.addDataset(dataset3); var allLines = linePlot.getAllSelections(); - var allLines2 = linePlot.getAllSelections(( linePlot)._datasetKeysInOrder); - assert.deepEqual(allLines, allLines2, "all lines retrieved"); - - svg.remove(); - }); - - it("retrieves correct selections (string arg)", () => { - var dataset3 = [ - { foo: 0, bar: 1 }, - { foo: 1, bar: 0.95 } - ]; - linePlot.addDataset("d3", dataset3); - - var allLines = linePlot.getAllSelections("d3"); - assert.strictEqual(allLines.size(), 1, "all lines retrieved"); - var selectionData = allLines.data(); - assert.include(selectionData, dataset3, "third dataset data in selection data"); + assert.strictEqual(allLines.size(), 2, "all lines retrieved"); svg.remove(); }); - it("retrieves correct selections (array arg)", () => { - var dataset3 = [ + it("retrieves correct selections", () => { + var dataset3 = new Plottable.Dataset([ { foo: 0, bar: 1 }, { foo: 1, bar: 0.95 } - ]; - linePlot.addDataset("d3", dataset3); + ]); + linePlot.addDataset(dataset3); - var allLines = linePlot.getAllSelections(["d3"]); + var allLines = linePlot.getAllSelections([dataset3]); assert.strictEqual(allLines.size(), 1, "all lines retrieved"); var selectionData = allLines.data(); - assert.include(selectionData, dataset3, "third dataset data in selection data"); + assert.include(selectionData, dataset3.data(), "third dataset data in selection data"); svg.remove(); }); - it("skips invalid keys", () => { - var dataset3 = [ + it("skips invalid Dataset", () => { + var dataset3 = new Plottable.Dataset([ { foo: 0, bar: 1 }, { foo: 1, bar: 0.95 } - ]; - linePlot.addDataset("d3", dataset3); + ]); + linePlot.addDataset(dataset3); + var dummyDataset = new Plottable.Dataset([]); - var allLines = linePlot.getAllSelections(["d3", "test"]); + var allLines = linePlot.getAllSelections([dataset3, dummyDataset]); assert.strictEqual(allLines.size(), 1, "all lines retrieved"); var selectionData = allLines.data(); - assert.include(selectionData, dataset3, "third dataset data in selection data"); + assert.include(selectionData, dataset3.data(), "third dataset data in selection data"); svg.remove(); }); @@ -276,11 +210,11 @@ describe("Plots", () => { describe("getAllPlotData()", () => { it("retrieves correct data", () => { - var dataset3 = [ + var dataset3 = new Plottable.Dataset([ { foo: 0, bar: 1 }, { foo: 1, bar: 0.95 } - ]; - linePlot.addDataset("d3", dataset3); + ]); + linePlot.addDataset(dataset3); var allLines = linePlot.getAllPlotData().selection; assert.strictEqual(allLines.size(), linePlot.datasets().length, "single line per dataset"); @@ -292,31 +226,31 @@ describe("Plots", () => { var lines: D3.Selection; var d0: any, d1: any; var d0Px: Plottable.Point, d1Px: Plottable.Point; - var dataset3: any[]; + var dataset2: Plottable.Dataset; - function assertPlotDataEqual(expected: Plottable.Plot.PlotData, actual: Plottable.Plot.PlotData, msg: string) { - assert.deepEqual(expected.data, actual.data, msg); - assert.closeTo(expected.pixelPoints[0].x, actual.pixelPoints[0].x, 0.01, msg); - assert.closeTo(expected.pixelPoints[0].y, actual.pixelPoints[0].y, 0.01, msg); - assert.deepEqual(expected.selection, actual.selection, msg); + function assertPlotDataEqual(actual: Plottable.Plots.PlotData, expected: Plottable.Plots.PlotData, msg: string) { + assert.deepEqual(actual.data, expected.data, msg); + assert.closeTo(actual.pixelPoints[0].x, expected.pixelPoints[0].x, 0.01, msg); + assert.closeTo(actual.pixelPoints[0].y, expected.pixelPoints[0].y, 0.01, msg); + assert.deepEqual(actual.selection, expected.selection, msg); } beforeEach(() => { - dataset3 = [ + dataset2 = new Plottable.Dataset([ { foo: 0, bar: 0.75 }, { foo: 1, bar: 0.25 } - ]; + ]); - linePlot.addDataset("d3", dataset3); + linePlot.addDataset(dataset2); lines = d3.selectAll(".line-plot .line"); - d0 = dataset3[0]; + d0 = dataset2.data()[0]; d0Px = { x: xScale.scale(xAccessor(d0)), y: yScale.scale(yAccessor(d0)) }; - d1 = dataset3[1]; + d1 = dataset2.data()[1]; d1Px = { x: xScale.scale(xAccessor(d1)), y: yScale.scale(yAccessor(d1)) @@ -327,26 +261,26 @@ describe("Plots", () => { var expected = { data: [d0], pixelPoints: [d0Px], - selection: d3.selectAll([lines[0][2]]) + selection: d3.selectAll([lines[0][1]]) }; var closest = linePlot.getClosestPlotData({x: d0Px.x, y: d0Px.y - 1}); - assertPlotDataEqual(expected, closest, "if above a point, it is closest"); + assertPlotDataEqual(closest, expected, "if above a point, it is closest"); closest = linePlot.getClosestPlotData({x: d0Px.x, y: d0Px.y + 1}); - assertPlotDataEqual(expected, closest, "if below a point, it is closest"); + assertPlotDataEqual(closest, expected, "if below a point, it is closest"); closest = linePlot.getClosestPlotData({x: d0Px.x + 1, y: d0Px.y + 1}); - assertPlotDataEqual(expected, closest, "if right of a point, it is closest"); + assertPlotDataEqual(closest, expected, "if right of a point, it is closest"); expected = { data: [d1], pixelPoints: [d1Px], - selection: d3.selectAll([lines[0][2]]) + selection: d3.selectAll([lines[0][1]]) }; closest = linePlot.getClosestPlotData({x: d1Px.x - 1, y: d1Px.y}); - assertPlotDataEqual(expected, closest, "if left of a point, it is closest"); + assertPlotDataEqual(closest, expected, "if left of a point, it is closest"); svg.remove(); }); @@ -360,17 +294,17 @@ describe("Plots", () => { x: xScale.scale(xAccessor(d1)), y: yScale.scale(yAccessor(d1)) }], - selection: d3.selectAll([lines[0][2]]) + selection: d3.selectAll([lines[0][1]]) }; var closest = linePlot.getClosestPlotData({ x: xScale.scale(0.25), y: d1Px.y }); - assertPlotDataEqual(expected, closest, "only in-view points are considered"); + assertPlotDataEqual(closest, expected, "only in-view points are considered"); svg.remove(); }); it("handles empty plots gracefully", () => { - linePlot = new Plottable.Plot.Line(xScale, yScale); + linePlot = new Plottable.Plots.Line(xScale, yScale); var closest = linePlot.getClosestPlotData({ x: d0Px.x, y: d0Px.y }); assert.lengthOf(closest.data, 0); @@ -383,11 +317,11 @@ describe("Plots", () => { it("retains original classes when class is projected", () => { var newClassProjector = () => "pink"; - linePlot.project("class", newClassProjector); + linePlot.attr("class", newClassProjector); linePlot.renderTo(svg); - var linePath = renderArea.select("." + Plottable._Drawer.Line.LINE_CLASS); + var linePath = renderArea.select("." + Plottable.Drawers.Line.LINE_CLASS); assert.isTrue(linePath.classed("pink")); - assert.isTrue(linePath.classed(Plottable._Drawer.Line.LINE_CLASS)); + assert.isTrue(linePath.classed(Plottable.Drawers.Line.LINE_CLASS)); svg.remove(); }); }); diff --git a/test/components/plots/newStylePlotTests.ts b/test/components/plots/newStylePlotTests.ts deleted file mode 100644 index 857a6c3b9d..0000000000 --- a/test/components/plots/newStylePlotTests.ts +++ /dev/null @@ -1,84 +0,0 @@ -/// - -var assert = chai.assert; -describe("Plots", () => { - describe("New Style Plots", () => { - var p: Plottable.Plot.AbstractPlot; - var oldWarn = Plottable._Util.Methods.warn; - - beforeEach(() => { - p = new Plottable.Plot.AbstractPlot(); - ( p)._getDrawer = (k: string) => new Plottable._Drawer.Element(k).svgElement("rect"); - }); - - afterEach(() => { - Plottable._Util.Methods.warn = oldWarn; - }); - - it("Datasets can be added and removed as expected", () => { - p.addDataset("foo", [1, 2, 3]); - var d2 = new Plottable.Dataset([4, 5, 6]); - p.addDataset("bar", d2); - p.addDataset([7, 8, 9]); - var d4 = new Plottable.Dataset([10, 11, 12]); - p.addDataset(d4); - - assert.deepEqual(( p)._datasetKeysInOrder, ["foo", "bar", "_0", "_1"], "dataset keys as expected"); - var datasets = p.datasets(); - assert.deepEqual(datasets[0].data(), [1, 2, 3]); - assert.equal(datasets[1], d2); - assert.deepEqual(datasets[2].data(), [7, 8, 9]); - assert.equal(datasets[3], d4); - - p.removeDataset("foo"); - p.removeDataset("_0"); - - assert.deepEqual(( p)._datasetKeysInOrder, ["bar", "_1"]); - assert.lengthOf(p.datasets(), 2); - }); - - it("Datasets are listened to appropriately", () => { - var callbackCounter = 0; - var callback = () => callbackCounter++; - ( p)._onDatasetUpdate = callback; - var d = new Plottable.Dataset([1, 2, 3]); - p.addDataset("foo", d); - assert.equal(callbackCounter, 1, "adding dataset triggers listener"); - d.data([1, 2, 3, 4]); - assert.equal(callbackCounter, 2, "modifying data triggers listener"); - p.removeDataset("foo"); - assert.equal(callbackCounter, 3, "removing dataset triggers listener"); - }); - - it("Datasets can be reordered", () => { - p.addDataset("foo", [1]); - p.addDataset("bar", [2]); - p.addDataset("baz", [3]); - assert.deepEqual(p.datasetOrder(), ["foo", "bar", "baz"]); - p.datasetOrder(["bar", "baz", "foo"]); - assert.deepEqual(p.datasetOrder(), ["bar", "baz", "foo"]); - var warned = 0; - Plottable._Util.Methods.warn = () => warned++; // suppress expected warnings - p.datasetOrder(["blah", "blee", "bar", "baz", "foo"]); - assert.equal(warned, 1); - assert.deepEqual(p.datasetOrder(), ["bar", "baz", "foo"]); - }); - - it("Has proper warnings", () => { - var warned = 0; - Plottable._Util.Methods.warn = () => warned++; - p.addDataset("_foo", []); - assert.equal(warned, 1); - p.addDataset("2", []); - p.addDataset("4", []); - - // get warning for not a permutation - p.datasetOrder(["_bar", "4", "2"]); - assert.equal(warned, 2); - - // do not get warning for a permutation - p.datasetOrder(["2", "_foo", "4"]); - assert.equal(warned, 2); - }); - }); -}); diff --git a/test/components/plots/piePlotTests.ts b/test/components/plots/piePlotTests.ts index 381913fd38..de479bbb87 100644 --- a/test/components/plots/piePlotTests.ts +++ b/test/components/plots/piePlotTests.ts @@ -6,9 +6,9 @@ describe("Plots", () => { describe("PiePlot", () => { // HACKHACK #1798: beforeEach being used below it("renders correctly with no data", () => { - var svg = generateSVG(400, 400); - var plot = new Plottable.Plot.Pie(); - plot.project("value", (d: any) => d.value); + var svg = TestMethods.generateSVG(400, 400); + var plot = new Plottable.Plots.Pie(); + plot.sectorValue((d) => d.value); assert.doesNotThrow(() => plot.renderTo(svg), Error); assert.strictEqual(plot.width(), 400, "was allocated width"); assert.strictEqual(plot.height(), 400, "was allocated height"); @@ -20,16 +20,16 @@ describe("Plots", () => { var svg: D3.Selection; var simpleDataset: Plottable.Dataset; var simpleData: any[]; - var piePlot: Plottable.Plot.Pie; + var piePlot: Plottable.Plots.Pie; var renderArea: D3.Selection; beforeEach(() => { - svg = generateSVG(500, 500); + svg = TestMethods.generateSVG(500, 500); simpleData = [{value: 5, value2: 10, type: "A"}, {value: 15, value2: 10, type: "B"}]; simpleDataset = new Plottable.Dataset(simpleData); - piePlot = new Plottable.Plot.Pie(); - piePlot.addDataset("simpleDataset", simpleDataset); - piePlot.project("value", "value"); + piePlot = new Plottable.Plots.Pie(); + piePlot.addDataset(simpleDataset); + piePlot.sectorValue((d) => d.value); piePlot.renderTo(svg); renderArea = ( piePlot)._renderArea; }); @@ -38,7 +38,7 @@ describe("Plots", () => { var arcPaths = renderArea.selectAll(".arc"); assert.lengthOf(arcPaths[0], 2, "only has two sectors"); var arcPath0 = d3.select(arcPaths[0][0]); - var pathPoints0 = normalizePath(arcPath0.attr("d")).split(/[A-Z]/).slice(1, 4); + var pathPoints0 = TestMethods.normalizePath(arcPath0.attr("d")).split(/[A-Z]/).slice(1, 4); var firstPathPoints0 = pathPoints0[0].split(","); assert.closeTo(parseFloat(firstPathPoints0[0]), 0, 1, "draws line vertically at beginning"); @@ -53,7 +53,7 @@ describe("Plots", () => { assert.closeTo(parseFloat(secondPathPoints0[1]), 0, 1, "draws line to origin"); var arcPath1 = d3.select(arcPaths[0][1]); - var pathPoints1 = normalizePath(arcPath1.attr("d")).split(/[A-Z]/).slice(1, 4); + var pathPoints1 = TestMethods.normalizePath(arcPath1.attr("d")).split(/[A-Z]/).slice(1, 4); var firstPathPoints1 = pathPoints1[0].split(","); assert.operator(parseFloat(firstPathPoints1[0]), ">", 0, "draws line to the right"); @@ -70,12 +70,12 @@ describe("Plots", () => { }); it("project value onto different attribute", () => { - piePlot.project("value", "value2"); + piePlot.sectorValue((d) => d.value2); var arcPaths = renderArea.selectAll(".arc"); assert.lengthOf(arcPaths[0], 2, "only has two sectors"); var arcPath0 = d3.select(arcPaths[0][0]); - var pathPoints0 = normalizePath(arcPath0.attr("d")).split(/[A-Z]/).slice(1, 4); + var pathPoints0 = TestMethods.normalizePath(arcPath0.attr("d")).split(/[A-Z]/).slice(1, 4); var firstPathPoints0 = pathPoints0[0].split(","); assert.closeTo(parseFloat(firstPathPoints0[0]), 0, 1, "draws line vertically at beginning"); @@ -86,7 +86,7 @@ describe("Plots", () => { assert.operator(parseFloat(arcDestPoint0[1]), ">", 0, "ends below the center"); var arcPath1 = d3.select(arcPaths[0][1]); - var pathPoints1 = normalizePath(arcPath1.attr("d")).split(/[A-Z]/).slice(1, 4); + var pathPoints1 = TestMethods.normalizePath(arcPath1.attr("d")).split(/[A-Z]/).slice(1, 4); var firstPathPoints1 = pathPoints1[0].split(","); assert.closeTo(parseFloat(firstPathPoints1[0]), 0, 1, "draws line vertically at beginning"); @@ -96,16 +96,16 @@ describe("Plots", () => { assert.closeTo(parseFloat(arcDestPoint1[0]), 0, 1, "ends on a line vertically from beginning"); assert.operator(parseFloat(arcDestPoint1[1]), "<", 0, "ends above the center"); - piePlot.project("value", "value"); + piePlot.sectorValue((d) => d.value); svg.remove(); }); it("innerRadius project", () => { - piePlot.project("inner-radius", () => 5); + piePlot.innerRadius(5); var arcPaths = renderArea.selectAll(".arc"); assert.lengthOf(arcPaths[0], 2, "only has two sectors"); - var pathPoints0 = normalizePath(d3.select(arcPaths[0][0]).attr("d")).split(/[A-Z]/).slice(1, 5); + var pathPoints0 = TestMethods.normalizePath(d3.select(arcPaths[0][0]).attr("d")).split(/[A-Z]/).slice(1, 5); var radiusPath0 = pathPoints0[2].split(",").map((coordinate: string) => parseFloat(coordinate)); assert.closeTo(radiusPath0[0], 5, 1, "stops line at innerRadius point"); @@ -117,16 +117,16 @@ describe("Plots", () => { assert.closeTo(innerArcPath0[5], 0, 1, "make inner arc to center"); assert.closeTo(innerArcPath0[6], -5, 1, "makes inner arc to top of inner circle"); - piePlot.project("inner-radius", () => 0); + piePlot.innerRadius(0); svg.remove(); }); it("outerRadius project", () => { - piePlot.project("outer-radius", () => 150); + piePlot.outerRadius(() => 150); var arcPaths = renderArea.selectAll(".arc"); assert.lengthOf(arcPaths[0], 2, "only has two sectors"); - var pathPoints0 = normalizePath(d3.select(arcPaths[0][0]).attr("d")).split(/[A-Z]/).slice(1, 5); + var pathPoints0 = TestMethods.normalizePath(d3.select(arcPaths[0][0]).attr("d")).split(/[A-Z]/).slice(1, 5); var radiusPath0 = pathPoints0[0].split(",").map((coordinate: string) => parseFloat(coordinate)); assert.closeTo(radiusPath0[0], 0, 1, "starts at outerRadius point"); @@ -138,31 +138,20 @@ describe("Plots", () => { assert.closeTo(outerArcPath0[5], 150, 1, "makes outer arc to right edge"); assert.closeTo(outerArcPath0[6], 0, 1, "makes outer arc to right edge"); - piePlot.project("outer-radius", () => 250); + piePlot.outerRadius(() => 250); svg.remove(); }); describe("getAllSelections", () => { - it("retrieves all dataset selections with no args", () => { var allSectors = piePlot.getAllSelections(); - var allSectors2 = piePlot.getAllSelections(( piePlot)._datasetKeysInOrder); - assert.deepEqual(allSectors, allSectors2, "all sectors retrieved"); - - svg.remove(); - }); - - it("retrieves correct selections (array arg)", () => { - var allSectors = piePlot.getAllSelections(["simpleDataset"]); assert.strictEqual(allSectors.size(), 2, "all sectors retrieved"); - var selectionData = allSectors.data(); - assert.includeMembers(selectionData.map((datum) => datum.data), simpleData, "dataset data in selection data"); svg.remove(); }); - it("retrieves correct selections (string arg)", () => { - var allSectors = piePlot.getAllSelections("simpleDataset"); + it("retrieves correct selections", () => { + var allSectors = piePlot.getAllSelections([simpleDataset]); assert.strictEqual(allSectors.size(), 2, "all sectors retrieved"); var selectionData = allSectors.data(); assert.includeMembers(selectionData.map((datum) => datum.data), simpleData, "dataset data in selection data"); @@ -170,9 +159,9 @@ describe("Plots", () => { svg.remove(); }); - it("skips invalid keys", () => { - var allSectors = piePlot.getAllSelections(["whoo"]); - assert.strictEqual(allSectors.size(), 0, "all sectors retrieved"); + it("skips invalid Datsets", () => { + var allSectors = piePlot.getAllSelections([new Plottable.Dataset([])]); + assert.strictEqual(allSectors.size(), 0, "no sectors retrieved"); svg.remove(); }); @@ -193,7 +182,7 @@ describe("Plots", () => { }); it("project fill", () => { - piePlot.project("fill", (d: any, i: number) => String(i), new Plottable.Scale.Color("10")); + piePlot.attr("fill", (d: any, i: number) => String(i), new Plottable.Scales.Color("10")); var arcPaths = renderArea.selectAll(".arc"); @@ -203,7 +192,7 @@ describe("Plots", () => { var arcPath1 = d3.select(arcPaths[0][1]); assert.strictEqual(arcPath1.attr("fill"), "#ff7f0e", "second sector filled appropriately"); - piePlot.project("fill", "type", new Plottable.Scale.Color("20")); + piePlot.attr("fill", (d) => d.type, new Plottable.Scales.Color("20")); arcPaths = renderArea.selectAll(".arc"); @@ -219,20 +208,20 @@ describe("Plots", () => { it("throws warnings on negative data", () => { var message: String; - var oldWarn = Plottable._Util.Methods.warn; - Plottable._Util.Methods.warn = (warn) => message = warn; - piePlot.removeDataset("simpleDataset"); + var oldWarn = Plottable.Utils.Methods.warn; + Plottable.Utils.Methods.warn = (warn) => message = warn; + piePlot.removeDataset(simpleDataset); var negativeDataset = new Plottable.Dataset([{value: -5}, {value: 15}]); - piePlot.addDataset("negativeDataset", negativeDataset); - assert.equal(message, "Negative values will not render correctly in a pie chart."); - Plottable._Util.Methods.warn = oldWarn; + piePlot.addDataset(negativeDataset); + assert.strictEqual(message, "Negative values will not render correctly in a pie chart."); + Plottable.Utils.Methods.warn = oldWarn; svg.remove(); }); }); describe("fail safe tests", () => { it("undefined, NaN and non-numeric strings not be represented in a Pie Chart", () => { - var svg = generateSVG(); + var svg = TestMethods.generateSVG(); var data1 = [ { v: 1 }, @@ -244,9 +233,9 @@ describe("Plots", () => { { v: 1 }, ]; - var plot = new Plottable.Plot.Pie(); - plot.addDataset(data1); - plot.project("value", "v"); + var plot = new Plottable.Plots.Pie(); + plot.addDataset(new Plottable.Dataset(data1)); + plot.sectorValue((d) => d.v); plot.renderTo(svg); @@ -260,7 +249,7 @@ describe("Plots", () => { }); it("nulls and 0s should be represented in a Pie Chart as DOM elements, but have radius 0", () => { - var svg = generateSVG(); + var svg = TestMethods.generateSVG(); var data1 = [ { v: 1 }, @@ -269,9 +258,9 @@ describe("Plots", () => { { v: 1 }, ]; - var plot = new Plottable.Plot.Pie(); - plot.addDataset(data1); - plot.project("value", "v"); + var plot = new Plottable.Plots.Pie(); + plot.addDataset(new Plottable.Dataset(data1)); + plot.sectorValue((d) => d.v); plot.renderTo(svg); diff --git a/test/components/plots/plotTests.ts b/test/components/plots/plotTests.ts index b10a7025bc..8eb28042a2 100644 --- a/test/components/plots/plotTests.ts +++ b/test/components/plots/plotTests.ts @@ -1,28 +1,30 @@ /// var assert = chai.assert; -class CountingPlot extends Plottable.Plot.AbstractPlot { +class CountingPlot extends Plottable.Plot { public renders: number = 0; - public _render() { + public render() { ++this.renders; - return super._render(); + return super.render(); } } describe("Plots", () => { - describe("Abstract Plot", () => { - + describe("Plot", () => { it("Plots default correctly", () => { - var r = new Plottable.Plot.AbstractPlot(); - assert.isTrue(r.clipPathEnabled, "clipPathEnabled defaults to true"); + var svg = TestMethods.generateSVG(400, 300); + var r = new Plottable.Plot(); + r.renderTo(svg); + TestMethods.verifyClipPath(r); + svg.remove(); }); it("Base Plot functionality works", () => { - var svg = generateSVG(400, 300); - var r = new Plottable.Plot.AbstractPlot(); - r._anchor(svg); - r._computeLayout(); + var svg = TestMethods.generateSVG(400, 300); + var r = new Plottable.Plot(); + r.anchor(svg); + r.computeLayout(); var renderArea = ( r)._content.select(".render-area"); assert.isNotNull(renderArea.node(), "there is a render-area"); svg.remove(); @@ -32,106 +34,116 @@ describe("Plots", () => { var dFoo = new Plottable.Dataset(["foo"], {cssClass: "bar"}); var dBar = new Plottable.Dataset(["bar"], {cssClass: "boo"}); var r = new CountingPlot(); - r.addDataset("foo", dFoo); + r.addDataset(dFoo); - assert.equal(1, r.renders, "initial render due to addDataset"); + assert.strictEqual(1, r.renders, "initial render due to addDataset"); - dFoo.broadcaster.broadcast(); - assert.equal(2, r.renders, "we re-render when our dataset changes"); + dFoo.data(dFoo.data()); + assert.strictEqual(2, r.renders, "we re-render when our dataset changes"); + r.addDataset(dBar); + assert.strictEqual(3, r.renders, "we should redraw when we add a dataset"); - r.addDataset("bar", dBar); - assert.equal(3, r.renders, "we should redraw when we add a dataset"); + dFoo.data(dFoo.data()); + assert.strictEqual(4, r.renders, "we should still listen to the first dataset"); - dFoo.broadcaster.broadcast(); - assert.equal(4, r.renders, "we should still listen to the first dataset"); + dFoo.data(dFoo.data()); + assert.strictEqual(5, r.renders, "we should listen to the new dataset"); + + r.removeDataset(dFoo); + assert.strictEqual(6, r.renders, "we re-render on dataset removal"); + dFoo.data(dFoo.data()); + assert.strictEqual(6, r.renders, "we don't listen to removed datasets"); + }); - dBar.broadcaster.broadcast(); - assert.equal(5, r.renders, "we should listen to the new dataset"); + it("datasets()", () => { + var dataset1 = new Plottable.Dataset([]); + var dataset2 = new Plottable.Dataset([]); - r.removeDataset("foo"); - assert.equal(6, r.renders, "we re-render on dataset removal"); - dFoo.broadcaster.broadcast(); - assert.equal(6, r.renders, "we don't listen to removed datasets"); + var plot = new Plottable.Plot(); + plot.addDataset(dataset1); + plot.addDataset(dataset2); + assert.deepEqual(plot.datasets(), [dataset1, dataset2], "retrieved Datasets in order they were added"); + plot.datasets([dataset2, dataset1]); + assert.deepEqual(plot.datasets(), [dataset2, dataset1], "order of Datasets was changed"); + + var dataset3 = new Plottable.Dataset([]); + plot.addDataset(dataset3); + assert.deepEqual(plot.datasets(), [dataset2, dataset1, dataset3], "adding further Datasets respects the order"); + + plot.removeDataset(dataset1); + assert.deepEqual(plot.datasets(), [dataset2, dataset3], "removing a Dataset leaves the remainder in the same order"); }); it("Updates its projectors when the Dataset is changed", () => { var d1 = new Plottable.Dataset([{x: 5, y: 6}], {cssClass: "bar"}); - var r = new Plottable.Plot.AbstractPlot(); - r.addDataset("d1", d1); + var r = new Plottable.Plot(); + r.addDataset(d1); var xScaleCalls: number = 0; var yScaleCalls: number = 0; - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); var metadataProjector = (d: any, i: number, m: any) => m.cssClass; - r.project("x", "x", xScale); - r.project("y", "y", yScale); - r.project("meta", metadataProjector); - xScale.broadcaster.registerListener("unitTest", (listenable: Plottable.Scale.Linear) => { - assert.equal(listenable, xScale, "Callback received the calling scale as the first argument"); + r.attr("x", (d) => d.x, xScale); + r.attr("y", (d) => d.y, yScale); + r.attr("meta", metadataProjector); + xScale.onUpdate((listenable: Plottable.Scales.Linear) => { + assert.strictEqual(listenable, xScale, "Callback received the calling scale as the first argument"); ++xScaleCalls; }); - yScale.broadcaster.registerListener("unitTest", (listenable: Plottable.Scale.Linear) => { - assert.equal(listenable, yScale, "Callback received the calling scale as the first argument"); + yScale.onUpdate((listenable: Plottable.Scales.Linear) => { + assert.strictEqual(listenable, yScale, "Callback received the calling scale as the first argument"); ++yScaleCalls; }); - assert.equal(0, xScaleCalls, "initially hasn't made any X callbacks"); - assert.equal(0, yScaleCalls, "initially hasn't made any Y callbacks"); + assert.strictEqual(0, xScaleCalls, "initially hasn't made any X callbacks"); + assert.strictEqual(0, yScaleCalls, "initially hasn't made any Y callbacks"); - d1.broadcaster.broadcast(); - assert.equal(1, xScaleCalls, "X scale was wired up to datasource correctly"); - assert.equal(1, yScaleCalls, "Y scale was wired up to datasource correctly"); + d1.data(d1.data()); + assert.strictEqual(1, xScaleCalls, "X scale was wired up to datasource correctly"); + assert.strictEqual(1, yScaleCalls, "Y scale was wired up to datasource correctly"); var d2 = new Plottable.Dataset([{x: 7, y: 8}], {cssClass: "boo"}); - r.removeDataset("d1"); + r.removeDataset(d1); r.addDataset(d2); - assert.equal(3, xScaleCalls, "Changing datasource fires X scale listeners (but doesn't coalesce callbacks)"); - assert.equal(3, yScaleCalls, "Changing datasource fires Y scale listeners (but doesn't coalesce callbacks)"); - - d1.broadcaster.broadcast(); - assert.equal(3, xScaleCalls, "X scale was unhooked from old datasource"); - assert.equal(3, yScaleCalls, "Y scale was unhooked from old datasource"); + assert.strictEqual(3, xScaleCalls, "Changing datasource fires X scale listeners (but doesn't coalesce callbacks)"); + assert.strictEqual(3, yScaleCalls, "Changing datasource fires Y scale listeners (but doesn't coalesce callbacks)"); - d2.broadcaster.broadcast(); - assert.equal(4, xScaleCalls, "X scale was hooked into new datasource"); - assert.equal(4, yScaleCalls, "Y scale was hooked into new datasource"); + d1.data(d1.data()); + assert.strictEqual(3, xScaleCalls, "X scale was unhooked from old datasource"); + assert.strictEqual(3, yScaleCalls, "Y scale was unhooked from old datasource"); - }); + d2.data(d2.data()); + assert.strictEqual(4, xScaleCalls, "X scale was hooked into new datasource"); + assert.strictEqual(4, yScaleCalls, "Y scale was hooked into new datasource"); - it("Plot automatically generates a Dataset if only data is provided", () => { - var data = ["foo", "bar"]; - var r = new Plottable.Plot.AbstractPlot().addDataset("foo", data); - var dataset = r.datasets()[0]; - assert.isNotNull(dataset, "A Dataset was automatically generated"); - assert.deepEqual(dataset.data(), data, "The generated Dataset has the correct data"); }); it("Plot.project works as intended", () => { - var r = new Plottable.Plot.AbstractPlot(); - var s = new Plottable.Scale.Linear().domain([0, 1]).range([0, 10]); - r.project("attr", "a", s); + var r = new Plottable.Plot(); + var s = new Plottable.Scales.Linear().domain([0, 1]).range([0, 10]); + r.attr("attr", (d) => d.a, s); var attrToProjector = ( r)._generateAttrToProjector(); var projector = attrToProjector["attr"]; - assert.equal(projector({"a": 0.5}, 0, null, null), 5, "projector works as intended"); + assert.strictEqual(projector({"a": 0.5}, 0, null, null), 5, "projector works as intended"); }); it("Changing Plot.dataset().data to [] causes scale to contract", () => { var ds1 = new Plottable.Dataset([0, 1, 2]); var ds2 = new Plottable.Dataset([1, 2, 3]); - var s = new Plottable.Scale.Linear(); - var svg1 = generateSVG(100, 100); - var svg2 = generateSVG(100, 100); - var r1 = new Plottable.Plot.AbstractPlot() - .addDataset(ds1) - .project("x", (x: number) => x, s) - .renderTo(svg1); - var r2 = new Plottable.Plot.AbstractPlot() - .addDataset(ds2) - .project("x", (x: number) => x, s) - .renderTo(svg2); + var s = new Plottable.Scales.Linear(); + var svg1 = TestMethods.generateSVG(100, 100); + var svg2 = TestMethods.generateSVG(100, 100); + new Plottable.Plot() + .addDataset(ds1) + .attr("x", (x: number) => x, s) + .renderTo(svg1); + new Plottable.Plot() + .addDataset(ds2) + .attr("x", (x: number) => x, s) + .renderTo(svg2); assert.deepEqual(s.domain(), [0, 3], "Simple domain combining"); ds1.data([]); assert.deepEqual(s.domain(), [1, 3], "Contracting domain due to projection becoming empty"); @@ -140,11 +152,12 @@ describe("Plots", () => { }); it("getAllSelections() with dataset retrieval", () => { - var svg = generateSVG(400, 400); - var plot = new Plottable.Plot.AbstractPlot(); + var svg = TestMethods.generateSVG(400, 400); + var plot = new Plottable.Plot(); // Create mock drawers with already drawn items - var mockDrawer1 = new Plottable._Drawer.AbstractDrawer("ds1"); + // HACKHACK #1984: Dataset keys are being removed, so this is the internal key + var mockDrawer1 = new Plottable.Drawers.AbstractDrawer("_0"); var renderArea1 = svg.append("g"); renderArea1.append("circle").attr("cx", 100).attr("cy", 100).attr("r", 10); ( mockDrawer1).setup = () => ( mockDrawer1)._renderArea = renderArea1; @@ -152,43 +165,46 @@ describe("Plots", () => { var renderArea2 = svg.append("g"); renderArea2.append("circle").attr("cx", 10).attr("cy", 10).attr("r", 10); - var mockDrawer2 = new Plottable._Drawer.AbstractDrawer("ds2"); + // HACKHACK #1984: Dataset keys are being removed, so this is the internal key + var mockDrawer2 = new Plottable.Drawers.AbstractDrawer("_1"); ( mockDrawer2).setup = () => ( mockDrawer2)._renderArea = renderArea2; ( mockDrawer2)._getSelector = () => "circle"; // Mock _getDrawer to return the mock drawers ( plot)._getDrawer = (key: string) => { - if (key === "ds1") { + if (key === "_0") { return mockDrawer1; } else { return mockDrawer2; } }; - plot.addDataset("ds1", [{value: 0}, {value: 1}, {value: 2}]); - plot.addDataset("ds2", [{value: 1}, {value: 2}, {value: 3}]); + var dataset1 = new Plottable.Dataset([{value: 0}, {value: 1}, {value: 2}]); + plot.addDataset(dataset1); + var dataset2 = new Plottable.Dataset([{value: 1}, {value: 2}, {value: 3}]); + plot.addDataset(dataset2); plot.renderTo(svg); var selections = plot.getAllSelections(); assert.strictEqual(selections.size(), 2, "all circle selections gotten"); - var oneSelection = plot.getAllSelections("ds1"); + var oneSelection = plot.getAllSelections([dataset1]); assert.strictEqual(oneSelection.size(), 1); - assert.strictEqual(numAttr(oneSelection, "cx"), 100, "retrieved selection in renderArea1"); + assert.strictEqual(TestMethods.numAttr(oneSelection, "cx"), 100, "retrieved selection in renderArea1"); - var oneElementSelection = plot.getAllSelections(["ds2"]); + var oneElementSelection = plot.getAllSelections([dataset2]); assert.strictEqual(oneElementSelection.size(), 1); - assert.strictEqual(numAttr(oneElementSelection, "cy"), 10, "retreived selection in renderArea2"); + assert.strictEqual(TestMethods.numAttr(oneElementSelection, "cy"), 10, "retreived selection in renderArea2"); - var nonExcludedSelection = plot.getAllSelections(["ds1"], true); + var nonExcludedSelection = plot.getAllSelections([dataset1], true); assert.strictEqual(nonExcludedSelection.size(), 1); - assert.strictEqual(numAttr(nonExcludedSelection, "cy"), 10, "retreived non-excluded selection in renderArea2"); + assert.strictEqual(TestMethods.numAttr(nonExcludedSelection, "cy"), 10, "retreived non-excluded selection in renderArea2"); svg.remove(); }); it("getAllPlotData() with dataset retrieval", () => { - var svg = generateSVG(400, 400); - var plot = new Plottable.Plot.AbstractPlot(); + var svg = TestMethods.generateSVG(400, 400); + var plot = new Plottable.Plot(); var data1 = [{value: 0}, {value: 1}, {value: 2}]; var data2 = [{value: 0}, {value: 1}, {value: 2}]; @@ -200,7 +216,8 @@ describe("Plots", () => { var data2PointConverter = (datum: any, index: number) => data2Points[index]; // Create mock drawers with already drawn items - var mockDrawer1 = new Plottable._Drawer.AbstractDrawer("ds1"); + // HACKHACK #1984: Dataset keys are being removed, so this is the internal key + var mockDrawer1 = new Plottable.Drawers.AbstractDrawer("_0"); var renderArea1 = svg.append("g"); renderArea1.append("circle").attr("cx", 100).attr("cy", 100).attr("r", 10); ( mockDrawer1).setup = () => ( mockDrawer1)._renderArea = renderArea1; @@ -209,22 +226,25 @@ describe("Plots", () => { var renderArea2 = svg.append("g"); renderArea2.append("circle").attr("cx", 10).attr("cy", 10).attr("r", 10); - var mockDrawer2 = new Plottable._Drawer.AbstractDrawer("ds2"); + // HACKHACK #1984: Dataset keys are being removed, so this is the internal key + var mockDrawer2 = new Plottable.Drawers.AbstractDrawer("_1"); ( mockDrawer2).setup = () => ( mockDrawer2)._renderArea = renderArea2; ( mockDrawer2)._getSelector = () => "circle"; ( mockDrawer2)._getPixelPoint = data2PointConverter; // Mock _getDrawer to return the mock drawers ( plot)._getDrawer = (key: string) => { - if (key === "ds1") { + if (key === "_0") { return mockDrawer1; } else { return mockDrawer2; } }; - plot.addDataset("ds1", data1); - plot.addDataset("ds2", data2); + var dataset1 = new Plottable.Dataset(data1); + plot.addDataset(dataset1); + var dataset2 = new Plottable.Dataset(data2); + plot.addDataset(dataset2); plot.renderTo(svg); var allPlotData = plot.getAllPlotData(); @@ -234,25 +254,25 @@ describe("Plots", () => { assert.includeMembers(allPlotData.pixelPoints, data1.map(data1PointConverter), "includes data1 points"); assert.includeMembers(allPlotData.pixelPoints, data2.map(data2PointConverter), "includes data2 points"); - var singlePlotData = plot.getAllPlotData("ds1"); + var singlePlotData = plot.getAllPlotData([dataset1]); var oneSelection = singlePlotData.selection; assert.strictEqual(oneSelection.size(), 1); - assert.strictEqual(numAttr(oneSelection, "cx"), 100, "retrieved selection in renderArea1"); + assert.strictEqual(TestMethods.numAttr(oneSelection, "cx"), 100, "retrieved selection in renderArea1"); assert.includeMembers(singlePlotData.data, data1, "includes data1 members"); assert.includeMembers(singlePlotData.pixelPoints, data1.map(data1PointConverter), "includes data1 points"); - var oneElementPlotData = plot.getAllPlotData(["ds2"]); + var oneElementPlotData = plot.getAllPlotData([dataset2]); var oneElementSelection = oneElementPlotData.selection; assert.strictEqual(oneElementSelection.size(), 1); - assert.strictEqual(numAttr(oneElementSelection, "cy"), 10, "retreieved selection in renderArea2"); + assert.strictEqual(TestMethods.numAttr(oneElementSelection, "cy"), 10, "retreieved selection in renderArea2"); assert.includeMembers(oneElementPlotData.data, data2, "includes data2 members"); assert.includeMembers(oneElementPlotData.pixelPoints, data2.map(data2PointConverter), "includes data2 points"); svg.remove(); }); it("getAllPlotData() with NaN pixel points", () => { - var svg = generateSVG(400, 400); - var plot = new Plottable.Plot.AbstractPlot(); + var svg = TestMethods.generateSVG(400, 400); + var plot = new Plottable.Plot(); var data = [{value: NaN}, {value: 1}, {value: 2}]; @@ -261,7 +281,7 @@ describe("Plots", () => { var dataPointConverter = (datum: any, index: number) => dataPoints[index]; // Create mock drawer with already drawn items - var mockDrawer = new Plottable._Drawer.AbstractDrawer("ds"); + var mockDrawer = new Plottable.Drawers.AbstractDrawer("ds"); var renderArea = svg.append("g"); var circles = renderArea.selectAll("circles").data(data); circles.enter().append("circle").attr("cx", 100).attr("cy", 100).attr("r", 10); @@ -273,7 +293,8 @@ describe("Plots", () => { // Mock _getDrawer to return the mock drawer ( plot)._getDrawer = () => mockDrawer; - plot.addDataset("ds", data); + var dataset = new Plottable.Dataset(data); + plot.addDataset(dataset); plot.renderTo(svg); var oneElementPlotData = plot.getAllPlotData(); @@ -290,8 +311,8 @@ describe("Plots", () => { }); it("getClosestPlotData", () => { - var svg = generateSVG(400, 400); - var plot = new Plottable.Plot.AbstractPlot(); + var svg = TestMethods.generateSVG(400, 400); + var plot = new Plottable.Plot(); var data1 = [{value: 0}, {value: 1}, {value: 2}]; var data2 = [{value: 0}, {value: 1}, {value: 2}]; @@ -303,7 +324,7 @@ describe("Plots", () => { var data2PointConverter = (datum: any, index: number) => data2Points[index]; // Create mock drawers with already drawn items - var mockDrawer1 = new Plottable._Drawer.AbstractDrawer("ds1"); + var mockDrawer1 = new Plottable.Drawers.AbstractDrawer("ds1"); var renderArea1 = svg.append("g"); renderArea1.append("circle").attr("cx", 100).attr("cy", 100).attr("r", 10); ( mockDrawer1).setup = () => ( mockDrawer1)._renderArea = renderArea1; @@ -312,7 +333,7 @@ describe("Plots", () => { var renderArea2 = svg.append("g"); renderArea2.append("circle").attr("cx", 10).attr("cy", 10).attr("r", 10); - var mockDrawer2 = new Plottable._Drawer.AbstractDrawer("ds2"); + var mockDrawer2 = new Plottable.Drawers.AbstractDrawer("ds2"); ( mockDrawer2).setup = () => ( mockDrawer2)._renderArea = renderArea2; ( mockDrawer2)._getSelector = () => "circle"; ( mockDrawer2)._getPixelPoint = data2PointConverter; @@ -326,8 +347,8 @@ describe("Plots", () => { } }; - plot.addDataset("ds1", data1); - plot.addDataset("ds2", data2); + plot.addDataset(new Plottable.Dataset(data1)); + plot.addDataset(new Plottable.Dataset(data2)); plot.renderTo(svg); var queryPoint = {x: 1, y: 11}; @@ -338,96 +359,61 @@ describe("Plots", () => { }); describe("Dataset removal", () => { - var plot: Plottable.Plot.AbstractPlot; + var plot: Plottable.Plot; var d1: Plottable.Dataset; var d2: Plottable.Dataset; beforeEach(() => { - plot = new Plottable.Plot.AbstractPlot(); + plot = new Plottable.Plot(); d1 = new Plottable.Dataset(); d2 = new Plottable.Dataset(); - plot.addDataset("foo", d1); - plot.addDataset("bar", d2); + plot.addDataset(d1); + plot.addDataset(d2); assert.deepEqual(plot.datasets(), [d1, d2], "datasets as expected"); }); - it("removeDataset can work on keys", () => { - plot.removeDataset("bar"); - assert.deepEqual(plot.datasets(), [d1], "second dataset removed"); - plot.removeDataset("foo"); - assert.deepEqual(plot.datasets(), [], "all datasets removed"); - }); - - it("removeDataset can work on datasets", () => { + it("removeDataset()", () => { plot.removeDataset(d2); assert.deepEqual(plot.datasets(), [d1], "second dataset removed"); plot.removeDataset(d1); assert.deepEqual(plot.datasets(), [], "all datasets removed"); }); - it("removeDataset ignores inputs that do not correspond to a dataset", () => { + it("removeDataset ignores Datasets not in the Plot", () => { var d3 = new Plottable.Dataset(); plot.removeDataset(d3); - plot.removeDataset("bad key"); assert.deepEqual(plot.datasets(), [d1, d2], "datasets as expected"); }); - - it("removeDataset functions on inputs that are data arrays, not datasets", () => { - var a1 = ["foo", "bar"]; - var a2 = [1, 2, 3]; - plot.addDataset(a1); - plot.addDataset(a2); - assert.lengthOf(plot.datasets(), 4, "there are four datasets"); - assert.equal(plot.datasets()[3].data(), a2, "second array dataset correct"); - assert.equal(plot.datasets()[2].data(), a1, "first array dataset correct"); - plot.removeDataset(a2); - plot.removeDataset(a1); - assert.deepEqual(plot.datasets(), [d1, d2], "datasets as expected"); - }); - - it("removeDataset behaves appropriately when the key 'undefined' is used", () => { - var a = [1, 2, 3]; - plot.addDataset("undefined", a); - assert.lengthOf(plot.datasets(), 3, "there are three datasets initially"); - plot.removeDataset("foofoofoofoofoofoofoofoo"); - assert.lengthOf(plot.datasets(), 3, "there are three datasets after bad key removal"); - plot.removeDataset(undefined); - assert.lengthOf(plot.datasets(), 3, "there are three datasets after removing `undefined`"); - plot.removeDataset([94, 93, 92]); - assert.lengthOf(plot.datasets(), 3, "there are three datasets after removing random dataset"); - plot.removeDataset("undefined"); - assert.lengthOf(plot.datasets(), 2, "the dataset called 'undefined' could be removed"); - }); }); - it("remove() disconnects plots from its scales", () => { - var r = new Plottable.Plot.AbstractPlot(); - var s = new Plottable.Scale.Linear(); - r.project("attr", "a", s); - r.remove(); - var key2callback = ( s).broadcaster._key2callback; - assert.isUndefined(key2callback.get(r), "the plot is no longer attached to the scale"); + it("destroy() disconnects plots from its scales", () => { + var plot2 = new Plottable.Plot(); + var scale = new Plottable.Scales.Linear(); + plot2.attr("attr", (d) => d.a, scale); + plot2.destroy(); + var scaleCallbacks = ( scale)._callbacks.values(); + assert.strictEqual(scaleCallbacks.length, 0, "the plot is no longer attached to the scale"); }); it("extent registration works as intended", () => { - var scale1 = new Plottable.Scale.Linear(); - var scale2 = new Plottable.Scale.Linear(); + var scale1 = new Plottable.Scales.Linear(); + var scale2 = new Plottable.Scales.Linear(); var d1 = new Plottable.Dataset([1, 2, 3]); var d2 = new Plottable.Dataset([4, 99, 999]); var d3 = new Plottable.Dataset([-1, -2, -3]); var id = (d: number) => d; - var plot1 = new Plottable.Plot.AbstractPlot(); - var plot2 = new Plottable.Plot.AbstractPlot(); - var svg = generateSVG(400, 400); + var plot1 = new Plottable.Plot(); + var plot2 = new Plottable.Plot(); + var svg = TestMethods.generateSVG(400, 400); plot1.attr("null", id, scale1); plot2.attr("null", id, scale1); plot1.renderTo(svg); plot2.renderTo(svg); function assertDomainIsClose(actualDomain: number[], expectedDomain: number[], msg: string) { - // to avoid floating point issues :/ + // to avoid floating point issues:/ assert.closeTo(actualDomain[0], expectedDomain[0], 0.01, msg); assert.closeTo(actualDomain[1], expectedDomain[1], 0.01, msg); } @@ -451,158 +437,39 @@ describe("Plots", () => { }); it("additionalPaint timing works properly", () => { - var animator = new Plottable.Animator.Base().delay(10).duration(10).maxIterativeDelay(0); - var x = new Plottable.Scale.Linear(); - var y = new Plottable.Scale.Linear(); - var plot = new Plottable.Plot.Bar(x, y).addDataset([]).animate(true); + var animator = new Plottable.Animators.Base().delay(10).duration(10).maxIterativeDelay(0); + var x = new Plottable.Scales.Linear(); + var y = new Plottable.Scales.Linear(); + var plot = new Plottable.Plots.Bar(x, y); + plot.addDataset(new Plottable.Dataset([])).animate(true); var recordedTime: number = -1; var additionalPaint = (x: number) => { recordedTime = Math.max(x, recordedTime); }; ( plot)._additionalPaint = additionalPaint; plot.animator("bars", animator); - var svg = generateSVG(); - plot.project("x", "x", x); - plot.project("y", "y", y); + var svg = TestMethods.generateSVG(); + plot.x((d) => d.x, x); + plot.y((d) => d.y, y); plot.renderTo(svg); + assert.strictEqual(recordedTime, 20, "additionalPaint passed appropriate time argument"); svg.remove(); - assert.equal(recordedTime, 20, "additionalPaint passed appropriate time argument"); }); it("extent calculation done in correct dataset order", () => { - var animator = new Plottable.Animator.Base().delay(10).duration(10).maxIterativeDelay(0); - var CategoryScale = new Plottable.Scale.Category(); - var dataset1 = [{key: "A"}]; - var dataset2 = [{key: "B"}]; - var plot = new Plottable.Plot.AbstractPlot() - .addDataset("b", dataset2) - .addDataset("a", dataset1); - plot.project("key", "key", CategoryScale); - - plot.datasetOrder(["a", "b"]); - - var svg = generateSVG(); + var categoryScale = new Plottable.Scales.Category(); + var dataset1 = new Plottable.Dataset([{key: "A"}]); + var dataset2 = new Plottable.Dataset([{key: "B"}]); + var plot = new Plottable.Plot(); + plot.addDataset(dataset2); + plot.addDataset(dataset1); + plot.attr("key", (d) => d.key, categoryScale); + + var svg = TestMethods.generateSVG(); plot.renderTo(svg); - assert.deepEqual(CategoryScale.domain(), ["A", "B"], "extent is in the right order"); - svg.remove(); - }); - }); - - describe("Abstract XY Plot", () => { - var svg: D3.Selection; - var xScale: Plottable.Scale.Linear; - var yScale: Plottable.Scale.Linear; - var xAccessor: any; - var yAccessor: any; - var simpleDataset: Plottable.Dataset; - var plot: Plottable.Plot.AbstractXYPlot; - - before(() => { - xAccessor = (d: any, i: number, u: any) => d.a + u.foo; - yAccessor = (d: any, i: number, u: any) => d.b + u.foo; - }); - - beforeEach(() => { - svg = generateSVG(500, 500); - simpleDataset = new Plottable.Dataset([{a: -5, b: 6}, {a: -2, b: 2}, {a: 2, b: -2}, {a: 5, b: -6}], {foo: 0}); - xScale = new Plottable.Scale.Linear(); - yScale = new Plottable.Scale.Linear(); - plot = new Plottable.Plot.AbstractXYPlot(xScale, yScale); - plot.addDataset(simpleDataset) - .project("x", xAccessor, xScale) - .project("y", yAccessor, yScale) - .renderTo(svg); - }); - - it("plot auto domain scale to visible points", () => { - xScale.domain([-3, 3]); - assert.deepEqual(yScale.domain(), [-7, 7], "domain has not been adjusted to visible points"); - plot.automaticallyAdjustYScaleOverVisiblePoints(true); - assert.deepEqual(yScale.domain(), [-2.5, 2.5], "domain has been adjusted to visible points"); - plot.automaticallyAdjustYScaleOverVisiblePoints(false); - plot.automaticallyAdjustXScaleOverVisiblePoints(true); - yScale.domain([-6, 6]); - assert.deepEqual(xScale.domain(), [-6, 6], "domain has been adjusted to visible points"); + assert.deepEqual(categoryScale.domain(), ["B", "A"], "extent is in the right order"); svg.remove(); }); - - it("no visible points", () => { - plot.automaticallyAdjustYScaleOverVisiblePoints(true); - xScale.domain([-0.5, 0.5]); - assert.deepEqual(yScale.domain(), [-7, 7], "domain has been not been adjusted"); - svg.remove(); - }); - - it("automaticallyAdjustYScaleOverVisiblePoints disables autoDomain", () => { - xScale.domain([-2, 2]); - plot.automaticallyAdjustYScaleOverVisiblePoints(true); - plot.renderTo(svg); - assert.deepEqual(yScale.domain(), [-2.5, 2.5], "domain has been been adjusted"); - svg.remove(); - }); - - it("show all data", () => { - plot.automaticallyAdjustYScaleOverVisiblePoints(true); - xScale.domain([-0.5, 0.5]); - plot.showAllData(); - assert.deepEqual(yScale.domain(), [-7, 7], "domain has been adjusted to show all data"); - assert.deepEqual(xScale.domain(), [-6, 6], "domain has been adjusted to show all data"); - svg.remove(); - }); - - it("show all data without auto adjust domain", () => { - plot.automaticallyAdjustYScaleOverVisiblePoints(true); - xScale.domain([-0.5, 0.5]); - plot.automaticallyAdjustYScaleOverVisiblePoints(false); - plot.showAllData(); - assert.deepEqual(yScale.domain(), [-7, 7], "domain has been adjusted to show all data"); - assert.deepEqual(xScale.domain(), [-6, 6], "domain has been adjusted to show all data"); - svg.remove(); - }); - - it("no cycle in auto domain on plot", () => { - var zScale = new Plottable.Scale.Linear().domain([-10, 10]); - plot.automaticallyAdjustYScaleOverVisiblePoints(true); - var plot2 = new Plottable.Plot.AbstractXYPlot(zScale, yScale) - .automaticallyAdjustXScaleOverVisiblePoints(true) - .project("x", xAccessor, zScale) - .project("y", yAccessor, yScale) - .addDataset(simpleDataset); - var plot3 = new Plottable.Plot.AbstractXYPlot(zScale, xScale) - .automaticallyAdjustYScaleOverVisiblePoints(true) - .project("x", xAccessor, zScale) - .project("y", yAccessor, xScale) - .addDataset(simpleDataset); - plot2.renderTo(svg); - plot3.renderTo(svg); - - xScale.domain([-2, 2]); - assert.deepEqual(yScale.domain(), [-2.5, 2.5], "y domain is adjusted by x domain using custom algorithm and domainer"); - assert.deepEqual(zScale.domain(), [-2.5, 2.5], "z domain is adjusted by y domain using custom algorithm and domainer"); - assert.deepEqual(xScale.domain(), [-2, 2], "x domain is not adjusted using custom algorithm and domainer"); - - svg.remove(); - }); - - it("listeners are deregistered after removal", () => { - plot.automaticallyAdjustYScaleOverVisiblePoints(true); - plot.remove(); - var key2callback = ( xScale).broadcaster._key2callback; - assert.isUndefined(key2callback.get("yDomainAdjustment" + plot.getID()), "the plot is no longer attached to the xScale"); - key2callback = ( yScale).broadcaster._key2callback; - assert.isUndefined(key2callback.get("xDomainAdjustment" + plot.getID()), "the plot is no longer attached to the yScale"); - svg.remove(); - }); - - it("listeners are deregistered for changed scale", () => { - plot.automaticallyAdjustYScaleOverVisiblePoints(true); - var newScale = new Plottable.Scale.Linear().domain([-10, 10]); - plot.project("x", xAccessor, newScale); - xScale.domain([-2, 2]); - assert.deepEqual(yScale.domain(), [-7, 7], "replaced xScale didn't adjust yScale"); - svg.remove(); - }); - }); }); diff --git a/test/components/plots/rectanglePlotTests.ts b/test/components/plots/rectanglePlotTests.ts index 933cc190b1..d7b0849397 100644 --- a/test/components/plots/rectanglePlotTests.ts +++ b/test/components/plots/rectanglePlotTests.ts @@ -15,7 +15,7 @@ describe("Plots", () => { ]; var VERIFY_CELLS = (cells: D3.Selection) => { - assert.equal(cells[0].length, 5); + assert.strictEqual(cells[0].length, 5); cells.each(function(d: D3.Selection, i: number) { var cell = d3.select(this); assert.closeTo(+cell.attr("height"), 50, 0.5, "Cell height is correct"); @@ -26,18 +26,18 @@ describe("Plots", () => { }; it("renders correctly", () => { - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var rectanglePlot = new Plottable.Plot.Rectangle(xScale, yScale); - rectanglePlot.addDataset(DATA) - .project("x", "x", xScale) - .project("y", "y", yScale) - .project("x1", "x", xScale) - .project("y1", "y", yScale) - .project("x2", "x2", xScale) - .project("y2", "y2", yScale) - .renderTo(svg); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var rectanglePlot = new Plottable.Plots.Rectangle(xScale, yScale); + rectanglePlot.addDataset(new Plottable.Dataset(DATA)); + rectanglePlot.x((d) => d.x, xScale) + .y((d) => d.y, yScale); + rectanglePlot.x1((d) => d.x, xScale) + .y1((d) => d.y, yScale) + .x2((d) => d.x2, xScale) + .y2((d) => d.y2, yScale) + .renderTo(svg); VERIFY_CELLS(( rectanglePlot)._renderArea.selectAll("rect")); svg.remove(); }); @@ -45,7 +45,7 @@ describe("Plots", () => { describe("fail safe tests", () => { it("illegal rectangles don't get displayed", () => { - var svg = generateSVG(); + var svg = TestMethods.generateSVG(); var data1 = [ { x: "A", y1: 1, y2: 2, v: 1 }, @@ -56,16 +56,15 @@ describe("Plots", () => { { x: "F", y1: 6, y2: 7, v: 6 } ]; - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Linear(); - var cScale = new Plottable.Scale.Color(); + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Linear(); - var plot = new Plottable.Plot.Grid(xScale, yScale, cScale); + var plot = new Plottable.Plots.Grid(xScale, yScale); plot - .project("x", "x", xScale) - .project("y", "y1", yScale) - .project("y2", "y2", yScale); - plot.addDataset(data1); + .x((d: any) => d.x, xScale) + .y((d: any) => d.y1, yScale) + .y2((d: any) => d.y2, yScale); + plot.addDataset(new Plottable.Dataset(data1)); plot.renderTo(svg); @@ -76,13 +75,13 @@ describe("Plots", () => { rectanglesSelection.each(function(d: any, i: number) { var sel = d3.select(this); - assert.isFalse(Plottable._Util.Methods.isNaN(+sel.attr("x")), + assert.isFalse(Plottable.Utils.Methods.isNaN(+sel.attr("x")), "x attribute should be valid for rectangle # " + i + ". Currently " + sel.attr("x")); - assert.isFalse(Plottable._Util.Methods.isNaN(+sel.attr("y")), + assert.isFalse(Plottable.Utils.Methods.isNaN(+sel.attr("y")), "y attribute should be valid for rectangle # " + i + ". Currently " + sel.attr("y")); - assert.isFalse(Plottable._Util.Methods.isNaN(+sel.attr("height")), + assert.isFalse(Plottable.Utils.Methods.isNaN(+sel.attr("height")), "height attribute should be valid for rectangle # " + i + ". Currently " + sel.attr("height")); - assert.isFalse(Plottable._Util.Methods.isNaN(+sel.attr("width")), + assert.isFalse(Plottable.Utils.Methods.isNaN(+sel.attr("width")), "width attribute should be valid for rectangle # " + i + ". Currently " + sel.attr("width")); }); diff --git a/test/components/plots/scatterPlotTests.ts b/test/components/plots/scatterPlotTests.ts index 0b24ed6e22..502d0629a6 100644 --- a/test/components/plots/scatterPlotTests.ts +++ b/test/components/plots/scatterPlotTests.ts @@ -5,32 +5,32 @@ var assert = chai.assert; describe("Plots", () => { describe("ScatterPlot", () => { it("renders correctly with no data", () => { - var svg = generateSVG(400, 400); - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - var plot = new Plottable.Plot.Scatter(xScale, yScale); - plot.project("x", (d: any) => d.x, xScale); - plot.project("y", (d: any) => d.y, yScale); + var svg = TestMethods.generateSVG(400, 400); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); + var plot = new Plottable.Plots.Scatter(xScale, yScale); + plot.x((d: any) => d.x, xScale); + plot.y((d: any) => d.y, yScale); assert.doesNotThrow(() => plot.renderTo(svg), Error); assert.strictEqual(plot.width(), 400, "was allocated width"); assert.strictEqual(plot.height(), 400, "was allocated height"); svg.remove(); }); - it("the accessors properly access data, index, and metadata", () => { - var svg = generateSVG(400, 400); - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); + it("the accessors properly access data, index and Dataset", () => { + var svg = TestMethods.generateSVG(400, 400); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); xScale.domain([0, 400]); yScale.domain([400, 0]); var data = [{x: 0, y: 0}, {x: 1, y: 1}]; var metadata = {foo: 10, bar: 20}; - var xAccessor = (d: any, i?: number, m?: any) => d.x + i * m.foo; - var yAccessor = (d: any, i?: number, m?: any) => m.bar; + var xAccessor = (d: any, i: number, dataset: Plottable.Dataset) => d.x + i * dataset.metadata().foo; + var yAccessor = (d: any, i: number, dataset: Plottable.Dataset) => dataset.metadata().bar; var dataset = new Plottable.Dataset(data, metadata); - var plot = new Plottable.Plot.Scatter(xScale, yScale) - .project("x", xAccessor) - .project("y", yAccessor); + var plot = new Plottable.Plots.Scatter(xScale, yScale) + .x(xAccessor) + .y(yAccessor); plot.addDataset(dataset); plot.renderTo(svg); var symbols = plot.getAllSelections(); @@ -66,16 +66,16 @@ describe("Plots", () => { }); it("getAllSelections()", () => { - var svg = generateSVG(400, 400); - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); + var svg = TestMethods.generateSVG(400, 400); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); var data = [{x: 0, y: 0}, {x: 1, y: 1}]; var data2 = [{x: 1, y: 2}, {x: 3, y: 4}]; - var plot = new Plottable.Plot.Scatter(xScale, yScale) - .project("x", "x", xScale) - .project("y", "y", yScale) - .addDataset(data) - .addDataset(data2); + var plot = new Plottable.Plots.Scatter(xScale, yScale) + .x((d) => d.x, xScale) + .y((d) => d.y, yScale) + .addDataset(new Plottable.Dataset(data)) + .addDataset(new Plottable.Dataset(data2)); plot.renderTo(svg); var allCircles = plot.getAllSelections(); assert.strictEqual(allCircles.size(), 4, "all circles retrieved"); @@ -87,7 +87,7 @@ describe("Plots", () => { }); it("getClosestPlotData()", () => { - function assertPlotDataEqual(expected: Plottable.Plot.PlotData, actual: Plottable.Plot.PlotData, + function assertPlotDataEqual(expected: Plottable.Plots.PlotData, actual: Plottable.Plots.PlotData, msg: string) { assert.deepEqual(expected.data, actual.data, msg); assert.closeTo(expected.pixelPoints[0].x, actual.pixelPoints[0].x, 0.01, msg); @@ -95,16 +95,16 @@ describe("Plots", () => { assert.deepEqual(expected.selection, actual.selection, msg); } - var svg = generateSVG(400, 400); - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); + var svg = TestMethods.generateSVG(400, 400); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); var data = [{x: 0, y: 0}, {x: 1, y: 1}]; var data2 = [{x: 1, y: 2}, {x: 3, y: 4}]; - var plot = new Plottable.Plot.Scatter(xScale, yScale) - .project("x", "x", xScale) - .project("y", "y", yScale) - .addDataset(data) - .addDataset(data2); + var plot = new Plottable.Plots.Scatter(xScale, yScale) + .x((d) => d.x, xScale) + .y((d) => d.y, yScale) + .addDataset(new Plottable.Dataset(data)) + .addDataset(new Plottable.Dataset(data2)); plot.renderTo(svg); var points = d3.selectAll(".scatter-plot path"); @@ -143,46 +143,8 @@ describe("Plots", () => { svg.remove(); }); - it("_getClosestStruckPoint()", () => { - var svg = generateSVG(400, 400); - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - xScale.domain([0, 400]); - yScale.domain([400, 0]); - - var data1 = [ - { x: 80, y: 200, size: 40 }, - { x: 100, y: 200, size: 40 }, - { x: 125, y: 200, size: 10 }, - { x: 138, y: 200, size: 10 } - ]; - - var plot = new Plottable.Plot.Scatter(xScale, yScale); - plot.addDataset(data1); - plot.project("x", "x").project("y", "y").project("size", "size"); - plot.renderTo(svg); - - var twoOverlappingCirclesResult = ( plot)._getClosestStruckPoint({ x: 85, y: 200 }, 10); - assert.strictEqual(twoOverlappingCirclesResult.data[0], data1[0], - "returns closest circle among circles that the test point touches"); - - var overlapAndCloseToPointResult = ( plot)._getClosestStruckPoint({ x: 118, y: 200 }, 10); - assert.strictEqual(overlapAndCloseToPointResult.data[0], data1[1], - "returns closest circle that test point touches, even if non-touched circles are closer"); - - var twoPointsInRangeResult = ( plot)._getClosestStruckPoint({ x: 130, y: 200 }, 10); - assert.strictEqual(twoPointsInRangeResult.data[0], data1[2], - "returns closest circle within range if test point does not touch any circles"); - - var farFromAnyPointsResult = ( plot)._getClosestStruckPoint({ x: 400, y: 400 }, 10); - assert.isNull(farFromAnyPointsResult.data, - "returns no data if no circle were within range and test point does not touch any circles"); - - svg.remove(); - }); - it("correctly handles NaN and undefined x and y values", () => { - var svg = generateSVG(400, 400); + var svg = TestMethods.generateSVG(400, 400); var data = [ { foo: 0.0, bar: 0.0 }, { foo: 0.2, bar: 0.2 }, @@ -191,12 +153,12 @@ describe("Plots", () => { { foo: 0.8, bar: 0.8 } ]; var dataset = new Plottable.Dataset(data); - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - var plot = new Plottable.Plot.Scatter(xScale, yScale); - plot.addDataset(dataset) - .project("x", "foo", xScale) - .project("y", "bar", yScale); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); + var plot = new Plottable.Plots.Scatter(xScale, yScale); + plot.addDataset(dataset); + plot.x((d) => d.foo, xScale) + .y((d) => d.bar, yScale); plot.renderTo(svg); var dataWithNaN = data.slice(); @@ -217,18 +179,16 @@ describe("Plots", () => { describe("Example ScatterPlot with quadratic series", () => { var svg: D3.Selection; - var xScale: Plottable.Scale.Linear; - var yScale: Plottable.Scale.Linear; - var circlePlot: Plottable.Plot.Scatter; + var xScale: Plottable.Scales.Linear; + var yScale: Plottable.Scales.Linear; + var circlePlot: Plottable.Plots.Scatter; var SVG_WIDTH = 600; var SVG_HEIGHT = 300; - var pixelAreaFull = {xMin: 0, xMax: SVG_WIDTH, yMin: 0, yMax: SVG_HEIGHT}; - var pixelAreaPart = {xMin: 200, xMax: 600, yMin: 100, yMax: 200}; var dataAreaFull = {xMin: 0, xMax: 9, yMin: 81, yMax: 0}; var dataAreaPart = {xMin: 3, xMax: 9, yMin: 54, yMax: 27}; var colorAccessor = (d: any, i: number, m: any) => d3.rgb(d.x, d.y, i).toString(); var circlesInArea: number; - var quadraticDataset = makeQuadraticSeries(10); + var quadraticDataset = new Plottable.Dataset(TestMethods.makeQuadraticSeries(10)); function getCirclePlotVerifier() { // creates a function that verifies that circles are drawn properly after accounting for svg transform @@ -250,20 +210,20 @@ describe("Plots", () => { circlesInArea++; assert.closeTo(x, xScale.scale(datum.x), 0.01, "the scaled/translated x is correct"); assert.closeTo(y, yScale.scale(datum.y), 0.01, "the scaled/translated y is correct"); - assert.equal(selection.attr("fill"), colorAccessor(datum, index, null), "fill is correct"); + assert.strictEqual(selection.attr("fill"), colorAccessor(datum, index, null), "fill is correct"); }; }; }; beforeEach(() => { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - xScale = new Plottable.Scale.Linear().domain([0, 9]); - yScale = new Plottable.Scale.Linear().domain([0, 81]); - circlePlot = new Plottable.Plot.Scatter(xScale, yScale); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + xScale = new Plottable.Scales.Linear().domain([0, 9]); + yScale = new Plottable.Scales.Linear().domain([0, 81]); + circlePlot = new Plottable.Plots.Scatter(xScale, yScale); circlePlot.addDataset(quadraticDataset); - circlePlot.project("fill", colorAccessor); - circlePlot.project("x", "x", xScale); - circlePlot.project("y", "y", yScale); + circlePlot.attr("fill", colorAccessor); + circlePlot.x((d) => d.x, xScale); + circlePlot.y((d) => d.y, yScale); circlePlot.renderTo(svg); }); @@ -271,15 +231,15 @@ describe("Plots", () => { assert.deepEqual(xScale.range(), [0, SVG_WIDTH], "xScale range was set by the renderer"); assert.deepEqual(yScale.range(), [SVG_HEIGHT, 0], "yScale range was set by the renderer"); circlePlot.getAllSelections().each(getCirclePlotVerifier()); - assert.equal(circlesInArea, 10, "10 circles were drawn"); + assert.strictEqual(circlesInArea, 10, "10 circles were drawn"); svg.remove(); }); it("rendering is idempotent", () => { - circlePlot._render(); - circlePlot._render(); + circlePlot.render(); + circlePlot.render(); circlePlot.getAllSelections().each(getCirclePlotVerifier()); - assert.equal(circlesInArea, 10, "10 circles were drawn"); + assert.strictEqual(circlesInArea, 10, "10 circles were drawn"); svg.remove(); }); @@ -294,7 +254,7 @@ describe("Plots", () => { it("the circles re-rendered properly", () => { var circles = circlePlot.getAllSelections(); circles.each(getCirclePlotVerifier()); - assert.equal(circlesInArea, 4, "four circles were found in the render area"); + assert.strictEqual(circlesInArea, 4, "four circles were found in the render area"); svg.remove(); }); }); diff --git a/test/components/plots/stackedAreaPlotTests.ts b/test/components/plots/stackedAreaPlotTests.ts index d2bce7e0b9..4bfe693d43 100644 --- a/test/components/plots/stackedAreaPlotTests.ts +++ b/test/components/plots/stackedAreaPlotTests.ts @@ -7,17 +7,17 @@ describe("Plots", () => { var svg: D3.Selection; var dataset1: Plottable.Dataset; var dataset2: Plottable.Dataset; - var xScale: Plottable.Scale.Linear; - var yScale: Plottable.Scale.Linear; - var renderer: Plottable.Plot.StackedArea; + var xScale: Plottable.Scales.Linear; + var yScale: Plottable.Scales.Linear; + var renderer: Plottable.Plots.StackedArea; var SVG_WIDTH = 600; var SVG_HEIGHT = 400; beforeEach(() => { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - xScale = new Plottable.Scale.Linear().domain([1, 3]); - yScale = new Plottable.Scale.Linear().domain([0, 4]); - var colorScale = new Plottable.Scale.Color("10").domain(["a", "b"]); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + xScale = new Plottable.Scales.Linear().domain([1, 3]); + yScale = new Plottable.Scales.Linear().domain([0, 4]); + var colorScale = new Plottable.Scales.Color("10").domain(["a", "b"]); var data1 = [ {x: 1, y: 1, type: "a"}, @@ -30,25 +30,25 @@ describe("Plots", () => { dataset1 = new Plottable.Dataset(data1); dataset2 = new Plottable.Dataset(data2); - renderer = new Plottable.Plot.StackedArea(xScale, yScale); - renderer.addDataset(data1); - renderer.addDataset(data2); - renderer.project("x", "x", xScale); - renderer.project("y", "y", yScale); - renderer.project("fill", "type", colorScale); - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); - var table = new Plottable.Component.Table([[renderer], [xAxis]]).renderTo(svg); + renderer = new Plottable.Plots.StackedArea(xScale, yScale); + renderer.addDataset(dataset1); + renderer.addDataset(dataset2); + renderer.x((d) => d.x, xScale); + renderer.y((d) => d.y, yScale); + renderer.attr("fill", "type", colorScale); + var xAxis = new Plottable.Axes.Numeric(xScale, "bottom"); + new Plottable.Components.Table([[renderer], [xAxis]]).renderTo(svg); }); it("renders correctly", () => { var areas = ( renderer)._renderArea.selectAll(".area"); var area0 = d3.select(areas[0][0]); - var d0 = normalizePath(area0.attr("d")).split(/[a-zA-Z]/); + var d0 = TestMethods.normalizePath(area0.attr("d")).split(/[a-zA-Z]/); var d0Ys = d0.slice(1, d0.length - 1).map((s) => parseFloat(s.split(",")[1])); assert.strictEqual(d0Ys.indexOf(0), -1, "bottom area never touches the top"); var area1 = d3.select(areas[0][1]); - var d1 = normalizePath(area1.attr("d")).split(/[a-zA-Z]/); + var d1 = TestMethods.normalizePath(area1.attr("d")).split(/[a-zA-Z]/); var d1Ys = d1.slice(1, d1.length - 1).map((s) => parseFloat(s.split(",")[1])); assert.notEqual(d1Ys.indexOf(0), -1, "touches the top"); @@ -62,15 +62,15 @@ describe("Plots", () => { describe("Stacked Area Plot no data", () => { var svg: D3.Selection; - var renderer: Plottable.Plot.StackedArea; + var renderer: Plottable.Plots.StackedArea; var SVG_WIDTH = 600; var SVG_HEIGHT = 400; beforeEach(() => { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var xScale = new Plottable.Scale.Linear().domain([1, 3]); - var yScale = new Plottable.Scale.Linear().domain([0, 4]); - var colorScale = new Plottable.Scale.Color("10"); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var xScale = new Plottable.Scales.Linear().domain([1, 3]); + var yScale = new Plottable.Scales.Linear().domain([0, 4]); + var colorScale = new Plottable.Scales.Color("10"); var data1: any[] = [ ]; @@ -79,13 +79,13 @@ describe("Plots", () => { {x: 3, y: 1, type: "b"} ]; - renderer = new Plottable.Plot.StackedArea(xScale, yScale); - renderer.addDataset(data1); - renderer.addDataset(data2); - renderer.project("fill", "type", colorScale); - renderer.project("x", "x", xScale); - renderer.project("y", "y", yScale); - new Plottable.Component.Table([[renderer]]).renderTo(svg); + renderer = new Plottable.Plots.StackedArea(xScale, yScale); + renderer.addDataset(new Plottable.Dataset(data1)); + renderer.addDataset(new Plottable.Dataset(data2)); + renderer.attr("fill", "type", colorScale); + renderer.x((d) => d.x, xScale); + renderer.y((d) => d.y, yScale); + new Plottable.Components.Table([[renderer]]).renderTo(svg); }); it("path elements rendered correctly", () => { @@ -104,17 +104,17 @@ describe("Plots", () => { describe("Stacked Area Plot Stacking", () => { var svg: D3.Selection; - var xScale: Plottable.Scale.Linear; - var yScale: Plottable.Scale.Linear; - var renderer: Plottable.Plot.StackedArea; + var xScale: Plottable.Scales.Linear; + var yScale: Plottable.Scales.Linear; + var renderer: Plottable.Plots.StackedArea; var SVG_WIDTH = 600; var SVG_HEIGHT = 400; beforeEach(() => { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - xScale = new Plottable.Scale.Linear().domain([1, 3]); - yScale = new Plottable.Scale.Linear(); - var colorScale = new Plottable.Scale.Color("10").domain(["a", "b"]); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + xScale = new Plottable.Scales.Linear().domain([1, 3]); + yScale = new Plottable.Scales.Linear(); + var colorScale = new Plottable.Scales.Color("10").domain(["a", "b"]); var data1 = [ {x: 1, y: 1, type: "a"}, @@ -125,12 +125,12 @@ describe("Plots", () => { {x: 3, y: 1, type: "b"} ]; - renderer = new Plottable.Plot.StackedArea(xScale, yScale); - renderer.addDataset(data1); - renderer.addDataset(data2); - renderer.project("fill", "type", colorScale); - renderer.project("x", "x", xScale); - renderer.project("y", "y", yScale); + renderer = new Plottable.Plots.StackedArea(xScale, yScale); + renderer.addDataset(new Plottable.Dataset(data1)); + renderer.addDataset(new Plottable.Dataset(data2)); + renderer.attr("fill", "type", colorScale); + renderer.x((d) => d.x, xScale); + renderer.y((d) => d.y, yScale); renderer.renderTo(svg); }); @@ -146,7 +146,8 @@ describe("Plots", () => { {x: 1, y: 0, type: "c"}, {x: 3, y: 0, type: "c"} ]; - renderer.addDataset("a", new Plottable.Dataset(data)); + var datasetA = new Plottable.Dataset(data); + renderer.addDataset(datasetA); renderer.renderTo(svg); assert.strictEqual(oldLowerBound, yScale.domain()[0], "lower bound doesn't change with 0 added"); @@ -159,7 +160,8 @@ describe("Plots", () => { {x: 1, y: 10, type: "d"}, {x: 3, y: 3, type: "d"} ]; - renderer.addDataset("b", new Plottable.Dataset(data)); + var datasetB = new Plottable.Dataset(data); + renderer.addDataset(datasetB); renderer.renderTo(svg); assert.closeTo(oldLowerBound, yScale.domain()[0], 2, "lower bound doesn't change on positive addition"); @@ -171,14 +173,15 @@ describe("Plots", () => { {x: 1, y: 0, type: "e"}, {x: 3, y: 1, type: "e"} ]; - renderer.addDataset("c", new Plottable.Dataset(data)); + var datasetC = new Plottable.Dataset(data); + renderer.addDataset(datasetC); renderer.renderTo(svg); assert.strictEqual(oldUpperBound, yScale.domain()[1], "upper bound doesn't increase since maximum doesn't increase"); - renderer.removeDataset("a"); - renderer.removeDataset("b"); - renderer.removeDataset("c"); + renderer.removeDataset(datasetA); + renderer.removeDataset(datasetB); + renderer.removeDataset(datasetC); svg.remove(); }); @@ -189,42 +192,45 @@ describe("Plots", () => { {x: 1, y: 0, type: "c"}, {x: 3, y: 0, type: "c"} ]; - renderer.addDataset("a", new Plottable.Dataset(data)); + var datasetA = new Plottable.Dataset(data); + renderer.addDataset(datasetA); data = [ {x: 1, y: 10, type: "d"}, {x: 3, y: 3, type: "d"} ]; - renderer.addDataset("b", new Plottable.Dataset(data)); + var datasetB = new Plottable.Dataset(data); + renderer.addDataset(datasetB); data = [ {x: 1, y: 0, type: "e"}, {x: 3, y: 1, type: "e"} ]; - renderer.addDataset("c", new Plottable.Dataset(data)); - renderer.project("x", "x", xScale); - renderer.project("y", "y", yScale); + var datasetC = new Plottable.Dataset(data); + renderer.addDataset(datasetC); + renderer.x((d) => d.x, xScale); + renderer.y((d) => d.y, yScale); renderer.renderTo(svg); assert.closeTo(16, yScale.domain()[1], 2, "Initially starts with around 14 at highest extent"); renderer.detach(); - renderer.removeDataset("a"); + renderer.removeDataset(datasetA); renderer.renderTo(svg); assert.closeTo(16, yScale.domain()[1], 2, "Remains with around 14 at highest extent"); var oldUpperBound = yScale.domain()[1]; renderer.detach(); - renderer.removeDataset("b"); + renderer.removeDataset(datasetB); renderer.renderTo(svg); assert.closeTo(oldUpperBound - 10, yScale.domain()[1], 2, "Highest extent decreases by around 10"); oldUpperBound = yScale.domain()[1]; renderer.detach(); - renderer.removeDataset("c"); + renderer.removeDataset(datasetC); renderer.renderTo(svg); assert.strictEqual(oldUpperBound, yScale.domain()[1], "Extent doesn't change if maximum doesn't change"); @@ -244,8 +250,8 @@ describe("Plots", () => { ]; var dataset = new Plottable.Dataset(data); renderer.addDataset(dataset); - renderer.project("x", "x", xScale); - renderer.project("y", "y", yScale); + renderer.x((d) => d.x, xScale); + renderer.y((d) => d.y, yScale); renderer.renderTo(svg); assert.strictEqual(oldLowerBound, yScale.domain()[0], "lower bound doesn't change with 0 added"); @@ -290,8 +296,8 @@ describe("Plots", () => { it("warning is thrown when datasets are updated with different domains", () => { var flag = false; - var oldWarn = Plottable._Util.Methods.warn; - ( Plottable._Util.Methods).warn = (msg: string) => { + var oldWarn = Plottable.Utils.Methods.warn; + ( Plottable.Utils.Methods).warn = (msg: string) => { if (msg.indexOf("domain") > -1) { flag = true; } }; @@ -301,7 +307,7 @@ describe("Plots", () => { var dataset = new Plottable.Dataset(missingDomainData); renderer.addDataset(dataset); - ( Plottable._Util.Methods).warn = oldWarn; + ( Plottable.Utils.Methods).warn = oldWarn; assert.isTrue(flag, "warning has been issued about differing domains"); svg.remove(); @@ -310,17 +316,17 @@ describe("Plots", () => { describe("Stacked Area Plot Project", () => { var svg: D3.Selection; - var xScale: Plottable.Scale.Linear; - var yScale: Plottable.Scale.Linear; - var renderer: Plottable.Plot.StackedArea; + var xScale: Plottable.Scales.Linear; + var yScale: Plottable.Scales.Linear; + var renderer: Plottable.Plots.StackedArea; var SVG_WIDTH = 600; var SVG_HEIGHT = 400; beforeEach(() => { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - xScale = new Plottable.Scale.Linear().domain([1, 3]); - yScale = new Plottable.Scale.Linear().domain([0, 4]); - var colorScale = new Plottable.Scale.Color("10").domain(["a", "b"]); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + xScale = new Plottable.Scales.Linear().domain([1, 3]); + yScale = new Plottable.Scales.Linear().domain([0, 4]); + var colorScale = new Plottable.Scales.Color("10").domain(["a", "b"]); var data1 = [ {x: 1, yTest: 1, type: "a"}, @@ -331,25 +337,25 @@ describe("Plots", () => { {x: 3, yTest: 1, type: "b"} ]; - renderer = new Plottable.Plot.StackedArea(xScale, yScale); - renderer.project("y", "yTest", yScale); - renderer.project("x", "x", xScale); - renderer.addDataset(data1); - renderer.addDataset(data2); - renderer.project("fill", "type", colorScale); - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); - var table = new Plottable.Component.Table([[renderer], [xAxis]]).renderTo(svg); + renderer = new Plottable.Plots.StackedArea(xScale, yScale); + renderer.y((d) => d.yTest, yScale); + renderer.x((d) => d.x, xScale); + renderer.addDataset(new Plottable.Dataset(data1)); + renderer.addDataset(new Plottable.Dataset(data2)); + renderer.attr("fill", (d) => d.type, colorScale); + var xAxis = new Plottable.Axes.Numeric(xScale, "bottom"); + new Plottable.Components.Table([[renderer], [xAxis]]).renderTo(svg); }); it("renders correctly", () => { var areas = ( renderer)._renderArea.selectAll(".area"); var area0 = d3.select(areas[0][0]); - var d0 = normalizePath(area0.attr("d")).split(/[a-zA-Z]/); + var d0 = TestMethods.normalizePath(area0.attr("d")).split(/[a-zA-Z]/); var d0Ys = d0.slice(1, d0.length - 1).map((s) => parseFloat(s.split(",")[1])); assert.strictEqual(d0Ys.indexOf(0), -1, "bottom area never touches the top"); var area1 = d3.select(areas[0][1]); - var d1 = normalizePath(area1.attr("d")).split(/[a-zA-Z]/); + var d1 = TestMethods.normalizePath(area1.attr("d")).split(/[a-zA-Z]/); var d1Ys = d1.slice(1, d1.length - 1).map((s) => parseFloat(s.split(",")[1])); assert.notEqual(d1Ys.indexOf(0), -1, "touches the top"); @@ -360,7 +366,7 @@ describe("Plots", () => { }); it("project works correctly", () => { - renderer.project("check", "type"); + renderer.attr("check", (d) => d.type); var areas = ( renderer)._renderArea.selectAll(".area"); var area0 = d3.select(areas[0][0]); assert.strictEqual(area0.attr("check"), "a", "projector has been applied to first area"); @@ -374,79 +380,85 @@ describe("Plots", () => { describe("fail safe tests", () => { it("0 as a string coerces correctly and is not subject to off by one errors", () => { - var data1 = [ + var data0 = [ { x: 1, y: 1, fill: "blue" }, { x: 2, y: 2, fill: "blue" }, { x: 3, y: 3, fill: "blue" }, ]; - var data2 = [ + var data1 = [ { x: 1, y: 1, fill: "red" }, { x: 2, y: "0", fill: "red" }, { x: 3, y: 3, fill: "red" }, ]; - var data3 = [ + var data2 = [ { x: 1, y: 1, fill: "green" }, { x: 2, y: 2, fill: "green" }, { x: 3, y: 3, fill: "green" }, ]; - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - - var plot = new Plottable.Plot.StackedArea(xScale, yScale); - plot.addDataset("d1", data1); - plot.addDataset("d2", data2); - plot.addDataset("d3", data3); - plot.project("fill", "fill"); - plot.project("x", "x", xScale).project("y", "y", yScale); - - var ds1Point2Offset = ( plot)._key2PlotDatasetKey.get("d1").plotMetadata.offsets.get(2); - var ds2Point2Offset = ( plot)._key2PlotDatasetKey.get("d2").plotMetadata.offsets.get(2); - var ds3Point2Offset = ( plot)._key2PlotDatasetKey.get("d3").plotMetadata.offsets.get(2); - - assert.strictEqual(ds1Point2Offset, 0, - "dataset1 (blue) should have no offset on middle point"); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); + + var plot = new Plottable.Plots.StackedArea(xScale, yScale); + var dataset0 = new Plottable.Dataset(data0); + plot.addDataset(dataset0); + var dataset1 = new Plottable.Dataset(data1); + plot.addDataset(dataset1); + var dataset2 = new Plottable.Dataset(data2); + plot.addDataset(dataset2); + plot.attr("fill", "fill"); + plot.x((d: any) => d.x, xScale).y((d: any) => d.y, yScale); + + var ds0Point2Offset = ( plot)._key2PlotDatasetKey.get("_0").plotMetadata.offsets.get(2); + var ds1Point2Offset = ( plot)._key2PlotDatasetKey.get("_1").plotMetadata.offsets.get(2); + var ds2Point2Offset = ( plot)._key2PlotDatasetKey.get("_2").plotMetadata.offsets.get(2); + + assert.strictEqual(ds0Point2Offset, 0, + "dataset0 (blue) sh1uld have no offset on middle point"); + assert.strictEqual(ds1Point2Offset, 2, + "dataset1 (red) should have this offset and be on top of blue dataset"); assert.strictEqual(ds2Point2Offset, 2, - "dataset2 (red) should have this offset and be on top of blue dataset"); - assert.strictEqual(ds3Point2Offset, 2, - "dataset3 (green) should have this offset because the red dataset (ds2) has no height in this point"); + "dataset2 (green) should have this offset because the red dataset has no height in this point"); }); it("null defaults to 0", () => { - var data1 = [ + var data0 = [ { x: 1, y: 1, fill: "blue" }, { x: 2, y: 2, fill: "blue" }, { x: 3, y: 3, fill: "blue" }, ]; - var data2 = [ + var data1 = [ { x: 1, y: 1, fill: "red" }, { x: 2, y: "0", fill: "red" }, { x: 3, y: 3, fill: "red" }, ]; - var data3 = [ + var data2 = [ { x: 1, y: 1, fill: "green" }, { x: 2, y: 2, fill: "green" }, { x: 3, y: 3, fill: "green" }, ]; - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - - var plot = new Plottable.Plot.StackedArea(xScale, yScale); - plot.addDataset("d1", data1); - plot.addDataset("d2", data2); - plot.addDataset("d3", data3); - plot.project("fill", "fill"); - plot.project("x", "x", xScale).project("y", "y", yScale); - - var ds1Point2Offset = ( plot)._key2PlotDatasetKey.get("d1").plotMetadata.offsets.get(2); - var ds2Point2Offset = ( plot)._key2PlotDatasetKey.get("d2").plotMetadata.offsets.get(2); - var ds3Point2Offset = ( plot)._key2PlotDatasetKey.get("d3").plotMetadata.offsets.get(2); - - assert.strictEqual(ds1Point2Offset, 0, - "dataset1 (blue) should have no offset on middle point"); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); + + var plot = new Plottable.Plots.StackedArea(xScale, yScale); + var dataset0 = new Plottable.Dataset(data0); + plot.addDataset(dataset0); + var dataset1 = new Plottable.Dataset(data1); + plot.addDataset(dataset1); + var dataset2 = new Plottable.Dataset(data2); + plot.addDataset(dataset2); + plot.attr("fill", "fill"); + plot.x((d: any) => d.x, xScale).y((d: any) => d.y, yScale); + + var ds0Point2Offset = ( plot)._key2PlotDatasetKey.get("_0").plotMetadata.offsets.get(2); + var ds1Point2Offset = ( plot)._key2PlotDatasetKey.get("_1").plotMetadata.offsets.get(2); + var ds2Point2Offset = ( plot)._key2PlotDatasetKey.get("_2").plotMetadata.offsets.get(2); + + assert.strictEqual(ds0Point2Offset, 0, + "dataset0 (blue) should have no offset on middle point"); + assert.strictEqual(ds1Point2Offset, 2, + "dataset1 (red) should have this offset and be on top of blue dataset"); assert.strictEqual(ds2Point2Offset, 2, - "dataset2 (red) should have this offset and be on top of blue dataset"); - assert.strictEqual(ds3Point2Offset, 2, - "dataset3 (green) should have this offset because the red dataset (ds2) has no height in this point"); + "dataset2 (green) should have this offset because the red dataset has no height in this point"); }); }); diff --git a/test/components/plots/stackedBarPlotTests.ts b/test/components/plots/stackedBarPlotTests.ts index 6eab26a098..a94c88f249 100644 --- a/test/components/plots/stackedBarPlotTests.ts +++ b/test/components/plots/stackedBarPlotTests.ts @@ -7,9 +7,9 @@ describe("Plots", () => { var svg: D3.Selection; var dataset1: Plottable.Dataset; var dataset2: Plottable.Dataset; - var xScale: Plottable.Scale.Category; - var yScale: Plottable.Scale.Linear; - var renderer: Plottable.Plot.StackedBar; + var xScale: Plottable.Scales.Category; + var yScale: Plottable.Scales.Linear; + var renderer: Plottable.Plots.StackedBar; var SVG_WIDTH = 600; var SVG_HEIGHT = 400; var axisHeight = 0; @@ -18,9 +18,9 @@ describe("Plots", () => { var originalData2: any[]; beforeEach(() => { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - xScale = new Plottable.Scale.Category(); - yScale = new Plottable.Scale.Linear().domain([0, 3]); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + xScale = new Plottable.Scales.Category(); + yScale = new Plottable.Scales.Linear().domain([0, 3]); originalData1 = [ {x: "A", y: 1}, @@ -42,14 +42,14 @@ describe("Plots", () => { dataset1 = new Plottable.Dataset(data1); dataset2 = new Plottable.Dataset(data2); - renderer = new Plottable.Plot.StackedBar(xScale, yScale); + renderer = new Plottable.Plots.StackedBar(xScale, yScale); renderer.addDataset(dataset1); renderer.addDataset(dataset2); - renderer.project("x", "x", xScale); - renderer.project("y", "y", yScale); + renderer.x((d) => d.x, xScale); + renderer.y((d) => d.y, yScale); renderer.baseline(0); - var xAxis = new Plottable.Axis.Category(xScale, "bottom"); - var table = new Plottable.Component.Table([[renderer], [xAxis]]).renderTo(svg); + var xAxis = new Plottable.Axes.Category(xScale, "bottom"); + new Plottable.Components.Table([[renderer], [xAxis]]).renderTo(svg); axisHeight = xAxis.height(); bandWidth = xScale.rangeBand(); }); @@ -65,25 +65,26 @@ describe("Plots", () => { var bar2X = bar2.data()[0].x; var bar3X = bar3.data()[0].x; // check widths - assert.closeTo(numAttr(bar0, "width"), bandWidth, 2); - assert.closeTo(numAttr(bar1, "width"), bandWidth, 2); - assert.closeTo(numAttr(bar2, "width"), bandWidth, 2); - assert.closeTo(numAttr(bar3, "width"), bandWidth, 2); + assert.closeTo(TestMethods.numAttr(bar0, "width"), bandWidth, 2); + assert.closeTo(TestMethods.numAttr(bar1, "width"), bandWidth, 2); + assert.closeTo(TestMethods.numAttr(bar2, "width"), bandWidth, 2); + assert.closeTo(TestMethods.numAttr(bar3, "width"), bandWidth, 2); // check heights - assert.closeTo(numAttr(bar0, "height"), (400 - axisHeight) / 3, 0.01, "height is correct for bar0"); - assert.closeTo(numAttr(bar1, "height"), (400 - axisHeight) / 3 * 2, 0.01, "height is correct for bar1"); - assert.closeTo(numAttr(bar2, "height"), (400 - axisHeight) / 3 * 2, 0.01, "height is correct for bar2"); - assert.closeTo(numAttr(bar3, "height"), (400 - axisHeight) / 3, 0.01, "height is correct for bar3"); + assert.closeTo(TestMethods.numAttr(bar0, "height"), (400 - axisHeight) / 3, 0.01, "height is correct for bar0"); + assert.closeTo(TestMethods.numAttr(bar1, "height"), (400 - axisHeight) / 3 * 2, 0.01, "height is correct for bar1"); + assert.closeTo(TestMethods.numAttr(bar2, "height"), (400 - axisHeight) / 3 * 2, 0.01, "height is correct for bar2"); + assert.closeTo(TestMethods.numAttr(bar3, "height"), (400 - axisHeight) / 3, 0.01, "height is correct for bar3"); // check that bar is aligned on the center of the scale - assert.closeTo(numAttr(bar0, "x") + numAttr(bar0, "width") / 2, xScale.scale(bar0X), 0.01, "x pos correct for bar0"); - assert.closeTo(numAttr(bar1, "x") + numAttr(bar1, "width") / 2, xScale.scale(bar1X), 0.01, "x pos correct for bar1"); - assert.closeTo(numAttr(bar2, "x") + numAttr(bar2, "width") / 2, xScale.scale(bar2X), 0.01, "x pos correct for bar2"); - assert.closeTo(numAttr(bar3, "x") + numAttr(bar3, "width") / 2, xScale.scale(bar3X), 0.01, "x pos correct for bar3"); + var centerX = (selection: D3.Selection) => TestMethods.numAttr(selection, "x") + TestMethods.numAttr(selection, "width") / 2; + assert.closeTo(centerX(bar0), xScale.scale(bar0X), 0.01, "x pos correct for bar0"); + assert.closeTo(centerX(bar1), xScale.scale(bar1X), 0.01, "x pos correct for bar1"); + assert.closeTo(centerX(bar2), xScale.scale(bar2X), 0.01, "x pos correct for bar2"); + assert.closeTo(centerX(bar3), xScale.scale(bar3X), 0.01, "x pos correct for bar3"); // now check y values to ensure they do indeed stack - assert.closeTo(numAttr(bar0, "y"), (400 - axisHeight) / 3 * 2, 0.01, "y is correct for bar0"); - assert.closeTo(numAttr(bar1, "y"), (400 - axisHeight) / 3, 0.01, "y is correct for bar1"); - assert.closeTo(numAttr(bar2, "y"), 0, 0.01, "y is correct for bar2"); - assert.closeTo(numAttr(bar3, "y"), 0, 0.01, "y is correct for bar3"); + assert.closeTo(TestMethods.numAttr(bar0, "y"), (400 - axisHeight) / 3 * 2, 0.01, "y is correct for bar0"); + assert.closeTo(TestMethods.numAttr(bar1, "y"), (400 - axisHeight) / 3, 0.01, "y is correct for bar1"); + assert.closeTo(TestMethods.numAttr(bar2, "y"), 0, 0.01, "y is correct for bar2"); + assert.closeTo(TestMethods.numAttr(bar3, "y"), 0, 0.01, "y is correct for bar3"); assert.deepEqual(dataset1.data(), originalData1, "underlying data is not modified"); assert.deepEqual(dataset2.data(), originalData2, "underlying data is not modified"); @@ -91,7 +92,7 @@ describe("Plots", () => { }); it("considers lying within a bar's y-range to mean it is closest", () => { - function assertPlotDataEqual(expected: Plottable.Plot.PlotData, actual: Plottable.Plot.PlotData, + function assertPlotDataEqual(expected: Plottable.Plots.PlotData, actual: Plottable.Plots.PlotData, msg: string) { assert.deepEqual(expected.data, actual.data, msg); assert.closeTo(expected.pixelPoints[0].x, actual.pixelPoints[0].x, 0.01, msg); @@ -137,18 +138,17 @@ describe("Plots", () => { describe("Stacked Bar Plot Negative Values", () => { var svg: D3.Selection; - var xScale: Plottable.Scale.Category; - var yScale: Plottable.Scale.Linear; - var plot: Plottable.Plot.StackedBar; + var xScale: Plottable.Scales.Category; + var yScale: Plottable.Scales.Linear; + var plot: Plottable.Plots.StackedBar; var SVG_WIDTH = 600; var SVG_HEIGHT = 400; var axisHeight = 0; - var bandWidth = 0; beforeEach(() => { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - xScale = new Plottable.Scale.Category(); - yScale = new Plottable.Scale.Linear(); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + xScale = new Plottable.Scales.Category(); + yScale = new Plottable.Scales.Linear(); var data1 = [ {x: "A", y: -1}, @@ -167,16 +167,16 @@ describe("Plots", () => { {x: "B", y: 4} ]; - plot = new Plottable.Plot.StackedBar(xScale, yScale); - plot.addDataset(data1); - plot.addDataset(data2); - plot.addDataset(data3); - plot.addDataset(data4); - plot.project("x", "x", xScale); - plot.project("y", "y", yScale); + plot = new Plottable.Plots.StackedBar(xScale, yScale); + plot.addDataset(new Plottable.Dataset(data1)); + plot.addDataset(new Plottable.Dataset(data2)); + plot.addDataset(new Plottable.Dataset(data3)); + plot.addDataset(new Plottable.Dataset(data4)); + plot.x((d) => d.x, xScale); + plot.y((d) => d.y, yScale); plot.baseline(0); - var xAxis = new Plottable.Axis.Category(xScale, "bottom"); - var table = new Plottable.Component.Table([[plot], [xAxis]]).renderTo(svg); + var xAxis = new Plottable.Axes.Category(xScale, "bottom"); + new Plottable.Components.Table([[plot], [xAxis]]).renderTo(svg); axisHeight = xAxis.height(); }); @@ -191,12 +191,12 @@ describe("Plots", () => { var bar6 = d3.select(bars[0][6]); var bar7 = d3.select(bars[0][7]); // check stacking order - assert.operator(numAttr(bar0, "y"), "<", numAttr(bar2, "y"), "'A' bars added below the baseline in dataset order"); - assert.operator(numAttr(bar2, "y"), "<", numAttr(bar4, "y"), "'A' bars added below the baseline in dataset order"); - assert.operator(numAttr(bar4, "y"), "<", numAttr(bar6, "y"), "'A' bars added below the baseline in dataset order"); + assert.operator(TestMethods.numAttr(bar0, "y"), "<", TestMethods.numAttr(bar2, "y"), "'A' bars below baseline in dataset order"); + assert.operator(TestMethods.numAttr(bar2, "y"), "<", TestMethods.numAttr(bar4, "y"), "'A' bars below baseline in dataset order"); + assert.operator(TestMethods.numAttr(bar4, "y"), "<", TestMethods.numAttr(bar6, "y"), "'A' bars below baseline in dataset order"); - assert.operator(numAttr(bar1, "y"), "<", numAttr(bar5, "y"), "'B' bars added below the baseline in dataset order"); - assert.operator(numAttr(bar3, "y"), ">", numAttr(bar7, "y"), "'B' bars added above the baseline in dataset order"); + assert.operator(TestMethods.numAttr(bar1, "y"), "<", TestMethods.numAttr(bar5, "y"), "'B' bars below baseline in dataset order"); + assert.operator(TestMethods.numAttr(bar3, "y"), ">", TestMethods.numAttr(bar7, "y"), "'B' bars above baseline in dataset order"); svg.remove(); }); @@ -211,18 +211,18 @@ describe("Plots", () => { var svg: D3.Selection; var dataset1: Plottable.Dataset; var dataset2: Plottable.Dataset; - var xScale: Plottable.Scale.Linear; - var yScale: Plottable.Scale.Category; - var renderer: Plottable.Plot.StackedBar; + var xScale: Plottable.Scales.Linear; + var yScale: Plottable.Scales.Category; + var renderer: Plottable.Plots.StackedBar; var SVG_WIDTH = 600; var SVG_HEIGHT = 400; var rendererWidth: number; var bandWidth = 0; beforeEach(() => { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - xScale = new Plottable.Scale.Linear().domain([0, 6]); - yScale = new Plottable.Scale.Category(); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + xScale = new Plottable.Scales.Linear().domain([0, 6]); + yScale = new Plottable.Scales.Category(); var data1 = [ {name: "jon", y: 0, type: "q1"}, @@ -235,14 +235,14 @@ describe("Plots", () => { dataset1 = new Plottable.Dataset(data1); dataset2 = new Plottable.Dataset(data2); - renderer = new Plottable.Plot.StackedBar(xScale, yScale, false); - renderer.project("y", "name", yScale); - renderer.project("x", "y", xScale); - renderer.addDataset(data1); - renderer.addDataset(data2); + renderer = new Plottable.Plots.StackedBar(xScale, yScale, false); + renderer.y((d) => d.name, yScale); + renderer.x((d) => d.y, xScale); + renderer.addDataset(new Plottable.Dataset(data1)); + renderer.addDataset(new Plottable.Dataset(data2)); renderer.baseline(0); - var yAxis = new Plottable.Axis.Category(yScale, "left"); - var table = new Plottable.Component.Table([[yAxis, renderer]]).renderTo(svg); + var yAxis = new Plottable.Axes.Category(yScale, "left"); + new Plottable.Components.Table([[yAxis, renderer]]).renderTo(svg); rendererWidth = renderer.width(); bandWidth = yScale.rangeBand(); }); @@ -254,15 +254,15 @@ describe("Plots", () => { var bar2 = d3.select(bars[0][2]); var bar3 = d3.select(bars[0][3]); // check heights - assert.closeTo(numAttr(bar0, "height"), bandWidth, 2); - assert.closeTo(numAttr(bar1, "height"), bandWidth, 2); - assert.closeTo(numAttr(bar2, "height"), bandWidth, 2); - assert.closeTo(numAttr(bar3, "height"), bandWidth, 2); + assert.closeTo(TestMethods.numAttr(bar0, "height"), bandWidth, 2); + assert.closeTo(TestMethods.numAttr(bar1, "height"), bandWidth, 2); + assert.closeTo(TestMethods.numAttr(bar2, "height"), bandWidth, 2); + assert.closeTo(TestMethods.numAttr(bar3, "height"), bandWidth, 2); // check widths - assert.closeTo(numAttr(bar0, "width"), 0, 0.01, "width is correct for bar0"); - assert.closeTo(numAttr(bar1, "width"), rendererWidth / 3, 0.01, "width is correct for bar1"); - assert.closeTo(numAttr(bar2, "width"), rendererWidth / 3, 0.01, "width is correct for bar2"); - assert.closeTo(numAttr(bar3, "width"), rendererWidth / 3 * 2, 0.01, "width is correct for bar3"); + assert.closeTo(TestMethods.numAttr(bar0, "width"), 0, 0.01, "width is correct for bar0"); + assert.closeTo(TestMethods.numAttr(bar1, "width"), rendererWidth / 3, 0.01, "width is correct for bar1"); + assert.closeTo(TestMethods.numAttr(bar2, "width"), rendererWidth / 3, 0.01, "width is correct for bar2"); + assert.closeTo(TestMethods.numAttr(bar3, "width"), rendererWidth / 3 * 2, 0.01, "width is correct for bar3"); var bar0Y = bar0.data()[0].name; var bar1Y = bar1.data()[0].name; @@ -270,31 +270,30 @@ describe("Plots", () => { var bar3Y = bar3.data()[0].name; // check that bar is aligned on the center of the scale - assert.closeTo(numAttr(bar0, "y") + numAttr(bar0, "height") / 2, yScale.scale(bar0Y), 0.01, "y pos correct for bar0"); - assert.closeTo(numAttr(bar1, "y") + numAttr(bar1, "height") / 2, yScale.scale(bar1Y), 0.01, "y pos correct for bar1"); - assert.closeTo(numAttr(bar2, "y") + numAttr(bar2, "height") / 2, yScale.scale(bar2Y), 0.01, "y pos correct for bar2"); - assert.closeTo(numAttr(bar3, "y") + numAttr(bar3, "height") / 2, yScale.scale(bar3Y), 0.01, "y pos correct for bar3"); + var centerY = (selection: D3.Selection) => TestMethods.numAttr(selection, "y") + TestMethods.numAttr(selection, "height") / 2; + assert.closeTo(centerY(bar0), yScale.scale(bar0Y), 0.01, "y pos correct for bar0"); + assert.closeTo(centerY(bar1), yScale.scale(bar1Y), 0.01, "y pos correct for bar1"); + assert.closeTo(centerY(bar2), yScale.scale(bar2Y), 0.01, "y pos correct for bar2"); + assert.closeTo(centerY(bar3), yScale.scale(bar3Y), 0.01, "y pos correct for bar3"); // now check x values to ensure they do indeed stack - assert.closeTo(numAttr(bar0, "x"), 0, 0.01, "x is correct for bar0"); - assert.closeTo(numAttr(bar1, "x"), 0, 0.01, "x is correct for bar1"); - assert.closeTo(numAttr(bar2, "x"), 0, 0.01, "x is correct for bar2"); - assert.closeTo(numAttr(bar3, "x"), rendererWidth / 3, 0.01, "x is correct for bar3"); + assert.closeTo(TestMethods.numAttr(bar0, "x"), 0, 0.01, "x is correct for bar0"); + assert.closeTo(TestMethods.numAttr(bar1, "x"), 0, 0.01, "x is correct for bar1"); + assert.closeTo(TestMethods.numAttr(bar2, "x"), 0, 0.01, "x is correct for bar2"); + assert.closeTo(TestMethods.numAttr(bar3, "x"), rendererWidth / 3, 0.01, "x is correct for bar3"); svg.remove(); }); }); describe("Stacked Bar Plot Weird Values", () => { var svg: D3.Selection; - var plot: Plottable.Plot.StackedBar; + var plot: Plottable.Plots.StackedBar; var SVG_WIDTH = 600; var SVG_HEIGHT = 400; - var numAttr = (s: D3.Selection, a: string) => parseFloat(s.attr(a)); - beforeEach(() => { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Linear(); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Linear(); var data1 = [ {x: "A", y: 1, type: "a"}, @@ -310,14 +309,14 @@ describe("Plots", () => { {x: "C", y: 7, type: "c"} ]; - plot = new Plottable.Plot.StackedBar(xScale, yScale); - plot.addDataset(data1); - plot.addDataset(data2); - plot.addDataset(data3); - plot.project("x", "x", xScale); - plot.project("y", "y", yScale); - var xAxis = new Plottable.Axis.Category(xScale, "bottom"); - var table = new Plottable.Component.Table([[plot], [xAxis]]).renderTo(svg); + plot = new Plottable.Plots.StackedBar(xScale, yScale); + plot.addDataset(new Plottable.Dataset(data1)); + plot.addDataset(new Plottable.Dataset(data2)); + plot.addDataset(new Plottable.Dataset(data3)); + plot.x((d) => d.x, xScale); + plot.y((d) => d.y, yScale); + var xAxis = new Plottable.Axes.Category(xScale, "bottom"); + new Plottable.Components.Table([[plot], [xAxis]]).renderTo(svg); }); it("renders correctly", () => { @@ -331,16 +330,16 @@ describe("Plots", () => { var cBars = [d3.select(bars[0][2]), d3.select(bars[0][6])]; - assert.closeTo(numAttr(aBars[0], "x"), numAttr(aBars[1], "x"), 0.01, "A bars at same x position"); - assert.operator(numAttr(aBars[0], "y"), ">", numAttr(aBars[1], "y"), "first dataset A bar under second"); + assert.closeTo(TestMethods.numAttr(aBars[0], "x"), TestMethods.numAttr(aBars[1], "x"), 0.01, "A bars at same x position"); + assert.operator(TestMethods.numAttr(aBars[0], "y"), ">", TestMethods.numAttr(aBars[1], "y"), "first dataset A bar under second"); - assert.closeTo(numAttr(bBars[0], "x"), numAttr(bBars[1], "x"), 0.01, "B bars at same x position"); - assert.closeTo(numAttr(bBars[1], "x"), numAttr(bBars[2], "x"), 0.01, "B bars at same x position"); - assert.operator(numAttr(bBars[0], "y"), ">", numAttr(bBars[1], "y"), "first dataset B bar under second"); - assert.operator(numAttr(bBars[1], "y"), ">", numAttr(bBars[2], "y"), "second dataset B bar under third"); + assert.closeTo(TestMethods.numAttr(bBars[0], "x"), TestMethods.numAttr(bBars[1], "x"), 0.01, "B bars at same x position"); + assert.closeTo(TestMethods.numAttr(bBars[1], "x"), TestMethods.numAttr(bBars[2], "x"), 0.01, "B bars at same x position"); + assert.operator(TestMethods.numAttr(bBars[0], "y"), ">", TestMethods.numAttr(bBars[1], "y"), "first dataset B bar under second"); + assert.operator(TestMethods.numAttr(bBars[1], "y"), ">", TestMethods.numAttr(bBars[2], "y"), "second dataset B bar under third"); - assert.closeTo(numAttr(cBars[0], "x"), numAttr(cBars[1], "x"), 0.01, "C bars at same x position"); - assert.operator(numAttr(cBars[0], "y"), ">", numAttr(cBars[1], "y"), "first dataset C bar under second"); + assert.closeTo(TestMethods.numAttr(cBars[0], "x"), TestMethods.numAttr(cBars[1], "x"), 0.01, "C bars at same x position"); + assert.operator(TestMethods.numAttr(cBars[0], "y"), ">", TestMethods.numAttr(cBars[1], "y"), "first dataset C bar under second"); svg.remove(); }); @@ -348,16 +347,14 @@ describe("Plots", () => { describe("Horizontal Stacked Bar Plot Non-Overlapping Datasets", () => { var svg: D3.Selection; - var plot: Plottable.Plot.StackedBar; + var plot: Plottable.Plots.StackedBar; var SVG_WIDTH = 600; var SVG_HEIGHT = 400; - var numAttr = (s: D3.Selection, a: string) => parseFloat(s.attr(a)); - beforeEach(() => { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Category(); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Category(); var data1 = [ {y: "A", x: 1, type: "a"}, @@ -373,12 +370,12 @@ describe("Plots", () => { {y: "C", x: 7, type: "c"} ]; - plot = new Plottable.Plot.StackedBar(xScale, yScale, false); - plot.addDataset(data1); - plot.addDataset(data2); - plot.addDataset(data3); - plot.project("x", "x", xScale); - plot.project("y", "y", yScale); + plot = new Plottable.Plots.StackedBar(xScale, yScale, false); + plot.addDataset(new Plottable.Dataset(data1)); + plot.addDataset(new Plottable.Dataset(data2)); + plot.addDataset(new Plottable.Dataset(data3)); + plot.x((d) => d.x, xScale); + plot.y((d) => d.y, yScale); plot.renderTo(svg); }); @@ -393,22 +390,21 @@ describe("Plots", () => { var cBars = [d3.select(bars[0][2]), d3.select(bars[0][6])]; - assert.closeTo(numAttr(aBars[0], "y"), numAttr(aBars[1], "y"), 0.01, "A bars at same y position"); - assert.operator(numAttr(aBars[0], "x"), "<", numAttr(aBars[1], "x"), "first dataset A bar under second"); + assert.closeTo(TestMethods.numAttr(aBars[0], "y"), TestMethods.numAttr(aBars[1], "y"), 0.01, "A bars at same y position"); + assert.operator(TestMethods.numAttr(aBars[0], "x"), "<", TestMethods.numAttr(aBars[1], "x"), "first dataset A bar under second"); - assert.closeTo(numAttr(bBars[0], "y"), numAttr(bBars[1], "y"), 0.01, "B bars at same y position"); - assert.closeTo(numAttr(bBars[1], "y"), numAttr(bBars[2], "y"), 0.01, "B bars at same y position"); - assert.operator(numAttr(bBars[0], "x"), "<", numAttr(bBars[1], "x"), "first dataset B bar under second"); - assert.operator(numAttr(bBars[1], "x"), "<", numAttr(bBars[2], "x"), "second dataset B bar under third"); + assert.closeTo(TestMethods.numAttr(bBars[0], "y"), TestMethods.numAttr(bBars[1], "y"), 0.01, "B bars at same y position"); + assert.closeTo(TestMethods.numAttr(bBars[1], "y"), TestMethods.numAttr(bBars[2], "y"), 0.01, "B bars at same y position"); + assert.operator(TestMethods.numAttr(bBars[0], "x"), "<", TestMethods.numAttr(bBars[1], "x"), "first dataset B bar under second"); + assert.operator(TestMethods.numAttr(bBars[1], "x"), "<", TestMethods.numAttr(bBars[2], "x"), "second dataset B bar under third"); - assert.closeTo(numAttr(cBars[0], "y"), numAttr(cBars[1], "y"), 0.01, "C bars at same y position"); - assert.operator(numAttr(cBars[0], "x"), "<", numAttr(cBars[1], "x"), "first dataset C bar under second"); + assert.closeTo(TestMethods.numAttr(cBars[0], "y"), TestMethods.numAttr(cBars[1], "y"), 0.01, "C bars at same y position"); + assert.operator(TestMethods.numAttr(cBars[0], "x"), "<", TestMethods.numAttr(cBars[1], "x"), "first dataset C bar under second"); svg.remove(); }); }); - describe("fail safe tests", () => { it("conversion fails should be silent in Plot.StackedBar", () => { @@ -418,23 +414,23 @@ describe("Plots", () => { var data2 = [ { x: "A", y: 1, fill: "red"}, ]; - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Linear(); + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Linear(); - var plot = new Plottable.Plot.StackedBar(xScale, yScale); - plot.addDataset("d1", data1); - plot.addDataset("d2", data2); - plot.project("fill", "fill"); - plot.project("x", "x", xScale).project("y", "y", yScale); + var plot = new Plottable.Plots.StackedBar(xScale, yScale); + plot.addDataset(new Plottable.Dataset(data1)); + plot.addDataset(new Plottable.Dataset(data2)); + plot.attr("fill", "fill"); + plot.x((d: any) => d.x, xScale).y((d: any) => d.y, yScale); - var ds1FirstColumnOffset = ( plot)._key2PlotDatasetKey.get("d1").plotMetadata.offsets.get("A"); - var ds2FirstColumnOffset = ( plot)._key2PlotDatasetKey.get("d2").plotMetadata.offsets.get("A"); + var ds1FirstColumnOffset = ( plot)._key2PlotDatasetKey.get("_0").plotMetadata.offsets.get("A"); + var ds2FirstColumnOffset = ( plot)._key2PlotDatasetKey.get("_1").plotMetadata.offsets.get("A"); - assert.strictEqual(typeof ds1FirstColumnOffset, "number", "ds1 offset should be a number"); - assert.strictEqual(typeof ds2FirstColumnOffset, "number", "ds2 offset should be a number"); + assert.strictEqual(typeof ds1FirstColumnOffset, "number", "ds0 offset should be a number"); + assert.strictEqual(typeof ds2FirstColumnOffset, "number", "ds1 offset should be a number"); - assert.isFalse(Plottable._Util.Methods.isNaN(ds1FirstColumnOffset), "ds1 offset should not be NaN"); - assert.isFalse(Plottable._Util.Methods.isNaN(ds1FirstColumnOffset), "ds2 offset should not be NaN"); + assert.isFalse(Plottable.Utils.Methods.isNaN(ds1FirstColumnOffset), "ds0 offset should not be NaN"); + assert.isFalse(Plottable.Utils.Methods.isNaN(ds1FirstColumnOffset), "ds1 offset should not be NaN"); }); it("bad values on the primary axis should default to 0 (be ignored)", () => { @@ -454,28 +450,29 @@ describe("Plots", () => { { x: "A", y: 3, fill: "pink"}, ]; - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Linear(); + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Linear(); - var plot = new Plottable.Plot.StackedBar(xScale, yScale); - plot.addDataset("d1", data1); - plot.addDataset("d2", data2); - plot.addDataset("d3", data3); - plot.addDataset("d4", data4); - plot.addDataset("d5", data5); - plot.project("fill", "fill"); - plot.project("x", "x", xScale).project("y", "y", yScale); + var plot = new Plottable.Plots.StackedBar(xScale, yScale); + plot.addDataset(new Plottable.Dataset(data1)); + plot.addDataset(new Plottable.Dataset(data2)); + plot.addDataset(new Plottable.Dataset(data3)); + plot.addDataset(new Plottable.Dataset(data4)); + plot.addDataset(new Plottable.Dataset(data5)); + plot.attr("fill", "fill"); + plot.x((d: any) => d.x, xScale).y((d: any) => d.y, yScale); - var offset1 = ( plot)._key2PlotDatasetKey.get("d1").plotMetadata.offsets.get("A"); - var offset3 = ( plot)._key2PlotDatasetKey.get("d3").plotMetadata.offsets.get("A"); - var offset5 = ( plot)._key2PlotDatasetKey.get("d5").plotMetadata.offsets.get("A"); + var keys = ( plot)._key2PlotDatasetKey.keys(); + var offset0 = ( plot)._key2PlotDatasetKey.get(keys[0]).plotMetadata.offsets.get("A"); + var offset2 = ( plot)._key2PlotDatasetKey.get(keys[2]).plotMetadata.offsets.get("A"); + var offset4 = ( plot)._key2PlotDatasetKey.get(keys[4]).plotMetadata.offsets.get("A"); - assert.strictEqual(offset1, 0, + assert.strictEqual(offset0, 0, "Plot columns should start from offset 0 (at the very bottom)"); - assert.strictEqual(offset3, 1, - "Bar 3 should have offset 1, because bar 2 was not rendered"); - assert.strictEqual(offset5, 3, - "Bar 5 should have offset 3, because bar 4 was not rendered"); + assert.strictEqual(offset2, 1, + "third bar should have offset 1, because second bar was not rendered"); + assert.strictEqual(offset4, 3, + "fifth bar should have offset 3, because fourth bar was not rendered"); }); }); }); diff --git a/test/components/plots/stackedPlotTests.ts b/test/components/plots/stackedPlotTests.ts index 2aff5b9846..9846602fda 100644 --- a/test/components/plots/stackedPlotTests.ts +++ b/test/components/plots/stackedPlotTests.ts @@ -5,161 +5,163 @@ var assert = chai.assert; describe("Plots", () => { describe("Stacked Plot Stacking", () => { - var stackedPlot: Plottable.Plot.AbstractStacked; - var SVG_WIDTH = 600; - var SVG_HEIGHT = 400; + var stackedPlot: Plottable.Stacked; beforeEach(() => { - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - stackedPlot = new Plottable.Plot.AbstractStacked(xScale, yScale); - stackedPlot.project("x", "x", xScale); - stackedPlot.project("y", "y", yScale); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); + stackedPlot = new Plottable.Stacked(xScale, yScale); + stackedPlot.x((d) => d.x, xScale); + stackedPlot.y((d) => d.y, yScale); - ( stackedPlot)._getDrawer = (key: string) => new Plottable._Drawer.AbstractDrawer(key); + ( stackedPlot)._getDrawer = (key: string) => new Plottable.Drawers.AbstractDrawer(key); ( stackedPlot)._isVertical = true; }); it("uses positive offset on stacking the 0 value", () => { - var data1 = [ + var data0 = [ {x: 1, y: 1}, {x: 3, y: 1} ]; - var data2 = [ + var data1 = [ {x: 1, y: 0}, {x: 3, y: 1} ]; - var data3 = [ + var data2 = [ {x: 1, y: -1}, {x: 3, y: 1} ]; - var data4 = [ + var data3 = [ {x: 1, y: 1}, {x: 3, y: 1} ]; - var data5 = [ + var data4 = [ {x: 1, y: 0}, {x: 3, y: 1} ]; - stackedPlot.addDataset("d1", data1); - stackedPlot.addDataset("d2", data2); - stackedPlot.addDataset("d3", data3); - stackedPlot.addDataset("d4", data4); - stackedPlot.addDataset("d5", data5); - - var ds2PlotMetadata = ( stackedPlot)._key2PlotDatasetKey.get("d2").plotMetadata; - var ds5PlotMetadata = ( stackedPlot)._key2PlotDatasetKey.get("d5").plotMetadata; - assert.strictEqual(ds2PlotMetadata.offsets.get("1"), 1, "positive offset was used"); - assert.strictEqual(ds5PlotMetadata.offsets.get("1"), 2, "positive offset was used"); + stackedPlot.addDataset(new Plottable.Dataset(data0)); + stackedPlot.addDataset(new Plottable.Dataset(data1)); + stackedPlot.addDataset(new Plottable.Dataset(data2)); + stackedPlot.addDataset(new Plottable.Dataset(data3)); + stackedPlot.addDataset(new Plottable.Dataset(data4)); + + // HACKHACK #1984: Dataset keys are being removed, so these are internal keys + var keys = ( stackedPlot)._key2PlotDatasetKey.keys(); + var ds1PlotMetadata = ( stackedPlot)._key2PlotDatasetKey.get(keys[1]).plotMetadata; + var ds4PlotMetadata = ( stackedPlot)._key2PlotDatasetKey.get(keys[4]).plotMetadata; + assert.strictEqual(ds1PlotMetadata.offsets.get("1"), 1, "positive offset was used"); + assert.strictEqual(ds4PlotMetadata.offsets.get("1"), 2, "positive offset was used"); }); it("uses negative offset on stacking the 0 value on all negative/0 valued data", () => { - var data1 = [ + var data0 = [ {x: 1, y: -2} ]; - var data2 = [ + var data1 = [ {x: 1, y: 0} ]; - var data3 = [ + var data2 = [ {x: 1, y: -1} ]; - var data4 = [ + var data3 = [ {x: 1, y: 0} ]; - stackedPlot.addDataset("d1", data1); - stackedPlot.addDataset("d2", data2); - stackedPlot.addDataset("d3", data3); - stackedPlot.addDataset("d4", data4); - - var ds2PlotMetadata = ( stackedPlot)._key2PlotDatasetKey.get("d2").plotMetadata; - var ds4PlotMetadata = ( stackedPlot)._key2PlotDatasetKey.get("d4").plotMetadata; - assert.strictEqual(ds2PlotMetadata.offsets.get("1"), -2, "positive offset was used"); - assert.strictEqual(ds4PlotMetadata.offsets.get("1"), -3, "positive offset was used"); + stackedPlot.addDataset(new Plottable.Dataset(data0)); + stackedPlot.addDataset(new Plottable.Dataset(data1)); + stackedPlot.addDataset(new Plottable.Dataset(data2)); + stackedPlot.addDataset(new Plottable.Dataset(data3)); + + // HACKHACK #1984: Dataset keys are being removed, so these are internal keys + var keys = ( stackedPlot)._key2PlotDatasetKey.keys(); + var ds1PlotMetadata = ( stackedPlot)._key2PlotDatasetKey.get(keys[1]).plotMetadata; + var ds3PlotMetadata = ( stackedPlot)._key2PlotDatasetKey.get(keys[3]).plotMetadata; + assert.strictEqual(ds1PlotMetadata.offsets.get("1"), -2, "positive offset was used"); + assert.strictEqual(ds3PlotMetadata.offsets.get("1"), -3, "positive offset was used"); }); it("project can be called after addDataset", () => { - var data1 = [ + var data0 = [ { a: 1, b: 2 } ]; - var data2 = [ + var data1 = [ { a: 1, b: 4 } ]; - stackedPlot.addDataset("d1", data1); - stackedPlot.addDataset("d2", data2); - var ds1PlotMetadata = ( stackedPlot)._key2PlotDatasetKey.get("d1").plotMetadata; - var ds2PlotMetadata = ( stackedPlot)._key2PlotDatasetKey.get("d2").plotMetadata; + stackedPlot.addDataset(new Plottable.Dataset(data0)); + stackedPlot.addDataset(new Plottable.Dataset(data1)); + + // HACKHACK #1984: Dataset keys are being removed, so these are internal keys + var keys = ( stackedPlot)._key2PlotDatasetKey.keys(); + var ds0PlotMetadata = ( stackedPlot)._key2PlotDatasetKey.get(keys[0]).plotMetadata; + var ds1PlotMetadata = ( stackedPlot)._key2PlotDatasetKey.get(keys[1]).plotMetadata; - assert.isTrue(isNaN(ds1PlotMetadata.offsets.get("1")), "stacking is initially incorrect"); + assert.isTrue(isNaN(ds0PlotMetadata.offsets.get("1")), "stacking is initially incorrect"); - stackedPlot.project("x", "a"); - stackedPlot.project("y", "b"); + stackedPlot.x((d) => d.a); + stackedPlot.y((d) => d.b); - assert.strictEqual(ds2PlotMetadata.offsets.get("1"), 2, "stacking was done correctly"); + assert.strictEqual(ds1PlotMetadata.offsets.get("1"), 2, "stacking was done correctly"); }); it("strings are coerced to numbers for stacking", () => { - var data1 = [ + var data0 = [ { x: 1, y: "-2" } ]; - var data2 = [ + var data1 = [ { x: 1, y: "3" } ]; - var data3 = [ + var data2 = [ { x: 1, y: "-1" } ]; - var data4 = [ + var data3 = [ { x: 1, y: "5" } ]; - var data5 = [ + var data4 = [ { x: 1, y: "1" } ]; - var data6 = [ + var data5 = [ { x: 1, y: "-1" } ]; - stackedPlot.addDataset("d1", data1); - stackedPlot.addDataset("d2", data2); - stackedPlot.addDataset("d3", data3); - stackedPlot.addDataset("d4", data4); - stackedPlot.addDataset("d5", data5); - stackedPlot.addDataset("d6", data6); - - var ds3PlotMetadata = ( stackedPlot)._key2PlotDatasetKey.get("d3").plotMetadata; - var ds4PlotMetadata = ( stackedPlot)._key2PlotDatasetKey.get("d4").plotMetadata; - var ds5PlotMetadata = ( stackedPlot)._key2PlotDatasetKey.get("d5").plotMetadata; - var ds6PlotMetadata = ( stackedPlot)._key2PlotDatasetKey.get("d6").plotMetadata; - - assert.strictEqual(ds3PlotMetadata.offsets.get("1"), -2, "stacking on data1 numerical y value"); - assert.strictEqual(ds4PlotMetadata.offsets.get("1"), 3, "stacking on data2 numerical y value"); - assert.strictEqual(ds5PlotMetadata.offsets.get("1"), 8, "stacking on data1 + data3 numerical y values"); - assert.strictEqual(ds6PlotMetadata.offsets.get("1"), -3, "stacking on data2 + data4 numerical y values"); + stackedPlot.addDataset(new Plottable.Dataset(data0)); + stackedPlot.addDataset(new Plottable.Dataset(data1)); + stackedPlot.addDataset(new Plottable.Dataset(data2)); + stackedPlot.addDataset(new Plottable.Dataset(data3)); + stackedPlot.addDataset(new Plottable.Dataset(data4)); + stackedPlot.addDataset(new Plottable.Dataset(data5)); + + // HACKHACK #1984: Dataset keys are being removed, so these are internal keys + var keys = ( stackedPlot)._key2PlotDatasetKey.keys(); + var ds2PlotMetadata = ( stackedPlot)._key2PlotDatasetKey.get(keys[2]).plotMetadata; + var ds3PlotMetadata = ( stackedPlot)._key2PlotDatasetKey.get(keys[3]).plotMetadata; + var ds4PlotMetadata = ( stackedPlot)._key2PlotDatasetKey.get(keys[4]).plotMetadata; + var ds5PlotMetadata = ( stackedPlot)._key2PlotDatasetKey.get(keys[5]).plotMetadata; + + assert.strictEqual(ds2PlotMetadata.offsets.get("1"), -2, "stacking on data1 numerical y value"); + assert.strictEqual(ds3PlotMetadata.offsets.get("1"), 3, "stacking on data2 numerical y value"); + assert.strictEqual(ds4PlotMetadata.offsets.get("1"), 8, "stacking on data1 + data3 numerical y values"); + assert.strictEqual(ds5PlotMetadata.offsets.get("1"), -3, "stacking on data2 + data4 numerical y values"); assert.deepEqual(( stackedPlot)._stackedExtent, [-4, 9], "stacked extent is as normal"); }); it("stacks correctly on empty data", () => { - var data1: any[] = [ - ]; - var data2: any[] = [ - ]; - - stackedPlot.addDataset(data1); - stackedPlot.addDataset(data2); + var dataset1 = new Plottable.Dataset([]); + var dataset2 = new Plottable.Dataset([]); - assert.deepEqual(data1, [], "empty data causes no stacking to happen"); - assert.deepEqual(data2, [], "empty data causes no stacking to happen"); + assert.doesNotThrow(() => stackedPlot.addDataset(dataset1), Error); + assert.doesNotThrow(() => stackedPlot.addDataset(dataset2), Error); }); it("does not crash on stacking no datasets", () => { - var data1 = [ + var dataset1 = new Plottable.Dataset([ {x: 1, y: -2} - ]; + ]); - stackedPlot.addDataset("a", data1); - assert.doesNotThrow(() => stackedPlot.removeDataset("a"), Error); + stackedPlot.addDataset(dataset1); + assert.doesNotThrow(() => stackedPlot.removeDataset(dataset1), Error); }); }); @@ -167,35 +169,35 @@ describe("Plots", () => { var svg: D3.Selection; var SVG_WIDTH = 600; var SVG_HEIGHT = 400; - var yScale: Plottable.Scale.Linear; - var xScale: Plottable.Scale.Linear; - var data1: any[]; - var data2: any[]; + var yScale: Plottable.Scales.Linear; + var xScale: Plottable.Scales.Linear; + var dataset1: Plottable.Dataset; + var dataset2: Plottable.Dataset; beforeEach(() => { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - xScale = new Plottable.Scale.Linear().domain([1, 2]); - yScale = new Plottable.Scale.Linear(); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + xScale = new Plottable.Scales.Linear().domain([1, 2]); + yScale = new Plottable.Scales.Linear(); - data1 = [ + dataset1 = new Plottable.Dataset([ {x: 1, y: 1}, {x: 2, y: 2}, {x: 3, y: 8} - ]; + ]); - data2 = [ + dataset2 = new Plottable.Dataset([ {x: 1, y: 2}, {x: 2, y: 2}, {x: 3, y: 3} - ]; + ]); }); it("auto scales correctly on stacked area", () => { - var plot = new Plottable.Plot.StackedArea(xScale, yScale) - .addDataset(data1) - .addDataset(data2) - .project("x", "x", xScale) - .project("y", "y", yScale); + var plot = new Plottable.Plots.StackedArea(xScale, yScale); + plot.addDataset(dataset1) + .addDataset(dataset2); + plot.x((d) => d.x, xScale) + .y((d: any) => d.y, yScale); (plot).automaticallyAdjustYScaleOverVisiblePoints(true); plot.renderTo(svg); assert.deepEqual(yScale.domain(), [0, 4.5], "auto scales takes stacking into account"); @@ -203,11 +205,11 @@ describe("Plots", () => { }); it("auto scales correctly on stacked bar", () => { - var plot = new Plottable.Plot.StackedBar(xScale, yScale) - .addDataset(data1) - .addDataset(data2) - .project("x", "x", xScale) - .project("y", "y", yScale); + var plot = new Plottable.Plots.StackedBar(xScale, yScale); + plot.addDataset(dataset1) + .addDataset(dataset2); + plot.x((d) => d.x, xScale) + .y((d: any) => d.y, yScale); (plot).automaticallyAdjustYScaleOverVisiblePoints(true); plot.renderTo(svg); assert.deepEqual(yScale.domain(), [0, 4.5], "auto scales takes stacking into account"); @@ -219,35 +221,36 @@ describe("Plots", () => { var svg: D3.Selection; var SVG_WIDTH = 600; var SVG_HEIGHT = 400; - var yScale: Plottable.Scale.Linear; - var xScale: Plottable.Scale.Category; - var data1: any[]; - var data2: any[]; + var yScale: Plottable.Scales.Linear; + var xScale: Plottable.Scales.Category; + var dataset1: Plottable.Dataset; + var dataset2: Plottable.Dataset; beforeEach(() => { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - xScale = new Plottable.Scale.Category().domain(["a", "b"]); - yScale = new Plottable.Scale.Linear(); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + xScale = new Plottable.Scales.Category().domain(["a", "b"]); + yScale = new Plottable.Scales.Linear(); - data1 = [ + dataset1 = new Plottable.Dataset([ {x: "a", y: 1}, {x: "b", y: 2}, {x: "c", y: 8} - ]; + ]); - data2 = [ + dataset2 = new Plottable.Dataset([ {x: "a", y: 2}, {x: "b", y: 2}, {x: "c", y: 3} - ]; + ]); }); - it("auto scales correctly on stacked area", () => { - var plot = new Plottable.Plot.StackedArea(yScale, yScale) - .addDataset(data1) - .addDataset(data2) - .project("x", "x", xScale) - .project("y", "y", yScale); + // TODO: #2003 - The test should be taking in xScales but the StackedArea signature disallows category scales + it.skip("auto scales correctly on stacked area", () => { + var plot = new Plottable.Plots.StackedArea(yScale, yScale); + plot.addDataset(dataset1) + .addDataset(dataset2); + plot.x((d) => d.x, yScale) + .y((d: any) => d.y, yScale); (plot).automaticallyAdjustYScaleOverVisiblePoints(true); plot.renderTo(svg); assert.deepEqual(yScale.domain(), [0, 4.5], "auto scales takes stacking into account"); @@ -255,11 +258,11 @@ describe("Plots", () => { }); it("auto scales correctly on stacked bar", () => { - var plot = new Plottable.Plot.StackedBar(xScale, yScale) - .addDataset(data1) - .addDataset(data2) - .project("x", "x", xScale) - .project("y", "y", yScale); + var plot = new Plottable.Plots.StackedBar(xScale, yScale); + plot.addDataset(dataset1) + .addDataset(dataset2); + plot.x((d) => d.x, xScale) + .y((d: any) => d.y, yScale); (plot).automaticallyAdjustYScaleOverVisiblePoints(true); plot.renderTo(svg); assert.deepEqual(yScale.domain(), [0, 4.5], "auto scales takes stacking into account"); @@ -269,19 +272,19 @@ describe("Plots", () => { describe("scale extent updates", () => { var svg: D3.Selection; - var xScale: Plottable.Scale.Category; - var yScale: Plottable.Scale.Linear; - var stackedBarPlot: Plottable.Plot.StackedBar; + var xScale: Plottable.Scales.Category; + var yScale: Plottable.Scales.Linear; + var stackedBarPlot: Plottable.Plots.StackedBar; beforeEach(() => { - svg = generateSVG(600, 400); + svg = TestMethods.generateSVG(600, 400); - xScale = new Plottable.Scale.Category(); - yScale = new Plottable.Scale.Linear(); + xScale = new Plottable.Scales.Category(); + yScale = new Plottable.Scales.Linear(); - stackedBarPlot = new Plottable.Plot.StackedBar(xScale, yScale); - stackedBarPlot.project("x", "key", xScale); - stackedBarPlot.project("y", "value", yScale); + stackedBarPlot = new Plottable.Plots.StackedBar(xScale, yScale); + stackedBarPlot.x((d) => d.key, xScale); + stackedBarPlot.y((d) => d.value, yScale); stackedBarPlot.renderTo(svg); }); @@ -304,9 +307,10 @@ describe("Plots", () => { { key: "b", value: -2 } ]; + var dataset1 = new Plottable.Dataset(data1); var dataset2 = new Plottable.Dataset(data2); - stackedBarPlot.addDataset("d1", data1); - stackedBarPlot.addDataset("d2", dataset2); + stackedBarPlot.addDataset(dataset1); + stackedBarPlot.addDataset(dataset2); assert.closeTo(yScale.domain()[0], -6, 1, "min stacked extent is as normal"); assert.closeTo(yScale.domain()[1], 4, 1, "max stacked extent is as normal"); @@ -316,7 +320,6 @@ describe("Plots", () => { assert.closeTo(yScale.domain()[0], -4, 1, "min stacked extent decreases in magnitude"); assert.closeTo(yScale.domain()[1], 2, 1, "max stacked extent decreases in magnitude"); }); - }); }); diff --git a/test/components/plots/xyPlotTests.ts b/test/components/plots/xyPlotTests.ts new file mode 100644 index 0000000000..03a0f7bde1 --- /dev/null +++ b/test/components/plots/xyPlotTests.ts @@ -0,0 +1,114 @@ +/// +var assert = chai.assert; + +describe("Plots", () => { + describe("XY Plot", () => { + var svg: D3.Selection; + var xScale: Plottable.Scales.Linear; + var yScale: Plottable.Scales.Linear; + var plot: Plottable.XYPlot; + var simpleDataset = new Plottable.Dataset([ + { a: -6, b: 6 }, + { a: -2, b: 2 }, + { a: 2, b: -2 }, + { a: 6, b: -6 } + ]); + var xAccessor = (d: any) => d.a; + var yAccessor = (d: any) => d.b; + + beforeEach(() => { + svg = TestMethods.generateSVG(500, 500); + xScale = new Plottable.Scales.Linear(); + yScale = new Plottable.Scales.Linear(); + plot = new Plottable.XYPlot(xScale, yScale); + plot.addDataset(simpleDataset); + plot.x(xAccessor, xScale) + .y(yAccessor, yScale) + .renderTo(svg); + }); + + it("automatically adjusting Y domain over visible points", () => { + xScale.domain([-3, 3]); + assert.deepEqual(yScale.domain(), [-7, 7], "domain has not been adjusted to visible points"); + plot.automaticallyAdjustYScaleOverVisiblePoints(true); + assert.deepEqual(yScale.domain(), [-2.5, 2.5], "domain has been adjusted to visible points"); + plot.automaticallyAdjustYScaleOverVisiblePoints(false); + svg.remove(); + }); + + it("automatically adjusting Y domain when no points are visible", () => { + plot.automaticallyAdjustYScaleOverVisiblePoints(true); + xScale.domain([-0.5, 0.5]); + assert.deepEqual(yScale.domain(), [-1, 1], "domain equivalent to that with empty dataset"); + svg.remove(); + }); + + it("automatically adjusting Y domain when X scale is replaced", () => { + plot.automaticallyAdjustYScaleOverVisiblePoints(true); + var newXScale = new Plottable.Scales.Linear().domain([-3, 3]); + plot.x(xAccessor, newXScale); + assert.deepEqual(yScale.domain(), [-2.5, 2.5], "domain has been adjusted to visible points on new X scale domain"); + xScale.domain([-2, 2]); + assert.deepEqual(yScale.domain(), [-2.5, 2.5], "changing domain of original X scale doesn't affect Y scale's domain"); + svg.remove(); + }); + + it("automatically adjusting X domain over visible points", () => { + yScale.domain([-3, 3]); + assert.deepEqual(xScale.domain(), [-7, 7], "domain has not been adjusted to visible points"); + plot.automaticallyAdjustXScaleOverVisiblePoints(true); + assert.deepEqual(xScale.domain(), [-2.5, 2.5], "domain has been adjusted to visible points"); + plot.automaticallyAdjustXScaleOverVisiblePoints(false); + svg.remove(); + }); + + it("automatically adjusting X domain when no points are visible", () => { + plot.automaticallyAdjustXScaleOverVisiblePoints(true); + yScale.domain([-0.5, 0.5]); + assert.deepEqual(xScale.domain(), [-1, 1], "domain equivalent to that with empty dataset"); + svg.remove(); + }); + + it("automatically adjusting X domain when Y scale is replaced", () => { + plot.automaticallyAdjustXScaleOverVisiblePoints(true); + var newYScale = new Plottable.Scales.Linear().domain([-3, 3]); + plot.y(yAccessor, newYScale); + assert.deepEqual(xScale.domain(), [-2.5, 2.5], "domain has been adjusted to visible points on new Y scale domain"); + yScale.domain([-2, 2]); + assert.deepEqual(xScale.domain(), [-2.5, 2.5], "changing domain of original Y scale doesn't affect X scale's domain"); + svg.remove(); + }); + + it("showAllData()", () => { + plot.automaticallyAdjustYScaleOverVisiblePoints(true); + xScale.domain([-0.5, 0.5]); + plot.showAllData(); + assert.deepEqual(yScale.domain(), [-7, 7], "domain has been adjusted to show all data"); + assert.deepEqual(xScale.domain(), [-7, 7], "domain has been adjusted to show all data"); + svg.remove(); + }); + + it("show all data without auto adjust domain", () => { + plot.automaticallyAdjustYScaleOverVisiblePoints(true); + xScale.domain([-0.5, 0.5]); + plot.automaticallyAdjustYScaleOverVisiblePoints(false); + plot.showAllData(); + assert.deepEqual(yScale.domain(), [-7, 7], "domain has been adjusted to show all data"); + assert.deepEqual(xScale.domain(), [-7, 7], "domain has been adjusted to show all data"); + svg.remove(); + }); + + it("listeners are deregistered after removal", () => { + plot.automaticallyAdjustYScaleOverVisiblePoints(true); + plot.destroy(); + + var xScaleCallbacks = ( xScale)._callbacks.values(); + assert.strictEqual(xScaleCallbacks.length, 0, "the plot is no longer attached to xScale"); + + var yScaleCallbacks = ( yScale)._callbacks.values(); + assert.strictEqual(yScaleCallbacks.length, 0, "the plot is no longer attached to yScale"); + + svg.remove(); + }); + }); +}); diff --git a/test/components/selectionBoxLayerTests.ts b/test/components/selectionBoxLayerTests.ts index bcdc93dd31..a4cc79687b 100644 --- a/test/components/selectionBoxLayerTests.ts +++ b/test/components/selectionBoxLayerTests.ts @@ -4,8 +4,8 @@ var assert = chai.assert; describe("SelectionBoxLayer", () => { it("boxVisible()", () => { - var svg = generateSVG(); - var sbl = new Plottable.Component.SelectionBoxLayer(); + var svg = TestMethods.generateSVG(); + var sbl = new Plottable.Components.SelectionBoxLayer(); sbl.renderTo(svg); var selectionBox = svg.select(".selection-box"); @@ -23,8 +23,8 @@ describe("SelectionBoxLayer", () => { }); it("bounds()", () => { - var svg = generateSVG(); - var sbl = new Plottable.Component.SelectionBoxLayer(); + var svg = TestMethods.generateSVG(); + var sbl = new Plottable.Components.SelectionBoxLayer(); var topLeft: Plottable.Point = { x: 100, @@ -44,7 +44,7 @@ describe("SelectionBoxLayer", () => { function assertCorrectRendering(expectedTL: Plottable.Point, expectedBR: Plottable.Point, msg: string) { var selectionBox = svg.select(".selection-box"); - var bbox = Plottable._Util.DOM.getBBox(selectionBox); + var bbox = Plottable.Utils.DOM.getBBox(selectionBox); assert.strictEqual(bbox.x, expectedTL.x, msg + " (x-origin)"); assert.strictEqual(bbox.x, expectedTL.y, msg + " (y-origin)"); assert.strictEqual(bbox.width, expectedBR.x - expectedTL.x, msg + " (width)"); @@ -69,10 +69,10 @@ describe("SelectionBoxLayer", () => { }); it("has an effective size of 0, but will occupy all offered space", () => { - var sbl = new Plottable.Component.SelectionBoxLayer(); - var request = sbl._requestedSpace(400, 400); - verifySpaceRequest(request, 0, 0, false, false, "occupies and asks for no space"); - assert.isTrue(sbl._isFixedWidth(), "fixed width"); - assert.isTrue(sbl._isFixedHeight(), "fixed height"); + var sbl = new Plottable.Components.SelectionBoxLayer(); + var request = sbl.requestedSpace(400, 400); + TestMethods.verifySpaceRequest(request, 0, 0, "does not request any space"); + assert.isTrue(sbl.fixedWidth(), "fixed width"); + assert.isTrue(sbl.fixedHeight(), "fixed height"); }); }); diff --git a/test/components/timeAxisTests.ts b/test/components/timeAxisTests.ts index 6183106562..93f64ab8c0 100644 --- a/test/components/timeAxisTests.ts +++ b/test/components/timeAxisTests.ts @@ -3,26 +3,26 @@ var assert = chai.assert; describe("TimeAxis", () => { - var scale: Plottable.Scale.Time; - var axis: Plottable.Axis.Time; + var scale: Plottable.Scales.Time; + var axis: Plottable.Axes.Time; beforeEach(() => { - scale = new Plottable.Scale.Time(); - axis = new Plottable.Axis.Time(scale, "bottom"); + scale = new Plottable.Scales.Time(); + axis = new Plottable.Axes.Time(scale, "bottom"); }); it("can not initialize vertical time axis", () => { - assert.throws(() => new Plottable.Axis.Time(scale, "left"), "horizontal"); - assert.throws(() => new Plottable.Axis.Time(scale, "right"), "horizontal"); + assert.throws(() => new Plottable.Axes.Time(scale, "left"), "horizontal"); + assert.throws(() => new Plottable.Axes.Time(scale, "right"), "horizontal"); }); it("cannot change time axis orientation to vertical", () => { - assert.throws(() => axis.orient("left"), "horizontal"); - assert.throws(() => axis.orient("right"), "horizontal"); - assert.equal(axis.orient(), "bottom", "orientation unchanged"); + assert.throws(() => axis.orientation("left"), "horizontal"); + assert.throws(() => axis.orientation("right"), "horizontal"); + assert.strictEqual(axis.orientation(), "bottom", "orientation unchanged"); }); it("Computing the default ticks doesn't error out for edge cases", () => { - var svg = generateSVG(400, 100); + var svg = TestMethods.generateSVG(400, 100); scale.range([0, 400]); // very large time span @@ -37,7 +37,7 @@ describe("TimeAxis", () => { }); it("Tick labels don't overlap", () => { - var svg = generateSVG(400, 100); + var svg = TestMethods.generateSVG(400, 100); scale.range([0, 400]); function checkDomain(domain: any[]) { @@ -46,7 +46,7 @@ describe("TimeAxis", () => { function checkLabelsForContainer(container: D3.Selection) { var visibleTickLabels = container - .selectAll("." + Plottable.Axis.AbstractAxis.TICK_LABEL_CLASS) + .selectAll("." + Plottable.Axis.TICK_LABEL_CLASS) .filter(function(d: any, i: number) { return d3.select(this).style("visibility") === "visible"; }); @@ -58,7 +58,7 @@ describe("TimeAxis", () => { box1 = visibleTickLabels[0][i].getBoundingClientRect(); box2 = visibleTickLabels[0][j].getBoundingClientRect(); - assert.isFalse(Plottable._Util.DOM.boxesOverlap(box1, box2), "tick labels don't overlap"); + assert.isFalse(Plottable.Utils.DOM.boxesOverlap(box1, box2), "tick labels don't overlap"); } } } @@ -84,13 +84,13 @@ describe("TimeAxis", () => { }); it("custom possible axis configurations", () => { - var svg = generateSVG(800, 100); - var scale = new Plottable.Scale.Time(); - var axis = new Plottable.Axis.Time(scale, "bottom"); + var svg = TestMethods.generateSVG(800, 100); + var scale = new Plottable.Scales.Time(); + var axis = new Plottable.Axes.Time(scale, "bottom"); var configurations = axis.axisConfigurations(); var newPossibleConfigurations = configurations.slice(0, 3); newPossibleConfigurations.forEach(axisConfig => axisConfig.forEach(tierConfig => { - tierConfig.interval = d3.time.minute; + tierConfig.interval = Plottable.TimeInterval.minute; tierConfig.step += 3; })); axis.axisConfigurations(newPossibleConfigurations); @@ -101,66 +101,66 @@ describe("TimeAxis", () => { scale.range([0, 800]); axis.renderTo(svg); var configs = newPossibleConfigurations[( axis)._mostPreciseConfigIndex]; - assert.deepEqual(configs[0].interval, d3.time.minute, "axis used new time unit"); + assert.deepEqual(configs[0].interval, Plottable.TimeInterval.minute, "axis used new time unit"); assert.deepEqual(configs[0].step, 4, "axis used new step"); svg.remove(); }); it("renders end ticks on either side", () => { var width = 500; - var svg = generateSVG(width, 100); - scale.domain(["2010", "2014"]); + var svg = TestMethods.generateSVG(width, 100); + scale.domain([new Date("2010-01-01"), new Date("2014-01-01")]); axis.renderTo(svg); var firstTick = d3.select(".tick-mark"); - assert.equal(firstTick.attr("x1"), 0, "xPos (x1) of first end tick is at the beginning of the axis container"); - assert.equal(firstTick.attr("x2"), 0, "xPos (x2) of first end tick is at the beginning of the axis container"); + assert.strictEqual(firstTick.attr("x1"), "0", "xPos (x1) of first end tick is at the beginning of the axis container"); + assert.strictEqual(firstTick.attr("x2"), "0", "xPos (x2) of first end tick is at the beginning of the axis container"); var lastTick = d3.select(d3.selectAll(".tick-mark")[0].pop()); - assert.equal(lastTick.attr("x1"), width, "xPos (x1) of last end tick is at the end of the axis container"); - assert.equal(lastTick.attr("x2"), width, "xPos (x2) of last end tick is at the end of the axis container"); + assert.strictEqual(lastTick.attr("x1"), String(width), "xPos (x1) of last end tick is at the end of the axis container"); + assert.strictEqual(lastTick.attr("x2"), String(width), "xPos (x2) of last end tick is at the end of the axis container"); svg.remove(); }); it("adds a class corresponding to the end-tick for the first and last ticks", () => { var width = 500; - var svg = generateSVG(width, 100); - scale.domain(["2010", "2014"]); + var svg = TestMethods.generateSVG(width, 100); + scale.domain([new Date("2010-01-01"), new Date("2014-01-01")]); axis.renderTo(svg); - var firstTick = d3.select("." + Plottable.Axis.AbstractAxis.TICK_MARK_CLASS); - assert.isTrue(firstTick.classed(Plottable.Axis.AbstractAxis.END_TICK_MARK_CLASS), "first end tick has the end-tick-mark class"); - var lastTick = d3.select(d3.selectAll("." + Plottable.Axis.AbstractAxis.TICK_MARK_CLASS)[0].pop()); - assert.isTrue(lastTick.classed(Plottable.Axis.AbstractAxis.END_TICK_MARK_CLASS), "last end tick has the end-tick-mark class"); + var firstTick = d3.select("." + Plottable.Axis.TICK_MARK_CLASS); + assert.isTrue(firstTick.classed(Plottable.Axis.END_TICK_MARK_CLASS), "first end tick has the end-tick-mark class"); + var lastTick = d3.select(d3.selectAll("." + Plottable.Axis.TICK_MARK_CLASS)[0].pop()); + assert.isTrue(lastTick.classed(Plottable.Axis.END_TICK_MARK_CLASS), "last end tick has the end-tick-mark class"); svg.remove(); }); it("tick labels do not overlap with tick marks", () => { - var svg = generateSVG(400, 100); - scale = new Plottable.Scale.Time(); + var svg = TestMethods.generateSVG(400, 100); + scale = new Plottable.Scales.Time(); scale.domain([new Date("2009-12-20"), new Date("2011-01-01")]); - axis = new Plottable.Axis.Time(scale, "bottom"); + axis = new Plottable.Axes.Time(scale, "bottom"); axis.renderTo(svg); - var tickRects = d3.selectAll("." + Plottable.Axis.AbstractAxis.TICK_MARK_CLASS)[0].map((mark: Element) => mark.getBoundingClientRect()); - var labelRects = d3.selectAll("." + Plottable.Axis.AbstractAxis.TICK_LABEL_CLASS) + var tickRects = d3.selectAll("." + Plottable.Axis.TICK_MARK_CLASS)[0].map((mark: Element) => mark.getBoundingClientRect()); + var labelRects = d3.selectAll("." + Plottable.Axis.TICK_LABEL_CLASS) .filter(function(d: Element, i: number) { return d3.select(this).style("visibility") === "visible"; })[0].map((label: Element) => label.getBoundingClientRect()); labelRects.forEach(function(labelRect: ClientRect) { tickRects.forEach(function(tickRect: ClientRect) { - assert.isFalse(Plottable._Util.DOM.boxesOverlap(labelRect, tickRect), "visible label does not overlap with a tick"); + assert.isFalse(Plottable.Utils.DOM.boxesOverlap(labelRect, tickRect), "visible label does not overlap with a tick"); }); }); svg.remove(); }); it("if the time only uses one tier, there should be no space left for the second tier", () => { - var svg = generateSVG(); - var xScale = new Plottable.Scale.Time(); + var svg = TestMethods.generateSVG(); + var xScale = new Plottable.Scales.Time(); xScale.domain([new Date("2013-03-23 12:00"), new Date("2013-04-03 0:00")]); - var xAxis = new Plottable.Axis.Time(xScale, "bottom"); + var xAxis = new Plottable.Axes.Time(xScale, "bottom"); xAxis.gutter(0); xAxis.axisConfigurations([ [ - {interval: d3.time.day, step: 2, formatter: Plottable.Formatters.time("%a %e")} + {interval: Plottable.TimeInterval.day, step: 2, formatter: Plottable.Formatters.time("%a %e")} ], ]); @@ -170,8 +170,8 @@ describe("TimeAxis", () => { xAxis.axisConfigurations([ [ - {interval: d3.time.day, step: 2, formatter: Plottable.Formatters.time("%a %e")}, - {interval: d3.time.day, step: 2, formatter: Plottable.Formatters.time("%a %e")} + {interval: Plottable.TimeInterval.day, step: 2, formatter: Plottable.Formatters.time("%a %e")}, + {interval: Plottable.TimeInterval.day, step: 2, formatter: Plottable.Formatters.time("%a %e")} ], ]); @@ -179,10 +179,9 @@ describe("TimeAxis", () => { assert.strictEqual(twoTierSize, oneTierSize * 2, "two-tier axis is twice as tall as one-tier axis"); - xAxis.axisConfigurations([ [ - {interval: d3.time.day, step: 2, formatter: Plottable.Formatters.time("%a %e")} + {interval: Plottable.TimeInterval.day, step: 2, formatter: Plottable.Formatters.time("%a %e")} ], ]); @@ -195,19 +194,18 @@ describe("TimeAxis", () => { }); it("three tier time axis should be possible", () => { - - var svg = generateSVG(); - var xScale = new Plottable.Scale.Time(); + var svg = TestMethods.generateSVG(); + var xScale = new Plottable.Scales.Time(); xScale.domain([new Date("2013-03-23 12:00"), new Date("2013-04-03 0:00")]); - var xAxis = new Plottable.Axis.Time(xScale, "bottom"); + var xAxis = new Plottable.Axes.Time(xScale, "bottom"); xAxis.gutter(0); xAxis.renderTo(svg); xAxis.axisConfigurations([ [ - {interval: d3.time.day, step: 2, formatter: Plottable.Formatters.time("%a %e")}, - {interval: d3.time.day, step: 2, formatter: Plottable.Formatters.time("%a %e")}, + {interval: Plottable.TimeInterval.day, step: 2, formatter: Plottable.Formatters.time("%a %e")}, + {interval: Plottable.TimeInterval.day, step: 2, formatter: Plottable.Formatters.time("%a %e")}, ], ]); @@ -215,9 +213,9 @@ describe("TimeAxis", () => { xAxis.axisConfigurations([ [ - {interval: d3.time.day, step: 2, formatter: Plottable.Formatters.time("%a %e")}, - {interval: d3.time.day, step: 2, formatter: Plottable.Formatters.time("%a %e")}, - {interval: d3.time.day, step: 2, formatter: Plottable.Formatters.time("%a %e")}, + {interval: Plottable.TimeInterval.day, step: 2, formatter: Plottable.Formatters.time("%a %e")}, + {interval: Plottable.TimeInterval.day, step: 2, formatter: Plottable.Formatters.time("%a %e")}, + {interval: Plottable.TimeInterval.day, step: 2, formatter: Plottable.Formatters.time("%a %e")}, ], ]); @@ -231,14 +229,14 @@ describe("TimeAxis", () => { }); it("many tier Axis.Time should not exceed the drawing area", () => { - var svg = generateSVG(400, 50); - var xScale = new Plottable.Scale.Time(); + var svg = TestMethods.generateSVG(400, 50); + var xScale = new Plottable.Scales.Time(); xScale.domain([new Date("2013-03-23 12:00"), new Date("2013-04-03 0:00")]); - var xAxis = new Plottable.Axis.Time(xScale, "bottom"); + var xAxis = new Plottable.Axes.Time(xScale, "bottom"); var tiersToCreate = 15; var configuration = Array.apply(null, Array(tiersToCreate)).map(() => { - return {interval: d3.time.day, step: 2, formatter: Plottable.Formatters.time("%a %e") }; + return {interval: Plottable.TimeInterval.day, step: 2, formatter: Plottable.Formatters.time("%a %e") }; }); xAxis.axisConfigurations([configuration]); @@ -251,13 +249,13 @@ describe("TimeAxis", () => { Math.floor(axisBoundingRect.top) <= Math.ceil(innerRect.top) + window.Pixel_CloseTo_Requirement; }; - var numberOfVisibleTiers = ( xAxis)._element - .selectAll("." + Plottable.Axis.Time.TIME_AXIS_TIER_CLASS) + ( xAxis)._element + .selectAll("." + Plottable.Axes.Time.TIME_AXIS_TIER_CLASS) .each(function(e: any, i: number) { var sel = d3.select(this); var visibility = sel.style("visibility"); - //HACKHACK window.getComputedStyle() is behaving weirdly in IE9. Further investigation required + // HACKHACK window.getComputedStyle() is behaving weirdly in IE9. Further investigation required if (visibility === "inherit") { visibility = getStyleInIE9(sel[0][0]); } diff --git a/test/components/interactive/xDragBoxLayerTests.ts b/test/components/xDragBoxLayerTests.ts similarity index 85% rename from test/components/interactive/xDragBoxLayerTests.ts rename to test/components/xDragBoxLayerTests.ts index e5642d8ca9..4ef0502d89 100644 --- a/test/components/interactive/xDragBoxLayerTests.ts +++ b/test/components/xDragBoxLayerTests.ts @@ -1,4 +1,4 @@ -/// +/// var assert = chai.assert; @@ -8,8 +8,8 @@ describe("Interactive Components", () => { var SVG_HEIGHT = 400; it("bounds()", () => { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var dbl = new Plottable.Component.XDragBoxLayer(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var dbl = new Plottable.Components.XDragBoxLayer(); dbl.boxVisible(true); dbl.renderTo(svg); @@ -37,8 +37,8 @@ describe("Interactive Components", () => { }); it("resizes only in x", () => { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var dbl = new Plottable.Component.XDragBoxLayer(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var dbl = new Plottable.Components.XDragBoxLayer(); dbl.boxVisible(true); dbl.resizable(true); dbl.renderTo(svg); @@ -63,7 +63,7 @@ describe("Interactive Components", () => { y: SVG_HEIGHT / 2 }; var target = dbl.background(); - triggerFakeDragSequence(target, actualBounds.bottomRight, dragTo); + TestMethods.triggerFakeDragSequence(target, actualBounds.bottomRight, dragTo); actualBounds = dbl.bounds(); assert.strictEqual(actualBounds.bottomRight.x, dragTo.x, "resized in x"); assert.strictEqual(actualBounds.topLeft.y, 0, "box still starts at top"); @@ -72,8 +72,8 @@ describe("Interactive Components", () => { }); it("stays full height after resizing", () => { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var dbl = new Plottable.Component.XDragBoxLayer(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var dbl = new Plottable.Components.XDragBoxLayer(); dbl.boxVisible(true); dbl.resizable(true); dbl.renderTo(svg); diff --git a/test/components/interactive/yDragBoxLayerTests.ts b/test/components/yDragBoxLayerTests.ts similarity index 85% rename from test/components/interactive/yDragBoxLayerTests.ts rename to test/components/yDragBoxLayerTests.ts index 18164dccd4..3d603cad1e 100644 --- a/test/components/interactive/yDragBoxLayerTests.ts +++ b/test/components/yDragBoxLayerTests.ts @@ -1,4 +1,4 @@ -/// +/// var assert = chai.assert; @@ -8,8 +8,8 @@ describe("Interactive Components", () => { var SVG_HEIGHT = 400; it("bounds()", () => { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var dbl = new Plottable.Component.YDragBoxLayer(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var dbl = new Plottable.Components.YDragBoxLayer(); dbl.boxVisible(true); dbl.renderTo(svg); @@ -37,8 +37,8 @@ describe("Interactive Components", () => { }); it("resizes only in y", () => { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var dbl = new Plottable.Component.YDragBoxLayer(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var dbl = new Plottable.Components.YDragBoxLayer(); dbl.boxVisible(true); dbl.resizable(true); dbl.renderTo(svg); @@ -63,7 +63,7 @@ describe("Interactive Components", () => { y: SVG_HEIGHT * 3 / 4 }; var target = dbl.background(); - triggerFakeDragSequence(target, actualBounds.bottomRight, dragTo); + TestMethods.triggerFakeDragSequence(target, actualBounds.bottomRight, dragTo); actualBounds = dbl.bounds(); assert.strictEqual(actualBounds.topLeft.x, 0, "box still starts at left"); assert.strictEqual(actualBounds.bottomRight.x, dbl.width(), "box still ends at right"); @@ -72,8 +72,8 @@ describe("Interactive Components", () => { }); it("stays full width after resizing", () => { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var dbl = new Plottable.Component.YDragBoxLayer(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var dbl = new Plottable.Components.YDragBoxLayer(); dbl.boxVisible(true); dbl.resizable(true); dbl.renderTo(svg); diff --git a/test/core/broadcasterTests.ts b/test/core/broadcasterTests.ts deleted file mode 100644 index 7807291ed7..0000000000 --- a/test/core/broadcasterTests.ts +++ /dev/null @@ -1,70 +0,0 @@ -/// - -var assert = chai.assert; - -describe("Broadcasters", () => { - var b: Plottable.Core.Broadcaster; - var called: boolean; - var cb: any; - var listenable: Object = {}; - - beforeEach(() => { - b = new Plottable.Core.Broadcaster(listenable); - called = false; - cb = () => { called = true; }; - }); - it("listeners are called by the broadcast method", () => { - b.registerListener(null, cb); - b.broadcast(); - assert.isTrue(called, "callback was called"); - }); - - it("same listener can only be associated with one callback", () => { - var called2 = false; - var cb2 = () => { called2 = true; }; - var listener = {}; - b.registerListener(listener, cb); - b.registerListener(listener, cb2); - b.broadcast(); - assert.isFalse(called, "first (overwritten) callback not called"); - assert.isTrue(called2, "second callback was called"); - }); - - it("listeners can be deregistered", () => { - var listener = {}; - b.registerListener(listener, cb); - b.deregisterListener(listener); - b.broadcast(); - assert.isFalse(called, "callback was not called after deregistering only listener"); - - b.registerListener(5, cb); - b.registerListener(6, cb); - b.deregisterAllListeners(); - b.broadcast(); - assert.isFalse(called, "callback was not called after deregistering all listeners"); - - b.registerListener(5, cb); - b.registerListener(6, cb); - b.deregisterListener(5); - b.broadcast(); - assert.isTrue(called, "callback was called even after 1/2 listeners were deregistered"); - }); - - it("arguments are passed through to callback", () => { - var g2 = {}; - var g3 = "foo"; - var cb = (arg1: any, arg2: any, arg3: any) => { - assert.strictEqual(listenable, arg1, "broadcaster passed through"); - assert.strictEqual(g2, arg2, "g2 passed through"); - assert.strictEqual(g3, arg3, "g3 passed through"); - called = true; - }; - b.registerListener(null, cb); - b.broadcast(g2, g3); - assert.isTrue(called, "the cb was called"); - }); - - it("deregistering an unregistered listener doesn't throw an error", () => { - assert.doesNotThrow(() => b.deregisterListener({}) ); - }); -}); diff --git a/test/core/componentContainerTests.ts b/test/core/componentContainerTests.ts deleted file mode 100644 index 01c33c4ab7..0000000000 --- a/test/core/componentContainerTests.ts +++ /dev/null @@ -1,63 +0,0 @@ -/// - -var assert = chai.assert; - - -describe("ComponentContainer", () => { - - it("_addComponent()", () => { - var container = new Plottable.Component.AbstractComponentContainer(); - var c1 = new Plottable.Component.AbstractComponent(); - var c2 = new Plottable.Component.AbstractComponent(); - var c3 = new Plottable.Component.AbstractComponent(); - - assert.isTrue(container._addComponent(c1), "returns true on successful adding"); - assert.deepEqual(container.components(), [c1], "component was added"); - - container._addComponent(c2); - assert.deepEqual(container.components(), [c1, c2], "can append components"); - - container._addComponent(c3, true); - assert.deepEqual(container.components(), [c3, c1, c2], "can prepend components"); - - assert.isFalse(container._addComponent(null), "returns false for null arguments"); - assert.deepEqual(container.components(), [c3, c1, c2], "component list was unchanged"); - - assert.isFalse(container._addComponent(c1), "returns false if adding an already-added component"); - assert.deepEqual(container.components(), [c3, c1, c2], "component list was unchanged"); - }); - - it("_removeComponent()", () => { - var container = new Plottable.Component.AbstractComponentContainer(); - var c1 = new Plottable.Component.AbstractComponent(); - var c2 = new Plottable.Component.AbstractComponent(); - container._addComponent(c1); - container._addComponent(c2); - - container._removeComponent(c2); - assert.deepEqual(container.components(), [c1], "component 2 was removed"); - - container._removeComponent(c2); - assert.deepEqual(container.components(), [c1], - "there are no side effects from removing already-removed components"); - }); - - it("empty()", () => { - var container = new Plottable.Component.AbstractComponentContainer(); - assert.isTrue(container.empty()); - var c1 = new Plottable.Component.AbstractComponent(); - container._addComponent(c1); - assert.isFalse(container.empty()); - }); - - it("detachAll()", () => { - var container = new Plottable.Component.AbstractComponentContainer(); - var c1 = new Plottable.Component.AbstractComponent(); - var c2 = new Plottable.Component.AbstractComponent(); - container._addComponent(c1); - container._addComponent(c2); - container.detachAll(); - - assert.deepEqual(container.components(), [], "container was cleared of components"); - }); -}); diff --git a/test/core/componentGroupTests.ts b/test/core/componentGroupTests.ts index f7dccaa482..25b0574826 100644 --- a/test/core/componentGroupTests.ts +++ b/test/core/componentGroupTests.ts @@ -2,80 +2,161 @@ var assert = chai.assert; - describe("ComponentGroups", () => { - it("components in componentGroups overlap", () => { - var c1 = makeFixedSizeComponent(10, 10); - var c2 = new Plottable.Component.AbstractComponent(); - var c3 = new Plottable.Component.AbstractComponent(); + it("append()", () => { + var componentGroup = new Plottable.Components.Group(); + + var c1 = new Plottable.Component(); + componentGroup.append(c1); + assert.deepEqual(componentGroup.components(), [c1], "Component 1 was added to the Group"); + + var c2 = new Plottable.Component(); + componentGroup.append(c2); + assert.deepEqual(componentGroup.components(), [c1, c2], "appended Component 2 to the Group"); + + componentGroup.append(c1); + assert.deepEqual(componentGroup.components(), [c1, c2], "adding an already-added Component does nothing"); + + var svg = TestMethods.generateSVG(); + componentGroup.renderTo(svg); + var c3 = new Plottable.Component(); + componentGroup.append(c3); + assert.deepEqual(componentGroup.components(), [c1, c2, c3], "Components can be append()-ed after rendering"); - var cg = new Plottable.Component.Group([c1, c2, c3]); - var svg = generateSVG(400, 400); - cg._anchor(svg); - ( c1)._addBox("test-box1"); - ( c2)._addBox("test-box2"); - ( c3)._addBox("test-box3"); - cg._computeLayout()._render(); - var t1 = svg.select(".test-box1"); - var t2 = svg.select(".test-box2"); - var t3 = svg.select(".test-box3"); - assertWidthHeight(t1, 10, 10, "rect1 sized correctly"); - assertWidthHeight(t2, 400, 400, "rect2 sized correctly"); - assertWidthHeight(t3, 400, 400, "rect3 sized correctly"); svg.remove(); }); - it("components can be added before and after anchoring", () => { - var c1 = makeFixedSizeComponent(10, 10); - var c2 = makeFixedSizeComponent(20, 20); - var c3 = new Plottable.Component.AbstractComponent(); + it("can add null to a Group without failing", () => { + var cg1 = new Plottable.Components.Group(); + var c = new Plottable.Component; + + cg1.append(c); + + assert.strictEqual(cg1.components().length, 1, + "there should first be 1 element in the group"); + + assert.doesNotThrow(() => cg1.append(null)); + + assert.strictEqual(cg1.components().length, 1, + "adding null to a group should have no effect on the group"); + }); + + it("append()-ing a Component to the Group should detach() it from its current location", () => { + var c1 = new Plottable.Component; + var svg = TestMethods.generateSVG(); + c1.renderTo(svg); + var group = new Plottable.Components.Group(); + group.append(c1); + assert.isFalse(svg.node().hasChildNodes(), "Component was detach()-ed"); + svg.remove(); + }); + + it("remove()", () => { + var c0 = new Plottable.Component(); + var c1 = new Plottable.Component(); + var c2 = new Plottable.Component(); + var componentGroup = new Plottable.Components.Group([c0, c1, c2]); + + componentGroup.remove(c1); + assert.deepEqual(componentGroup.components(), [c0, c2], "removing a Component respects the order of the remaining Components"); + + var svg = TestMethods.generateSVG(); + c1.renderTo(svg); + componentGroup.remove(c1); + assert.deepEqual(componentGroup.components(), [c0, c2], + "removing a Component not in the Group does not remove Components from the Group"); + assert.strictEqual(svg.node().childNodes[0], ( ( c1)._element).node(), "The Component not in the Group stayed put"); + + svg.remove(); + }); + + it("detach()-ing a Component that is in the Group removes it from the Group", () => { + var c0 = new Plottable.Component(); + var componentGroup = new Plottable.Components.Group([c0]); + var svg = TestMethods.generateSVG(); + componentGroup.renderTo(svg); + c0.detach(); + assert.lengthOf(componentGroup.components(), 0, "Component is no longer in the Group"); + assert.isNull(c0.parent(), "Component disconnected from Group"); + svg.remove(); + }); + + it("can move components to other groups after anchoring", () => { + var svg = TestMethods.generateSVG(); + + var cg1 = new Plottable.Components.Group(); + var cg2 = new Plottable.Components.Group(); + var c = new Plottable.Component(); + + cg1.append(c); + + cg1.renderTo(svg); + cg2.renderTo(svg); + + assert.strictEqual(cg2.components().length, 0, + "second group should have no component before movement"); + + assert.strictEqual(cg1.components().length, 1, + "first group should have 1 component before movement"); + + assert.strictEqual(c.parent(), cg1, + "component's parent before moving should be the group 1" + ); + + assert.doesNotThrow(() => cg2.append(c), Error, + "should be able to move components between groups after anchoring" + ); + + assert.strictEqual(cg2.components().length, 1, + "second group should have 1 component after movement"); + + assert.strictEqual(cg1.components().length, 0, + "first group should have no components after movement"); - var cg = new Plottable.Component.Group([c1]); - var svg = generateSVG(400, 400); - cg.below(c2)._anchor(svg); + assert.strictEqual(c.parent(), cg2, + "component's parent after movement should be the group 2" + ); + + svg.remove(); + }); + + it("has()", () => { + var c0 = new Plottable.Component(); + var componentGroup = new Plottable.Components.Group([c0]); + assert.isTrue(componentGroup.has(c0), "correctly checks that Component is in the Group"); + componentGroup.remove(c0); + assert.isFalse(componentGroup.has(c0), "correctly checks that Component is no longer in the Group"); + componentGroup.append(c0); + assert.isTrue(componentGroup.has(c0), "correctly checks that Component is in the Group again"); + }); + + it("components in componentGroups overlap", () => { + var c1 = TestMethods.makeFixedSizeComponent(10, 10); + var c2 = new Plottable.Component(); + var c3 = new Plottable.Component(); + + var cg = new Plottable.Components.Group([c1, c2, c3]); + var svg = TestMethods.generateSVG(400, 400); + cg.anchor(svg); ( c1)._addBox("test-box1"); ( c2)._addBox("test-box2"); - cg._computeLayout()._render(); + ( c3)._addBox("test-box3"); + cg.computeLayout().render(); var t1 = svg.select(".test-box1"); var t2 = svg.select(".test-box2"); - assertWidthHeight(t1, 10, 10, "rect1 sized correctly"); - assertWidthHeight(t2, 20, 20, "rect2 sized correctly"); - cg.below(c3); - ( c3)._addBox("test-box3"); - cg._computeLayout()._render(); var t3 = svg.select(".test-box3"); - assertWidthHeight(t3, 400, 400, "rect3 sized correctly"); + TestMethods.assertWidthHeight(t1, 10, 10, "rect1 sized correctly"); + TestMethods.assertWidthHeight(t2, 400, 400, "rect2 sized correctly"); + TestMethods.assertWidthHeight(t3, 400, 400, "rect3 sized correctly"); svg.remove(); }); - it("componentGroup subcomponents have xOffset, yOffset of 0", () => { - var cg = new Plottable.Component.Group(); - var c1 = new Plottable.Component.AbstractComponent(); - var c2 = new Plottable.Component.AbstractComponent(); - cg.below(c1).below(c2); - - var svg = generateSVG(); - cg._anchor(svg); - cg._computeLayout(50, 50, 350, 350); - - var cgTranslate = d3.transform(( cg)._element.attr("transform")).translate; - var c1Translate = d3.transform(( c1)._element.attr("transform")).translate; - var c2Translate = d3.transform(( c2)._element.attr("transform")).translate; - assert.equal(cgTranslate[0], 50, "componentGroup has 50 xOffset"); - assert.equal(cgTranslate[1], 50, "componentGroup has 50 yOffset"); - assert.equal(c1Translate[0], 0, "componentGroup has 0 xOffset"); - assert.equal(c1Translate[1], 0, "componentGroup has 0 yOffset"); - assert.equal(c2Translate[0], 0, "componentGroup has 0 xOffset"); - assert.equal(c2Translate[1], 0, "componentGroup has 0 yOffset"); - svg.remove(); - }); - - it("detach() and _removeComponent work correctly for componentGroup", () => { - var c1 = new Plottable.Component.AbstractComponent().classed("component-1", true); - var c2 = new Plottable.Component.AbstractComponent().classed("component-2", true); - var cg = new Plottable.Component.Group([c1, c2]); + it("detach()", () => { + var c1 = new Plottable.Component().classed("component-1", true); + var c2 = new Plottable.Component().classed("component-2", true); + var cg = new Plottable.Components.Group([c1, c2]); - var svg = generateSVG(200, 200); + var svg = TestMethods.generateSVG(200, 200); cg.renderTo(svg); var c1Node = svg.select(".component-1").node(); @@ -109,33 +190,16 @@ describe("ComponentGroups", () => { svg.remove(); }); - it("detachAll() works as expected", () => { - var cg = new Plottable.Component.Group(); - var c1 = new Plottable.Component.AbstractComponent(); - var c2 = new Plottable.Component.AbstractComponent(); - var c3 = new Plottable.Component.AbstractComponent(); - assert.isTrue(cg.empty(), "cg initially empty"); - cg.below(c1).below(c2).below(c3); - assert.isFalse(cg.empty(), "cg not empty after merging components"); - cg.detachAll(); - assert.isTrue(cg.empty(), "cg empty after detachAll()"); - - assert.isFalse(( c1)._isAnchored, "c1 was detached"); - assert.isFalse(( c2)._isAnchored, "c2 was detached"); - assert.isFalse(( c3)._isAnchored, "c3 was detached"); - assert.lengthOf(cg.components(), 0, "cg has no components"); - }); - describe("requests space based on contents, but occupies total offered space", () => { var SVG_WIDTH = 400; var SVG_HEIGHT = 400; it("with no Components", () => { - var svg = generateSVG(); - var cg = new Plottable.Component.Group([]); + var svg = TestMethods.generateSVG(); + var cg = new Plottable.Components.Group([]); - var request = cg._requestedSpace(SVG_WIDTH, SVG_HEIGHT); - verifySpaceRequest(request, 0, 0, false, false, "empty Group doesn't request any space"); + var request = cg.requestedSpace(SVG_WIDTH, SVG_HEIGHT); + TestMethods.verifySpaceRequest(request, 0, 0, "empty Group doesn't request any space"); cg.renderTo(svg); assert.strictEqual(cg.width(), SVG_WIDTH, "occupies all offered width"); @@ -144,16 +208,16 @@ describe("ComponentGroups", () => { }); it("with a non-fixed-size Component", () => { - var svg = generateSVG(); - var c1 = new Plottable.Component.AbstractComponent(); - var c2 = new Plottable.Component.AbstractComponent(); - var cg = new Plottable.Component.Group([c1, c2]); + var svg = TestMethods.generateSVG(); + var c1 = new Plottable.Component(); + var c2 = new Plottable.Component(); + var cg = new Plottable.Components.Group([c1, c2]); - var groupRequest = cg._requestedSpace(SVG_WIDTH, SVG_HEIGHT); - var c1Request = c1._requestedSpace(SVG_WIDTH, SVG_HEIGHT); + var groupRequest = cg.requestedSpace(SVG_WIDTH, SVG_HEIGHT); + var c1Request = c1.requestedSpace(SVG_WIDTH, SVG_HEIGHT); assert.deepEqual(groupRequest, c1Request, "request reflects request of sub-component"); - assert.isFalse(cg._isFixedWidth(), "width is not fixed if subcomponents are not fixed width"); - assert.isFalse(cg._isFixedHeight(), "height is not fixed if subcomponents are not fixed height"); + assert.isFalse(cg.fixedWidth(), "width is not fixed if subcomponents are not fixed width"); + assert.isFalse(cg.fixedHeight(), "height is not fixed if subcomponents are not fixed height"); cg.renderTo(svg); assert.strictEqual(cg.width(), SVG_WIDTH, "occupies all offered width"); @@ -162,180 +226,24 @@ describe("ComponentGroups", () => { }); it("with fixed-size Components", () => { - var svg = generateSVG(); + var svg = TestMethods.generateSVG(); var tall = new Mocks.FixedSizeComponent(SVG_WIDTH / 4, SVG_WIDTH / 2); var wide = new Mocks.FixedSizeComponent(SVG_WIDTH / 2, SVG_WIDTH / 4); - var cg = new Plottable.Component.Group([tall, wide]); + var cg = new Plottable.Components.Group([tall, wide]); - var request = cg._requestedSpace(SVG_WIDTH, SVG_HEIGHT); - assert.strictEqual(request.width, SVG_WIDTH / 2, "requested enough space for widest Component"); - assert.isFalse(request.wantsWidth, "does not request more width if enough was supplied for widest Component"); - assert.strictEqual(request.height, SVG_HEIGHT / 2, "requested enough space for tallest Component"); - assert.isFalse(request.wantsHeight, "does not request more height if enough was supplied for tallest Component"); + var request = cg.requestedSpace(SVG_WIDTH, SVG_HEIGHT); + assert.strictEqual(request.minWidth, SVG_WIDTH / 2, "requested enough space for widest Component"); + assert.strictEqual(request.minHeight, SVG_HEIGHT / 2, "requested enough space for tallest Component"); - var constrainedRequest = cg._requestedSpace(SVG_WIDTH / 10, SVG_HEIGHT / 10); - assert.strictEqual(constrainedRequest.width, SVG_WIDTH / 2, "requested enough space for widest Component"); - assert.isTrue(constrainedRequest.wantsWidth, "requests more width if not enough was supplied for widest Component"); - assert.strictEqual(constrainedRequest.height, SVG_HEIGHT / 2, "requested enough space for tallest Component"); - assert.isTrue(constrainedRequest.wantsHeight, "requests more height if not enough was supplied for tallest Component"); + var constrainedRequest = cg.requestedSpace(SVG_WIDTH / 10, SVG_HEIGHT / 10); + assert.strictEqual(constrainedRequest.minWidth, SVG_WIDTH / 2, "requested enough space for widest Component"); + assert.strictEqual(constrainedRequest.minHeight, SVG_HEIGHT / 2, "requested enough space for tallest Component"); cg.renderTo(svg); assert.strictEqual(cg.width(), SVG_WIDTH, "occupies all offered width"); assert.strictEqual(cg.height(), SVG_HEIGHT, "occupies all offered height"); svg.remove(); }); - - it("can move components to other groups after anchoring", () => { - var svg = generateSVG(); - - var cg1 = new Plottable.Component.AbstractComponentContainer(); - var cg2 = new Plottable.Component.AbstractComponentContainer(); - var c = new Plottable.Component.AbstractComponent(); - - cg1._addComponent(c); - - cg1.renderTo(svg); - cg2.renderTo(svg); - - assert.strictEqual(cg2.components().length, 0, - "second group should have no component before movement"); - - assert.strictEqual(cg1.components().length, 1, - "first group should have 1 component before movement"); - - assert.strictEqual(c._parent(), cg1, - "component's parent before moving should be the group 1" - ); - - assert.doesNotThrow(() => cg2._addComponent(c), Error, - "should be able to move components between groups after anchoring" - ); - - assert.strictEqual(cg2.components().length, 1, - "second group should have 1 component after movement"); - - assert.strictEqual(cg1.components().length, 0, - "first group should have no components after movement"); - - assert.strictEqual(c._parent(), cg2, - "component's parent after movement should be the group 2" - ); - - svg.remove(); - }); - - it("can add null to a component without failing", () => { - var cg1 = new Plottable.Component.AbstractComponentContainer(); - var c = new Plottable.Component.AbstractComponent; - - cg1._addComponent(c); - - assert.strictEqual(cg1.components().length, 1, - "there should first be 1 element in the group"); - - assert.doesNotThrow(() => cg1._addComponent(null)); - - assert.strictEqual(cg1.components().length, 1, - "adding null to a group should have no effect on the group"); - }); }); - - describe("Merging components works as expected", () => { - var c1 = new Plottable.Component.AbstractComponent(); - var c2 = new Plottable.Component.AbstractComponent(); - var c3 = new Plottable.Component.AbstractComponent(); - var c4 = new Plottable.Component.AbstractComponent(); - - describe("above()", () => { - - it("Component.above works as expected (Component.above Component)", () => { - var cg: Plottable.Component.Group = c2.above(c1); - var innerComponents: Plottable.Component.AbstractComponent[] = cg.components(); - assert.lengthOf(innerComponents, 2, "There are two components"); - assert.equal(innerComponents[0], c1, "first component correct"); - assert.equal(innerComponents[1], c2, "second component correct"); - }); - - it("Component.above works as expected (Component.above ComponentGroup)", () => { - var cg = new Plottable.Component.Group([c1, c2, c3]); - var cg2 = c4.above(cg); - assert.equal(cg, cg2, "c4.above(cg) returns cg"); - var components: Plottable.Component.AbstractComponent[] = cg.components(); - assert.lengthOf(components, 4, "four components"); - assert.equal(components[2], c3, "third component in third"); - assert.equal(components[3], c4, "fourth component is last"); - }); - - it("Component.above works as expected (ComponentGroup.above Component)", () => { - var cg = new Plottable.Component.Group([c2, c3, c4]); - var cg2 = cg.above(c1); - assert.equal(cg, cg2, "cg.merge(c1) returns cg"); - var components: Plottable.Component.AbstractComponent[] = cg.components(); - assert.lengthOf(components, 4, "there are four components"); - assert.equal(components[0], c1, "first is first"); - assert.equal(components[3], c4, "fourth is fourth"); - }); - - it("Component.above works as expected (ComponentGroup.above ComponentGroup)", () => { - var cg1 = new Plottable.Component.Group([c1, c2]); - var cg2 = new Plottable.Component.Group([c3, c4]); - var cg = cg1.above(cg2); - assert.equal(cg, cg1, "merged == cg1"); - assert.notEqual(cg, cg2, "merged != cg2"); - var components: Plottable.Component.AbstractComponent[] = cg.components(); - assert.lengthOf(components, 3, "there are three inner components"); - assert.equal(components[0], cg2, "componentGroup2 inside componentGroup1"); - assert.equal(components[1], c1, "components are inside"); - assert.equal(components[2], c2, "components are inside"); - }); - - }); - - describe("below()", () => { - - it("Component.below works as expected (Component.below Component)", () => { - var cg: Plottable.Component.Group = c1.below(c2); - var innerComponents: Plottable.Component.AbstractComponent[] = cg.components(); - assert.lengthOf(innerComponents, 2, "There are two components"); - assert.equal(innerComponents[0], c1, "first component correct"); - assert.equal(innerComponents[1], c2, "second component correct"); - }); - - it("Component.below works as expected (Component.below ComponentGroup)", () => { - var cg = new Plottable.Component.Group([c2, c3, c4]); - var cg2 = c1.below(cg); - assert.equal(cg, cg2, "c1.below(cg) returns cg"); - var components: Plottable.Component.AbstractComponent[] = cg.components(); - assert.lengthOf(components, 4, "four components"); - assert.equal(components[0], c1, "first component in front"); - assert.equal(components[1], c2, "second component is second"); - }); - - it("Component.below works as expected (ComponentGroup.below Component)", () => { - var cg = new Plottable.Component.Group([c1, c2, c3]); - var cg2 = cg.below(c4); - assert.equal(cg, cg2, "cg.merge(c4) returns cg"); - var components: Plottable.Component.AbstractComponent[] = cg.components(); - assert.lengthOf(components, 4, "there are four components"); - assert.equal(components[0], c1, "first is first"); - assert.equal(components[3], c4, "fourth is fourth"); - }); - - it("Component.below works as expected (ComponentGroup.below ComponentGroup)", () => { - var cg1 = new Plottable.Component.Group([c1, c2]); - var cg2 = new Plottable.Component.Group([c3, c4]); - var cg = cg1.below(cg2); - assert.equal(cg, cg1, "merged group == cg1"); - assert.notEqual(cg, cg2, "merged group != cg2"); - var components: Plottable.Component.AbstractComponent[] = cg.components(); - assert.lengthOf(components, 3, "there are three inner components"); - assert.equal(components[0], c1, "components are inside"); - assert.equal(components[1], c2, "components are inside"); - assert.equal(components[2], cg2, "componentGroup2 inside componentGroup1"); - }); - - }); - - }); }); diff --git a/test/core/componentTests.ts b/test/core/componentTests.ts index 5cb9a7c583..3ad545484e 100644 --- a/test/core/componentTests.ts +++ b/test/core/componentTests.ts @@ -2,62 +2,163 @@ var assert = chai.assert; - -function assertComponentXY(component: Plottable.Component.AbstractComponent, x: number, y: number, message: string) { +function assertComponentXY(component: Plottable.Component, x: number, y: number, message: string) { // use to examine the private variables var translate = d3.transform(( component)._element.attr("transform")).translate; var xActual = translate[0]; var yActual = translate[1]; - assert.equal(xActual, x, "X: " + message); - assert.equal(yActual, y, "Y: " + message); + assert.strictEqual(xActual, x, "X: " + message); + assert.strictEqual(yActual, y, "Y: " + message); } describe("Component behavior", () => { var svg: D3.Selection; - var c: Plottable.Component.AbstractComponent; + var c: Plottable.Component; var SVG_WIDTH = 400; var SVG_HEIGHT = 300; beforeEach(() => { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - c = new Plottable.Component.AbstractComponent(); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + c = new Plottable.Component(); }); - describe("anchor", () => { - it("anchoring works as expected", () => { - c._anchor(svg); - assert.equal(( c)._element.node(), svg.select("g").node(), "the component anchored to a beneath the "); + describe("anchor()", () => { + it("anchor()-ing works as expected", () => { + c.anchor(svg); + assert.strictEqual(( c)._element.node(), svg.select("g").node(), "the component anchored to a beneath the "); assert.isTrue(svg.classed("plottable"), " was given \"plottable\" CSS class"); svg.remove(); }); - it("can re-anchor to a different element", () => { - c._anchor(svg); + it("can re-anchor() to a different element", () => { + c.anchor(svg); - var svg2 = generateSVG(SVG_WIDTH, SVG_HEIGHT); - c._anchor(svg2); - assert.equal(( c)._element.node(), svg2.select("g").node(), "the component re-achored under the second "); + var svg2 = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + c.anchor(svg2); + assert.strictEqual(( c)._element.node(), svg2.select("g").node(), "the component re-achored under the second "); assert.isTrue(svg2.classed("plottable"), "second was given \"plottable\" CSS class"); svg.remove(); svg2.remove(); }); + + describe("anchor() callbacks", () => { + it("callbacks called on anchor()-ing", () => { + var callbackCalled = false; + var passedComponent: Plottable.Component; + var callback = (component: Plottable.Component) => { + callbackCalled = true; + passedComponent = component; + }; + c.onAnchor(callback); + c.anchor(svg); + assert.isTrue(callbackCalled, "callback was called on anchor()-ing"); + assert.strictEqual(passedComponent, c, "callback was passed the Component that anchor()-ed"); + + var svg2 = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + callbackCalled = false; + c.anchor(svg2); + assert.isTrue(callbackCalled, "callback was called on anchor()-ing to a new "); + assert.strictEqual(passedComponent, c, "callback was passed the Component that anchor()-ed"); + + svg.remove(); + svg2.remove(); + }); + + it("callbacks called immediately if already anchor()-ed", () => { + var callbackCalled = false; + var passedComponent: Plottable.Component; + var callback = (component: Plottable.Component) => { + callbackCalled = true; + passedComponent = component; + }; + c.anchor(svg); + c.onAnchor(callback); + assert.isTrue(callbackCalled, "callback was immediately if Component was already anchor()-ed"); + assert.strictEqual(passedComponent, c, "callback was passed the Component that anchor()-ed"); + svg.remove(); + }); + + it("removing callbacks", () => { + var callbackCalled = false; + var callback = (component: Plottable.Component) => { + callbackCalled = true; + }; + c.onAnchor(callback); + c.offAnchor(callback); + c.anchor(svg); + assert.isFalse(callbackCalled, "removed callback is not called"); + svg.remove(); + }); + }); + }); + + describe("detach()", () => { + it("detach() works as expected", () => { + var c1 = new Plottable.Component(); + + c1.renderTo(svg); + assert.isTrue(svg.node().hasChildNodes(), "the svg has children"); + c1.detach(); + assert.isFalse(svg.node().hasChildNodes(), "the svg has no children"); + + svg.remove(); + }); + + it("components can be detach()-ed even if not anchor()-ed", () => { + var c = new Plottable.Component(); + c.detach(); // no error thrown + svg.remove(); + }); + + it("callbacks called on detach()-ing", () => { + c = new Plottable.Component(); + c.renderTo(svg); + + var callbackCalled = false; + var passedComponent: Plottable.Component; + var callback = (component: Plottable.Component) => { + callbackCalled = true; + passedComponent = component; + }; + c.onDetach(callback); + c.detach(); + assert.isTrue(callbackCalled, "callback was called when the Component was detach()-ed"); + assert.strictEqual(passedComponent, c, "callback was passed the Component that detach()-ed"); + svg.remove(); + }); + }); + + it("parent()", () => { + var c = new Plottable.Component(); + var acceptingContainer = { + has: (component: Plottable.Component) => true + }; + c.parent( acceptingContainer); + assert.strictEqual(c.parent(), acceptingContainer, "Component's parent was set if the Component is contained in the parent"); + var rejectingContainer = { + has: (component: Plottable.Component) => false + }; + assert.throws(() => c.parent( rejectingContainer), Error, "invalid parent"); + svg.remove(); }); describe("computeLayout", () => { it("computeLayout defaults and updates intelligently", () => { - c._anchor(svg); - c._computeLayout(); - assert.equal(c.width() , SVG_WIDTH, "computeLayout defaulted width to svg width"); - assert.equal(c.height(), SVG_HEIGHT, "computeLayout defaulted height to svg height"); - assert.equal(( c)._xOrigin, 0 , "xOrigin defaulted to 0"); - assert.equal(( c)._yOrigin, 0 , "yOrigin defaulted to 0"); + c.anchor(svg); + c.computeLayout(); + assert.strictEqual(c.width() , SVG_WIDTH, "computeLayout defaulted width to svg width"); + assert.strictEqual(c.height(), SVG_HEIGHT, "computeLayout defaulted height to svg height"); + var origin = c.origin(); + assert.strictEqual(origin.x, 0 , "xOrigin defaulted to 0"); + assert.strictEqual(origin.y, 0 , "yOrigin defaulted to 0"); svg.attr("width", 2 * SVG_WIDTH).attr("height", 2 * SVG_HEIGHT); - c._computeLayout(); - assert.equal(c.width() , 2 * SVG_WIDTH, "computeLayout updated width to new svg width"); - assert.equal(c.height(), 2 * SVG_HEIGHT, "computeLayout updated height to new svg height"); - assert.equal(( c)._xOrigin, 0 , "xOrigin is still 0"); - assert.equal(( c)._yOrigin, 0 , "yOrigin is still 0"); + c.computeLayout(); + assert.strictEqual(c.width() , 2 * SVG_WIDTH, "computeLayout updated width to new svg width"); + assert.strictEqual(c.height(), 2 * SVG_HEIGHT, "computeLayout updated height to new svg height"); + origin = c.origin(); + assert.strictEqual(origin.x, 0 , "xOrigin is still 0"); + assert.strictEqual(origin.y, 0 , "yOrigin is still 0"); svg.remove(); }); @@ -70,30 +171,32 @@ describe("Component behavior", () => { // Remove width/height attributes and style with CSS svg.attr("width", null).attr("height", null); - c._anchor(svg); - c._computeLayout(); - assert.equal(c.width(), 400, "defaults to width of parent if width is not specified on "); - assert.equal(c.height(), 200, "defaults to height of parent if width is not specified on "); - assert.equal(( c)._xOrigin, 0, "xOrigin defaulted to 0"); - assert.equal(( c)._yOrigin, 0, "yOrigin defaulted to 0"); - + c.anchor(svg); + c.computeLayout(); + assert.strictEqual(c.width(), 400, "defaults to width of parent if width is not specified on "); + assert.strictEqual(c.height(), 200, "defaults to height of parent if width is not specified on "); + var origin = c.origin(); + assert.strictEqual(origin.x, 0, "xOrigin defaulted to 0"); + assert.strictEqual(origin.y, 0, "yOrigin defaulted to 0"); svg.style("width", "50%").style("height", "50%"); - c._computeLayout(); + c.computeLayout(); - assert.equal(c.width(), 200, "computeLayout defaulted width to svg width"); - assert.equal(c.height(), 100, "computeLayout defaulted height to svg height"); - assert.equal(( c)._xOrigin, 0, "xOrigin defaulted to 0"); - assert.equal(( c)._yOrigin, 0, "yOrigin defaulted to 0"); + assert.strictEqual(c.width(), 200, "computeLayout defaulted width to svg width"); + assert.strictEqual(c.height(), 100, "computeLayout defaulted height to svg height"); + origin = c.origin(); + assert.strictEqual(origin.x, 0, "xOrigin defaulted to 0"); + assert.strictEqual(origin.y, 0, "yOrigin defaulted to 0"); svg.style("width", "25%").style("height", "25%"); - c._computeLayout(); + c.computeLayout(); - assert.equal(c.width(), 100, "computeLayout updated width to new svg width"); - assert.equal(c.height(), 50, "computeLayout updated height to new svg height"); - assert.equal(( c)._xOrigin, 0, "xOrigin is still 0"); - assert.equal(( c)._yOrigin, 0, "yOrigin is still 0"); + assert.strictEqual(c.width(), 100, "computeLayout updated width to new svg width"); + assert.strictEqual(c.height(), 50, "computeLayout updated height to new svg height"); + origin = c.origin(); + assert.strictEqual(origin.x, 0, "xOrigin is still 0"); + assert.strictEqual(origin.y, 0, "yOrigin is still 0"); // reset test page DOM parent.style("width", "auto"); @@ -103,28 +206,29 @@ describe("Component behavior", () => { it("computeLayout will not default when attached to non-root node", () => { var g = svg.append("g"); - c._anchor(g); - assert.throws(() => c._computeLayout(), "null arguments"); + c.anchor(g); + assert.throws(() => c.computeLayout(), "null arguments"); svg.remove(); }); it("computeLayout throws an error when called on un-anchored component", () => { - assert.throws(() => c._computeLayout(), Error, "anchor must be called before computeLayout"); + assert.throws(() => c.computeLayout(), Error); svg.remove(); }); it("computeLayout uses its arguments apropriately", () => { - var g = svg.append("g"); - var xOff = 10; - var yOff = 20; + var origin = { + x: 10, + y: 20 + }; var width = 100; var height = 200; - c._anchor(svg); - c._computeLayout(xOff, yOff, width, height); - var translate = getTranslate(( c)._element); - assert.deepEqual(translate, [xOff, yOff], "the element translated appropriately"); - assert.equal(c.width() , width, "the width set properly"); - assert.equal(c.height(), height, "the height set propery"); + c.anchor(svg); + c.computeLayout(origin, width, height); + var translate = TestMethods.getTranslate(( c)._element); + assert.deepEqual(translate, [origin.x, origin.y], "the element translated appropriately"); + assert.strictEqual(c.width() , width, "the width set properly"); + assert.strictEqual(c.height(), height, "the height set propery"); svg.remove(); }); }); @@ -143,90 +247,32 @@ describe("Component behavior", () => { svg.remove(); }); + it("component defaults are as expected", () => { + assert.strictEqual(c.xAlignment(), "left", "x alignment defaults to \"left\""); + assert.strictEqual(c.yAlignment(), "top", "y alignment defaults to \"top\""); + var layout = c.requestedSpace(1, 1); + assert.strictEqual(layout.minWidth, 0, "requested minWidth defaults to 0"); + assert.strictEqual(layout.minHeight, 0, "requested minHeight defaults to 0"); + svg.remove(); + }); + it("fixed-width component will align to the right spot", () => { - fixComponentSize(c, 100, 100); - c._anchor(svg); - c._computeLayout(); + TestMethods.fixComponentSize(c, 100, 100); + c.anchor(svg); + c.xAlignment("left").yAlignment("top"); + c.computeLayout(); assertComponentXY(c, 0, 0, "top-left component aligns correctly"); - c.xAlign("CENTER").yAlign("CENTER"); - c._computeLayout(); + c.xAlignment("center").yAlignment("center"); + c.computeLayout(); assertComponentXY(c, 150, 100, "center component aligns correctly"); - c.xAlign("RIGHT").yAlign("BOTTOM"); - c._computeLayout(); + c.xAlignment("right").yAlignment("bottom"); + c.computeLayout(); assertComponentXY(c, 300, 200, "bottom-right component aligns correctly"); svg.remove(); }); - it("components can be offset relative to their alignment, and throw errors if there is insufficient space", () => { - fixComponentSize(c, 100, 100); - c._anchor(svg); - c.xOffset(20).yOffset(20); - c._computeLayout(); - assertComponentXY(c, 20, 20, "top-left component offsets correctly"); - - c.xAlign("CENTER").yAlign("CENTER"); - c._computeLayout(); - assertComponentXY(c, 170, 120, "center component offsets correctly"); - - c.xAlign("RIGHT").yAlign("BOTTOM"); - c._computeLayout(); - assertComponentXY(c, 320, 220, "bottom-right component offsets correctly"); - - c.xOffset(0).yOffset(0); - c._computeLayout(); - assertComponentXY(c, 300, 200, "bottom-right component offset resets"); - - c.xOffset(-20).yOffset(-30); - c._computeLayout(); - assertComponentXY(c, 280, 170, "negative offsets work properly"); - - svg.remove(); - }); - - it("component defaults are as expected", () => { - var layout = c._requestedSpace(1, 1); - assert.equal(layout.width, 0, "requested width defaults to 0"); - assert.equal(layout.height, 0, "requested height defaults to 0"); - assert.equal(layout.wantsWidth , false, "_requestedSpace().wantsWidth defaults to false"); - assert.equal(layout.wantsHeight, false, "_requestedSpace().wantsHeight defaults to false"); - assert.equal(( c)._xAlignProportion, 0, "_xAlignProportion defaults to 0"); - assert.equal(( c)._yAlignProportion, 0, "_yAlignProportion defaults to 0"); - assert.equal(( c)._xOffset, 0, "xOffset defaults to 0"); - assert.equal(( c)._yOffset, 0, "yOffset defaults to 0"); - svg.remove(); - }); - - it("clipPath works as expected", () => { - assert.isFalse(c.clipPathEnabled, "clipPathEnabled defaults to false"); - c.clipPathEnabled = true; - var expectedClipPathID = c.getID(); - c._anchor(svg); - c._computeLayout(0, 0, 100, 100); - c._render(); - var expectedPrefix = /MSIE [5-9]/.test(navigator.userAgent) ? "" : document.location.href; - expectedPrefix = expectedPrefix.replace(/#.*/g, ""); - var expectedClipPathURL = "url(" + expectedPrefix + "#clipPath" + expectedClipPathID + ")"; - // IE 9 has clipPath like 'url("#clipPath")', must accomodate - var normalizeClipPath = (s: string) => s.replace(/"/g, ""); - assert.isTrue(normalizeClipPath(( c)._element.attr("clip-path")) === expectedClipPathURL, - "the element has clip-path url attached"); - var clipRect = ( c)._boxContainer.select(".clip-rect"); - assert.equal(clipRect.attr("width"), 100, "the clipRect has an appropriate width"); - assert.equal(clipRect.attr("height"), 100, "the clipRect has an appropriate height"); - svg.remove(); - }); - - it("componentID works as expected", () => { - var expectedID = ( Plottable.Core.PlottableObject)._nextID; - var c1 = new Plottable.Component.AbstractComponent(); - assert.equal(c1.getID(), expectedID, "component id on next component was as expected"); - var c2 = new Plottable.Component.AbstractComponent(); - assert.equal(c2.getID(), expectedID + 1, "future components increment appropriately"); - svg.remove(); - }); - it("boxes work as expected", () => { assert.throws(() => ( c)._addBox("pre-anchor"), Error, "Adding boxes before anchoring is currently disallowed"); c.renderTo(svg); @@ -238,51 +284,16 @@ describe("Component behavior", () => { boxStrings.forEach((s) => { var box = boxContainer.select(s); assert.isNotNull(box.node(), s + " box was created and placed inside boxContainer"); - var bb = Plottable._Util.DOM.getBBox(box); - assert.equal(bb.width, SVG_WIDTH, s + " width as expected"); - assert.equal(bb.height, SVG_HEIGHT, s + " height as expected"); + var bb = Plottable.Utils.DOM.getBBox(box); + assert.strictEqual(bb.width, SVG_WIDTH, s + " width as expected"); + assert.strictEqual(bb.height, SVG_HEIGHT, s + " height as expected"); }); svg.remove(); }); - it("hitboxes are created iff there are registered interactions that require hitboxes", () => { - function verifyHitbox(component: Plottable.Component.AbstractComponent) { - var hitBox = ( component)._hitBox; - assert.isNotNull(hitBox, "the hitbox was created"); - var hitBoxFill = hitBox.style("fill"); - var hitBoxFilled = hitBoxFill === "#ffffff" || hitBoxFill === "rgb(255, 255, 255)"; - assert.isTrue(hitBoxFilled, hitBoxFill + " <- this should be filled, so the hitbox will detect events"); - assert.equal(hitBox.style("opacity"), "0", "the hitBox is transparent, otherwise it would look weird"); - } - - c._anchor(svg); - assert.isUndefined(( c)._hitBox, "no hitBox was created when there were no registered interactions"); - svg.remove(); - svg = generateSVG(); - - // registration before anchoring - c = new Plottable.Component.AbstractComponent(); - var i = new Plottable.Interaction.AbstractInteraction(); - i._requiresHitbox = () => true; - c.registerInteraction(i); - c._anchor(svg); - verifyHitbox(c); - svg.remove(); - svg = generateSVG(); - - // registration after anchoring - c = new Plottable.Component.AbstractComponent(); - c._anchor(svg); - i = new Plottable.Interaction.AbstractInteraction(); - i._requiresHitbox = () => true; - c.registerInteraction(i); - verifyHitbox(c); - svg.remove(); - }); - it("errors are thrown on bad alignments", () => { - assert.throws(() => c.xAlign("foo"), Error, "Unsupported alignment"); - assert.throws(() => c.yAlign("foo"), Error, "Unsupported alignment"); + assert.throws(() => c.xAlignment("foo"), Error, "Unsupported alignment"); + assert.throws(() => c.yAlignment("foo"), Error, "Unsupported alignment"); svg.remove(); }); @@ -295,7 +306,7 @@ describe("Component behavior", () => { c.classed("CSS-PREANCHOR-REMOVE", false); assert.isFalse(c.classed("CSS-PREANCHOR-REMOVE")); - c._anchor(svg); + c.anchor(svg); assert.isTrue(c.classed("CSS-PREANCHOR-KEEP")); assert.isFalse(c.classed("CSS-PREANCHOR-REMOVE")); assert.isFalse(c.classed("CSS-POSTANCHOR")); @@ -304,100 +315,84 @@ describe("Component behavior", () => { c.classed("CSS-POSTANCHOR", false); assert.isFalse(c.classed("CSS-POSTANCHOR")); assert.isFalse(c.classed(undefined), "returns false when classed called w/ undefined"); - assert.equal(c.classed(undefined, true), c, "returns this when classed called w/ undefined and true"); + assert.strictEqual(c.classed(undefined, true), c, "returns this when classed called w/ undefined and true"); svg.remove(); }); - it("detach() works as expected", () => { - var c1 = new Plottable.Component.AbstractComponent(); - + it("can't reuse component if it's been destroy()-ed", () => { + var c1 = new Plottable.Component(); c1.renderTo(svg); - assert.isTrue(svg.node().hasChildNodes(), "the svg has children"); - c1.detach(); - assert.isFalse(svg.node().hasChildNodes(), "the svg has no children"); - - svg.remove(); - }); - - it("can't reuse component if it's been remove()-ed", () => { - var c1 = new Plottable.Component.AbstractComponent(); - c1.renderTo(svg); - c1.remove(); + c1.destroy(); assert.throws(() => c1.renderTo(svg), "reuse"); svg.remove(); }); - it("_invalidateLayout works as expected", () => { - var cg = new Plottable.Component.Group(); - var c = makeFixedSizeComponent(10, 10); - cg._addComponent(c); + it("redraw() works as expected", () => { + var cg = new Plottable.Components.Group(); + var c = TestMethods.makeFixedSizeComponent(10, 10); + cg.append(c); cg.renderTo(svg); - assert.equal(cg.height(), 300, "height() is the entire available height"); - assert.equal(cg.width(), 400, "width() is the entire available width"); - fixComponentSize(c, 50, 50); - c._invalidateLayout(); - assert.equal(cg.height(), 300, "height() after resizing is the entire available height"); - assert.equal(cg.width(), 400, "width() after resizing is the entire available width"); - svg.remove(); - }); - - it("components can be detached even if not anchored", () => { - var c = new Plottable.Component.AbstractComponent(); - c.detach(); // no error thrown + assert.strictEqual(cg.height(), 300, "height() is the entire available height"); + assert.strictEqual(cg.width(), 400, "width() is the entire available width"); + TestMethods.fixComponentSize(c, 50, 50); + c.redraw(); + assert.strictEqual(cg.height(), 300, "height() after resizing is the entire available height"); + assert.strictEqual(cg.width(), 400, "width() after resizing is the entire available width"); svg.remove(); }); it("component remains in own cell", () => { - var horizontalComponent = new Plottable.Component.AbstractComponent(); - var verticalComponent = new Plottable.Component.AbstractComponent(); - var placeHolder = new Plottable.Component.AbstractComponent(); - var t = new Plottable.Component.Table().addComponent(0, 0, verticalComponent) - .addComponent(0, 1, new Plottable.Component.AbstractComponent()) - .addComponent(1, 0, placeHolder) - .addComponent(1, 1, horizontalComponent); + var horizontalComponent = new Plottable.Component(); + var verticalComponent = new Plottable.Component(); + var placeHolder = new Plottable.Component(); + var t = new Plottable.Components.Table().add(verticalComponent, 0, 0) + .add(new Plottable.Component(), 0, 1) + .add(placeHolder, 1, 0) + .add(horizontalComponent, 1, 1); t.renderTo(svg); - horizontalComponent.xAlign("center"); - verticalComponent.yAlign("bottom"); + horizontalComponent.xAlignment("center"); + verticalComponent.yAlignment("bottom"); - assertBBoxNonIntersection(( verticalComponent)._element.select(".bounding-box"), + TestMethods.assertBBoxNonIntersection(( verticalComponent)._element.select(".bounding-box"), ( placeHolder)._element.select(".bounding-box")); - assertBBoxInclusion(( t)._boxContainer.select(".bounding-box"), ( horizontalComponent)._element.select(".bounding-box")); + TestMethods.assertBBoxInclusion(( t)._boxContainer.select(".bounding-box"), + ( horizontalComponent)._element.select(".bounding-box")); svg.remove(); }); it("Components will not translate if they are fixed width/height and request more space than offered", () => { // catches #1188 - var c: any = new Plottable.Component.AbstractComponent(); - c._requestedSpace = () => { return {width: 500, height: 500, wantsWidth: true, wantsHeight: true}; }; - c._fixedWidthFlag = true; - c._fixedHeightFlag = true; - c.xAlign("left"); - var t = new Plottable.Component.Table([[c]]); + var c = new Plottable.Component(); + c.requestedSpace = () => { return { minWidth: 500, minHeight: 500}; }; + ( c)._fixedWidthFlag = true; + ( c)._fixedHeightFlag = true; + c.xAlignment("left"); + var t = new Plottable.Components.Table([[c]]); t.renderTo(svg); - var transform = d3.transform(c._element.attr("transform")); + var transform = d3.transform(( c)._element.attr("transform")); assert.deepEqual(transform.translate, [0, 0], "the element was not translated"); svg.remove(); }); it("components do not render unless allocated space", () => { var renderFlag = false; - var c: any = new Plottable.Component.AbstractComponent(); - c._doRender = () => renderFlag = true; - c._anchor(svg); + var c: any = new Plottable.Component(); + c.renderImmediately = () => renderFlag = true; + c.anchor(svg); c._setup(); - c._render(); + c.render(); assert.isFalse(renderFlag, "no render until width/height set to nonzero"); c._width = 10; c._height = 0; - c._render(); + c.render(); assert.isTrue(renderFlag, "render still occurs if one of width/height is zero"); c._height = 10; - c._render(); + c.render(); assert.isTrue(renderFlag, "render occurs if width and height are positive"); svg.remove(); @@ -406,23 +401,23 @@ describe("Component behavior", () => { it("rendering to a new svg detaches the component", () => { var SVG_HEIGHT_1 = 300; var SVG_HEIGHT_2 = 50; - var svg1 = generateSVG(300, SVG_HEIGHT_1); - var svg2 = generateSVG(300, SVG_HEIGHT_2); + var svg1 = TestMethods.generateSVG(300, SVG_HEIGHT_1); + var svg2 = TestMethods.generateSVG(300, SVG_HEIGHT_2); - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - var plot = new Plottable.Plot.Line(xScale, yScale); - var group = new Plottable.Component.Group; + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); + var plot = new Plottable.Plots.Line(xScale, yScale); + var group = new Plottable.Components.Group; group.renderTo(svg1); - group._addComponent(plot); + group.append(plot); - assert.deepEqual(plot._parent(), group, "the plot should be inside the group"); + assert.deepEqual(plot.parent(), group, "the plot should be inside the group"); assert.strictEqual(plot.height(), SVG_HEIGHT_1, "the plot should occupy the entire space of the first svg"); plot.renderTo(svg2); - assert.equal(plot._parent(), null, "the plot should be outside the group"); + assert.strictEqual(plot.parent(), null, "the plot should be outside the group"); assert.strictEqual(plot.height(), SVG_HEIGHT_2, "the plot should occupy the entire space of the second svg"); svg1.remove(); @@ -433,60 +428,59 @@ describe("Component behavior", () => { describe("origin methods", () => { var cWidth = 100; var cHeight = 100; + + it("modifying returned value does not affect origin", () => { + c.renderTo(svg); + var receivedOrigin = c.origin(); + var delta = 10; + receivedOrigin.x += delta; + receivedOrigin.y += delta; + assert.notStrictEqual(receivedOrigin.x, c.origin().x, "receieved point can be modified without affecting origin (x)"); + assert.notStrictEqual(receivedOrigin.y, c.origin().y, "receieved point can be modified without affecting origin (y)"); + svg.remove(); + }); + it("origin() (top-level component)", () => { - fixComponentSize(c, cWidth, cHeight); + TestMethods.fixComponentSize(c, cWidth, cHeight); c.renderTo(svg); - c.xAlign("left").yAlign("top"); + c.xAlignment("left").yAlignment("top"); var origin = c.origin(); assert.strictEqual(origin.x, 0, "returns correct value (xAlign left)"); assert.strictEqual(origin.y, 0, "returns correct value (yAlign top)"); - c.xAlign("center").yAlign("center"); + c.xAlignment("center").yAlignment("center"); origin = c.origin(); assert.strictEqual(origin.x, (SVG_WIDTH - cWidth) / 2, "returns correct value (xAlign center)"); assert.strictEqual(origin.y, (SVG_HEIGHT - cHeight) / 2, "returns correct value (yAlign center)"); - c.xAlign("right").yAlign("bottom"); + c.xAlignment("right").yAlignment("bottom"); origin = c.origin(); assert.strictEqual(origin.x, SVG_WIDTH - cWidth, "returns correct value (xAlign right)"); assert.strictEqual(origin.y, SVG_HEIGHT - cHeight, "returns correct value (yAlign bottom)"); - c.xAlign("left").yAlign("top"); - var xOffsetValue = 40; - var yOffsetValue = 30; - c.xOffset(xOffsetValue); - c.yOffset(yOffsetValue); - origin = c.origin(); - assert.strictEqual(origin.x, xOffsetValue, "accounts for xOffset"); - assert.strictEqual(origin.y, yOffsetValue, "accounts for yOffset"); - svg.remove(); }); it("origin() (nested)", () => { - fixComponentSize(c, cWidth, cHeight); - var group = new Plottable.Component.Group([c]); - var groupXOffset = 40; - var groupYOffset = 30; - group.xOffset(groupXOffset); - group.yOffset(groupYOffset); + TestMethods.fixComponentSize(c, cWidth, cHeight); + var group = new Plottable.Components.Group([c]); group.renderTo(svg); var groupWidth = group.width(); var groupHeight = group.height(); - c.xAlign("left").yAlign("top"); + c.xAlignment("left").yAlignment("top"); var origin = c.origin(); assert.strictEqual(origin.x, 0, "returns correct value (xAlign left)"); assert.strictEqual(origin.y, 0, "returns correct value (yAlign top)"); - c.xAlign("center").yAlign("center"); + c.xAlignment("center").yAlignment("center"); origin = c.origin(); assert.strictEqual(origin.x, (groupWidth - cWidth) / 2, "returns correct value (xAlign center)"); assert.strictEqual(origin.y, (groupHeight - cHeight) / 2, "returns correct value (yAlign center)"); - c.xAlign("right").yAlign("bottom"); + c.xAlignment("right").yAlignment("bottom"); origin = c.origin(); assert.strictEqual(origin.x, groupWidth - cWidth, "returns correct value (xAlign right)"); assert.strictEqual(origin.y, groupHeight - cHeight, "returns correct value (yAlign bottom)"); @@ -495,62 +489,49 @@ describe("Component behavior", () => { }); it("originToSVG() (top-level component)", () => { - fixComponentSize(c, cWidth, cHeight); + TestMethods.fixComponentSize(c, cWidth, cHeight); c.renderTo(svg); - c.xAlign("left").yAlign("top"); + c.xAlignment("left").yAlignment("top"); var origin = c.originToSVG(); assert.strictEqual(origin.x, 0, "returns correct value (xAlign left)"); assert.strictEqual(origin.y, 0, "returns correct value (yAlign top)"); - c.xAlign("center").yAlign("center"); + c.xAlignment("center").yAlignment("center"); origin = c.originToSVG(); assert.strictEqual(origin.x, (SVG_WIDTH - cWidth) / 2, "returns correct value (xAlign center)"); assert.strictEqual(origin.y, (SVG_HEIGHT - cHeight) / 2, "returns correct value (yAlign center)"); - c.xAlign("right").yAlign("bottom"); + c.xAlignment("right").yAlignment("bottom"); origin = c.originToSVG(); assert.strictEqual(origin.x, SVG_WIDTH - cWidth, "returns correct value (xAlign right)"); assert.strictEqual(origin.y, SVG_HEIGHT - cHeight, "returns correct value (yAlign bottom)"); - c.xAlign("left").yAlign("top"); - var xOffsetValue = 40; - var yOffsetValue = 30; - c.xOffset(xOffsetValue); - c.yOffset(yOffsetValue); - origin = c.originToSVG(); - assert.strictEqual(origin.x, xOffsetValue, "accounts for xOffset"); - assert.strictEqual(origin.y, yOffsetValue, "accounts for yOffset"); - svg.remove(); }); it("originToSVG() (nested)", () => { - fixComponentSize(c, cWidth, cHeight); - var group = new Plottable.Component.Group([c]); - var groupXOffset = 40; - var groupYOffset = 30; - group.xOffset(groupXOffset); - group.yOffset(groupYOffset); + TestMethods.fixComponentSize(c, cWidth, cHeight); + var group = new Plottable.Components.Group([c]); group.renderTo(svg); var groupWidth = group.width(); var groupHeight = group.height(); - c.xAlign("left").yAlign("top"); + c.xAlignment("left").yAlignment("top"); var origin = c.originToSVG(); - assert.strictEqual(origin.x, groupXOffset, "returns correct value (xAlign left)"); - assert.strictEqual(origin.y, groupYOffset, "returns correct value (yAlign top)"); + assert.strictEqual(origin.x, 0, "returns correct value (xAlign left)"); + assert.strictEqual(origin.y, 0, "returns correct value (yAlign top)"); - c.xAlign("center").yAlign("center"); + c.xAlignment("center").yAlignment("center"); origin = c.originToSVG(); - assert.strictEqual(origin.x, (groupWidth - cWidth) / 2 + groupXOffset, "returns correct value (xAlign center)"); - assert.strictEqual(origin.y, (groupHeight - cHeight) / 2 + groupYOffset, "returns correct value (yAlign center)"); + assert.strictEqual(origin.x, (groupWidth - cWidth) / 2, "returns correct value (xAlign center)"); + assert.strictEqual(origin.y, (groupHeight - cHeight) / 2, "returns correct value (yAlign center)"); - c.xAlign("right").yAlign("bottom"); + c.xAlignment("right").yAlignment("bottom"); origin = c.originToSVG(); - assert.strictEqual(origin.x, groupWidth - cWidth + groupXOffset, "returns correct value (xAlign right)"); - assert.strictEqual(origin.y, groupHeight - cHeight + groupYOffset, "returns correct value (yAlign bottom)"); + assert.strictEqual(origin.x, groupWidth - cWidth, "returns correct value (xAlign right)"); + assert.strictEqual(origin.y, groupHeight - cHeight, "returns correct value (yAlign bottom)"); svg.remove(); }); diff --git a/test/core/datasetTests.ts b/test/core/datasetTests.ts index 08470bf1c3..2ce737fb94 100644 --- a/test/core/datasetTests.ts +++ b/test/core/datasetTests.ts @@ -6,15 +6,15 @@ describe("Dataset", () => { it("Updates listeners when the data is changed", () => { var ds = new Plottable.Dataset(); - var newData = [ 1, 2, 3 ]; + var newData = [1, 2, 3]; var callbackCalled = false; var callback = (listenable: Plottable.Dataset) => { - assert.equal(listenable, ds, "Callback received the Dataset as the first argument"); + assert.strictEqual(listenable, ds, "Callback received the Dataset as the first argument"); assert.deepEqual(ds.data(), newData, "Dataset arrives with correct data"); callbackCalled = true; }; - ds.broadcaster.registerListener(null, callback); + ds.onUpdate(callback); ds.data(newData); assert.isTrue(callbackCalled, "callback was called when the data was changed"); @@ -27,32 +27,35 @@ describe("Dataset", () => { var callbackCalled = false; var callback = (listenable: Plottable.Dataset) => { - assert.equal(listenable, ds, "Callback received the Dataset as the first argument"); + assert.strictEqual(listenable, ds, "Callback received the Dataset as the first argument"); assert.deepEqual(ds.metadata(), newMetadata, "Dataset arrives with correct metadata"); callbackCalled = true; }; - ds.broadcaster.registerListener(null, callback); + ds.onUpdate(callback); ds.metadata(newMetadata); assert.isTrue(callbackCalled, "callback was called when the metadata was changed"); }); - it("_getExtent works as expected with user metadata", () => { - var data = [1, 2, 3, 4, 1]; - var metadata = {foo: 11}; - var id = (d: any) => d; - var dataset = new Plottable.Dataset(data, metadata); - var plot = new Plottable.Plot.AbstractPlot().addDataset(dataset); - var a1 = (d: number, i: number, m: any) => d + i - 2; - assert.deepEqual(dataset._getExtent(a1, id), [-1, 5], "extent for numerical data works properly"); - var a2 = (d: number, i: number, m: any) => d + m.foo; - assert.deepEqual(dataset._getExtent(a2, id), [12, 15], "extent uses metadata appropriately"); - dataset.metadata({foo: -1}); - assert.deepEqual(dataset._getExtent(a2, id), [0, 3], "metadata change is reflected in extent results"); - var a3 = (d: number, i: number, m: any) => "_" + d; - assert.deepEqual(dataset._getExtent(a3, id), ["_1", "_2", "_3", "_4"], "extent works properly on string domains (no repeats)"); - var a_toString = (d: any) => (d + 2).toString(); - var coerce = (d: any) => +d; - assert.deepEqual(dataset._getExtent(a_toString, coerce), [3, 6], "type coercion works as expected"); + it("Removing listener from dataset should be possible", () => { + var ds = new Plottable.Dataset(); + + var newData1 = [1, 2, 3]; + var newData2 = [4, 5, 6]; + + var callbackCalled = false; + var callback = (listenable: Plottable.Dataset) => { + assert.strictEqual(listenable, ds, "Callback received the Dataset as the first argument"); + callbackCalled = true; + }; + ds.onUpdate(callback); + + ds.data(newData1); + assert.isTrue(callbackCalled, "callback was called when the data was changed"); + + callbackCalled = false; + ds.offUpdate(callback); + ds.data(newData2); + assert.isFalse(callbackCalled, "callback was called when the data was changed"); }); }); diff --git a/test/core/domainerTests.ts b/test/core/domainerTests.ts index e0ab246aec..e12a1eefa9 100644 --- a/test/core/domainerTests.ts +++ b/test/core/domainerTests.ts @@ -3,26 +3,28 @@ var assert = chai.assert; describe("Domainer", () => { - var scale: Plottable.Scale.Linear; + var scale: Plottable.Scales.Linear; var domainer: Plottable.Domainer; beforeEach(() => { - scale = new Plottable.Scale.Linear(); + scale = new Plottable.Scales.Linear(); domainer = new Plottable.Domainer(); }); it("pad() works in general case", () => { - scale._updateExtent("1", "x", [100, 200]); + scale.addExtentsProvider((scale: Plottable.Scale) => [[100, 200]]); + scale.autoDomain(); scale.domainer(new Plottable.Domainer().pad(0.2)); assert.closeTo(scale.domain()[0], 90, 0.1, "lower bound of domain correct"); assert.closeTo(scale.domain()[1], 210, 0.1, "upper bound of domain correct"); }); it("pad() works for date scales", () => { - var timeScale = new Plottable.Scale.Time(); + var timeScale = new Plottable.Scales.Time(); var f = d3.time.format("%x"); var d1 = f.parse("06/02/2014"); var d2 = f.parse("06/03/2014"); - timeScale._updateExtent("1", "x", [d1, d2]); + timeScale.addExtentsProvider((scale: Plottable.Scale) => [[d1, d2]]); + timeScale.autoDomain(); timeScale.domainer(new Plottable.Domainer().pad()); var dd1 = timeScale.domain()[0]; var dd2 = timeScale.domain()[1]; @@ -30,25 +32,8 @@ describe("Domainer", () => { assert.isNotNull(dd1.toDateString, "padDomain produced dates"); assert.notEqual(d1.valueOf(), dd1.valueOf(), "date1 changed"); assert.notEqual(d2.valueOf(), dd2.valueOf(), "date2 changed"); - assert.equal(dd1.valueOf(), dd1.valueOf(), "date1 is not NaN"); - assert.equal(dd2.valueOf(), dd2.valueOf(), "date2 is not NaN"); - }); - - it("pad() works on log scales", () => { - var logScale = new Plottable.Scale.Log(); - logScale._updateExtent("1", "x", [10, 100]); - logScale.range([0, 1]); - logScale.domainer(domainer.pad(2.0)); - assert.closeTo(logScale.domain()[0], 1, 0.001); - assert.closeTo(logScale.domain()[1], 1000, 0.001); - logScale.range([50, 60]); - logScale.autoDomain(); - assert.closeTo(logScale.domain()[0], 1, 0.001); - assert.closeTo(logScale.domain()[1], 1000, 0.001); - logScale.range([-1, -2]); - logScale.autoDomain(); - assert.closeTo(logScale.domain()[0], 1, 0.001); - assert.closeTo(logScale.domain()[1], 1000, 0.001); + assert.strictEqual(dd1.valueOf(), dd1.valueOf(), "date1 is not NaN"); + assert.strictEqual(dd2.valueOf(), dd2.valueOf(), "date2 is not NaN"); }); it("pad() defaults to [v-1, v+1] if there's only one numeric value", () => { @@ -62,13 +47,10 @@ describe("Domainer", () => { var d2 = new Date(2000, 5, 5); var dayBefore = new Date(2000, 5, 4); var dayAfter = new Date(2000, 5, 6); - var timeScale = new Plottable.Scale.Time(); - // the result of computeDomain() will be number[], but when it - // gets fed back into timeScale, it will be adjusted back to a Date. - // That's why I'm using _updateExtent() instead of domainer.computeDomain() - timeScale._updateExtent("1", "x", [d, d2]); - timeScale.domainer(new Plottable.Domainer().pad()); - assert.deepEqual(timeScale.domain(), [dayBefore, dayAfter]); + domainer.pad(); + var domain = domainer.computeDomain([[d, d2]], scale); + assert.strictEqual(domain[0], dayBefore.valueOf(), "domain start was set to the day before"); + assert.strictEqual(domain[1], dayAfter.valueOf(), "domain end was set to the day after"); }); it("pad() only takes the last value", () => { @@ -93,35 +75,36 @@ describe("Domainer", () => { }); it("paddingException(n) will not pad beyond n", () => { - domainer.pad(0.1).addPaddingException(0, "key").addPaddingException(200); + domainer.pad(0.1).addPaddingException("keyLeft", 0).addPaddingException("keyRight", 200); var domain = domainer.computeDomain([[0, 100]], scale); assert.deepEqual(domain, [0, 105], "padding exceptions can be added by key"); domain = domainer.computeDomain([[-100, 0]], scale); assert.deepEqual(domain, [-105, 0]); domain = domainer.computeDomain([[0, 200]], scale); assert.deepEqual(domain, [0, 200]); - domainer.removePaddingException("key"); + domainer.removePaddingException("keyLeft"); domain = domainer.computeDomain([[0, 200]], scale); assert.deepEqual(domain, [-10, 200], "paddingExceptions can be removed by key"); - domainer.removePaddingException(200); + domainer.removePaddingException("keyRight"); domain = domainer.computeDomain([[0, 200]], scale); assert.notEqual(domain[1], 200, "unregistered paddingExceptions can be removed using boolean argument"); }); it("paddingException(n) works on dates", () => { - var a = new Date(2000, 5, 5); - var b = new Date(2003, 0, 1); - domainer.pad().addPaddingException(a); - var timeScale = new Plottable.Scale.Time(); - timeScale._updateExtent("1", "x", [a, b]); + var startDate = new Date(2000, 5, 5); + var endDate = new Date(2003, 0, 1); + var timeScale = new Plottable.Scales.Time(); + timeScale.addExtentsProvider((scale: Plottable.Scale) => [[startDate, endDate]]); + timeScale.autoDomain(); + domainer.pad().addPaddingException("key", startDate); timeScale.domainer(domainer); var domain = timeScale.domain(); - assert.deepEqual(domain[0], a); - assert.isTrue(b < domain[1]); + assert.deepEqual(domain[0], startDate); + assert.isTrue(endDate < domain[1]); }); it("include(n) works an expected", () => { - domainer.addIncludedValue(5); + domainer.addIncludedValue("key1", 5); var domain = domainer.computeDomain([[0, 10]], scale); assert.deepEqual(domain, [0, 10]); domain = domainer.computeDomain([[0, 3]], scale); @@ -129,49 +112,48 @@ describe("Domainer", () => { domain = domainer.computeDomain([[100, 200]], scale); assert.deepEqual(domain, [5, 200]); - domainer.addIncludedValue(-3).addIncludedValue(0).addIncludedValue(10, "key"); + domainer.addIncludedValue("key2", -3).addIncludedValue("key3", 0).addIncludedValue("key4", 10); domain = domainer.computeDomain([[100, 200]], scale); assert.deepEqual(domain, [-3, 200]); domain = domainer.computeDomain([[0, 0]], scale); assert.deepEqual(domain, [-3, 10]); - domainer.removeIncludedValue("key"); + domainer.removeIncludedValue("key4"); domain = domainer.computeDomain([[100, 200]], scale); assert.deepEqual(domain, [-3, 200]); domain = domainer.computeDomain([[-100, -50]], scale); assert.deepEqual(domain, [-100, 5]); - domainer.addIncludedValue(10); + domainer.addIncludedValue("key5", 10); domain = domainer.computeDomain([[-100, -50]], scale); assert.deepEqual(domain, [-100, 10], "unregistered includedValues can be added"); - domainer.removeIncludedValue(10); + domainer.removeIncludedValue("key5"); domain = domainer.computeDomain([[-100, -50]], scale); assert.deepEqual(domain, [-100, 5], "unregistered includedValues can be removed with addOrRemove argument"); }); it("include(n) works on dates", () => { - var a = new Date(2000, 5, 4); - var b = new Date(2000, 5, 5); - var c = new Date(2000, 5, 6); - var d = new Date(2003, 0, 1); - domainer.addIncludedValue(b); - var timeScale = new Plottable.Scale.Time(); - timeScale._updateExtent("1", "x", [c, d]); + var includedDate = new Date(2000, 5, 5); + var startDate = new Date(2000, 5, 6); + var endDate = new Date(2003, 0, 1); + var timeScale = new Plottable.Scales.Time(); + timeScale.addExtentsProvider((scale: Plottable.Scale) => [[startDate, endDate]]); + timeScale.autoDomain(); + domainer.addIncludedValue("key", includedDate); timeScale.domainer(domainer); - assert.deepEqual(timeScale.domain(), [b, d]); + assert.deepEqual(timeScale.domain(), [includedDate, endDate], "domain was expanded to contain included date"); }); it("exceptions are setup properly on an area plot", () => { - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - var domainer = yScale.domainer(); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); var data = [{x: 0, y: 0, y0: 0}, {x: 5, y: 5, y0: 5}]; var dataset = new Plottable.Dataset(data); - var r = new Plottable.Plot.Area(xScale, yScale); + var r = new Plottable.Plots.Area(xScale, yScale); r.addDataset(dataset); - var svg = generateSVG(); - r.project("x", "x", xScale); - r.project("y", "y", yScale); + var svg = TestMethods.generateSVG(); + r.x((d) => d.x, xScale); + r.y((d) => d.y, yScale); r.renderTo(svg); function getExceptions() { @@ -189,14 +171,14 @@ describe("Domainer", () => { assert.deepEqual(getExceptions(), [0], "initializing the plot adds a padding exception at 0"); // assert.deepEqual(getExceptions(), [], "Initially there are no padding exceptions"); - r.project("y0", "y0", yScale); - assert.deepEqual(getExceptions(), [], "projecting a non-constant y0 removes the padding exception"); - r.project("y0", 0, yScale); + r.y0((d) => d.y0, yScale); + assert.deepEqual(getExceptions(), [], "projecting a non-constant y0 removes the padding exception 1"); + r.y0(0, yScale); assert.deepEqual(getExceptions(), [0], "projecting constant y0 adds the exception back"); - r.project("y0", () => 5, yScale); + r.y0(5, yScale); assert.deepEqual(getExceptions(), [5], "projecting a different constant y0 removed the old exception and added a new one"); - r.project("y0", "y0", yScale); - assert.deepEqual(getExceptions(), [], "projecting a non-constant y0 removes the padding exception"); + r.y0((d) => d.y0, yScale); + assert.deepEqual(getExceptions(), [], "projecting a non-constant y0 removes the padding exception 2"); dataset.data([{x: 0, y: 0, y0: 0}, {x: 5, y: 5, y0: 0}]); assert.deepEqual(getExceptions(), [0], "changing to constant values via change in datasource adds exception"); svg.remove(); diff --git a/test/core/metadataTests.ts b/test/core/metadataTests.ts index 0b6fab1d02..7e4681d9e8 100644 --- a/test/core/metadataTests.ts +++ b/test/core/metadataTests.ts @@ -3,39 +3,38 @@ var assert = chai.assert; describe("Metadata", () => { - var xScale: Plottable.Scale.Linear; - var yScale: Plottable.Scale.Linear; + var xScale: Plottable.Scales.Linear; + var yScale: Plottable.Scales.Linear; var data1 = [{x: 0, y: 0}, {x: 1, y: 1}]; var data2 = [{x: 2, y: 2}, {x: 3, y: 3}]; before(() => { - xScale = new Plottable.Scale.Linear(); - yScale = new Plottable.Scale.Linear(); + xScale = new Plottable.Scales.Linear(); + yScale = new Plottable.Scales.Linear(); xScale.domain([0, 400]); yScale.domain([400, 0]); }); it("plot metadata is set properly", () => { var d1 = new Plottable.Dataset(); - var r = new Plottable.Plot.AbstractPlot() - .addDataset("d1", d1) - .addDataset( d1) - .addDataset("d2", []) - .addDataset([]); + var d2 = new Plottable.Dataset(); + var r = new Plottable.Plot() + .addDataset(d1) + .addDataset(d2); ( r)._datasetKeysInOrder.forEach((key: string) => { var plotMetadata = ( r)._key2PlotDatasetKey.get(key).plotMetadata; assert.propertyVal(plotMetadata, "datasetKey", key, "metadata has correct dataset key"); }); }); - it("user metadata is applied", () => { - var svg = generateSVG(400, 400); + it("Dataset is passed in", () => { + var svg = TestMethods.generateSVG(400, 400); var metadata = {foo: 10, bar: 20}; - var xAccessor = (d: any, i: number, u: any) => d.x + i * u.foo; - var yAccessor = (d: any, i: number, u: any) => u.bar; + var xAccessor = (d: any, i: number, dataset: Plottable.Dataset) => d.x + i * dataset.metadata().foo; + var yAccessor = (d: any, i: number, dataset: Plottable.Dataset) => dataset.metadata().bar; var dataset = new Plottable.Dataset(data1, metadata); - var plot = new Plottable.Plot.Scatter(xScale, yScale) - .project("x", xAccessor) - .project("y", yAccessor); + var plot = new Plottable.Plots.Scatter(xScale, yScale) + .x(xAccessor, xScale) + .y(yAccessor, yScale); plot.addDataset(dataset); plot.renderTo(svg); var circles = plot.getAllSelections(); @@ -62,16 +61,16 @@ describe("Metadata", () => { }); it("user metadata is applied to associated dataset", () => { - var svg = generateSVG(400, 400); + var svg = TestMethods.generateSVG(400, 400); var metadata1 = {foo: 10}; var metadata2 = {foo: 30}; - var xAccessor = (d: any, i: number, u: any) => d.x + (i + 1) * u.foo; + var xAccessor = (d: any, i: number, dataset: Plottable.Dataset) => d.x + (i + 1) * dataset.metadata().foo; var yAccessor = () => 0; var dataset1 = new Plottable.Dataset(data1, metadata1); var dataset2 = new Plottable.Dataset(data2, metadata2); - var plot = new Plottable.Plot.Scatter(xScale, yScale) - .project("x", xAccessor) - .project("y", yAccessor); + var plot = new Plottable.Plots.Scatter(xScale, yScale) + .x(xAccessor, xScale) + .y(yAccessor, yScale); plot.addDataset(dataset1); plot.addDataset(dataset2); plot.renderTo(svg); @@ -94,20 +93,20 @@ describe("Metadata", () => { }); it("plot metadata is applied", () => { - var svg = generateSVG(400, 400); - var xAccessor = (d: any, i: number, u: any, m: any) => d.x + (i + 1) * m.foo; + var svg = TestMethods.generateSVG(400, 400); + var xAccessor = (d: any, i: number, dataset: Plottable.Dataset, m: any) => d.x + (i + 1) * m.foo; var yAccessor = () => 0; - var plot = new Plottable.Plot.Scatter(xScale, yScale) - .project("x", xAccessor) - .project("y", yAccessor); + var plot = new Plottable.Plots.Scatter(xScale, yScale) + .x(xAccessor, xScale) + .y(yAccessor, yScale); ( plot)._getPlotMetadataForDataset = (key: string) => { return { datasetKey: key, foo: 10 }; }; - plot.addDataset(data1); - plot.addDataset(data2); + plot.addDataset(new Plottable.Dataset(data1)); + plot.addDataset(new Plottable.Dataset(data2)); plot.renderTo(svg); var circles = plot.getAllSelections(); var c1 = d3.select(circles[0][0]); @@ -128,31 +127,33 @@ describe("Metadata", () => { }); it("plot metadata is per plot", () => { - var svg = generateSVG(400, 400); - var xAccessor = (d: any, i: number, u: any, m: any) => d.x + (i + 1) * m.foo; + var svg = TestMethods.generateSVG(400, 400); + var xAccessor = (d: any, i: number, dataset: Plottable.Dataset, m: any) => d.x + (i + 1) * m.foo; var yAccessor = () => 0; - var plot1 = new Plottable.Plot.Scatter(xScale, yScale) - .project("x", xAccessor) - .project("y", yAccessor); + var plot1 = new Plottable.Plots.Scatter(xScale, yScale) + .x(xAccessor, xScale) + .y(yAccessor, yScale); ( plot1)._getPlotMetadataForDataset = (key: string) => { return { datasetKey: key, foo: 10 }; }; - plot1.addDataset(data1); - plot1.addDataset(data2); - var plot2 = new Plottable.Plot.Scatter(xScale, yScale) - .project("x", xAccessor) - .project("y", yAccessor); + var dataset1 = new Plottable.Dataset(data1); + var dataset2 = new Plottable.Dataset(data2); + plot1.addDataset(dataset1); + plot1.addDataset(dataset2); + var plot2 = new Plottable.Plots.Scatter(xScale, yScale) + .x(xAccessor, xScale) + .y(yAccessor, yScale); ( plot2)._getPlotMetadataForDataset = (key: string) => { return { datasetKey: key, foo: 20 }; }; - plot2.addDataset(data1); - plot2.addDataset(data2); + plot2.addDataset(dataset1); + plot2.addDataset(dataset2); plot1.renderTo(svg); plot2.renderTo(svg); var circles = plot1.getAllSelections(); @@ -188,53 +189,46 @@ describe("Metadata", () => { svg.remove(); }); - it("_getExtent works as expected with plot metadata", () => { - var svg = generateSVG(400, 400); - var metadata = {foo: 11}; - var id = (d: any) => d; - var dataset = new Plottable.Dataset(data1, metadata); - var a = (d: any, i: number, u: any, m: any) => d.x + u.foo + m.foo; - var plot = new Plottable.Plot.AbstractPlot().project("a", a, xScale); - ( plot)._getPlotMetadataForDataset = (key: string) => { - return { - datasetKey: key, - foo: 5 - }; - }; - plot.addDataset(dataset); - plot.renderTo(svg); - assert.deepEqual(dataset._getExtent(a, id), [16, 17], "plot metadata is reflected in extent results"); - dataset.metadata({foo: 0}); - assert.deepEqual(dataset._getExtent(a, id), [5, 6], "plot metadata is reflected in extent results after change user metadata"); - svg.remove(); - }); - it("each plot passes metadata to projectors", () => { - var svg = generateSVG(400, 400); + var svg = TestMethods.generateSVG(400, 400); var metadata = {foo: 11}; var dataset1 = new Plottable.Dataset(data1, metadata); var dataset2 = new Plottable.Dataset(data2, metadata); - var checkPlot = (plot: Plottable.Plot.AbstractPlot) => { - plot.addDataset("ds1", dataset1) - .addDataset("ds2", dataset2) - .project("x", (d: any, i: number, u: any, m: Plottable.Plot.PlotMetadata) => d.x + u.foo + m.datasetKey.length) - .project("y", (d: any, i: number, u: any, m: Plottable.Plot.PlotMetadata) => d.y + u.foo - m.datasetKey.length); + var checkXYPlot = (plot: Plottable.XYPlot) => { + var xAccessor = (d: any, i: number, dataset: Plottable.Dataset, m: Plottable.Plots.PlotMetadata) => { + return d.x + dataset.metadata().foo + m.datasetKey.length; + }; + var yAccessor = (d: any, i: number, dataset: Plottable.Dataset, m: Plottable.Plots.PlotMetadata) => { + return d.y + dataset.metadata().foo - m.datasetKey.length; + }; + plot.addDataset(dataset1) + .addDataset(dataset2); + plot.x(xAccessor, xScale) + .y(yAccessor, yScale); + + // This should not crash. If some metadata is not passed, undefined property error will be raised during accessor call. + plot.renderTo(svg); + plot.destroy(); + }; + + var checkPiePlot = (plot: Plottable.Plots.Pie) => { + plot.sectorValue((d) => d.x).addDataset(dataset1); // This should not crash. If some metadata is not passed, undefined property error will be raised during accessor call. plot.renderTo(svg); - plot.remove(); + plot.destroy(); }; - checkPlot(new Plottable.Plot.Area(xScale, yScale)); - checkPlot(new Plottable.Plot.StackedArea(xScale, yScale)); - checkPlot(new Plottable.Plot.Bar(xScale, yScale)); - checkPlot(new Plottable.Plot.StackedBar(xScale, yScale)); - checkPlot(new Plottable.Plot.StackedBar(yScale, xScale, false)); - checkPlot(new Plottable.Plot.ClusteredBar(xScale, yScale)); - checkPlot(new Plottable.Plot.Pie().project("value", "x")); - checkPlot(new Plottable.Plot.Bar(xScale, yScale, false)); - checkPlot(new Plottable.Plot.Scatter(xScale, yScale)); + checkXYPlot(new Plottable.Plots.Area(xScale, yScale)); + checkXYPlot(new Plottable.Plots.StackedArea(xScale, yScale)); + checkXYPlot(new Plottable.Plots.Bar(xScale, yScale)); + checkXYPlot(new Plottable.Plots.StackedBar(xScale, yScale)); + checkXYPlot(new Plottable.Plots.StackedBar(yScale, xScale, false)); + checkXYPlot(new Plottable.Plots.ClusteredBar(xScale, yScale)); + checkXYPlot(new Plottable.Plots.Bar(xScale, yScale, false)); + checkXYPlot(new Plottable.Plots.Scatter(xScale, yScale)); + checkPiePlot(new Plottable.Plots.Pie()); svg.remove(); }); }); diff --git a/test/core/renderControllerTests.ts b/test/core/renderControllerTests.ts new file mode 100644 index 0000000000..41603581e9 --- /dev/null +++ b/test/core/renderControllerTests.ts @@ -0,0 +1,25 @@ +/// + +var assert = chai.assert; + +describe("RenderController", () => { + // HACKHACK: #2083 + it.skip("Components whose render() is triggered by another Component's render() will be drawn", () => { + var link1 = new Plottable.Component(); + var svg1 = TestMethods.generateSVG(); + link1.anchor(svg1).computeLayout(); + var link2 = new Plottable.Component(); + var svg2 = TestMethods.generateSVG(); + link2.anchor(svg2).computeLayout(); + + ( link1).renderImmediately = () => link2.render(); + var link2Rendered = false; + ( link2).renderImmediately = () => link2Rendered = true; + + link1.render(); + assert.isTrue(link2Rendered, "dependent Component was render()-ed"); + + svg1.remove(); + svg2.remove(); + }); +}); diff --git a/test/core/tableTests.ts b/test/core/tableTests.ts index 2b7aa5b1f9..9faad6740b 100644 --- a/test/core/tableTests.ts +++ b/test/core/tableTests.ts @@ -5,13 +5,12 @@ var assert = chai.assert; function generateBasicTable(nRows: number, nCols: number) { // makes a table with exactly nRows * nCols children in a regular grid, with each // child being a basic component - var table = new Plottable.Component.Table(); - var rows: Plottable.Component.AbstractComponent[][] = []; - var components: Plottable.Component.AbstractComponent[] = []; - for(var i = 0; i < nRows; i++) { - for(var j = 0; j < nCols; j++) { - var r = new Plottable.Component.AbstractComponent(); - table.addComponent(i, j, r); + var table = new Plottable.Components.Table(); + var components: Plottable.Component[] = []; + for (var i = 0; i < nRows; i++) { + for (var j = 0; j < nCols; j++) { + var r = new Plottable.Component(); + table.add(r, i, j); components.push(r); } } @@ -20,12 +19,12 @@ function generateBasicTable(nRows: number, nCols: number) { describe("Tables", () => { it("tables are classed properly", () => { - var table = new Plottable.Component.Table(); + var table = new Plottable.Components.Table(); assert.isTrue(table.classed("table")); }); it("padTableToSize works properly", () => { - var t = new Plottable.Component.Table(); + var t = new Plottable.Components.Table(); assert.deepEqual(( t)._rows, [], "the table rows is an empty list"); ( t)._padTableToSize(1, 1); var rows = ( t)._rows; @@ -37,90 +36,71 @@ describe("Tables", () => { ( t)._padTableToSize(5, 2); assert.lengthOf(rows, 5, "there are five rows"); - rows.forEach((r: Plottable.Component.AbstractComponent[]) => assert.lengthOf(r, 2, "there are two columsn per row")); - assert.equal(rows[0][0], firstComponent, "the first component is unchanged"); + rows.forEach((r: Plottable.Component[]) => assert.lengthOf(r, 2, "there are two columns per row")); + assert.strictEqual(rows[0][0], firstComponent, "the first component is unchanged"); }); it("table constructor can take a list of lists of components", () => { - var c0 = new Plottable.Component.AbstractComponent(); + var c0 = new Plottable.Component(); var row1 = [null, c0]; - var row2 = [new Plottable.Component.AbstractComponent(), null]; - var table = new Plottable.Component.Table([row1, row2]); - assert.equal(( table)._rows[0][1], c0, "the component is in the right spot"); - var c1 = new Plottable.Component.AbstractComponent(); - table.addComponent(2, 2, c1); - assert.equal(( table)._rows[2][2], c1, "the inserted component went to the right spot"); + var row2 = [new Plottable.Component(), null]; + var table = new Plottable.Components.Table([row1, row2]); + assert.strictEqual(( table)._rows[0][1], c0, "the component is in the right spot"); + var c1 = new Plottable.Component(); + table.add(c1, 2, 2); + assert.strictEqual(( table)._rows[2][2], c1, "the inserted component went to the right spot"); }); - it("tables can be constructed by adding components in matrix style", () => { - var table = new Plottable.Component.Table(); - var c1 = new Plottable.Component.AbstractComponent(); - var c2 = new Plottable.Component.AbstractComponent(); - table.addComponent(0, 0, c1); - table.addComponent(1, 1, c2); - var rows = ( table)._rows; - assert.lengthOf(rows, 2, "there are two rows"); - assert.lengthOf(rows[0], 2, "two cols in first row"); - assert.lengthOf(rows[1], 2, "two cols in second row"); - assert.equal(rows[0][0], c1, "first component added correctly"); - assert.equal(rows[1][1], c2, "second component added correctly"); - assert.isNull(rows[0][1], "component at (0, 1) is null"); - assert.isNull(rows[1][0], "component at (1, 0) is null"); - }); - - it("add a component where one already exists creates a new group", () => { - var c1 = new Plottable.Component.AbstractComponent(); - var c2 = new Plottable.Component.AbstractComponent(); - var c3 = new Plottable.Component.AbstractComponent(); - var t = new Plottable.Component.Table(); - - t.addComponent(0, 2, c1); - t.addComponent(0, 0, c2); - t.addComponent(0, 2, c3); - - assert.isTrue(Plottable.Component.Group.prototype.isPrototypeOf(( t)._rows[0][2]), "A group was created"); - - var components: Plottable.Component.AbstractComponent[] = ( t)._rows[0][2].components(); - assert.lengthOf(components, 2, "The group created should have 2 components"); - assert.equal(components[0], c1, "First element in the group at (0, 2) should be c1"); - assert.equal(components[1], c3, "Second element in the group at (0, 2) should be c3"); - }); - - it("add a component where a group already exists adds the component to the group", () => { - var c1 = new Plottable.Component.AbstractComponent(); - var c2 = new Plottable.Component.AbstractComponent(); - var grp = new Plottable.Component.Group([c1, c2]); - - var c3 = new Plottable.Component.AbstractComponent(); - - var t = new Plottable.Component.Table(); + describe("add()", () => { + it("adds Component and pads out other empty cells with null", () => { + var table = new Plottable.Components.Table(); + var c1 = new Plottable.Component(); + var c2 = new Plottable.Component(); + table.add(c1, 0, 0); + table.add(c2, 1, 1); + var rows = ( table)._rows; + assert.lengthOf(rows, 2, "there are two rows"); + assert.lengthOf(rows[0], 2, "two cols in first row"); + assert.lengthOf(rows[1], 2, "two cols in second row"); + assert.strictEqual(rows[0][0], c1, "first component added correctly"); + assert.strictEqual(rows[1][1], c2, "second component added correctly"); + assert.isNull(rows[0][1], "component at (0, 1) is null"); + assert.isNull(rows[1][0], "component at (1, 0) is null"); + }); - t.addComponent(0, 2, grp); - t.addComponent(0, 2, c3); - assert.isTrue(Plottable.Component.Group.prototype.isPrototypeOf(( t)._rows[0][2]), "The cell still contains a group"); + it("adding a Component where one already exists throws an Error", () => { + var c1 = new Plottable.Component(); + var t = new Plottable.Components.Table([[c1]]); + var c2 = new Plottable.Component(); + assert.throws(() => t.add(c2, 0, 0), Error, "occupied"); + }); - var components: Plottable.Component.AbstractComponent[] = ( t)._rows[0][2].components(); - assert.lengthOf(components, 3, "The group created should have 3 components"); - assert.equal(components[0], c1, "First element in the group at (0, 2) should still be c1"); - assert.equal(components[1], c2, "Second element in the group at (0, 2) should still be c2"); - assert.equal(components[2], c3, "The Component was added to the existing Group"); - }); + it("adding null to a table cell should throw an error", () => { + var c1 = new Plottable.Component(); + var t = new Plottable.Components.Table([[c1]]); - it("adding null to a table cell should throw an error", () => { - var c1 = new Plottable.Component.AbstractComponent(); - var t = new Plottable.Component.Table([[c1]]); + assert.throw(() => t.add(null, 0, 0), "Cannot add null to a table cell"); + }); - assert.throw(() => t.addComponent(0, 0, null), "Cannot add null to a table cell"); - }); + it("add()-ing a Component to the Group should detach() it from its current location", () => { + var c1 = new Plottable.Component; + var svg = TestMethods.generateSVG(); + c1.renderTo(svg); + var table = new Plottable.Components.Table(); + table.add(c1, 0, 0); + assert.isFalse(svg.node().hasChildNodes(), "Component was detach()-ed"); + svg.remove(); + }); - it("addComponent works even if a component is added with a high column and low row index", () => { - // Solves #180, a weird bug - var t = new Plottable.Component.Table(); - var svg = generateSVG(); - t.addComponent(1, 0, new Plottable.Component.AbstractComponent()); - t.addComponent(0, 2, new Plottable.Component.AbstractComponent()); - t.renderTo(svg); //would throw an error without the fix (tested); - svg.remove(); + it("add() works even if a component is added with a high column and low row index", () => { + // Solves #180, a weird bug + var t = new Plottable.Components.Table(); + var svg = TestMethods.generateSVG(); + t.add(new Plottable.Component(), 1, 0); + t.add(new Plottable.Component(), 0, 2); + t.renderTo(svg); // would throw an error without the fix (tested); + svg.remove(); + }); }); it("basic table with 2 rows 2 cols lays out properly", () => { @@ -128,19 +108,19 @@ describe("Tables", () => { var table = tableAndcomponents.table; var components = tableAndcomponents.components; - var svg = generateSVG(); + var svg = TestMethods.generateSVG(); table.renderTo(svg); var elements = components.map((r) => ( r)._element); - var translates = elements.map((e) => getTranslate(e)); + var translates = elements.map((e) => TestMethods.getTranslate(e)); assert.deepEqual(translates[0], [0, 0], "first element is centered at origin"); assert.deepEqual(translates[1], [200, 0], "second element is located properly"); assert.deepEqual(translates[2], [0, 200], "third element is located properly"); assert.deepEqual(translates[3], [200, 200], "fourth element is located properly"); - var bboxes = elements.map((e) => Plottable._Util.DOM.getBBox(e)); + var bboxes = elements.map((e) => Plottable.Utils.DOM.getBBox(e)); bboxes.forEach((b) => { - assert.equal(b.width, 200, "bbox is 200 pixels wide"); - assert.equal(b.height, 200, "bbox is 200 pixels tall"); + assert.strictEqual(b.width, 200, "bbox is 200 pixels wide"); + assert.strictEqual(b.height, 200, "bbox is 200 pixels tall"); }); svg.remove(); }); @@ -149,37 +129,37 @@ describe("Tables", () => { var tableAndcomponents = generateBasicTable(2, 2); var table = tableAndcomponents.table; var components = tableAndcomponents.components; - table.padding(5, 5); + table.rowPadding(5).columnPadding(5); - var svg = generateSVG(415, 415); + var svg = TestMethods.generateSVG(415, 415); table.renderTo(svg); var elements = components.map((r) => ( r)._element); - var translates = elements.map((e) => getTranslate(e)); - var bboxes = elements.map((e) => Plottable._Util.DOM.getBBox(e)); + var translates = elements.map((e) => TestMethods.getTranslate(e)); + var bboxes = elements.map((e) => Plottable.Utils.DOM.getBBox(e)); assert.deepEqual(translates[0], [0, 0], "first element is centered properly"); assert.deepEqual(translates[1], [210, 0], "second element is located properly"); assert.deepEqual(translates[2], [0, 210], "third element is located properly"); assert.deepEqual(translates[3], [210, 210], "fourth element is located properly"); bboxes.forEach((b) => { - assert.equal(b.width, 205, "bbox is 205 pixels wide"); - assert.equal(b.height, 205, "bbox is 205 pixels tall"); + assert.strictEqual(b.width, 205, "bbox is 205 pixels wide"); + assert.strictEqual(b.height, 205, "bbox is 205 pixels tall"); }); svg.remove(); }); it("table with fixed-size objects on every side lays out properly", () => { - var svg = generateSVG(); - var c4 = new Plottable.Component.AbstractComponent(); + var svg = TestMethods.generateSVG(); + var c4 = new Plottable.Component(); // [0 1 2] \\ // [3 4 5] \\ // [6 7 8] \\ // give the axis-like objects a minimum - var c1 = makeFixedSizeComponent(null, 30); - var c7 = makeFixedSizeComponent(null, 30); - var c3 = makeFixedSizeComponent(50, null); - var c5 = makeFixedSizeComponent(50, null); - var table = new Plottable.Component.Table([[null, c1, null], + var c1 = TestMethods.makeFixedSizeComponent(null, 30); + var c7 = TestMethods.makeFixedSizeComponent(null, 30); + var c3 = TestMethods.makeFixedSizeComponent(50, null); + var c5 = TestMethods.makeFixedSizeComponent(50, null); + var table = new Plottable.Components.Table([[null, c1, null], [c3 , c4, c5 ], [null, c7, null]]); @@ -188,8 +168,8 @@ describe("Tables", () => { table.renderTo(svg); var elements = components.map((r) => ( r)._element); - var translates = elements.map((e) => getTranslate(e)); - var bboxes = elements.map((e) => Plottable._Util.DOM.getBBox(e)); + var translates = elements.map((e) => TestMethods.getTranslate(e)); + var bboxes = elements.map((e) => Plottable.Utils.DOM.getBBox(e)); // test the translates assert.deepEqual(translates[0], [50, 0] , "top axis translate"); assert.deepEqual(translates[4], [50, 370], "bottom axis translate"); @@ -197,11 +177,11 @@ describe("Tables", () => { assert.deepEqual(translates[3], [350, 30], "right axis translate"); assert.deepEqual(translates[2], [50, 30] , "plot translate"); // test the bboxes - assertBBoxEquivalence(bboxes[0], [300, 30], "top axis bbox"); - assertBBoxEquivalence(bboxes[4], [300, 30], "bottom axis bbox"); - assertBBoxEquivalence(bboxes[1], [50, 340], "left axis bbox"); - assertBBoxEquivalence(bboxes[3], [50, 340], "right axis bbox"); - assertBBoxEquivalence(bboxes[2], [300, 340], "plot bbox"); + TestMethods.assertBBoxEquivalence(bboxes[0], [300, 30], "top axis bbox"); + TestMethods.assertBBoxEquivalence(bboxes[4], [300, 30], "bottom axis bbox"); + TestMethods.assertBBoxEquivalence(bboxes[1], [50, 340], "left axis bbox"); + TestMethods.assertBBoxEquivalence(bboxes[3], [50, 340], "right axis bbox"); + TestMethods.assertBBoxEquivalence(bboxes[2], [300, 340], "plot bbox"); svg.remove(); }); @@ -209,44 +189,44 @@ describe("Tables", () => { var tableAndcomponents = generateBasicTable(3, 3); var table = tableAndcomponents.table; var components = tableAndcomponents.components; - components.forEach((c) => fixComponentSize(c, 10, 10)); - assert.isTrue(table._isFixedWidth(), "fixed width when all subcomponents fixed width"); - assert.isTrue(table._isFixedHeight(), "fixedHeight when all subcomponents fixed height"); - fixComponentSize(components[0], null, 10); - assert.isFalse(table._isFixedWidth(), "width not fixed when some subcomponent width not fixed"); - assert.isTrue(table._isFixedHeight(), "the height is still fixed when some subcomponent width not fixed"); - fixComponentSize(components[8], 10, null); - fixComponentSize(components[0], 10, 10); - assert.isTrue(table._isFixedWidth(), "width fixed again once no subcomponent width not fixed"); - assert.isFalse(table._isFixedHeight(), "height unfixed now that a subcomponent has unfixed height"); + components.forEach((c) => TestMethods.fixComponentSize(c, 10, 10)); + assert.isTrue(table.fixedWidth(), "fixed width when all subcomponents fixed width"); + assert.isTrue(table.fixedHeight(), "fixedHeight when all subcomponents fixed height"); + TestMethods.fixComponentSize(components[0], null, 10); + assert.isFalse(table.fixedWidth(), "width not fixed when some subcomponent width not fixed"); + assert.isTrue(table.fixedHeight(), "the height is still fixed when some subcomponent width not fixed"); + TestMethods.fixComponentSize(components[8], 10, null); + TestMethods.fixComponentSize(components[0], 10, 10); + assert.isTrue(table.fixedWidth(), "width fixed again once no subcomponent width not fixed"); + assert.isFalse(table.fixedHeight(), "height unfixed now that a subcomponent has unfixed height"); }); - it.skip("table._requestedSpace works properly", () => { + it.skip("table.requestedSpace works properly", () => { // [0 1] // [2 3] - var c0 = new Plottable.Component.AbstractComponent(); - var c1 = makeFixedSizeComponent(50, 50); - var c2 = makeFixedSizeComponent(20, 50); - var c3 = makeFixedSizeComponent(20, 20); + var c0 = new Plottable.Component(); + var c1 = TestMethods.makeFixedSizeComponent(50, 50); + var c2 = TestMethods.makeFixedSizeComponent(20, 50); + var c3 = TestMethods.makeFixedSizeComponent(20, 20); - var table = new Plottable.Component.Table([[c0, c1], [c2, c3]]); + var table = new Plottable.Components.Table([[c0, c1], [c2, c3]]); - var spaceRequest = table._requestedSpace(30, 30); - verifySpaceRequest(spaceRequest, 30, 30, true, true, "1"); + var spaceRequest = table.requestedSpace(30, 30); + TestMethods.verifySpaceRequest(spaceRequest, 30, 30, "1"); - spaceRequest = table._requestedSpace(50, 50); - verifySpaceRequest(spaceRequest, 50, 50, true, true, "2"); + spaceRequest = table.requestedSpace(50, 50); + TestMethods.verifySpaceRequest(spaceRequest, 50, 50, "2"); - spaceRequest = table._requestedSpace(90, 90); - verifySpaceRequest(spaceRequest, 70, 90, false, true, "3"); + spaceRequest = table.requestedSpace(90, 90); + TestMethods.verifySpaceRequest(spaceRequest, 70, 90, "3"); - spaceRequest = table._requestedSpace(200, 200); - verifySpaceRequest(spaceRequest, 70, 100, false, false, "4"); + spaceRequest = table.requestedSpace(200, 200); + TestMethods.verifySpaceRequest(spaceRequest, 70, 100, "4"); }); describe("table._iterateLayout works properly", () => { // This test battery would have caught #405 - function verifyLayoutResult(result: Plottable.Component._IterateLayoutResult, + function verifyLayoutResult(result: any, cPS: number[], rPS: number[], gW: number[], gH: number[], wW: boolean, wH: boolean, id: string) { assert.deepEqual(result.colProportionalSpace, cPS, "colProportionalSpace:" + id); @@ -257,97 +237,103 @@ describe("Tables", () => { assert.deepEqual(result.wantsHeight, wH, "wantsHeight:" + id); } - var c1 = new Plottable.Component.AbstractComponent(); - var c2 = new Plottable.Component.AbstractComponent(); - var c3 = new Plottable.Component.AbstractComponent(); - var c4 = new Plottable.Component.AbstractComponent(); - var table = new Plottable.Component.Table([ - [c1, c2], - [c3, c4]]); - it("iterateLayout works in the easy case where there is plenty of space and everything is satisfied on first go", () => { - fixComponentSize(c1, 50, 50); - fixComponentSize(c4, 20, 10); + var c1 = new Mocks.FixedSizeComponent(50, 50); + var c2 = new Plottable.Component(); + var c3 = new Plottable.Component(); + var c4 = new Mocks.FixedSizeComponent(20, 10); + var table = new Plottable.Components.Table([ + [c1, c2], + [c3, c4] + ]); var result = ( table)._iterateLayout(500, 500); verifyLayoutResult(result, [215, 215], [220, 220], [50, 20], [50, 10], false, false, ""); }); it.skip("iterateLayout works in the difficult case where there is a shortage of space and layout requires iterations", () => { - fixComponentSize(c1, 490, 50); + var c1 = new Mocks.FixedSizeComponent(490, 50); + var c2 = new Plottable.Component(); + var c3 = new Plottable.Component(); + var c4 = new Plottable.Component(); + var table = new Plottable.Components.Table([ + [c1, c2], + [c3, c4] + ]); var result = ( table)._iterateLayout(500, 500); verifyLayoutResult(result, [0, 0], [220, 220], [480, 20], [50, 10], true, false, ""); }); it("iterateLayout works in the case where all components are fixed-size", () => { - fixComponentSize(c1, 50, 50); - fixComponentSize(c2, 50, 50); - fixComponentSize(c3, 50, 50); - fixComponentSize(c4, 50, 50); + var c1 = new Mocks.FixedSizeComponent(50, 50); + var c2 = new Mocks.FixedSizeComponent(50, 50); + var c3 = new Mocks.FixedSizeComponent(50, 50); + var c4 = new Mocks.FixedSizeComponent(50, 50); + var table = new Plottable.Components.Table([ + [c1, c2], + [c3, c4] + ]); var result = ( table)._iterateLayout(100, 100); - verifyLayoutResult(result, [0, 0], [0, 0], [50, 50], [50, 50], false, false, "..when there's exactly enough space"); + verifyLayoutResult(result, [0, 0], [0, 0], [50, 50], [50, 50], false, false, "when there's exactly enough space"); result = ( table)._iterateLayout(80, 80); - verifyLayoutResult(result, [0, 0], [0, 0], [40, 40], [40, 40], true, true, "..when there's not enough space"); + verifyLayoutResult(result, [0, 0], [0, 0], [50, 50], [50, 50], true, true, "still requests more space if constrained"); + result = ( table)._iterateLayout(80, 80, true); + verifyLayoutResult(result, [0, 0], [0, 0], [40, 40], [40, 40], true, true, "accepts suboptimal layout if it's the final offer"); result = ( table)._iterateLayout(120, 120); // If there is extra space in a fixed-size table, the extra space should not be allocated to proportional space - verifyLayoutResult(result, [0, 0], [0, 0], [50, 50], [50, 50], false, false, "..when there's extra space"); - }); - - it.skip("iterateLayout works in the tricky case when components can be unsatisfied but request little space", () => { - table = new Plottable.Component.Table([[c1, c2]]); - fixComponentSize(c1, null, null); - c2._requestedSpace = (w: number, h: number) => { - return { - width: w >= 200 ? 200 : 0, - height: h >= 200 ? 200 : 0, - wantsWidth: w < 200, - wantsHeight: h < 200 - }; - }; - var result = ( table)._iterateLayout(200, 200); - verifyLayoutResult(result, [0, 0], [0], [0, 200], [200], false, false, "when there's sufficient space"); - result = ( table)._iterateLayout(150, 200); - verifyLayoutResult(result, [150, 0], [0], [0, 0], [200], true, false, "when there's insufficient space"); + verifyLayoutResult(result, [0, 0], [0, 0], [50, 50], [50, 50], false, false, "when there's extra space"); }); }); - describe("table._removeComponent works properly", () => { - var c1 = new Plottable.Component.AbstractComponent(); - var c2 = new Plottable.Component.AbstractComponent(); - var c3 = new Plottable.Component.AbstractComponent(); - var c4 = new Plottable.Component.AbstractComponent(); - var c5 = new Plottable.Component.AbstractComponent(); - var c6 = new Plottable.Component.AbstractComponent(); - var table: Plottable.Component.Table; - it("table._removeComponent works in basic case", () => { - table = new Plottable.Component.Table([[c1, c2], [c3, c4], [c5, c6]]); - table._removeComponent(c4); + describe("remove()", () => { + var c1 = new Plottable.Component(); + var c2 = new Plottable.Component(); + var c3 = new Plottable.Component(); + var c4 = new Plottable.Component(); + var c5 = new Plottable.Component(); + var c6 = new Plottable.Component(); + var table: Plottable.Components.Table; + it("works in basic case", () => { + table = new Plottable.Components.Table([[c1, c2], [c3, c4], [c5, c6]]); + table.remove(c4); assert.deepEqual(( table)._rows, [[c1, c2], [c3, null], [c5, c6]], "remove one element"); }); - it("table._removeComponent does nothing when component is not found", () => { - table = new Plottable.Component.Table([[c1, c2], [c3, c4]]); - table._removeComponent (c5); + it("does nothing when component is not found", () => { + table = new Plottable.Components.Table([[c1, c2], [c3, c4]]); + table.remove(c5); assert.deepEqual(( table)._rows, [[c1, c2], [c3, c4]], "remove nonexistent component"); }); - it("table._removeComponent removing component twice should have same effect as removing it once", () => { - table = new Plottable.Component.Table([[c1, c2, c3], [c4, c5, c6]]); + it("removing component twice should have same effect as removing it once", () => { + table = new Plottable.Components.Table([[c1, c2, c3], [c4, c5, c6]]); - table._removeComponent(c1); + table.remove(c1); assert.deepEqual(( table)._rows, [[null, c2, c3], [c4, c5, c6]], "item twice"); - table._removeComponent(c1); + table.remove(c1); assert.deepEqual(( table)._rows, [[null, c2, c3], [c4, c5, c6]], "item twice"); }); - it("table._removeComponent doesn't do anything weird when called with null", () => { - table = new Plottable.Component.Table([[c1, null], [c2, c3]]); - - table._removeComponent(null); - assert.deepEqual(( table)._rows, [[c1, null], [c2, c3]]); + it("detach()-ing a Component removes it from the Table", () => { + table = new Plottable.Components.Table([[c1]]); + var svg = TestMethods.generateSVG(); + table.renderTo(svg); + c1.detach(); + assert.deepEqual(( table)._rows, [[null]], "calling detach() on the Component removed it from the Table"); + svg.remove(); }); }); + + it("has()", () => { + var c0 = new Plottable.Component(); + var componentGroup = new Plottable.Components.Table([[c0]]); + assert.isTrue(componentGroup.has(c0), "correctly checks that Component is in the Table"); + componentGroup.remove(c0); + assert.isFalse(componentGroup.has(c0), "correctly checks that Component is no longer in the Table"); + componentGroup.add(c0, 1, 1); + assert.isTrue(componentGroup.has(c0), "correctly checks that Component is in the Table again"); + }); }); diff --git a/test/dispatchers/dispatcherTests.ts b/test/dispatchers/dispatcherTests.ts index a9a09845f0..989624363a 100644 --- a/test/dispatchers/dispatcherTests.ts +++ b/test/dispatchers/dispatcherTests.ts @@ -3,74 +3,73 @@ var assert = chai.assert; describe("Dispatchers", () => { - describe("AbstractDispatcher", () => { + describe("Dispatcher", () => { it("_connect() and _disconnect()", () => { - var dispatcher = new Plottable.Dispatcher.AbstractDispatcher(); + var dispatcher = new Plottable.Dispatcher(); var callbackCalls = 0; ( dispatcher)._event2Callback["click"] = () => callbackCalls++; var d3document = d3.select(document); ( dispatcher)._connect(); - triggerFakeUIEvent("click", d3document); + TestMethods.triggerFakeUIEvent("click", d3document); assert.strictEqual(callbackCalls, 1, "connected correctly (callback was called)"); ( dispatcher)._connect(); callbackCalls = 0; - triggerFakeUIEvent("click", d3document); + TestMethods.triggerFakeUIEvent("click", d3document); assert.strictEqual(callbackCalls, 1, "can't double-connect (callback only called once)"); ( dispatcher)._disconnect(); callbackCalls = 0; - triggerFakeUIEvent("click", d3document); + TestMethods.triggerFakeUIEvent("click", d3document); assert.strictEqual(callbackCalls, 0, "disconnected correctly (callback not called)"); }); - it("won't _disconnect() if broadcasters still have listeners", () => { - var dispatcher = new Plottable.Dispatcher.AbstractDispatcher(); + it("won't _disconnect() if dispatcher still have listeners", () => { + var dispatcher = new Plottable.Dispatcher(); var callbackWasCalled = false; ( dispatcher)._event2Callback["click"] = () => callbackWasCalled = true; - var b = new Plottable.Core.Broadcaster(dispatcher); - var key = "unit test"; - b.registerListener(key, () => null); - ( dispatcher)._broadcasters = [b]; + var callback = () => null; + var callbackSet = new Plottable.Utils.CallbackSet(); + callbackSet.add(callback); + ( dispatcher)._callbacks = [callbackSet]; var d3document = d3.select(document); ( dispatcher)._connect(); - triggerFakeUIEvent("click", d3document); + TestMethods.triggerFakeUIEvent("click", d3document); assert.isTrue(callbackWasCalled, "connected correctly (callback was called)"); ( dispatcher)._disconnect(); callbackWasCalled = false; - triggerFakeUIEvent("click", d3document); - assert.isTrue(callbackWasCalled, "didn't disconnect while broadcaster had listener"); + TestMethods.triggerFakeUIEvent("click", d3document); + assert.isTrue(callbackWasCalled, "didn't disconnect while dispatcher had listener"); - b.deregisterListener(key); + callbackSet.delete(callback); ( dispatcher)._disconnect(); callbackWasCalled = false; - triggerFakeUIEvent("click", d3document); - assert.isFalse(callbackWasCalled, "disconnected when broadcaster had no listeners"); + TestMethods.triggerFakeUIEvent("click", d3document); + assert.isFalse(callbackWasCalled, "disconnected when dispatcher had no listeners"); }); - it("_setCallback()", () => { - var dispatcher = new Plottable.Dispatcher.AbstractDispatcher(); - var b = new Plottable.Core.Broadcaster(dispatcher); + it("setCallback()", () => { + var dispatcher = new Plottable.Dispatcher(); + var callbackSet = new Plottable.Utils.CallbackSet(); - var key = "unit test"; var callbackWasCalled = false; var callback = () => callbackWasCalled = true; - ( dispatcher)._setCallback(b, key, callback); - b.broadcast(); - assert.isTrue(callbackWasCalled, "callback was called after setting with _setCallback()"); + ( dispatcher).setCallback(callbackSet, callback); + callbackSet.callCallbacks(); + assert.isTrue(callbackWasCalled, "callback was called after setting with setCallback()"); - ( dispatcher)._setCallback(b, key, null); + ( dispatcher).unsetCallback(callbackSet, callback); callbackWasCalled = false; - b.broadcast(); - assert.isFalse(callbackWasCalled, "callback was removed by calling _setCallback() with null"); + callbackSet.callCallbacks(); + assert.isFalse(callbackWasCalled, "callback was removed by calling setCallback() with null"); }); }); }); diff --git a/test/dispatchers/keyDispatcherTests.ts b/test/dispatchers/keyDispatcherTests.ts index bad0c4839d..e7e3ba8566 100644 --- a/test/dispatchers/keyDispatcherTests.ts +++ b/test/dispatchers/keyDispatcherTests.ts @@ -5,7 +5,7 @@ var assert = chai.assert; describe("Dispatchers", () => { describe("Key Dispatcher", () => { it("triggers callback on mousedown", () => { - var ked = Plottable.Dispatcher.Key.getDispatcher(); + var ked = Plottable.Dispatchers.Key.getDispatcher(); var keyCodeToSend = 65; @@ -16,13 +16,12 @@ describe("Dispatchers", () => { assert.isNotNull(e, "key event was passed to the callback"); }; - var keyString = "unit test"; - ked.onKeyDown(keyString, callback); + ked.onKeyDown(callback); $("body").simulate("keydown", { keyCode: keyCodeToSend }); assert.isTrue(keyDowned, "callback when a key was pressed"); - ked.onKeyDown(keyString, null); // clean up + ked.offKeyDown(callback); // clean up }); }); }); diff --git a/test/dispatchers/mouseDispatcherTests.ts b/test/dispatchers/mouseDispatcherTests.ts index ea4776eab5..478a586af8 100644 --- a/test/dispatchers/mouseDispatcherTests.ts +++ b/test/dispatchers/mouseDispatcherTests.ts @@ -4,26 +4,22 @@ var assert = chai.assert; describe("Dispatchers", () => { describe("Mouse Dispatcher", () => { - function assertPointsClose(actual: Plottable.Point, expected: Plottable.Point, epsilon: number, message: String) { - assert.closeTo(actual.x, expected.x, epsilon, message + " (x)"); - assert.closeTo(actual.y, expected.y, epsilon, message + " (y)"); - }; it("getDispatcher() creates only one Dispatcher.Mouse per ", () => { - var svg = generateSVG(); + var svg = TestMethods.generateSVG(); - var md1 = Plottable.Dispatcher.Mouse.getDispatcher( svg.node()); + var md1 = Plottable.Dispatchers.Mouse.getDispatcher( svg.node()); assert.isNotNull(md1, "created a new Dispatcher on an SVG"); - var md2 = Plottable.Dispatcher.Mouse.getDispatcher( svg.node()); + var md2 = Plottable.Dispatchers.Mouse.getDispatcher( svg.node()); assert.strictEqual(md1, md2, "returned the existing Dispatcher if called again with same "); svg.remove(); }); it("getLastMousePosition() defaults to a non-null value", () => { - var svg = generateSVG(); + var svg = TestMethods.generateSVG(); - var md = Plottable.Dispatcher.Mouse.getDispatcher( svg.node()); + var md = Plottable.Dispatchers.Mouse.getDispatcher( svg.node()); var p = md.getLastMousePosition(); assert.isNotNull(p, "returns a value after initialization"); assert.isNotNull(p.x, "x value is set"); @@ -34,30 +30,30 @@ describe("Dispatchers", () => { it("can remove callbacks by passing null", () => { var targetWidth = 400, targetHeight = 400; - var target = generateSVG(targetWidth, targetHeight); + var target = TestMethods.generateSVG(targetWidth, targetHeight); // HACKHACK: PhantomJS can't measure SVGs unless they have something in them occupying space target.append("rect").attr("width", targetWidth).attr("height", targetHeight); var targetX = 17; var targetY = 76; - var md = Plottable.Dispatcher.Mouse.getDispatcher( target.node()); + var md = Plottable.Dispatchers.Mouse.getDispatcher( target.node()); var cb1Called = false; var cb1 = (p: Plottable.Point, e: MouseEvent) => cb1Called = true; var cb2Called = false; var cb2 = (p: Plottable.Point, e: MouseEvent) => cb2Called = true; - md.onMouseMove("callback1", cb1); - md.onMouseMove("callback2", cb2); - triggerFakeMouseEvent("mousemove", target, targetX, targetY); + md.onMouseMove(cb1); + md.onMouseMove(cb2); + TestMethods.triggerFakeMouseEvent("mousemove", target, targetX, targetY); assert.isTrue(cb1Called, "callback 1 was called on mousemove"); assert.isTrue(cb2Called, "callback 2 was called on mousemove"); cb1Called = false; cb2Called = false; - md.onMouseMove("callback1", null); - triggerFakeMouseEvent("mousemove", target, targetX, targetY); + md.offMouseMove(cb1); + TestMethods.triggerFakeMouseEvent("mousemove", target, targetX, targetY); assert.isFalse(cb1Called, "callback was not called after blanking"); assert.isTrue(cb2Called, "callback 2 was still called"); @@ -66,34 +62,33 @@ describe("Dispatchers", () => { it("doesn't call callbacks if not in the DOM", () => { var targetWidth = 400, targetHeight = 400; - var target = generateSVG(targetWidth, targetHeight); + var target = TestMethods.generateSVG(targetWidth, targetHeight); // HACKHACK: PhantomJS can't measure SVGs unless they have something in them occupying space target.append("rect").attr("width", targetWidth).attr("height", targetHeight); var targetX = 17; var targetY = 76; - var md = Plottable.Dispatcher.Mouse.getDispatcher( target.node()); + var md = Plottable.Dispatchers.Mouse.getDispatcher( target.node()); var callbackWasCalled = false; var callback = (p: Plottable.Point, e: MouseEvent) => callbackWasCalled = true; - var keyString = "notInDomTest"; - md.onMouseMove(keyString, callback); - triggerFakeMouseEvent("mousemove", target, targetX, targetY); + md.onMouseMove(callback); + TestMethods.triggerFakeMouseEvent("mousemove", target, targetX, targetY); assert.isTrue(callbackWasCalled, "callback was called on mousemove"); target.remove(); callbackWasCalled = false; - triggerFakeMouseEvent("mousemove", target, targetX, targetY); + TestMethods.triggerFakeMouseEvent("mousemove", target, targetX, targetY); assert.isFalse(callbackWasCalled, "callback was not called after was removed from DOM"); - md.onMouseMove(keyString, null); + md.offMouseMove(callback); }); it("calls callbacks on mouseover, mousemove, and mouseout", () => { var targetWidth = 400, targetHeight = 400; - var target = generateSVG(targetWidth, targetHeight); + var target = TestMethods.generateSVG(targetWidth, targetHeight); // HACKHACK: PhantomJS can't measure SVGs unless they have something in them occupying space target.append("rect").attr("width", targetWidth).attr("height", targetHeight); @@ -104,34 +99,33 @@ describe("Dispatchers", () => { y: targetY }; - var md = Plottable.Dispatcher.Mouse.getDispatcher( target.node()); + var md = Plottable.Dispatchers.Mouse.getDispatcher( target.node()); var callbackWasCalled = false; var callback = (p: Plottable.Point, e: MouseEvent) => { callbackWasCalled = true; - assertPointsClose(p, expectedPoint, 0.5, "mouse position is correct"); + TestMethods.assertPointsClose(p, expectedPoint, 0.5, "mouse position is correct"); assert.isNotNull(e, "mouse event was passed to the callback"); }; - var keyString = "unit test"; - md.onMouseMove(keyString, callback); + md.onMouseMove(callback); - triggerFakeMouseEvent("mouseover", target, targetX, targetY); + TestMethods.triggerFakeMouseEvent("mouseover", target, targetX, targetY); assert.isTrue(callbackWasCalled, "callback was called on mouseover"); callbackWasCalled = false; - triggerFakeMouseEvent("mousemove", target, targetX, targetY); + TestMethods.triggerFakeMouseEvent("mousemove", target, targetX, targetY); assert.isTrue(callbackWasCalled, "callback was called on mousemove"); callbackWasCalled = false; - triggerFakeMouseEvent("mouseout", target, targetX, targetY); + TestMethods.triggerFakeMouseEvent("mouseout", target, targetX, targetY); assert.isTrue(callbackWasCalled, "callback was called on mouseout"); - md.onMouseMove(keyString, null); + md.offMouseMove(callback); target.remove(); }); it("onMouseDown()", () => { var targetWidth = 400, targetHeight = 400; - var target = generateSVG(targetWidth, targetHeight); + var target = TestMethods.generateSVG(targetWidth, targetHeight); // HACKHACK: PhantomJS can't measure SVGs unless they have something in them occupying space target.append("rect").attr("width", targetWidth).attr("height", targetHeight); @@ -142,28 +136,27 @@ describe("Dispatchers", () => { y: targetY }; - var md = Plottable.Dispatcher.Mouse.getDispatcher( target.node()); + var md = Plottable.Dispatchers.Mouse.getDispatcher( target.node()); var callbackWasCalled = false; var callback = (p: Plottable.Point, e: MouseEvent) => { callbackWasCalled = true; - assertPointsClose(p, expectedPoint, 0.5, "mouse position is correct"); + TestMethods.assertPointsClose(p, expectedPoint, 0.5, "mouse position is correct"); assert.isNotNull(e, "mouse event was passed to the callback"); }; - var keyString = "unit test"; - md.onMouseDown(keyString, callback); + md.onMouseDown(callback); - triggerFakeMouseEvent("mousedown", target, targetX, targetY); + TestMethods.triggerFakeMouseEvent("mousedown", target, targetX, targetY); assert.isTrue(callbackWasCalled, "callback was called on mousedown"); - md.onMouseDown(keyString, null); + md.offMouseDown(callback); target.remove(); }); it("onMouseUp()", () => { var targetWidth = 400, targetHeight = 400; - var target = generateSVG(targetWidth, targetHeight); + var target = TestMethods.generateSVG(targetWidth, targetHeight); // HACKHACK: PhantomJS can't measure SVGs unless they have something in them occupying space target.append("rect").attr("width", targetWidth).attr("height", targetHeight); @@ -174,22 +167,21 @@ describe("Dispatchers", () => { y: targetY }; - var md = Plottable.Dispatcher.Mouse.getDispatcher( target.node()); + var md = Plottable.Dispatchers.Mouse.getDispatcher( target.node()); var callbackWasCalled = false; var callback = (p: Plottable.Point, e: MouseEvent) => { callbackWasCalled = true; - assertPointsClose(p, expectedPoint, 0.5, "mouse position is correct"); + TestMethods.assertPointsClose(p, expectedPoint, 0.5, "mouse position is correct"); assert.isNotNull(e, "mouse event was passed to the callback"); }; - var keyString = "unit test"; - md.onMouseUp(keyString, callback); + md.onMouseUp(callback); - triggerFakeMouseEvent("mouseup", target, targetX, targetY); + TestMethods.triggerFakeMouseEvent("mouseup", target, targetX, targetY); assert.isTrue(callbackWasCalled, "callback was called on mouseup"); - md.onMouseUp(keyString, null); + md.offMouseUp(callback); target.remove(); }); @@ -200,7 +192,7 @@ describe("Dispatchers", () => { return; } var targetWidth = 400, targetHeight = 400; - var svg = generateSVG(targetWidth, targetHeight); + var svg = TestMethods.generateSVG(targetWidth, targetHeight); // HACKHACK: PhantomJS can't measure SVGs unless they have something in them occupying space svg.append("rect").attr("width", targetWidth).attr("height", targetHeight); @@ -212,40 +204,35 @@ describe("Dispatchers", () => { }; var targetDeltaY = 10; - var md = Plottable.Dispatcher.Mouse.getDispatcher( svg.node()); + var md = Plottable.Dispatchers.Mouse.getDispatcher( svg.node()); var callbackWasCalled = false; var callback = (p: Plottable.Point, e: WheelEvent) => { callbackWasCalled = true; assert.strictEqual(e.deltaY, targetDeltaY, "deltaY value was passed to callback"); - assertPointsClose(p, expectedPoint, 0.5, "mouse position is correct"); + TestMethods.assertPointsClose(p, expectedPoint, 0.5, "mouse position is correct"); assert.isNotNull(e, "mouse event was passed to the callback"); }; - var keyString = "unit test"; - md.onWheel(keyString, callback); + md.onWheel(callback); - triggerFakeWheelEvent("wheel", svg, targetX, targetY, targetDeltaY); + TestMethods.triggerFakeWheelEvent("wheel", svg, targetX, targetY, targetDeltaY); assert.isTrue(callbackWasCalled, "callback was called on wheel"); - md.onWheel(keyString, null); + md.offWheel(callback); svg.remove(); }); it("onDblClick()", () => { var targetWidth = 400, targetHeight = 400; - var target = generateSVG(targetWidth, targetHeight); + var target = TestMethods.generateSVG(targetWidth, targetHeight); // HACKHACK: PhantomJS can't measure SVGs unless they have something in them occupying space target.append("rect").attr("width", targetWidth).attr("height", targetHeight); var targetX = 17; var targetY = 76; - var expectedPoint = { - x: targetX, - y: targetY - }; - var md = Plottable.Dispatcher.Mouse.getDispatcher( target.node()); + var md = Plottable.Dispatchers.Mouse.getDispatcher( target.node()); var callbackWasCalled = false; var callback = (p: Plottable.Point, e: MouseEvent) => { @@ -253,13 +240,12 @@ describe("Dispatchers", () => { assert.isNotNull(e, "mouse event was passed to the callback"); }; - var keyString = "unit test"; - md.onDblClick(keyString, callback); + md.onDblClick(callback); - triggerFakeMouseEvent("dblclick", target, targetX, targetY); + TestMethods.triggerFakeMouseEvent("dblclick", target, targetX, targetY); assert.isTrue(callbackWasCalled, "callback was called on dblClick"); - md.onDblClick(keyString, null); + md.offDblClick(callback); target.remove(); }); }); diff --git a/test/dispatchers/touchDispatcherTests.ts b/test/dispatchers/touchDispatcherTests.ts index 8f7dcf8398..6486833157 100644 --- a/test/dispatchers/touchDispatcherTests.ts +++ b/test/dispatchers/touchDispatcherTests.ts @@ -5,11 +5,11 @@ var assert = chai.assert; describe("Dispatchers", () => { describe("Touch Dispatcher", () => { it("getDispatcher() creates only one Dispatcher.Touch per ", () => { - var svg = generateSVG(); + var svg = TestMethods.generateSVG(); - var td1 = Plottable.Dispatcher.Touch.getDispatcher( svg.node()); + var td1 = Plottable.Dispatchers.Touch.getDispatcher( svg.node()); assert.isNotNull(td1, "created a new Dispatcher on an SVG"); - var td2 = Plottable.Dispatcher.Touch.getDispatcher( svg.node()); + var td2 = Plottable.Dispatchers.Touch.getDispatcher( svg.node()); assert.strictEqual(td1, td2, "returned the existing Dispatcher if called again with same "); svg.remove(); @@ -17,7 +17,7 @@ describe("Dispatchers", () => { it("onTouchStart()", () => { var targetWidth = 400, targetHeight = 400; - var target = generateSVG(targetWidth, targetHeight); + var target = TestMethods.generateSVG(targetWidth, targetHeight); // HACKHACK: PhantomJS can't measure SVGs unless they have something in them occupying space target.append("rect").attr("width", targetWidth).attr("height", targetHeight); @@ -31,30 +31,29 @@ describe("Dispatchers", () => { }); var ids = targetXs.map((targetX, i) => i); - var td = Plottable.Dispatcher.Touch.getDispatcher( target.node()); + var td = Plottable.Dispatchers.Touch.getDispatcher( target.node()); var callbackWasCalled = false; var callback = function(ids: number[], points: { [id: number]: Plottable.Point; }, e: TouchEvent) { callbackWasCalled = true; ids.forEach((id) => { - assertPointsClose(points[id], expectedPoints[id], 0.5, "touch position is correct"); + TestMethods.assertPointsClose(points[id], expectedPoints[id], 0.5, "touch position is correct"); }); assert.isNotNull(e, "TouchEvent was passed to the Dispatcher"); }; - var keyString = "unit test"; - td.onTouchStart(keyString, callback); + td.onTouchStart(callback); - triggerFakeTouchEvent("touchstart", target, expectedPoints, ids); + TestMethods.triggerFakeTouchEvent("touchstart", target, expectedPoints, ids); assert.isTrue(callbackWasCalled, "callback was called on touchstart"); - td.onTouchStart(keyString, null); + td.offTouchStart(callback); target.remove(); }); it("onTouchMove()", () => { var targetWidth = 400, targetHeight = 400; - var target = generateSVG(targetWidth, targetHeight); + var target = TestMethods.generateSVG(targetWidth, targetHeight); // HACKHACK: PhantomJS can't measure SVGs unless they have something in them occupying space target.append("rect").attr("width", targetWidth).attr("height", targetHeight); @@ -68,30 +67,29 @@ describe("Dispatchers", () => { }); var ids = targetXs.map((targetX, i) => i); - var td = Plottable.Dispatcher.Touch.getDispatcher( target.node()); + var td = Plottable.Dispatchers.Touch.getDispatcher( target.node()); var callbackWasCalled = false; var callback = function(ids: number[], points: { [id: number]: Plottable.Point; }, e: TouchEvent) { callbackWasCalled = true; ids.forEach((id) => { - assertPointsClose(points[id], expectedPoints[id], 0.5, "touch position is correct"); + TestMethods.assertPointsClose(points[id], expectedPoints[id], 0.5, "touch position is correct"); }); assert.isNotNull(e, "TouchEvent was passed to the Dispatcher"); }; - var keyString = "unit test"; - td.onTouchMove(keyString, callback); + td.onTouchMove(callback); - triggerFakeTouchEvent("touchmove", target, expectedPoints, ids); + TestMethods.triggerFakeTouchEvent("touchmove", target, expectedPoints, ids); assert.isTrue(callbackWasCalled, "callback was called on touchmove"); - td.onTouchMove(keyString, null); + td.offTouchMove(callback); target.remove(); }); it("onTouchEnd()", () => { var targetWidth = 400, targetHeight = 400; - var target = generateSVG(targetWidth, targetHeight); + var target = TestMethods.generateSVG(targetWidth, targetHeight); // HACKHACK: PhantomJS can't measure SVGs unless they have something in them occupying space target.append("rect").attr("width", targetWidth).attr("height", targetHeight); @@ -105,30 +103,65 @@ describe("Dispatchers", () => { }); var ids = targetXs.map((targetX, i) => i); - var td = Plottable.Dispatcher.Touch.getDispatcher( target.node()); + var td = Plottable.Dispatchers.Touch.getDispatcher( target.node()); var callbackWasCalled = false; var callback = function(ids: number[], points: { [id: number]: Plottable.Point; }, e: TouchEvent) { callbackWasCalled = true; ids.forEach((id) => { - assertPointsClose(points[id], expectedPoints[id], 0.5, "touch position is correct"); + TestMethods.assertPointsClose(points[id], expectedPoints[id], 0.5, "touch position is correct"); }); assert.isNotNull(e, "TouchEvent was passed to the Dispatcher"); }; - var keyString = "unit test"; - td.onTouchEnd(keyString, callback); + td.onTouchEnd(callback); - triggerFakeTouchEvent("touchend", target, expectedPoints, ids); + TestMethods.triggerFakeTouchEvent("touchend", target, expectedPoints, ids); assert.isTrue(callbackWasCalled, "callback was called on touchend"); - td.onTouchEnd(keyString, null); + td.offTouchEnd(callback); + target.remove(); + }); + + it("onTouchCancel()", () => { + var targetWidth = 400, targetHeight = 400; + var target = TestMethods.generateSVG(targetWidth, targetHeight); + // HACKHACK: PhantomJS can't measure SVGs unless they have something in them occupying space + target.append("rect").attr("width", targetWidth).attr("height", targetHeight); + + var targetXs = [17, 18, 12, 23, 44]; + var targetYs = [77, 78, 52, 43, 14]; + var expectedPoints = targetXs.map((targetX, i) => { + return { + x: targetX, + y: targetYs[i] + }; + }); + var ids = targetXs.map((targetX, i) => i); + + var td = Plottable.Dispatchers.Touch.getDispatcher( target.node()); + + var callbackWasCalled = false; + var callback = function(ids: number[], points: { [id: number]: Plottable.Point; }, e: TouchEvent) { + callbackWasCalled = true; + ids.forEach((id) => { + TestMethods.assertPointsClose(points[id], expectedPoints[id], 0.5, "touch position is correct"); + }); + assert.isNotNull(e, "TouchEvent was passed to the Dispatcher"); + }; + + td.onTouchCancel(callback); + + TestMethods.triggerFakeTouchEvent("touchcancel", target, expectedPoints, ids); + assert.isTrue(callbackWasCalled, "callback was called on touchend"); + + td.offTouchCancel(callback); target.remove(); }); it("doesn't call callbacks if not in the DOM", () => { var targetWidth = 400, targetHeight = 400; - var target = generateSVG(targetWidth, targetHeight); + var target = TestMethods.generateSVG(targetWidth, targetHeight); // HACKHACK: PhantomJS can't measure SVGs unless they have something in them occupying space target.append("rect").attr("width", targetWidth).attr("height", targetHeight); @@ -142,7 +175,7 @@ describe("Dispatchers", () => { }); var ids = targetXs.map((targetX, i) => i); - var td = Plottable.Dispatcher.Touch.getDispatcher( target.node()); + var td = Plottable.Dispatchers.Touch.getDispatcher( target.node()); var callbackWasCalled = false; var callback = function(ids: number[], points: { [id: number]: Plottable.Point; }, e: TouchEvent) { @@ -150,17 +183,16 @@ describe("Dispatchers", () => { assert.isNotNull(e, "TouchEvent was passed to the Dispatcher"); }; - var keyString = "notInDomTest"; - td.onTouchMove(keyString, callback); - triggerFakeTouchEvent("touchmove", target, expectedPoints, ids); + td.onTouchMove(callback); + TestMethods.triggerFakeTouchEvent("touchmove", target, expectedPoints, ids); assert.isTrue(callbackWasCalled, "callback was called on touchmove"); target.remove(); callbackWasCalled = false; - triggerFakeTouchEvent("touchmove", target, expectedPoints, ids); + TestMethods.triggerFakeTouchEvent("touchmove", target, expectedPoints, ids); assert.isFalse(callbackWasCalled, "callback was not called after was removed from DOM"); - td.onTouchMove(keyString, null); + td.offTouchMove(callback); }); }); }); diff --git a/test/drawers/arcDrawerTests.ts b/test/drawers/arcDrawerTests.ts index d1c8c71158..b602208d36 100644 --- a/test/drawers/arcDrawerTests.ts +++ b/test/drawers/arcDrawerTests.ts @@ -3,19 +3,18 @@ describe("Drawers", () => { describe("Arc Drawer", () => { it("getPixelPoint", () => { - var svg = generateSVG(300, 300); + var svg = TestMethods.generateSVG(300, 300); var data = [{value: 10}, {value: 10}, {value: 10}, {value: 10}]; - var piePlot = new Plottable.Plot.Pie(); + var piePlot = new Plottable.Plots.Pie(); - var drawer = new Plottable._Drawer.Arc("one"); + var drawer = new Plottable.Drawers.Arc("_0"); // HACKHACK #1984: Dataset keys are being removed, so this is the internal key ( piePlot)._getDrawer = () => drawer; - piePlot.addDataset("one", data); - piePlot.project("value", "value"); + piePlot.addDataset(new Plottable.Dataset(data)); + piePlot.sectorValue((d) => d.value); piePlot.renderTo(svg); piePlot.getAllSelections().each(function (datum: any, index: number) { - var selection = d3.select(this); var pixelPoint = drawer._getPixelPoint(datum, index); var radius = 75; var angle = Math.PI / 4 + ((Math.PI * index) / 2); diff --git a/test/drawers/drawerTests.ts b/test/drawers/drawerTests.ts index f16877770a..4f44998f45 100644 --- a/test/drawers/drawerTests.ts +++ b/test/drawers/drawerTests.ts @@ -1,6 +1,6 @@ /// -class MockAnimator implements Plottable.Animator.PlotAnimator { +class MockAnimator implements Plottable.Animators.PlotAnimator { private time: number; private callback: Function; constructor(time: number, callback?: Function) { @@ -11,7 +11,6 @@ class MockAnimator implements Plottable.Animator.PlotAnimator { return this.time; } - public animate(selection: any, attrToProjector: Plottable.AttributeToProjector): any { if (this.callback) { this.callback(); @@ -20,8 +19,8 @@ class MockAnimator implements Plottable.Animator.PlotAnimator { } } -class MockDrawer extends Plottable._Drawer.AbstractDrawer { - public _drawStep(step: Plottable._Drawer.DrawStep) { +class MockDrawer extends Plottable.Drawers.AbstractDrawer { + public _drawStep(step: Plottable.Drawers.DrawStep) { step.animator.animate(this._getRenderArea(), step.attrToProjector); } } @@ -33,20 +32,20 @@ describe("Drawers", () => { var svg: D3.Selection; var drawer: MockDrawer; before(() => { - oldTimeout = Plottable._Util.Methods.setTimeout; - Plottable._Util.Methods.setTimeout = function(f: Function, time: number, ...args: any[]) { + oldTimeout = Plottable.Utils.Methods.setTimeout; + Plottable.Utils.Methods.setTimeout = function(f: Function, time: number, ...args: any[]) { timings.push(time); return oldTimeout(f, time, args); }; }); after(() => { - Plottable._Util.Methods.setTimeout = oldTimeout; + Plottable.Utils.Methods.setTimeout = oldTimeout; }); beforeEach(() => { timings = []; - svg = generateSVG(); + svg = TestMethods.generateSVG(); drawer = new MockDrawer("foo"); drawer.setup(svg); }); @@ -56,10 +55,10 @@ describe("Drawers", () => { }); it("drawer timing works as expected for null animators", () => { - var a1 = new Plottable.Animator.Null(); - var a2 = new Plottable.Animator.Null(); - var ds1: Plottable._Drawer.DrawStep = {attrToProjector: {}, animator: a1}; - var ds2: Plottable._Drawer.DrawStep = {attrToProjector: {}, animator: a2}; + var a1 = new Plottable.Animators.Null(); + var a2 = new Plottable.Animators.Null(); + var ds1: Plottable.Drawers.DrawStep = {attrToProjector: {}, animator: a1}; + var ds2: Plottable.Drawers.DrawStep = {attrToProjector: {}, animator: a2}; var steps = [ds1, ds2]; drawer.draw([], steps, null, null); assert.deepEqual(timings, [0, 0], "setTimeout called twice with 0 time both times"); @@ -82,17 +81,17 @@ describe("Drawers", () => { var a1 = new MockAnimator(20, callback1); var a2 = new MockAnimator(10, callback2); var a3 = new MockAnimator(0, callback3); - var ds1: Plottable._Drawer.DrawStep = {attrToProjector: {}, animator: a1}; - var ds2: Plottable._Drawer.DrawStep = {attrToProjector: {}, animator: a2}; - var ds3: Plottable._Drawer.DrawStep = {attrToProjector: {}, animator: a3}; + var ds1: Plottable.Drawers.DrawStep = {attrToProjector: {}, animator: a1}; + var ds2: Plottable.Drawers.DrawStep = {attrToProjector: {}, animator: a2}; + var ds3: Plottable.Drawers.DrawStep = {attrToProjector: {}, animator: a3}; var steps = [ds1, ds2, ds3]; drawer.draw([], steps, null, null); assert.deepEqual(timings, [0, 20, 30], "setTimeout called with appropriate times"); }); it("_getSelection", () => { - var svg = generateSVG(300, 300); - var drawer = new Plottable._Drawer.AbstractDrawer("test"); + var svg = TestMethods.generateSVG(300, 300); + var drawer = new Plottable.Drawers.AbstractDrawer("test"); drawer.setup(svg.append("g")); ( drawer)._getSelector = () => "circle"; var data = [{one: 2, two: 1}, {one: 33, two: 21}, {one: 11, two: 10}]; diff --git a/test/drawers/lineDrawerTests.ts b/test/drawers/lineDrawerTests.ts index e631942c95..94815b2cb2 100644 --- a/test/drawers/lineDrawerTests.ts +++ b/test/drawers/lineDrawerTests.ts @@ -3,18 +3,18 @@ describe("Drawers", () => { describe("Line Drawer", () => { it("getPixelPoint", () => { - var svg = generateSVG(300, 300); + var svg = TestMethods.generateSVG(300, 300); var data = [{a: 12, b: 10}, {a: 13, b: 24}, {a: 14, b: 21}, {a: 15, b: 14}]; - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - var linePlot = new Plottable.Plot.Line(xScale, yScale); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); + var linePlot = new Plottable.Plots.Line(xScale, yScale); - var drawer = new Plottable._Drawer.Line("one"); + var drawer = new Plottable.Drawers.Line("_0"); // HACKHACK #1984: Dataset keys are being removed, so this is the internal key ( linePlot)._getDrawer = () => drawer; - linePlot.addDataset("one", data); - linePlot.project("x", "a", xScale); - linePlot.project("y", "b", yScale); + linePlot.addDataset(new Plottable.Dataset(data)); + linePlot.x((d) => d.a, xScale); + linePlot.y((d) => d.b, yScale); linePlot.renderTo(svg); data.forEach((datum: any, index: number) => { @@ -27,18 +27,18 @@ describe("Drawers", () => { }); it("getSelection", () => { - var svg = generateSVG(300, 300); + var svg = TestMethods.generateSVG(300, 300); var data = [{a: 12, b: 10}, {a: 13, b: 24}, {a: 14, b: 21}, {a: 15, b: 14}]; - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - var linePlot = new Plottable.Plot.Line(xScale, yScale); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); + var linePlot = new Plottable.Plots.Line(xScale, yScale); - var drawer = new Plottable._Drawer.Line("one"); + var drawer = new Plottable.Drawers.Line("_0"); // HACKHACK #1984: Dataset keys are being removed, so this is the internal key ( linePlot)._getDrawer = () => drawer; - linePlot.addDataset("one", data); - linePlot.project("x", "a", xScale); - linePlot.project("y", "b", yScale); + linePlot.addDataset(new Plottable.Dataset(data)); + linePlot.x((d) => d.a, xScale); + linePlot.y((d) => d.b, yScale); linePlot.renderTo(svg); var lineSelection = linePlot.getAllSelections(); diff --git a/test/drawers/rectDrawerTests.ts b/test/drawers/rectDrawerTests.ts index 7353a18f1f..908f76abfb 100644 --- a/test/drawers/rectDrawerTests.ts +++ b/test/drawers/rectDrawerTests.ts @@ -3,18 +3,18 @@ describe("Drawers", () => { describe("Rect Drawer", () => { it("getPixelPoint vertical", () => { - var svg = generateSVG(300, 300); + var svg = TestMethods.generateSVG(300, 300); var data = [{a: "foo", b: 10}, {a: "bar", b: 24}]; - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Linear(); - var barPlot = new Plottable.Plot.Bar(xScale, yScale); + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Linear(); + var barPlot = new Plottable.Plots.Bar(xScale, yScale); - var drawer = new Plottable._Drawer.Rect("one", true); + var drawer = new Plottable.Drawers.Rect("_0", true); // HACKHACK #1984: Dataset keys are being removed, so this is the internal key ( barPlot)._getDrawer = () => drawer; - barPlot.addDataset("one", data); - barPlot.project("x", "a", xScale); - barPlot.project("y", "b", yScale); + barPlot.addDataset(new Plottable.Dataset(data)); + barPlot.x((d) => d.a, xScale); + barPlot.y((d) => d.b, yScale); barPlot.renderTo(svg); barPlot.getAllSelections().each(function (datum: any, index: number) { @@ -28,18 +28,18 @@ describe("Drawers", () => { }); it("getPixelPoint horizontal", () => { - var svg = generateSVG(300, 300); + var svg = TestMethods.generateSVG(300, 300); var data = [{ a: "foo", b: 10 }, { a: "bar", b: 24 }]; - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Category(); - var barPlot = new Plottable.Plot.Bar(xScale, yScale, false); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Category(); + var barPlot = new Plottable.Plots.Bar(xScale, yScale, false); - var drawer = new Plottable._Drawer.Rect("one", false); + var drawer = new Plottable.Drawers.Rect("_0", false); // HACKHACK #1984: Dataset keys are being removed, so this is the internal key ( barPlot)._getDrawer = () => drawer; - barPlot.addDataset("one", data); - barPlot.project("x", "b", xScale); - barPlot.project("y", "a", yScale); + barPlot.addDataset(new Plottable.Dataset(data)); + barPlot.x((d) => d.x, xScale); + barPlot.y((d) => d.y, yScale); barPlot.renderTo(svg); barPlot.getAllSelections().each(function(datum: any, index: number) { diff --git a/test/globalInitialization.ts b/test/globalInitialization.ts index 1c60586b23..cd32212979 100644 --- a/test/globalInitialization.ts +++ b/test/globalInitialization.ts @@ -7,7 +7,7 @@ interface Window { before(() => { // Set the render policy to immediate to make sure ETE tests can check DOM change immediately - Plottable.Core.RenderController.setRenderPolicy("immediate"); + Plottable.RenderController.setRenderPolicy("immediate"); // Taken from https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser var isFirefox = navigator.userAgent.indexOf("Firefox") !== -1; if (window.PHANTOMJS) { @@ -20,7 +20,6 @@ before(() => { }); after(() => { - var parent: D3.Selection = getSVGParent(); var mocha = d3.select("#mocha-report"); if (mocha.node() != null) { var suites = mocha.selectAll(".suite"); diff --git a/test/interactions/clickInteractionTests.ts b/test/interactions/clickInteractionTests.ts index 463172ce22..7cfb9b8323 100644 --- a/test/interactions/clickInteractionTests.ts +++ b/test/interactions/clickInteractionTests.ts @@ -8,12 +8,12 @@ describe("Interactions", () => { var SVG_HEIGHT = 400; it("onClick", () => { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var c = new Plottable.Component.AbstractComponent(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var c = new Plottable.Component(); c.renderTo(svg); - var clickInteraction = new Plottable.Interaction.Click(); - c.registerInteraction(clickInteraction); + var clickInteraction = new Plottable.Interactions.Click(); + clickInteraction.attachTo(c); var callbackCalled = false; var lastPoint: Plottable.Point; @@ -22,64 +22,141 @@ describe("Interactions", () => { lastPoint = p; }; clickInteraction.onClick(callback); - assert.strictEqual(clickInteraction.onClick(), callback, "callback can be retrieved"); - triggerFakeMouseEvent("mousedown", c.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); - triggerFakeMouseEvent("mouseup", c.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mousedown", c.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mouseup", c.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); assert.isTrue(callbackCalled, "callback called on clicking Component (mouse)"); assert.deepEqual(lastPoint, { x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }, "was passed correct point (mouse)"); callbackCalled = false; lastPoint = null; - triggerFakeMouseEvent("mousedown", c.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); - triggerFakeMouseEvent("mouseup", c.content(), SVG_WIDTH / 4, SVG_HEIGHT / 4); + TestMethods.triggerFakeMouseEvent("mousedown", c.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mouseup", c.content(), SVG_WIDTH / 4, SVG_HEIGHT / 4); assert.isTrue(callbackCalled, "callback called on clicking Component (mouse)"); assert.deepEqual(lastPoint, { x: SVG_WIDTH / 4, y: SVG_HEIGHT / 4 }, "was passed mouseup point (mouse)"); callbackCalled = false; - triggerFakeMouseEvent("mousedown", c.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); - triggerFakeMouseEvent("mouseup", c.content(), SVG_WIDTH * 2, SVG_HEIGHT * 2); + TestMethods.triggerFakeMouseEvent("mousedown", c.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mouseup", c.content(), SVG_WIDTH * 2, SVG_HEIGHT * 2); assert.isFalse(callbackCalled, "callback not called if released outside component (mouse)"); - triggerFakeMouseEvent("mousedown", c.content(), SVG_WIDTH * 2, SVG_HEIGHT * 2); - triggerFakeMouseEvent("mouseup", c.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mousedown", c.content(), SVG_WIDTH * 2, SVG_HEIGHT * 2); + TestMethods.triggerFakeMouseEvent("mouseup", c.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); assert.isFalse(callbackCalled, "callback not called if started outside component (mouse)"); - triggerFakeMouseEvent("mousedown", c.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); - triggerFakeMouseEvent("mousemove", c.content(), SVG_WIDTH * 2, SVG_HEIGHT * 2); - triggerFakeMouseEvent("mouseup", c.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mousedown", c.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mousemove", c.content(), SVG_WIDTH * 2, SVG_HEIGHT * 2); + TestMethods.triggerFakeMouseEvent("mouseup", c.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); assert.isTrue(callbackCalled, "callback called even if moved outside component (mouse)"); callbackCalled = false; lastPoint = null; - triggerFakeTouchEvent("touchstart", c.content(), [{x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2}]); - triggerFakeTouchEvent("touchend", c.content(), [{x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2}]); + TestMethods.triggerFakeTouchEvent("touchstart", c.content(), [{x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2}]); + TestMethods.triggerFakeTouchEvent("touchend", c.content(), [{x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2}]); assert.isTrue(callbackCalled, "callback called on entering Component (touch)"); assert.deepEqual(lastPoint, { x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }, "was passed correct point (touch)"); callbackCalled = false; lastPoint = null; - triggerFakeTouchEvent("touchstart", c.content(), [{x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2}]); - triggerFakeTouchEvent("touchend", c.content(), [{x: SVG_WIDTH / 4, y: SVG_HEIGHT / 4}]); + TestMethods.triggerFakeTouchEvent("touchstart", c.content(), [{x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2}]); + TestMethods.triggerFakeTouchEvent("touchend", c.content(), [{x: SVG_WIDTH / 4, y: SVG_HEIGHT / 4}]); assert.isTrue(callbackCalled, "callback called on clicking Component (mouse)"); assert.deepEqual(lastPoint, { x: SVG_WIDTH / 4, y: SVG_HEIGHT / 4 }, "was passed mouseup point (touch)"); callbackCalled = false; - triggerFakeTouchEvent("touchstart", c.content(), [{x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2}]); - triggerFakeTouchEvent("touchend", c.content(), [{x: SVG_WIDTH * 2, y: SVG_HEIGHT * 2}]); + TestMethods.triggerFakeTouchEvent("touchstart", c.content(), [{x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2}]); + TestMethods.triggerFakeTouchEvent("touchend", c.content(), [{x: SVG_WIDTH * 2, y: SVG_HEIGHT * 2}]); assert.isFalse(callbackCalled, "callback not called if released outside component (touch)"); callbackCalled = false; - triggerFakeTouchEvent("touchstart", c.content(), [{x: SVG_WIDTH * 2, y: SVG_HEIGHT * 2}]); - triggerFakeTouchEvent("touchend", c.content(), [{x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2}]); + TestMethods.triggerFakeTouchEvent("touchstart", c.content(), [{x: SVG_WIDTH * 2, y: SVG_HEIGHT * 2}]); + TestMethods.triggerFakeTouchEvent("touchend", c.content(), [{x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2}]); assert.isFalse(callbackCalled, "callback not called if started outside component (touch)"); - triggerFakeTouchEvent("touchstart", c.content(), [{x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2}]); - triggerFakeTouchEvent("touchmove", c.content(), [{x: SVG_WIDTH * 2, y: SVG_HEIGHT * 2}]); - triggerFakeTouchEvent("touchend", c.content(), [{x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2}]); + TestMethods.triggerFakeTouchEvent("touchstart", c.content(), [{x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2}]); + TestMethods.triggerFakeTouchEvent("touchmove", c.content(), [{x: SVG_WIDTH * 2, y: SVG_HEIGHT * 2}]); + TestMethods.triggerFakeTouchEvent("touchend", c.content(), [{x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2}]); assert.isTrue(callbackCalled, "callback called even if moved outside component (touch)"); svg.remove(); }); + + it("offClick()", () => { + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var component = new Plottable.Component(); + component.renderTo(svg); + var clickInteraction = new Plottable.Interactions.Click(); + + clickInteraction.attachTo(component); + + var callbackWasCalled = false; + var callback = () => callbackWasCalled = true; + + clickInteraction.onClick(callback); + TestMethods.triggerFakeMouseEvent("mousedown", component.content(), 0, 0); + TestMethods.triggerFakeMouseEvent("mouseup", component.content(), 0, 0); + assert.isTrue(callbackWasCalled, "Click interaction should trigger the callback"); + + clickInteraction.offClick(callback); + callbackWasCalled = false; + TestMethods.triggerFakeMouseEvent("mousedown", component.content(), 0, 0); + TestMethods.triggerFakeMouseEvent("mouseup", component.content(), 0, 0); + assert.isFalse(callbackWasCalled, "Callback should be disconnected from the click interaction"); + + svg.remove(); + }); + + it("multiple click listeners", () => { + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var component = new Plottable.Component(); + component.renderTo(svg); + var clickInteraction = new Plottable.Interactions.Click(); + + clickInteraction.attachTo(component); + + var callback1WasCalled = false; + var callback1 = () => callback1WasCalled = true; + + var callback2WasCalled = false; + var callback2 = () => callback2WasCalled = true; + + clickInteraction.onClick(callback1); + clickInteraction.onClick(callback2); + TestMethods.triggerFakeMouseEvent("mousedown", component.content(), 0, 0); + TestMethods.triggerFakeMouseEvent("mouseup", component.content(), 0, 0); + assert.isTrue(callback1WasCalled, "Click interaction should trigger the first callback"); + assert.isTrue(callback2WasCalled, "Click interaction should trigger the second callback"); + + clickInteraction.offClick(callback1); + callback1WasCalled = false; + callback2WasCalled = false; + TestMethods.triggerFakeMouseEvent("mousedown", component.content(), 0, 0); + TestMethods.triggerFakeMouseEvent("mouseup", component.content(), 0, 0); + assert.isFalse(callback1WasCalled, "Callback1 should be disconnected from the click interaction"); + assert.isTrue(callback2WasCalled, "Callback2 should still exist on the click interaction"); + + svg.remove(); + + }); + + it("cancelling touches cancels any ongoing clicks", () => { + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var c = new Plottable.Component(); + c.renderTo(svg); + + var clickInteraction = new Plottable.Interactions.Click(); + clickInteraction.attachTo(c); + + var callbackCalled = false; + var callback = () => callbackCalled = true; + clickInteraction.onClick(callback); + + TestMethods.triggerFakeTouchEvent("touchstart", c.content(), [{x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2}]); + TestMethods.triggerFakeTouchEvent("touchcancel", c.content(), [{x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2}]); + TestMethods.triggerFakeTouchEvent("touchend", c.content(), [{x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2}]); + assert.isFalse(callbackCalled, "callback not called since click was interrupted"); + + svg.remove(); + }); }); }); diff --git a/test/interactions/doubleClickInteractionTests.ts b/test/interactions/doubleClickInteractionTests.ts index 54ab812f08..e35e971e24 100644 --- a/test/interactions/doubleClickInteractionTests.ts +++ b/test/interactions/doubleClickInteractionTests.ts @@ -9,18 +9,18 @@ describe("Interactions", () => { describe("onDblClick generic callback", () => { var svg: D3.Selection; - var dblClickInteraction: Plottable.Interaction.DoubleClick; - var component: Plottable.Component.AbstractComponent; + var dblClickInteraction: Plottable.Interactions.DoubleClick; + var component: Plottable.Component; var doubleClickedPoint: Plottable.Point = null; var dblClickCallback = (p: Plottable.Point) => doubleClickedPoint = p; beforeEach(() => { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - component = new Plottable.Component.AbstractComponent(); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + component = new Plottable.Component(); component.renderTo(svg); - dblClickInteraction = new Plottable.Interaction.DoubleClick(); - component.registerInteraction(dblClickInteraction); + dblClickInteraction = new Plottable.Interactions.DoubleClick(); + dblClickInteraction.attachTo(component); dblClickInteraction.onDoubleClick(dblClickCallback); }); @@ -29,44 +29,91 @@ describe("Interactions", () => { doubleClickedPoint = null; }); - it ("onDblClick callback can be retrieved", () => { - assert.strictEqual(dblClickInteraction.onDoubleClick(), dblClickCallback, "callback can be retrieved"); + it("double click interaction accepts multiple callbacks", () => { + var userClickPoint = {x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2}; + + var newCallback1WasCalled = false; + var newCallback1 = () => newCallback1WasCalled = true; + + var newCallback2WasCalled = false; + var newCallback2 = () => newCallback2WasCalled = true; + + dblClickInteraction.onDoubleClick(newCallback1); + dblClickInteraction.onDoubleClick(newCallback2); + + TestMethods.triggerFakeMouseEvent("mousedown", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeMouseEvent("mouseup", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeMouseEvent("mousedown", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeMouseEvent("mouseup", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeMouseEvent("dblclick", component.content(), userClickPoint.x, userClickPoint.y); + + assert.isTrue(newCallback1WasCalled, "Callback 1 should be called on double click"); + assert.isTrue(newCallback2WasCalled, "Callback 2 should be called on double click"); + + newCallback1WasCalled = false; + newCallback2WasCalled = false; + + dblClickInteraction.offDoubleClick(newCallback1); + + TestMethods.triggerFakeMouseEvent("mousedown", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeMouseEvent("mouseup", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeMouseEvent("mousedown", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeMouseEvent("mouseup", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeMouseEvent("dblclick", component.content(), userClickPoint.x, userClickPoint.y); + + assert.isFalse(newCallback1WasCalled, "Callback 1 should be disconnected from the interaction"); + assert.isTrue(newCallback2WasCalled, "Callback 2 should still be connected to the interaction"); + svg.remove(); }); - it ("callback sets correct point on normal case", () => { + it("callback sets correct point on normal case", () => { var userClickPoint = {x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2}; - triggerFakeMouseEvent("mousedown", component.content(), userClickPoint.x, userClickPoint.y); - triggerFakeMouseEvent("mouseup", component.content(), userClickPoint.x, userClickPoint.y); - triggerFakeMouseEvent("mousedown", component.content(), userClickPoint.x, userClickPoint.y); - triggerFakeMouseEvent("mouseup", component.content(), userClickPoint.x, userClickPoint.y); - triggerFakeMouseEvent("dblclick", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeMouseEvent("mousedown", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeMouseEvent("mouseup", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeMouseEvent("mousedown", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeMouseEvent("mouseup", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeMouseEvent("dblclick", component.content(), userClickPoint.x, userClickPoint.y); assert.deepEqual(doubleClickedPoint, userClickPoint, "was passed correct point (mouse)"); svg.remove(); }); - it ("callback not called if clicked in different locations", () => { + it("callback not called if clicked in different locations", () => { + var userClickPoint = {x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2}; + + TestMethods.triggerFakeMouseEvent("mousedown", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeMouseEvent("mouseup", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeMouseEvent("mousedown", component.content(), userClickPoint.x + 10, userClickPoint.y + 10); + TestMethods.triggerFakeMouseEvent("mouseup", component.content(), userClickPoint.x + 10, userClickPoint.y + 10); + TestMethods.triggerFakeMouseEvent("dblclick", component.content(), userClickPoint.x + 10, userClickPoint.y + 10); + assert.deepEqual(doubleClickedPoint, null, "point never set"); + + svg.remove(); + }); + + it("callback not called does not receive dblclick confirmation", () => { var userClickPoint = {x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2}; - triggerFakeMouseEvent("mousedown", component.content(), userClickPoint.x, userClickPoint.y); - triggerFakeMouseEvent("mouseup", component.content(), userClickPoint.x, userClickPoint.y); - triggerFakeMouseEvent("mousedown", component.content(), userClickPoint.x + 10, userClickPoint.y + 10); - triggerFakeMouseEvent("mouseup", component.content(), userClickPoint.x + 10, userClickPoint.y + 10); - triggerFakeMouseEvent("dblclick", component.content(), userClickPoint.x + 10, userClickPoint.y + 10); + TestMethods.triggerFakeMouseEvent("mousedown", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeMouseEvent("mouseup", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeMouseEvent("mousedown", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeMouseEvent("mouseup", component.content(), userClickPoint.x, userClickPoint.y); assert.deepEqual(doubleClickedPoint, null, "point never set"); svg.remove(); }); - it ("callback not called does not receive dblclick confirmation", () => { + it("callback not called does not receive dblclick confirmation", () => { var userClickPoint = {x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2}; - triggerFakeMouseEvent("mousedown", component.content(), userClickPoint.x, userClickPoint.y); - triggerFakeMouseEvent("mouseup", component.content(), userClickPoint.x, userClickPoint.y); - triggerFakeMouseEvent("mousedown", component.content(), userClickPoint.x, userClickPoint.y); - triggerFakeMouseEvent("mouseup", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeTouchEvent("touchstart", component.content(), [{x: userClickPoint.x, y: userClickPoint.y}]); + TestMethods.triggerFakeTouchEvent("touchend", component.content(), [{x: userClickPoint.x, y: userClickPoint.y}]); + TestMethods.triggerFakeTouchEvent("touchstart", component.content(), [{x: userClickPoint.x, y: userClickPoint.y}]); + TestMethods.triggerFakeTouchEvent("touchend", component.content(), [{x: userClickPoint.x, y: userClickPoint.y}]); + TestMethods.triggerFakeTouchEvent("touchcancel", component.content(), [{x: userClickPoint.x, y: userClickPoint.y}]); + TestMethods.triggerFakeMouseEvent("dblclick", component.content(), userClickPoint.x, userClickPoint.y); assert.deepEqual(doubleClickedPoint, null, "point never set"); svg.remove(); diff --git a/test/interactions/dragInteractionTests.ts b/test/interactions/dragInteractionTests.ts index 4accda8fa8..92edcb03d3 100644 --- a/test/interactions/dragInteractionTests.ts +++ b/test/interactions/dragInteractionTests.ts @@ -34,11 +34,11 @@ describe("Interactions", () => { }; it("onDragStart()", () => { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var c = new Plottable.Component.AbstractComponent(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var c = new Plottable.Component(); c.renderTo(svg); - var drag = new Plottable.Interaction.Drag(); + var drag = new Plottable.Interactions.Drag(); var startCallbackCalled = false; var receivedStart: Plottable.Point; var startCallback = (p: Plottable.Point) => { @@ -46,47 +46,53 @@ describe("Interactions", () => { receivedStart = p; }; drag.onDragStart(startCallback); - c.registerInteraction(drag); + drag.attachTo(c); var target = c.background(); - triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); + TestMethods.triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); assert.isTrue(startCallbackCalled, "callback was called on beginning drag (mousedown)"); assert.deepEqual(receivedStart, startPoint, "was passed the correct point"); startCallbackCalled = false; receivedStart = null; - triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y, 2); + TestMethods.triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y, 2); assert.isFalse(startCallbackCalled, "callback is not called on right-click"); startCallbackCalled = false; receivedStart = null; - triggerFakeTouchEvent("touchstart", target, [{x: startPoint.x, y: startPoint.y}]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{x: startPoint.x, y: startPoint.y}]); assert.isTrue(startCallbackCalled, "callback was called on beginning drag (touchstart)"); assert.deepEqual(receivedStart, startPoint, "was passed the correct point"); startCallbackCalled = false; - triggerFakeMouseEvent("mousedown", target, outsidePointPos.x, outsidePointPos.y); + TestMethods.triggerFakeMouseEvent("mousedown", target, outsidePointPos.x, outsidePointPos.y); assert.isFalse(startCallbackCalled, "does not trigger callback if drag starts outside the Component (positive) (mousedown)"); - triggerFakeMouseEvent("mousedown", target, outsidePointNeg.x, outsidePointNeg.y); + TestMethods.triggerFakeMouseEvent("mousedown", target, outsidePointNeg.x, outsidePointNeg.y); assert.isFalse(startCallbackCalled, "does not trigger callback if drag starts outside the Component (negative) (mousedown)"); - triggerFakeTouchEvent("touchstart", target, [{x: outsidePointPos.x, y: outsidePointPos.y}]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{x: outsidePointPos.x, y: outsidePointPos.y}]); assert.isFalse(startCallbackCalled, "does not trigger callback if drag starts outside the Component (positive) (touchstart)"); - triggerFakeTouchEvent("touchstart", target, [{x: outsidePointNeg.x, y: outsidePointNeg.y}]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{x: outsidePointNeg.x, y: outsidePointNeg.y}]); assert.isFalse(startCallbackCalled, "does not trigger callback if drag starts outside the Component (negative) (touchstart)"); - assert.strictEqual(drag.onDragStart(), startCallback, "retrieves the callback if called with no arguments"); - drag.onDragStart(null); - assert.isNull(drag.onDragStart(), "removes the callback if called with null"); + drag.offDragStart(startCallback); + + startCallbackCalled = false; + TestMethods.triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); + assert.isFalse(startCallbackCalled, "callback was decoupled from the interaction"); + + TestMethods.triggerFakeTouchEvent("touchstart", target, [{x: startPoint.x, y: startPoint.y}]); + assert.isFalse(startCallbackCalled, "callback was decoupled from the interaction"); + svg.remove(); }); it("onDrag()", () => { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var c = new Plottable.Component.AbstractComponent(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var c = new Plottable.Component(); c.renderTo(svg); - var drag = new Plottable.Interaction.Drag(); + var drag = new Plottable.Interactions.Drag(); var moveCallbackCalled = false; var receivedStart: Plottable.Point; var receivedEnd: Plottable.Point; @@ -96,35 +102,43 @@ describe("Interactions", () => { receivedEnd = end; }; drag.onDrag(moveCallback); - c.registerInteraction(drag); + drag.attachTo(c); var target = c.background(); - triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); - triggerFakeMouseEvent("mousemove", target, endPoint.x, endPoint.y); + TestMethods.triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); + TestMethods.triggerFakeMouseEvent("mousemove", target, endPoint.x, endPoint.y); assert.isTrue(moveCallbackCalled, "callback was called on dragging (mousemove)"); assert.deepEqual(receivedStart, startPoint, "was passed the correct starting point"); assert.deepEqual(receivedEnd, endPoint, "was passed the correct current point"); receivedStart = null; receivedEnd = null; - triggerFakeTouchEvent("touchstart", target, [{x: startPoint.x, y: startPoint.y}]); - triggerFakeTouchEvent("touchmove", target, [{x: endPoint.x, y: endPoint.y}]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{x: startPoint.x, y: startPoint.y}]); + TestMethods.triggerFakeTouchEvent("touchmove", target, [{x: endPoint.x, y: endPoint.y}]); assert.isTrue(moveCallbackCalled, "callback was called on dragging (touchmove)"); assert.deepEqual(receivedStart, startPoint, "was passed the correct starting point"); assert.deepEqual(receivedEnd, endPoint, "was passed the correct current point"); - assert.strictEqual(drag.onDrag(), moveCallback, "retrieves the callback if called with no arguments"); - drag.onDrag(null); - assert.isNull(drag.onDrag(), "removes the callback if called with null"); + drag.offDrag(moveCallback); + + moveCallbackCalled = false; + TestMethods.triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); + TestMethods.triggerFakeMouseEvent("mousemove", target, endPoint.x, endPoint.y); + assert.isFalse(moveCallbackCalled, "callback was decoupled from interaction"); + + TestMethods.triggerFakeTouchEvent("touchstart", target, [{x: startPoint.x, y: startPoint.y}]); + TestMethods.triggerFakeTouchEvent("touchmove", target, [{x: endPoint.x, y: endPoint.y}]); + assert.isFalse(moveCallbackCalled, "callback was decoupled from interaction"); + svg.remove(); }); it("onDragEnd()", () => { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var c = new Plottable.Component.AbstractComponent(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var c = new Plottable.Component(); c.renderTo(svg); - var drag = new Plottable.Interaction.Drag(); + var drag = new Plottable.Interactions.Drag(); var endCallbackCalled = false; var receivedStart: Plottable.Point; var receivedEnd: Plottable.Point; @@ -134,42 +148,118 @@ describe("Interactions", () => { receivedEnd = end; }; drag.onDragEnd(endCallback); - c.registerInteraction(drag); + drag.attachTo(c); var target = c.background(); - triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); - triggerFakeMouseEvent("mouseup", target, endPoint.x, endPoint.y); + TestMethods.triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); + TestMethods.triggerFakeMouseEvent("mouseup", target, endPoint.x, endPoint.y); assert.isTrue(endCallbackCalled, "callback was called on drag ending (mouseup)"); assert.deepEqual(receivedStart, startPoint, "was passed the correct starting point"); assert.deepEqual(receivedEnd, endPoint, "was passed the correct current point"); receivedStart = null; receivedEnd = null; - triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); - triggerFakeMouseEvent("mouseup", target, endPoint.x, endPoint.y, 2); + TestMethods.triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); + TestMethods.triggerFakeMouseEvent("mouseup", target, endPoint.x, endPoint.y, 2); assert.isTrue(endCallbackCalled, "callback was not called on mouseup from the right-click button"); - triggerFakeMouseEvent("mouseup", target, endPoint.x, endPoint.y); // end the drag + TestMethods.triggerFakeMouseEvent("mouseup", target, endPoint.x, endPoint.y); // end the drag receivedStart = null; receivedEnd = null; - triggerFakeTouchEvent("touchstart", target, [{x: startPoint.x, y: startPoint.y}]); - triggerFakeTouchEvent("touchend", target, [{x: endPoint.x, y: endPoint.y}]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{x: startPoint.x, y: startPoint.y}]); + TestMethods.triggerFakeTouchEvent("touchend", target, [{x: endPoint.x, y: endPoint.y}]); assert.isTrue(endCallbackCalled, "callback was called on drag ending (touchend)"); assert.deepEqual(receivedStart, startPoint, "was passed the correct starting point"); assert.deepEqual(receivedEnd, endPoint, "was passed the correct current point"); - assert.strictEqual(drag.onDragEnd(), endCallback, "retrieves the callback if called with no arguments"); - drag.onDragEnd(null); - assert.isNull(drag.onDragEnd(), "removes the callback if called with null"); + drag.offDragEnd(endCallback); + + endCallbackCalled = false; + TestMethods.triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); + TestMethods.triggerFakeMouseEvent("mouseup", target, endPoint.x, endPoint.y); + assert.isFalse(endCallbackCalled, "callback was called on drag ending (mouseup)"); + + TestMethods.triggerFakeTouchEvent("touchstart", target, [{ x: startPoint.x, y: startPoint.y }]); + TestMethods.triggerFakeTouchEvent("touchend", target, [{ x: endPoint.x, y: endPoint.y }]); + assert.isFalse(endCallbackCalled, "callback decoupled from interaction"); + + svg.remove(); + }); + + it("multiple interactions on drag", () => { + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var component = new Plottable.Component(); + component.renderTo(svg); + + var drag = new Plottable.Interactions.Drag(); + var startCallback1Called = false; + var startCallback2Called = false; + var moveCallback1Called = false; + var moveCallback2Called = false; + var endCallback1Called = false; + var endCallback2Called = false; + + var startCallback1 = () => startCallback1Called = true; + var startCallback2 = () => startCallback2Called = true; + var moveCallback1 = () => moveCallback1Called = true; + var moveCallback2 = () => moveCallback2Called = true; + var endCallback1 = () => endCallback1Called = true; + var endCallback2 = () => endCallback2Called = true; + + drag.onDragStart(startCallback1); + drag.onDragStart(startCallback2); + drag.onDrag(moveCallback1); + drag.onDrag(moveCallback2); + drag.onDragEnd(endCallback1); + drag.onDragEnd(endCallback2); + + drag.attachTo(component); + + var target = component.background(); + TestMethods.triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); + assert.isTrue(startCallback1Called, "callback 1 was called on beginning drag (mousedown)"); + assert.isTrue(startCallback2Called, "callback 2 was called on beginning drag (mousedown)"); + + TestMethods.triggerFakeMouseEvent("mousemove", target, endPoint.x, endPoint.y); + assert.isTrue(moveCallback1Called, "callback 1 was called on dragging (mousemove)"); + assert.isTrue(moveCallback2Called, "callback 2 was called on dragging (mousemove)"); + + TestMethods.triggerFakeMouseEvent("mouseup", target, endPoint.x, endPoint.y); + assert.isTrue(endCallback1Called, "callback 1 was called on drag ending (mouseup)"); + assert.isTrue(endCallback2Called, "callback 2 was called on drag ending (mouseup)"); + + startCallback1Called = false; + startCallback2Called = false; + moveCallback1Called = false; + moveCallback2Called = false; + endCallback1Called = false; + endCallback2Called = false; + + drag.offDragStart(startCallback1); + drag.offDrag(moveCallback1); + drag.offDragEnd(endCallback1); + + TestMethods.triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); + assert.isFalse(startCallback1Called, "callback 1 was disconnected from drag start interaction"); + assert.isTrue(startCallback2Called, "callback 2 is still connected to the drag start interaction"); + + TestMethods.triggerFakeMouseEvent("mousemove", target, endPoint.x, endPoint.y); + assert.isFalse(moveCallback1Called, "callback 1 was disconnected from drag interaction"); + assert.isTrue(moveCallback2Called, "callback 2 is still connected to the drag interaction"); + + TestMethods.triggerFakeMouseEvent("mouseup", target, endPoint.x, endPoint.y); + assert.isFalse(endCallback1Called, "callback 1 was disconnected from the drag end interaction"); + assert.isTrue(endCallback2Called, "callback 2 is still connected to the drag end interaction"); + svg.remove(); }); it("constrainToComponent()", () => { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var c = new Plottable.Component.AbstractComponent(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var c = new Plottable.Component(); c.renderTo(svg); - var drag = new Plottable.Interaction.Drag(); + var drag = new Plottable.Interactions.Drag(); assert.isTrue(drag.constrainToComponent(), "constrains by default"); var receivedStart: Plottable.Point; @@ -185,75 +275,104 @@ describe("Interactions", () => { }; drag.onDragEnd(endCallback); - c.registerInteraction(drag); + drag.attachTo(c); var target = c.content(); - triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); - triggerFakeMouseEvent("mousemove", target, outsidePointPos.x, outsidePointPos.y); + TestMethods.triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); + TestMethods.triggerFakeMouseEvent("mousemove", target, outsidePointPos.x, outsidePointPos.y); assert.deepEqual(receivedEnd, constrainedPos, "dragging outside the Component is constrained (positive) (mousemove)"); - triggerFakeMouseEvent("mousemove", target, outsidePointNeg.x, outsidePointNeg.y); + TestMethods.triggerFakeMouseEvent("mousemove", target, outsidePointNeg.x, outsidePointNeg.y); assert.deepEqual(receivedEnd, constrainedNeg, "dragging outside the Component is constrained (negative) (mousemove)"); receivedEnd = null; - triggerFakeTouchEvent("touchmove", target, [{x: outsidePointPos.x, y: outsidePointPos.y}]); + TestMethods.triggerFakeTouchEvent("touchmove", target, [{x: outsidePointPos.x, y: outsidePointPos.y}]); assert.deepEqual(receivedEnd, constrainedPos, "dragging outside the Component is constrained (positive) (touchmove)"); - triggerFakeTouchEvent("touchmove", target, [{x: outsidePointNeg.x, y: outsidePointNeg.y}]); + TestMethods.triggerFakeTouchEvent("touchmove", target, [{x: outsidePointNeg.x, y: outsidePointNeg.y}]); assert.deepEqual(receivedEnd, constrainedNeg, "dragging outside the Component is constrained (negative) (touchmove)"); receivedEnd = null; - triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); - triggerFakeMouseEvent("mouseup", target, outsidePointPos.x, outsidePointPos.y); + TestMethods.triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); + TestMethods.triggerFakeMouseEvent("mouseup", target, outsidePointPos.x, outsidePointPos.y); assert.deepEqual(receivedEnd, constrainedPos, "dragging outside the Component is constrained (positive) (mouseup)"); - triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); - triggerFakeMouseEvent("mouseup", target, outsidePointNeg.x, outsidePointNeg.y); + TestMethods.triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); + TestMethods.triggerFakeMouseEvent("mouseup", target, outsidePointNeg.x, outsidePointNeg.y); assert.deepEqual(receivedEnd, constrainedNeg, "dragging outside the Component is constrained (negative) (mouseup)"); receivedEnd = null; - triggerFakeTouchEvent("touchstart", target, [{x: startPoint.x, y: startPoint.y}]); - triggerFakeTouchEvent("touchend", target, [{x: outsidePointPos.x, y: outsidePointPos.y}]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{x: startPoint.x, y: startPoint.y}]); + TestMethods.triggerFakeTouchEvent("touchend", target, [{x: outsidePointPos.x, y: outsidePointPos.y}]); assert.deepEqual(receivedEnd, constrainedPos, "dragging outside the Component is constrained (positive) (touchend)"); - triggerFakeTouchEvent("touchstart", target, [{x: startPoint.x, y: startPoint.y}]); - triggerFakeTouchEvent("touchend", target, [{x: outsidePointNeg.x, y: outsidePointNeg.y}]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{x: startPoint.x, y: startPoint.y}]); + TestMethods.triggerFakeTouchEvent("touchend", target, [{x: outsidePointNeg.x, y: outsidePointNeg.y}]); assert.deepEqual(receivedEnd, constrainedNeg, "dragging outside the Component is constrained (negative) (touchend)"); drag.constrainToComponent(false); - triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); - triggerFakeMouseEvent("mousemove", target, outsidePointPos.x, outsidePointPos.y); + TestMethods.triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); + TestMethods.triggerFakeMouseEvent("mousemove", target, outsidePointPos.x, outsidePointPos.y); assert.deepEqual(receivedEnd, outsidePointPos, "dragging outside the Component is no longer constrained (positive) (mousemove)"); - triggerFakeMouseEvent("mousemove", target, outsidePointNeg.x, outsidePointNeg.y); + TestMethods.triggerFakeMouseEvent("mousemove", target, outsidePointNeg.x, outsidePointNeg.y); assert.deepEqual(receivedEnd, outsidePointNeg, "dragging outside the Component is no longer constrained (negative) (mousemove)"); receivedEnd = null; - triggerFakeTouchEvent("touchmove", target, [{x: outsidePointPos.x, y: outsidePointPos.y}]); + TestMethods.triggerFakeTouchEvent("touchmove", target, [{x: outsidePointPos.x, y: outsidePointPos.y}]); assert.deepEqual(receivedEnd, outsidePointPos, "dragging outside the Component is no longer constrained (positive) (touchmove)"); - triggerFakeTouchEvent("touchmove", target, [{x: outsidePointNeg.x, y: outsidePointNeg.y}]); + TestMethods.triggerFakeTouchEvent("touchmove", target, [{x: outsidePointNeg.x, y: outsidePointNeg.y}]); assert.deepEqual(receivedEnd, outsidePointNeg, "dragging outside the Component is no longer constrained (negative) (touchmove)"); receivedEnd = null; - triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); - triggerFakeMouseEvent("mouseup", target, outsidePointPos.x, outsidePointPos.y); + TestMethods.triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); + TestMethods.triggerFakeMouseEvent("mouseup", target, outsidePointPos.x, outsidePointPos.y); assert.deepEqual(receivedEnd, outsidePointPos, "dragging outside the Component is no longer constrained (positive) (mouseup)"); - triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); - triggerFakeMouseEvent("mouseup", target, outsidePointNeg.x, outsidePointNeg.y); + TestMethods.triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); + TestMethods.triggerFakeMouseEvent("mouseup", target, outsidePointNeg.x, outsidePointNeg.y); assert.deepEqual(receivedEnd, outsidePointNeg, "dragging outside the Component is no longer constrained (negative) (mouseup)"); receivedEnd = null; - triggerFakeTouchEvent("touchstart", target, [{x: startPoint.x, y: startPoint.y}]); - triggerFakeTouchEvent("touchend", target, [{x: outsidePointPos.x, y: outsidePointPos.y}]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{x: startPoint.x, y: startPoint.y}]); + TestMethods.triggerFakeTouchEvent("touchend", target, [{x: outsidePointPos.x, y: outsidePointPos.y}]); assert.deepEqual(receivedEnd, outsidePointPos, "dragging outside the Component is no longer constrained (positive) (touchend)"); - triggerFakeTouchEvent("touchstart", target, [{x: startPoint.x, y: startPoint.y}]); - triggerFakeTouchEvent("touchend", target, [{x: outsidePointNeg.x, y: outsidePointNeg.y}]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{x: startPoint.x, y: startPoint.y}]); + TestMethods.triggerFakeTouchEvent("touchend", target, [{x: outsidePointNeg.x, y: outsidePointNeg.y}]); assert.deepEqual(receivedEnd, outsidePointNeg, "dragging outside the Component is no longer constrained (negative) (touchend)"); svg.remove(); }); + + it("touchcancel cancels the current drag", () => { + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var c = new Plottable.Component(); + c.renderTo(svg); + + var drag = new Plottable.Interactions.Drag(); + var moveCallbackCalled = false; + var receivedStart: Plottable.Point; + var receivedEnd: Plottable.Point; + var moveCallback = (start: Plottable.Point, end: Plottable.Point) => { + moveCallbackCalled = true; + receivedStart = start; + receivedEnd = end; + }; + drag.onDrag(moveCallback); + drag.attachTo(c); + + var target = c.background(); + receivedStart = null; + receivedEnd = null; + TestMethods.triggerFakeTouchEvent("touchstart", target, [{x: startPoint.x, y: startPoint.y}]); + TestMethods.triggerFakeTouchEvent("touchmove", target, [{x: endPoint.x - 10, y: endPoint.y - 10}]); + TestMethods.triggerFakeTouchEvent("touchcancel", target, [{x: endPoint.x - 10, y: endPoint.y - 10}]); + TestMethods.triggerFakeTouchEvent("touchmove", target, [{x: endPoint.x, y: endPoint.y}]); + assert.notEqual(receivedEnd, endPoint, "was not passed touch point after cancelled"); + + svg.remove(); + }); }); }); diff --git a/test/interactions/hoverInteractionTests.ts b/test/interactions/hoverInteractionTests.ts index 9d931e8473..e02580a820 100644 --- a/test/interactions/hoverInteractionTests.ts +++ b/test/interactions/hoverInteractionTests.ts @@ -44,9 +44,8 @@ describe("Interactions", () => { var outData: Plottable.Interaction.HoverData; var outCallbackCalled = false; - beforeEach(() => { - svg = generateSVG(); + svg = TestMethods.generateSVG(); testTarget = new TestHoverable(); testTarget.classed("test-hoverable", true); testTarget.renderTo(svg); @@ -64,13 +63,13 @@ describe("Interactions", () => { outData = hd; }); - testTarget.registerInteraction(hoverInteraction); + hoverInteraction.attachTo(testTarget); target = testTarget.background(); }); it("correctly triggers onHoverOver() callbacks (mouse events)", () => { overCallbackCalled = false; - triggerFakeMouseEvent("mouseover", target, 100, 200); + TestMethods.triggerFakeMouseEvent("mouseover", target, 100, 200); assert.isTrue(overCallbackCalled, "onHoverOver was called on mousing over a target area"); assert.deepEqual(overData.pixelPositions, [testTarget.leftPoint], "onHoverOver was called with the correct pixel position (mouse onto left)"); @@ -78,21 +77,21 @@ describe("Interactions", () => { "onHoverOver was called with the correct data (mouse onto left)"); overCallbackCalled = false; - triggerFakeMouseEvent("mousemove", target, 100, 200); + TestMethods.triggerFakeMouseEvent("mousemove", target, 100, 200); assert.isFalse(overCallbackCalled, "onHoverOver isn't called if the hover data didn't change"); overCallbackCalled = false; - triggerFakeMouseEvent("mousemove", target, 200, 200); + TestMethods.triggerFakeMouseEvent("mousemove", target, 200, 200); assert.isTrue(overCallbackCalled, "onHoverOver was called when mousing into a new region"); assert.deepEqual(overData.pixelPositions, [testTarget.rightPoint], "onHoverOver was called with the correct pixel position (left --> center)"); assert.deepEqual(overData.data, ["right"], "onHoverOver was called with the new data only (left --> center)"); - triggerFakeMouseEvent("mouseout", target, 401, 200); + TestMethods.triggerFakeMouseEvent("mouseout", target, 401, 200); overCallbackCalled = false; - triggerFakeMouseEvent("mouseover", target, 200, 200); + TestMethods.triggerFakeMouseEvent("mouseover", target, 200, 200); assert.deepEqual(overData.pixelPositions, [testTarget.leftPoint, testTarget.rightPoint], "onHoverOver was called with the correct pixel positions"); assert.deepEqual(overData.data, ["left", "right"], "onHoverOver is called with the correct data"); @@ -102,7 +101,7 @@ describe("Interactions", () => { it("correctly triggers onHoverOver() callbacks (touch events)", () => { overCallbackCalled = false; - triggerFakeTouchEvent("touchstart", target, [{x: 100, y: 200}]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{x: 100, y: 200}]); assert.isTrue(overCallbackCalled, "onHoverOver was called on touching a target area"); assert.deepEqual(overData.pixelPositions, [testTarget.leftPoint], "onHoverOver was called with the correct pixel position (mouse onto left)"); @@ -110,21 +109,21 @@ describe("Interactions", () => { "onHoverOver was called with the correct data (mouse onto left)"); overCallbackCalled = false; - triggerFakeTouchEvent("touchstart", target, [{x: 100, y: 200}]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{x: 100, y: 200}]); assert.isFalse(overCallbackCalled, "onHoverOver isn't called if the hover data didn't change"); overCallbackCalled = false; - triggerFakeTouchEvent("touchstart", target, [{x: 200, y: 200}]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{x: 200, y: 200}]); assert.isTrue(overCallbackCalled, "onHoverOver was called when touch moves into a new region"); assert.deepEqual(overData.pixelPositions, [testTarget.rightPoint], "onHoverOver was called with the correct pixel position (left --> center)"); assert.deepEqual(overData.data, ["right"], "onHoverOver was called with the new data only (left --> center)"); - triggerFakeTouchEvent("touchstart", target, [{x: 401, y: 200}]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{x: 401, y: 200}]); overCallbackCalled = false; - triggerFakeTouchEvent("touchstart", target, [{x: 200, y: 200}]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{x: 200, y: 200}]); assert.deepEqual(overData.pixelPositions, [testTarget.leftPoint, testTarget.rightPoint], "onHoverOver was called with the correct pixel positions"); assert.deepEqual(overData.data, ["left", "right"], "onHoverOver is called with the correct data"); @@ -133,15 +132,15 @@ describe("Interactions", () => { }); it("correctly triggers onHoverOut() callbacks (mouse events)", () => { - triggerFakeMouseEvent("mouseover", target, 100, 200); + TestMethods.triggerFakeMouseEvent("mouseover", target, 100, 200); outCallbackCalled = false; - triggerFakeMouseEvent("mousemove", target, 200, 200); + TestMethods.triggerFakeMouseEvent("mousemove", target, 200, 200); assert.isFalse(outCallbackCalled, "onHoverOut isn't called when mousing into a new region without leaving the old one"); outCallbackCalled = false; - triggerFakeMouseEvent("mousemove", target, 300, 200); + TestMethods.triggerFakeMouseEvent("mousemove", target, 300, 200); assert.isTrue(outCallbackCalled, "onHoverOut was called when the hover data changes"); assert.deepEqual(outData.pixelPositions, [testTarget.leftPoint], "onHoverOut was called with the correct pixel position (center --> right)"); @@ -149,15 +148,15 @@ describe("Interactions", () => { "onHoverOut was called with the correct data (center --> right)"); outCallbackCalled = false; - triggerFakeMouseEvent("mouseout", target, 401, 200); + TestMethods.triggerFakeMouseEvent("mouseout", target, 401, 200); assert.isTrue(outCallbackCalled, "onHoverOut is called on mousing out of the Component"); assert.deepEqual(outData.pixelPositions, [testTarget.rightPoint], "onHoverOut was called with the correct pixel position"); assert.deepEqual(outData.data, ["right"], "onHoverOut was called with the correct data"); outCallbackCalled = false; - triggerFakeMouseEvent("mouseover", target, 200, 200); - triggerFakeMouseEvent("mouseout", target, 200, 401); + TestMethods.triggerFakeMouseEvent("mouseover", target, 200, 200); + TestMethods.triggerFakeMouseEvent("mouseout", target, 200, 401); assert.deepEqual(outData.pixelPositions, [testTarget.leftPoint, testTarget.rightPoint], "onHoverOut was called with the correct pixel positions"); assert.deepEqual(outData.data, ["left", "right"], "onHoverOut is called with the correct data"); @@ -166,15 +165,15 @@ describe("Interactions", () => { }); it("correctly triggers onHoverOut() callbacks (touch events)", () => { - triggerFakeTouchEvent("touchstart", target, [{x: 100, y: 200}]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{x: 100, y: 200}]); outCallbackCalled = false; - triggerFakeTouchEvent("touchstart", target, [{x: 200, y: 200}]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{x: 200, y: 200}]); assert.isFalse(outCallbackCalled, "onHoverOut isn't called when mousing into a new region without leaving the old one"); outCallbackCalled = false; - triggerFakeTouchEvent("touchstart", target, [{x: 300, y: 200}]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{x: 300, y: 200}]); assert.isTrue(outCallbackCalled, "onHoverOut was called when the hover data changes"); assert.deepEqual(outData.pixelPositions, [testTarget.leftPoint], "onHoverOut was called with the correct pixel position (center --> right)"); @@ -182,15 +181,15 @@ describe("Interactions", () => { "onHoverOut was called with the correct data (center --> right)"); outCallbackCalled = false; - triggerFakeTouchEvent("touchstart", target, [{x: 401, y: 200}]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{x: 401, y: 200}]); assert.isTrue(outCallbackCalled, "onHoverOut is called on mousing out of the Component"); assert.deepEqual(outData.pixelPositions, [testTarget.rightPoint], "onHoverOut was called with the correct pixel position"); assert.deepEqual(outData.data, ["right"], "onHoverOut was called with the correct data"); outCallbackCalled = false; - triggerFakeTouchEvent("touchstart", target, [{x: 200, y: 200}]); - triggerFakeTouchEvent("touchstart", target, [{x: 200, y: 401}]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{x: 200, y: 200}]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{x: 200, y: 401}]); assert.deepEqual(outData.pixelPositions, [testTarget.leftPoint, testTarget.rightPoint], "onHoverOut was called with the correct pixel positions"); assert.deepEqual(outData.data, ["left", "right"], "onHoverOut is called with the correct data"); @@ -199,21 +198,21 @@ describe("Interactions", () => { }); it("getCurrentHoverData()", () => { - triggerFakeMouseEvent("mouseover", target, 100, 200); + TestMethods.triggerFakeMouseEvent("mouseover", target, 100, 200); var currentlyHovered = hoverInteraction.getCurrentHoverData(); assert.deepEqual(currentlyHovered.pixelPositions, [testTarget.leftPoint], "retrieves pixel positions corresponding to the current position"); assert.deepEqual(currentlyHovered.data, ["left"], "retrieves data corresponding to the current position"); - triggerFakeMouseEvent("mousemove", target, 200, 200); + TestMethods.triggerFakeMouseEvent("mousemove", target, 200, 200); currentlyHovered = hoverInteraction.getCurrentHoverData(); assert.deepEqual(currentlyHovered.pixelPositions, [testTarget.leftPoint, testTarget.rightPoint], "retrieves pixel positions corresponding to the current position"); assert.deepEqual(currentlyHovered.data, ["left", "right"], "retrieves data corresponding to the current position"); - triggerFakeMouseEvent("mouseout", target, 401, 200); + TestMethods.triggerFakeMouseEvent("mouseout", target, 401, 200); currentlyHovered = hoverInteraction.getCurrentHoverData(); assert.isNull(currentlyHovered.data, "returns null if not currently hovering"); diff --git a/test/interactions/interactionTests.ts b/test/interactions/interactionTests.ts index 995b38e7d2..a64bb28a2b 100644 --- a/test/interactions/interactionTests.ts +++ b/test/interactions/interactionTests.ts @@ -3,118 +3,156 @@ var assert = chai.assert; describe("Interactions", () => { - describe("PanZoomInteraction", () => { - it("Pans properly", () => { - // The only difference between pan and zoom is internal to d3 - // Simulating zoom events is painful, so panning will suffice here - var xScale = new Plottable.Scale.Linear().domain([0, 11]); - var yScale = new Plottable.Scale.Linear().domain([11, 0]); - - var svg = generateSVG(); - var dataset = makeLinearSeries(11); - var plot = new Plottable.Plot.Scatter(xScale, yScale).addDataset(dataset); - plot.project("x", "x", xScale); - plot.project("y", "y", yScale); - plot.renderTo(svg); - - var xDomainBefore = xScale.domain(); - var yDomainBefore = yScale.domain(); - - var interaction = new Plottable.Interaction.PanZoom(xScale, yScale); - plot.registerInteraction(interaction); - - var hb = plot.hitBox().node(); - var dragDistancePixelX = 10; - var dragDistancePixelY = 20; - $(hb).simulate("drag", { - dx: dragDistancePixelX, - dy: dragDistancePixelY - }); - - var xDomainAfter = xScale.domain(); - var yDomainAfter = yScale.domain(); - - assert.notDeepEqual(xDomainAfter, xDomainBefore, "x domain was changed by panning"); - assert.notDeepEqual(yDomainAfter, yDomainBefore, "y domain was changed by panning"); - - function getSlope(scale: Plottable.Scale.Linear) { - var range = scale.range(); - var domain = scale.domain(); - return (domain[1] - domain[0]) / (range[1] - range[0]); - }; - - var expectedXDragChange = -dragDistancePixelX * getSlope(xScale); - var expectedYDragChange = -dragDistancePixelY * getSlope(yScale); - - assert.closeTo(xDomainAfter[0] - xDomainBefore[0], expectedXDragChange, 1, "x domain changed by the correct amount"); - assert.closeTo(yDomainAfter[0] - yDomainBefore[0], expectedYDragChange, 1, "y domain changed by the correct amount"); + describe("Interaction", () => { + var SVG_WIDTH = 400; + var SVG_HEIGHT = 400; + + it("attaching/detaching a component modifies the state of the interaction", () => { + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var component = new Plottable.Component(); + var interaction = new Plottable.Interaction(); + component.renderTo(svg); + + interaction.attachTo(component); + assert.strictEqual((interaction)._componentAttachedTo, component, + "the _componentAttachedTo field should contain the component the interaction is attached to"); + + interaction.detachFrom(component); + assert.isNull((interaction)._componentAttachedTo, + "the _componentAttachedTo field should be blanked upon detaching"); svg.remove(); }); - it("Resets zoom when the scale domain changes", () => { - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); + it("can attach interaction to component", () => { + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var component = new Plottable.Component(); + component.renderTo(svg); - var svg = generateSVG(); - var c = new Plottable.Component.AbstractComponent(); - c.renderTo(svg); + var clickInteraction = new Plottable.Interactions.Click(); - var pzi = new Plottable.Interaction.PanZoom(xScale, yScale); - c.registerInteraction(pzi); + var callbackCalled = false; + var callback = () => callbackCalled = true; + clickInteraction.onClick(callback); - var zoomBeforeX = ( pzi)._zoom; - xScale.domain([10, 1000]); - var zoomAfterX = ( pzi)._zoom; - assert.notStrictEqual(zoomBeforeX, zoomAfterX, "D3 Zoom was regenerated after x scale domain changed"); + clickInteraction.attachTo(component); - var zoomBeforeY = ( pzi)._zoom; - yScale.domain([10, 1000]); - var zoomAfterY = ( pzi)._zoom; - assert.notStrictEqual(zoomBeforeY, zoomAfterY, "D3 Zoom was regenerated after y scale domain changed"); + TestMethods.triggerFakeMouseEvent("mousedown", component.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mouseup", component.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + assert.isTrue(callbackCalled, "callback called on clicking Component (mouse)"); svg.remove(); }); - }); - describe("KeyInteraction", () => { - it("Triggers appropriate callback for the key pressed", () => { - var svg = generateSVG(400, 400); - var component = new Plottable.Component.AbstractComponent(); + it("can detach interaction from component", () => { + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var component = new Plottable.Component(); component.renderTo(svg); - var ki = new Plottable.Interaction.Key(); + var clickInteraction = new Plottable.Interactions.Click(); + + var callbackCalled = false; + var callback = () => callbackCalled = true; + clickInteraction.onClick(callback); - var aCode = 65; // "a" key - var bCode = 66; // "b" key + clickInteraction.attachTo(component); - var aCallbackCalled = false; - var aCallback = () => aCallbackCalled = true; - var bCallbackCalled = false; - var bCallback = () => bCallbackCalled = true; + TestMethods.triggerFakeMouseEvent("mousedown", component.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mouseup", component.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + assert.isTrue(callbackCalled, "callback called on clicking Component (mouse)"); - ki.on(aCode, aCallback); - ki.on(bCode, bCallback); - component.registerInteraction(ki); + callbackCalled = false; + clickInteraction.detachFrom(component); - var $target = $(component.background().node()); + TestMethods.triggerFakeMouseEvent("mousedown", component.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mouseup", component.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + assert.isFalse(callbackCalled, "callback was removed from component and should not be called"); - triggerFakeMouseEvent("mouseover", component.background(), 100, 100); - $target.simulate("keydown", { keyCode: aCode }); - assert.isTrue(aCallbackCalled, "callback for \"a\" was called when \"a\" key was pressed"); - assert.isFalse(bCallbackCalled, "callback for \"b\" was not called when \"a\" key was pressed"); + svg.remove(); + }); - aCallbackCalled = false; - $target.simulate("keydown", { keyCode: bCode }); - assert.isFalse(aCallbackCalled, "callback for \"a\" was not called when \"b\" key was pressed"); - assert.isTrue(bCallbackCalled, "callback for \"b\" was called when \"b\" key was pressed"); + it("calling detachFrom() on a detached Interaction has no effect", () => { + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var component = new Plottable.Component(); - triggerFakeMouseEvent("mouseout", component.background(), -100, -100); - aCallbackCalled = false; - $target.simulate("keydown", { keyCode: aCode }); - assert.isFalse(aCallbackCalled, "callback for \"a\" was not called when not moused over the Component"); + var clickInteraction = new Plottable.Interactions.Click(); + + assert.doesNotThrow(() => { + clickInteraction.detachFrom(component); + }, "detaching an Interaction which was not attached should not throw an error"); + + clickInteraction.attachTo(component); + clickInteraction.detachFrom(component); + assert.doesNotThrow(() => { + clickInteraction.detachFrom(component); + }, "calling detachFrom() twice should not throw an error"); + + component.renderTo(svg); + + clickInteraction.attachTo(component); + clickInteraction.detachFrom(component); + assert.doesNotThrow(() => { + clickInteraction.detachFrom(component); + }, "calling detachFrom() twice should not throw an error even if the Component is anchored"); svg.remove(); + + }); + + it("can move interaction from one component to another", () => { + var svg1 = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var svg2 = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var component1 = new Plottable.Component(); + var component2 = new Plottable.Component(); + + component1.renderTo(svg1); + component2.renderTo(svg2); + + var clickInteraction = new Plottable.Interactions.Click(); + + var callbackCalled = false; + var callback = () => callbackCalled = true; + clickInteraction.onClick(callback); + + clickInteraction.attachTo(component1); + + callbackCalled = false; + TestMethods.triggerFakeMouseEvent("mousedown", component1.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mouseup", component1.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + assert.isTrue(callbackCalled, "Round 1 callback called for component 1"); + + callbackCalled = false; + TestMethods.triggerFakeMouseEvent("mousedown", component2.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mouseup", component2.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + assert.isFalse(callbackCalled, "Round 1 callback not called for component 2"); + + clickInteraction.detachFrom(component1); + clickInteraction.attachTo(component2); + + callbackCalled = false; + TestMethods.triggerFakeMouseEvent("mousedown", component1.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mouseup", component1.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + assert.isFalse(callbackCalled, "Round 2 (after longhand attaching) callback not called for component 1"); + + callbackCalled = false; + TestMethods.triggerFakeMouseEvent("mousedown", component2.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mouseup", component2.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + assert.isTrue(callbackCalled, "Round 2 (after longhand attaching) callback called for component 2"); + + clickInteraction.attachTo(component1); + + callbackCalled = false; + TestMethods.triggerFakeMouseEvent("mousedown", component1.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mouseup", component1.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + assert.isTrue(callbackCalled, "Round 3 (after shorthand attaching) callback called for component 1"); + + callbackCalled = false; + TestMethods.triggerFakeMouseEvent("mousedown", component2.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mouseup", component2.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + assert.isFalse(callbackCalled, "Round 3 (after shorthand attaching) callback not called for component 2"); + + svg1.remove(); + svg2.remove(); }); }); }); diff --git a/test/interactions/keyInteractionTests.ts b/test/interactions/keyInteractionTests.ts new file mode 100644 index 0000000000..2f0359a806 --- /dev/null +++ b/test/interactions/keyInteractionTests.ts @@ -0,0 +1,143 @@ +/// + +var assert = chai.assert; + +describe("Interactions", () => { + describe("KeyInteraction", () => { + it("Triggers appropriate callback for the key pressed", () => { + var svg = TestMethods.generateSVG(400, 400); + var component = new Plottable.Component(); + component.renderTo(svg); + + var keyInteraction = new Plottable.Interactions.Key(); + + var aCode = 65; // "a" key + var bCode = 66; // "b" key + + var aCallbackCalled = false; + var aCallback = () => aCallbackCalled = true; + var bCallbackCalled = false; + var bCallback = () => bCallbackCalled = true; + + keyInteraction.onKey(aCode, aCallback); + keyInteraction.onKey(bCode, bCallback); + keyInteraction.attachTo(component); + + var $target = $(component.background().node()); + + TestMethods.triggerFakeMouseEvent("mouseover", component.background(), 100, 100); + $target.simulate("keydown", { keyCode: aCode }); + assert.isTrue(aCallbackCalled, "callback for \"a\" was called when \"a\" key was pressed"); + assert.isFalse(bCallbackCalled, "callback for \"b\" was not called when \"a\" key was pressed"); + + aCallbackCalled = false; + $target.simulate("keydown", { keyCode: bCode }); + assert.isFalse(aCallbackCalled, "callback for \"a\" was not called when \"b\" key was pressed"); + assert.isTrue(bCallbackCalled, "callback for \"b\" was called when \"b\" key was pressed"); + + TestMethods.triggerFakeMouseEvent("mouseout", component.background(), -100, -100); + aCallbackCalled = false; + $target.simulate("keydown", { keyCode: aCode }); + assert.isFalse(aCallbackCalled, "callback for \"a\" was not called when not moused over the Component"); + + svg.remove(); + }); + + it("appropriate keyCode is sent to the callback", () => { + var svg = TestMethods.generateSVG(400, 400); + var component = new Plottable.Component(); + component.renderTo(svg); + + var keyInteraction = new Plottable.Interactions.Key(); + + var bCode = 66; // "b" key + + var bCallbackCalled = false; + var bCallback = (keyCode: number) => { + bCallbackCalled = true; + assert.strictEqual(keyCode, bCode, "keyCode 65(a) was sent to the callback"); + }; + + keyInteraction.onKey(bCode, bCallback); + + keyInteraction.attachTo(component); + + var $target = $(component.background().node()); + + TestMethods.triggerFakeMouseEvent("mouseover", component.background(), 100, 100); + $target.simulate("keydown", { keyCode: bCode }); + assert.isTrue(bCallbackCalled, "callback for \"b\" was called when \"b\" key was pressed"); + + svg.remove(); + }); + + it("canceling callbacks is possible", () => { + var svg = TestMethods.generateSVG(400, 400); + var component = new Plottable.Component(); + component.renderTo(svg); + + var keyInteraction = new Plottable.Interactions.Key(); + + var aCode = 65; // "a" key + + var aCallbackCalled = false; + var aCallback = () => aCallbackCalled = true; + + keyInteraction.onKey(aCode, aCallback); + + keyInteraction.attachTo(component); + + var $target = $(component.background().node()); + + TestMethods.triggerFakeMouseEvent("mouseover", component.background(), 100, 100); + $target.simulate("keydown", { keyCode: aCode }); + assert.isTrue(aCallbackCalled, "callback for \"a\" was called when \"a\" key was pressed"); + + keyInteraction.offKey(aCode, aCallback); + aCallbackCalled = false; + $target.simulate("keydown", { keyCode: aCode }); + assert.isFalse(aCallbackCalled, "callback for \"a\" was disconnected from the interaction"); + + keyInteraction.onKey(aCode, aCallback); + $target.simulate("keydown", { keyCode: aCode }); + assert.isTrue(aCallbackCalled, "callback for \"a\" was properly connected back to the interaction"); + + svg.remove(); + }); + + it("multiple callbacks are possible", () => { + var svg = TestMethods.generateSVG(400, 400); + var component = new Plottable.Component(); + component.renderTo(svg); + + var keyInteraction = new Plottable.Interactions.Key(); + + var aCode = 65; // "a" key + + var aCallback1Called = false; + var aCallback1 = () => aCallback1Called = true; + var aCallback2Called = false; + var aCallback2 = () => aCallback2Called = true; + + keyInteraction.onKey(aCode, aCallback1); + keyInteraction.onKey(aCode, aCallback2); + keyInteraction.attachTo(component); + + var $target = $(component.background().node()); + + TestMethods.triggerFakeMouseEvent("mouseover", component.background(), 100, 100); + $target.simulate("keydown", { keyCode: aCode }); + assert.isTrue(aCallback1Called, "callback 1 for \"a\" was called when \"a\" key was pressed"); + assert.isTrue(aCallback1Called, "callback 2 for \"b\" was called when \"a\" key was pressed"); + + keyInteraction.offKey(aCode, aCallback1); + aCallback1Called = false; + aCallback2Called = false; + $target.simulate("keydown", { keyCode: aCode }); + assert.isFalse(aCallback1Called, "callback 1 for \"a\" was disconnected from the interaction"); + assert.isTrue(aCallback2Called, "callback 2 for \"a\" is still connected to the interaction"); + + svg.remove(); + }); + }); +}); diff --git a/test/interactions/panZoomInteractionTests.ts b/test/interactions/panZoomInteractionTests.ts new file mode 100644 index 0000000000..cb7d9fbf2c --- /dev/null +++ b/test/interactions/panZoomInteractionTests.ts @@ -0,0 +1,125 @@ +/// + +var assert = chai.assert; + +describe("Interactions", () => { + describe("PanZoomInteraction", () => { + var svg: D3.Selection; + var SVG_WIDTH = 400; + var SVG_HEIGHT = 500; + + var eventTarget: D3.Selection; + + var xScale: Plottable.QuantitativeScale; + var yScale: Plottable.QuantitativeScale; + + beforeEach(() => { + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + + var component = new Plottable.Component(); + component.renderTo(svg); + + xScale = new Plottable.Scales.Linear(); + xScale.domain([0, SVG_WIDTH / 2]).range([0, SVG_WIDTH]); + yScale = new Plottable.Scales.Linear(); + yScale.domain([0, SVG_HEIGHT / 2]).range([0, SVG_HEIGHT]); + (new Plottable.Interactions.PanZoom(xScale, yScale)).attachTo(component); + + eventTarget = component.background(); + }); + + describe("Panning", () => { + + it("dragging a certain amount will translate the scale correctly (mouse)", () => { + var startPoint = { x: SVG_WIDTH / 4, y: SVG_HEIGHT / 4 }; + var endPoint = { x: SVG_WIDTH / 2, y: SVG_HEIGHT * 3 / 4 }; + TestMethods.triggerFakeMouseEvent("mousedown", eventTarget, startPoint.x, startPoint.y); + TestMethods.triggerFakeMouseEvent("mousemove", eventTarget, endPoint.x, endPoint.y); + TestMethods.triggerFakeMouseEvent("mouseend", eventTarget, endPoint.x, endPoint.y); + assert.deepEqual(xScale.domain(), [-SVG_WIDTH / 8, SVG_WIDTH * 3 / 8], "xScale pans to the correct domain via drag (mouse)"); + assert.deepEqual(yScale.domain(), [-SVG_HEIGHT / 4, SVG_HEIGHT / 4], "yScale pans to the correct domain via drag (mouse)"); + svg.remove(); + }); + + it("dragging to outside the component will translate the scale correctly (mouse)", () => { + var startPoint = { x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }; + var endPoint = { x: -SVG_WIDTH / 2, y: -SVG_HEIGHT / 2 }; + TestMethods.triggerFakeMouseEvent("mousedown", eventTarget, startPoint.x, startPoint.y); + TestMethods.triggerFakeMouseEvent("mousemove", eventTarget, endPoint.x, endPoint.y); + TestMethods.triggerFakeMouseEvent("mouseend", eventTarget, endPoint.x, endPoint.y); + assert.deepEqual(xScale.domain(), [SVG_WIDTH / 2, SVG_WIDTH], "xScale pans to the correct domain via drag (mouse)"); + assert.deepEqual(yScale.domain(), [SVG_HEIGHT / 2, SVG_HEIGHT], "yScale pans to the correct domain via drag (mouse)"); + svg.remove(); + }); + + it("dragging a certain amount will translate the scale correctly (touch)", () => { + // HACKHACK PhantomJS doesn't implement fake creation of WheelEvents + // https://github.com/ariya/phantomjs/issues/11289 + if ( window.PHANTOMJS ) { + svg.remove(); + return; + } + + var startPoint = { x: SVG_WIDTH / 4, y: SVG_HEIGHT / 4 }; + var endPoint = { x: SVG_WIDTH / 2, y: SVG_HEIGHT * 3 / 4 }; + TestMethods.triggerFakeTouchEvent("touchstart", eventTarget, [startPoint]); + TestMethods.triggerFakeTouchEvent("touchmove", eventTarget, [endPoint]); + TestMethods.triggerFakeTouchEvent("touchend", eventTarget, [endPoint]); + assert.deepEqual(xScale.domain(), [-SVG_WIDTH / 8, SVG_WIDTH * 3 / 8], "xScale pans to the correct domain via drag (touch)"); + assert.deepEqual(yScale.domain(), [-SVG_HEIGHT / 4, SVG_HEIGHT / 4], "yScale pans to the correct domain via drag (touch)"); + svg.remove(); + }); + + it("dragging to outside the component will translate the scale correctly (touch)", () => { + var startPoint = { x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }; + var endPoint = { x: -SVG_WIDTH / 2, y: -SVG_HEIGHT / 2 }; + TestMethods.triggerFakeTouchEvent("touchstart", eventTarget, [startPoint]); + TestMethods.triggerFakeTouchEvent("touchmove", eventTarget, [endPoint]); + TestMethods.triggerFakeTouchEvent("touchend", eventTarget, [endPoint]); + assert.deepEqual(xScale.domain(), [SVG_WIDTH / 2, SVG_WIDTH], "xScale pans to the correct domain via drag (touch)"); + assert.deepEqual(yScale.domain(), [SVG_HEIGHT / 2, SVG_HEIGHT], "yScale pans to the correct domain via drag (touch)"); + svg.remove(); + }); + + }); + + it("mousewheeling a certain amount will magnify the scale correctly", () => { + // HACKHACK PhantomJS doesn't implement fake creation of WheelEvents + // https://github.com/ariya/phantomjs/issues/11289 + if ( window.PHANTOMJS ) { + svg.remove(); + return; + } + + var scrollPoint = { x: SVG_WIDTH / 4, y: SVG_HEIGHT / 4 }; + var deltaY = 500; + + TestMethods.triggerFakeWheelEvent( "wheel", svg, scrollPoint.x, scrollPoint.y, deltaY ); + + assert.deepEqual(xScale.domain(), [-SVG_WIDTH / 8, SVG_WIDTH * 7 / 8], "xScale zooms to the correct domain via scroll"); + assert.deepEqual(yScale.domain(), [-SVG_HEIGHT / 8, SVG_HEIGHT * 7 / 8], "yScale zooms to the correct domain via scroll"); + svg.remove(); + }); + + it("pinching a certain amount will magnify the scale correctly", () => { + // HACKHACK PhantomJS doesn't implement fake creation of WheelEvents + // https://github.com/ariya/phantomjs/issues/11289 + if ( window.PHANTOMJS ) { + svg.remove(); + return; + } + + var startPoint = { x: SVG_WIDTH / 4, y: SVG_HEIGHT / 4 }; + var startPoint2 = { x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }; + TestMethods.triggerFakeTouchEvent( "touchstart", eventTarget, [startPoint, startPoint2], [0, 1] ); + + var endPoint = { x: SVG_WIDTH * 3 / 4, y: SVG_HEIGHT * 3 / 4 }; + TestMethods.triggerFakeTouchEvent("touchmove", eventTarget, [endPoint], [1] ); + TestMethods.triggerFakeTouchEvent("touchend", eventTarget, [endPoint], [1] ); + assert.deepEqual(xScale.domain(), [SVG_WIDTH / 16, SVG_WIDTH * 5 / 16], "xScale transforms to the correct domain via pinch"); + assert.deepEqual(yScale.domain(), [SVG_HEIGHT / 16, SVG_HEIGHT * 5 / 16], "yScale transforms to the correct domain via pinch"); + svg.remove(); + }); + + }); +}); diff --git a/test/interactions/pointerInteractionTests.ts b/test/interactions/pointerInteractionTests.ts index 15cc1bdaa1..661fe9474b 100644 --- a/test/interactions/pointerInteractionTests.ts +++ b/test/interactions/pointerInteractionTests.ts @@ -8,12 +8,12 @@ describe("Interactions", () => { var SVG_HEIGHT = 400; it("onPointerEnter", () => { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var c = new Plottable.Component.AbstractComponent(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var c = new Plottable.Component(); c.renderTo(svg); - var pointerInteraction = new Plottable.Interaction.Pointer(); - c.registerInteraction(pointerInteraction); + var pointerInteraction = new Plottable.Interactions.Pointer(); + pointerInteraction.attachTo(c); var callbackCalled = false; var lastPoint: Plottable.Point; @@ -22,47 +22,46 @@ describe("Interactions", () => { lastPoint = p; }; pointerInteraction.onPointerEnter(callback); - assert.strictEqual(pointerInteraction.onPointerEnter(), callback, "callback can be retrieved"); var target = c.background(); - triggerFakeMouseEvent("mousemove", target, SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mousemove", target, SVG_WIDTH / 2, SVG_HEIGHT / 2); assert.isTrue(callbackCalled, "callback called on entering Component (mouse)"); assert.deepEqual(lastPoint, { x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }, "was passed correct point (mouse)"); callbackCalled = false; - triggerFakeMouseEvent("mousemove", target, SVG_WIDTH / 4, SVG_HEIGHT / 4); + TestMethods.triggerFakeMouseEvent("mousemove", target, SVG_WIDTH / 4, SVG_HEIGHT / 4); assert.isFalse(callbackCalled, "callback not called again if already in Component (mouse)"); - triggerFakeMouseEvent("mousemove", target, 2 * SVG_WIDTH, 2 * SVG_HEIGHT); + TestMethods.triggerFakeMouseEvent("mousemove", target, 2 * SVG_WIDTH, 2 * SVG_HEIGHT); assert.isFalse(callbackCalled, "not called when moving outside of the Component (mouse)"); callbackCalled = false; lastPoint = null; - triggerFakeTouchEvent("touchstart", target, [{x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2}]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2}]); assert.isTrue(callbackCalled, "callback called on entering Component (touch)"); assert.deepEqual(lastPoint, { x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }, "was passed correct point (touch)"); callbackCalled = false; - triggerFakeTouchEvent("touchstart", target, [{x: SVG_WIDTH / 4, y: SVG_HEIGHT / 4}]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{x: SVG_WIDTH / 4, y: SVG_HEIGHT / 4}]); assert.isFalse(callbackCalled, "callback not called again if already in Component (touch)"); - triggerFakeTouchEvent("touchstart", target, [{x: 2 * SVG_WIDTH, y: 2 * SVG_HEIGHT}]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{x: 2 * SVG_WIDTH, y: 2 * SVG_HEIGHT}]); assert.isFalse(callbackCalled, "not called when moving outside of the Component (touch)"); - pointerInteraction.onPointerEnter(null); - triggerFakeMouseEvent("mousemove", target, SVG_WIDTH / 2, SVG_HEIGHT / 2); + pointerInteraction.offPointerEnter(callback); + TestMethods.triggerFakeMouseEvent("mousemove", target, SVG_WIDTH / 2, SVG_HEIGHT / 2); assert.isFalse(callbackCalled, "callback removed by passing null"); svg.remove(); }); it("onPointerMove", () => { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var c = new Plottable.Component.AbstractComponent(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var c = new Plottable.Component(); c.renderTo(svg); - var pointerInteraction = new Plottable.Interaction.Pointer(); - c.registerInteraction(pointerInteraction); + var pointerInteraction = new Plottable.Interactions.Pointer(); + pointerInteraction.attachTo(c); var callbackCalled = false; var lastPoint: Plottable.Point; @@ -71,50 +70,49 @@ describe("Interactions", () => { lastPoint = p; }; pointerInteraction.onPointerMove(callback); - assert.strictEqual(pointerInteraction.onPointerMove(), callback, "callback can be retrieved"); var target = c.background(); - triggerFakeMouseEvent("mousemove", target, SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mousemove", target, SVG_WIDTH / 2, SVG_HEIGHT / 2); assert.isTrue(callbackCalled, "callback called on entering Component (mouse)"); assert.deepEqual(lastPoint, { x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }, "was passed correct point (mouse)"); callbackCalled = false; - triggerFakeMouseEvent("mousemove", target, SVG_WIDTH / 4, SVG_HEIGHT / 4); + TestMethods.triggerFakeMouseEvent("mousemove", target, SVG_WIDTH / 4, SVG_HEIGHT / 4); assert.isTrue(callbackCalled, "callback on moving inside Component (mouse)"); assert.deepEqual(lastPoint, { x: SVG_WIDTH / 4, y: SVG_HEIGHT / 4 }, "was passed correct point (mouse)"); callbackCalled = false; - triggerFakeMouseEvent("mousemove", target, 2 * SVG_WIDTH, 2 * SVG_HEIGHT); + TestMethods.triggerFakeMouseEvent("mousemove", target, 2 * SVG_WIDTH, 2 * SVG_HEIGHT); assert.isFalse(callbackCalled, "not called when moving outside of the Component (mouse)"); callbackCalled = false; - triggerFakeTouchEvent("touchstart", target, [{x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2}]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2}]); assert.isTrue(callbackCalled, "callback called on entering Component (touch)"); assert.deepEqual(lastPoint, { x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }, "was passed correct point (touch)"); callbackCalled = false; - triggerFakeTouchEvent("touchstart", target, [{x: SVG_WIDTH / 4, y: SVG_HEIGHT / 4}]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{x: SVG_WIDTH / 4, y: SVG_HEIGHT / 4}]); assert.isTrue(callbackCalled, "callback on moving inside Component (touch)"); assert.deepEqual(lastPoint, { x: SVG_WIDTH / 4, y: SVG_HEIGHT / 4 }, "was passed correct point (touch)"); callbackCalled = false; - triggerFakeTouchEvent("touchstart", target, [{x: 2 * SVG_WIDTH, y: 2 * SVG_HEIGHT}]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{x: 2 * SVG_WIDTH, y: 2 * SVG_HEIGHT}]); assert.isFalse(callbackCalled, "not called when moving outside of the Component (touch)"); - pointerInteraction.onPointerMove(null); - triggerFakeMouseEvent("mousemove", target, SVG_WIDTH / 2, SVG_HEIGHT / 2); + pointerInteraction.offPointerMove(callback); + TestMethods.triggerFakeMouseEvent("mousemove", target, SVG_WIDTH / 2, SVG_HEIGHT / 2); assert.isFalse(callbackCalled, "callback removed by passing null"); svg.remove(); }); it("onPointerExit", () => { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var c = new Plottable.Component.AbstractComponent(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var c = new Plottable.Component(); c.renderTo(svg); - var pointerInteraction = new Plottable.Interaction.Pointer(); - c.registerInteraction(pointerInteraction); + var pointerInteraction = new Plottable.Interactions.Pointer(); + pointerInteraction.attachTo(c); var callbackCalled = false; var lastPoint: Plottable.Point; @@ -123,41 +121,112 @@ describe("Interactions", () => { lastPoint = p; }; pointerInteraction.onPointerExit(callback); - assert.strictEqual(pointerInteraction.onPointerExit(), callback, "callback can be retrieved"); var target = c.background(); - triggerFakeMouseEvent("mousemove", target, 2 * SVG_WIDTH, 2 * SVG_HEIGHT); + TestMethods.triggerFakeMouseEvent("mousemove", target, 2 * SVG_WIDTH, 2 * SVG_HEIGHT); assert.isFalse(callbackCalled, "not called when moving outside of the Component (mouse)"); - triggerFakeMouseEvent("mousemove", target, SVG_WIDTH / 2, SVG_HEIGHT / 2); - triggerFakeMouseEvent("mousemove", target, 2 * SVG_WIDTH, 2 * SVG_HEIGHT); + TestMethods.triggerFakeMouseEvent("mousemove", target, SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mousemove", target, 2 * SVG_WIDTH, 2 * SVG_HEIGHT); assert.isTrue(callbackCalled, "callback called on exiting Component (mouse)"); assert.deepEqual(lastPoint, { x: 2 * SVG_WIDTH, y: 2 * SVG_HEIGHT }, "was passed correct point (mouse)"); callbackCalled = false; - triggerFakeMouseEvent("mousemove", target, 3 * SVG_WIDTH, 3 * SVG_HEIGHT); + TestMethods.triggerFakeMouseEvent("mousemove", target, 3 * SVG_WIDTH, 3 * SVG_HEIGHT); assert.isFalse(callbackCalled, "callback not called again if already outside of Component (mouse)"); callbackCalled = false; lastPoint = null; - triggerFakeTouchEvent("touchstart", target, [{x: 2 * SVG_WIDTH, y: 2 * SVG_HEIGHT}]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{x: 2 * SVG_WIDTH, y: 2 * SVG_HEIGHT}]); assert.isFalse(callbackCalled, "not called when moving outside of the Component (touch)"); - triggerFakeTouchEvent("touchstart", target, [{x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2}]); - triggerFakeTouchEvent("touchstart", target, [{x: 2 * SVG_WIDTH, y: 2 * SVG_HEIGHT}]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2}]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{x: 2 * SVG_WIDTH, y: 2 * SVG_HEIGHT}]); assert.isTrue(callbackCalled, "callback called on exiting Component (touch)"); assert.deepEqual(lastPoint, { x: 2 * SVG_WIDTH, y: 2 * SVG_HEIGHT }, "was passed correct point (touch)"); callbackCalled = false; - triggerFakeTouchEvent("touchstart", target, [{x: 3 * SVG_WIDTH, y: 3 * SVG_HEIGHT}]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{x: 3 * SVG_WIDTH, y: 3 * SVG_HEIGHT}]); assert.isFalse(callbackCalled, "callback not called again if already outside of Component (touch)"); - pointerInteraction.onPointerExit(null); - triggerFakeMouseEvent("mousemove", target, SVG_WIDTH / 2, SVG_HEIGHT / 2); - triggerFakeMouseEvent("mousemove", target, 2 * SVG_WIDTH, 2 * SVG_HEIGHT); + pointerInteraction.offPointerExit(callback); + TestMethods.triggerFakeMouseEvent("mousemove", target, SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mousemove", target, 2 * SVG_WIDTH, 2 * SVG_HEIGHT); assert.isFalse(callbackCalled, "callback removed by passing null"); svg.remove(); }); + + it("multiple callbacks can be added to pointer interaction", () => { + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var component = new Plottable.Component(); + component.renderTo(svg); + + var pointer = new Plottable.Interactions.Pointer(); + var enterCallback1Called = false; + var enterCallback2Called = false; + var moveCallback1Called = false; + var moveCallback2Called = false; + var exitCallback1Called = false; + var exitCallback2Called = false; + + var enterCallback1 = () => enterCallback1Called = true; + var enterCallback2 = () => enterCallback2Called = true; + var moveCallback1 = () => moveCallback1Called = true; + var moveCallback2 = () => moveCallback2Called = true; + var exitCallback1 = () => exitCallback1Called = true; + var exitCallback2 = () => exitCallback2Called = true; + + pointer.onPointerEnter(enterCallback1); + pointer.onPointerEnter(enterCallback2); + pointer.onPointerMove(moveCallback1); + pointer.onPointerMove(moveCallback2); + pointer.onPointerExit(exitCallback1); + pointer.onPointerExit(exitCallback2); + + pointer.attachTo(component); + + var target = component.background(); + var insidePoint = { x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }; + var outsidePoint = { x: SVG_WIDTH * 2, y: SVG_HEIGHT * 2 }; + TestMethods.triggerFakeMouseEvent("mousemove", target, outsidePoint.x, outsidePoint.y); + TestMethods.triggerFakeMouseEvent("mousemove", target, insidePoint.x, insidePoint.y); + TestMethods.triggerFakeMouseEvent("mousemove", target, outsidePoint.x, outsidePoint.y); + + assert.isTrue(enterCallback1Called, "callback 1 was called on entering Component (mouse)"); + assert.isTrue(enterCallback2Called, "callback 2 was called on entering Component (mouse)"); + + assert.isTrue(moveCallback1Called, "callback 1 was called on moving inside Component (mouse)"); + assert.isTrue(moveCallback2Called, "callback 2 was called on moving inside Component (mouse)"); + + assert.isTrue(exitCallback1Called, "callback 1 was called on exiting Component (mouse)"); + assert.isTrue(exitCallback2Called, "callback 2 was called on exiting Component (mouse)"); + + enterCallback1Called = false; + enterCallback2Called = false; + moveCallback1Called = false; + moveCallback2Called = false; + exitCallback1Called = false; + exitCallback2Called = false; + + pointer.offPointerEnter(enterCallback1); + pointer.offPointerMove(moveCallback1); + pointer.offPointerExit(exitCallback1); + + TestMethods.triggerFakeMouseEvent("mousemove", target, outsidePoint.x, outsidePoint.y); + TestMethods.triggerFakeMouseEvent("mousemove", target, insidePoint.x, insidePoint.y); + TestMethods.triggerFakeMouseEvent("mousemove", target, outsidePoint.x, outsidePoint.y); + + assert.isFalse(enterCallback1Called, "callback 1 was disconnected from pointer enter interaction"); + assert.isTrue(enterCallback2Called, "callback 2 is still connected to the pointer enter interaction"); + + assert.isFalse(moveCallback1Called, "callback 1 was disconnected from pointer interaction"); + assert.isTrue(moveCallback2Called, "callback 2 is still connected to the pointer interaction"); + + assert.isFalse(exitCallback1Called, "callback 1 was disconnected from the pointer exit interaction"); + assert.isTrue(exitCallback2Called, "callback 2 is still connected to the pointer exit interaction"); + + svg.remove(); + }); }); }); diff --git a/test/mocks.ts b/test/mocks.ts index e1a08c673e..f9d88afc26 100644 --- a/test/mocks.ts +++ b/test/mocks.ts @@ -1,25 +1,29 @@ /// module Mocks { - export class FixedSizeComponent extends Plottable.Component.AbstractComponent { - public fixedWidth: number; - public fixedHeight: number; + export class FixedSizeComponent extends Plottable.Component { + public fsWidth: number; + public fsHeight: number; constructor(width = 0, height = 0) { super(); - this.fixedWidth = width; - this.fixedHeight = height; - this._fixedWidthFlag = true; - this._fixedHeightFlag = true; + this.fsWidth = width; + this.fsHeight = height; } - public _requestedSpace(availableWidth : number, availableHeight: number): Plottable._SpaceRequest { + public requestedSpace(availableWidth: number, availableHeight: number): Plottable.SpaceRequest { return { - width: this.fixedWidth, - height: this.fixedHeight, - wantsWidth : availableWidth < this.fixedWidth, - wantsHeight: availableHeight < this.fixedHeight + minWidth: this.fsWidth, + minHeight: this.fsHeight }; } + + public fixedWidth() { + return true; + } + + public fixedHeight() { + return true; + } } } diff --git a/test/scales/coordinatorTests.ts b/test/scales/coordinatorTests.ts deleted file mode 100644 index 3643ba22a0..0000000000 --- a/test/scales/coordinatorTests.ts +++ /dev/null @@ -1,23 +0,0 @@ -/// - -var assert = chai.assert; - -describe("Coordinators", () => { - describe("ScaleDomainCoordinator", () => { - it("domains are coordinated", () => { - var s1 = new Plottable.Scale.Linear(); - var s2 = new Plottable.Scale.Linear(); - var s3 = new Plottable.Scale.Linear(); - var dc = new Plottable._Util.ScaleDomainCoordinator([s1, s2, s3]); - s1.domain([0, 100]); - assert.deepEqual(s1.domain(), [0, 100]); - assert.deepEqual(s1.domain(), s2.domain()); - assert.deepEqual(s1.domain(), s3.domain()); - - s1.domain([-100, 5000]); - assert.deepEqual(s1.domain(), [-100, 5000]); - assert.deepEqual(s1.domain(), s2.domain()); - assert.deepEqual(s1.domain(), s3.domain()); - }); - }); -}); diff --git a/test/scales/modifiedLogScaleTests.ts b/test/scales/modifiedLogScaleTests.ts new file mode 100644 index 0000000000..d0ef5134bc --- /dev/null +++ b/test/scales/modifiedLogScaleTests.ts @@ -0,0 +1,111 @@ +/// + +var assert = chai.assert; + +describe("Scales", () => { + describe("Modified Log Scale", () => { + var scale: Plottable.Scales.ModifiedLog; + var base = 10; + var epsilon = 0.00001; + beforeEach(() => { + scale = new Plottable.Scales.ModifiedLog(base); + }); + + it("is an increasing, continuous function that can go negative", () => { + d3.range(-base * 2, base * 2, base / 20).forEach((x: number) => { + // increasing + assert.operator(scale.scale(x - epsilon), "<", scale.scale(x)); + assert.operator(scale.scale(x), "<", scale.scale(x + epsilon)); + // continuous + assert.closeTo(scale.scale(x - epsilon), scale.scale(x), epsilon); + assert.closeTo(scale.scale(x), scale.scale(x + epsilon), epsilon); + }); + assert.closeTo(scale.scale(0), 0, epsilon); + }); + + it("Has log() behavior at values > base", () => { + [10, 100, 23103.4, 5].forEach((x) => { + assert.closeTo(scale.scale(x), Math.log(x) / Math.log(10), 0.1); + }); + }); + + it("x = invert(scale(x))", () => { + [0, 1, base, 100, 0.001, -1, -0.3, -base, base - 0.001].forEach((x) => { + assert.closeTo(x, scale.invert(scale.scale(x)), epsilon); + assert.closeTo(x, scale.scale(scale.invert(x)), epsilon); + }); + }); + + it("domain defaults to [0, base]", () => { + scale = new Plottable.Scales.ModifiedLog(base); + assert.deepEqual(scale.domain(), [0, base]); + }); + + it("works with a Domainer", () => { + scale.addExtentsProvider((scale: Plottable.Scale) => [[0, base * 2]]); + var domain = scale.domain(); + scale.domainer(new Plottable.Domainer().pad(0.1)); + assert.operator(scale.domain()[0], "<", domain[0]); + assert.operator(domain[1], "<", scale.domain()[1]); + + scale.domainer(new Plottable.Domainer().nice()); + assert.operator(scale.domain()[0], "<=", domain[0]); + assert.operator(domain[1], "<=", scale.domain()[1]); + + scale = new Plottable.Scales.ModifiedLog(base); + scale.domainer(new Plottable.Domainer()); + assert.deepEqual(scale.domain(), [0, base]); + }); + + it("gives reasonable values for ticks()", () => { + var providedExtents = [[0, base / 2]]; + scale.addExtentsProvider((scale: Plottable.Scale) => providedExtents); + scale.autoDomain(); + var ticks = scale.ticks(); + assert.operator(ticks.length, ">", 0); + + providedExtents = [[-base * 2, base * 2]]; + scale.autoDomain(); + ticks = scale.ticks(); + var beforePivot = ticks.filter((x) => x <= -base); + var afterPivot = ticks.filter((x) => base <= x); + var betweenPivots = ticks.filter((x) => -base < x && x < base); + assert.operator(beforePivot.length, ">", 0, "should be ticks before -base"); + assert.operator(afterPivot.length, ">", 0, "should be ticks after base"); + assert.operator(betweenPivots.length, ">", 0, "should be ticks between -base and base"); + }); + + it("works on inverted domain", () => { + scale.addExtentsProvider((scale: Plottable.Scale) => [[200, -100]]); + scale.autoDomain(); + var range = scale.range(); + assert.closeTo(scale.scale(-100), range[1], epsilon); + assert.closeTo(scale.scale(200), range[0], epsilon); + var a = [-100, -10, -3, 0, 1, 3.64, 50, 60, 200]; + var b = a.map((x) => scale.scale(x)); + // should be decreasing function; reverse is sorted + assert.deepEqual(b.slice().reverse(), b.slice().sort((x, y) => x - y)); + + var ticks = scale.ticks(); + assert.deepEqual(ticks, ticks.slice().sort((x, y) => x - y), "ticks should be sorted"); + assert.deepEqual(ticks, Plottable.Utils.Methods.uniq(ticks), "ticks should not be repeated"); + var beforePivot = ticks.filter((x) => x <= -base); + var afterPivot = ticks.filter((x) => base <= x); + var betweenPivots = ticks.filter((x) => -base < x && x < base); + assert.operator(beforePivot.length, ">", 0, "should be ticks before -base"); + assert.operator(afterPivot.length, ">", 0, "should be ticks after base"); + assert.operator(betweenPivots.length, ">", 0, "should be ticks between -base and base"); + }); + + it("ticks() is always non-empty", () => { + var desiredExtents: number[][] = []; + scale.addExtentsProvider((scale: Plottable.Scale) => desiredExtents); + [[2, 9], [0, 1], [1, 2], [0.001, 0.01], [-0.1, 0.1], [-3, -2]].forEach((extent) => { + desiredExtents = [extent]; + scale.autoDomain(); + var ticks = scale.ticks(); + assert.operator(ticks.length, ">", 0); + }); + }); + }); +}); diff --git a/test/scales/scaleTests.ts b/test/scales/scaleTests.ts index d3e37999be..b23fb382ca 100644 --- a/test/scales/scaleTests.ts +++ b/test/scales/scaleTests.ts @@ -3,54 +3,49 @@ var assert = chai.assert; describe("Scales", () => { - it("Scale's copy() works correctly", () => { - var testCallback = (listenable: any) => { - return true; // doesn't do anything - }; - var scale = new Plottable.Scale.Linear(); - scale.broadcaster.registerListener(null, testCallback); - var scaleCopy = scale.copy(); - assert.deepEqual(scale.domain(), scaleCopy.domain(), "Copied scale has the same domain as the original."); - assert.deepEqual(scale.range(), scaleCopy.range(), "Copied scale has the same range as the original."); - assert.notDeepEqual(scale.broadcaster, scaleCopy.broadcaster, - "Broadcasters are not copied over"); - }); - it("Scale alerts listeners when its domain is updated", () => { - var scale = new Plottable.Scale.Linear(); + var scale = new Plottable.Scale(d3.scale.identity()); + var callbackWasCalled = false; - var testCallback = (listenable: Plottable.Scale.Linear) => { - assert.equal(listenable, scale, "Callback received the calling scale as the first argument"); + var testCallback = (listenable: Plottable.Scale) => { + assert.strictEqual(listenable, scale, "Callback received the calling scale as the first argument"); callbackWasCalled = true; }; - scale.broadcaster.registerListener(null, testCallback); + scale.onUpdate(testCallback); scale.domain([0, 10]); assert.isTrue(callbackWasCalled, "The registered callback was called"); + }); + it("Scale update listeners can be turned off", () => { + var scale = new Plottable.Scale(d3.scale.identity()); - ( scale)._autoDomainAutomatically = true; - scale._updateExtent("1", "x", [0.08, 9.92]); - callbackWasCalled = false; - scale.domainer(new Plottable.Domainer().nice()); - assert.isTrue(callbackWasCalled, "The registered callback was called when nice() is used to set the domain"); + var callbackWasCalled = false; + var testCallback = (listenable: Plottable.Scale) => { + assert.strictEqual(listenable, scale, "Callback received the calling scale as the first argument"); + callbackWasCalled = true; + }; + scale.onUpdate(testCallback); + scale.domain([0, 10]); + assert.isTrue(callbackWasCalled, "The registered callback was called"); callbackWasCalled = false; - scale.domainer(new Plottable.Domainer().pad()); - assert.isTrue(callbackWasCalled, "The registered callback was called when padDomain() is used to set the domain"); + scale.offUpdate(testCallback); + scale.domain([11, 19]); + assert.isFalse(callbackWasCalled, "The registered callback was not called because the callback was removed"); }); describe("autoranging behavior", () => { var data: any[]; var dataset: Plottable.Dataset; - var scale: Plottable.Scale.Linear; + var scale: Plottable.Scales.Linear; beforeEach(() => { data = [{foo: 2, bar: 1}, {foo: 5, bar: -20}, {foo: 0, bar: 0}]; dataset = new Plottable.Dataset(data); - scale = new Plottable.Scale.Linear(); + scale = new Plottable.Scales.Linear(); }); it("scale autoDomain flag is not overwritten without explicitly setting the domain", () => { - scale._updateExtent("1", "x", d3.extent(data, (e) => e.foo)); + scale.addExtentsProvider((scale: Plottable.Scale) => [d3.extent(data, (e) => e.foo)]); scale.domainer(new Plottable.Domainer().pad().nice()); assert.isTrue(( scale)._autoDomainAutomatically, "the autoDomain flag is still set after autoranginging and padding and nice-ing"); @@ -59,11 +54,11 @@ describe("Scales", () => { }); it("scale autorange works as expected with single dataset", () => { - var svg = generateSVG(100, 100); - var renderer = new Plottable.Plot.AbstractPlot() - .addDataset(dataset) - .project("x", "foo", scale) - .renderTo(svg); + var svg = TestMethods.generateSVG(100, 100); + new Plottable.Plot() + .addDataset(dataset) + .attr("x", (d) => d.foo, scale) + .renderTo(svg); assert.deepEqual(scale.domain(), [0, 5], "scale domain was autoranged properly"); data.push({foo: 100, bar: 200}); dataset.data(data); @@ -72,21 +67,21 @@ describe("Scales", () => { }); it("scale reference counting works as expected", () => { - var svg1 = generateSVG(100, 100); - var svg2 = generateSVG(100, 100); - var renderer1 = new Plottable.Plot.AbstractPlot() + var svg1 = TestMethods.generateSVG(100, 100); + var svg2 = TestMethods.generateSVG(100, 100); + var renderer1 = new Plottable.Plot() .addDataset(dataset) - .project("x", "foo", scale); + .attr("x", (d) => d.foo, scale); renderer1.renderTo(svg1); - var renderer2 = new Plottable.Plot.AbstractPlot() + var renderer2 = new Plottable.Plot() .addDataset(dataset) - .project("x", "foo", scale); + .attr("x", (d) => d.foo, scale); renderer2.renderTo(svg2); - var otherScale = new Plottable.Scale.Linear(); - renderer1.project("x", "foo", otherScale); + var otherScale = new Plottable.Scales.Linear(); + renderer1.attr("x", (d) => d.foo, otherScale); dataset.data([{foo: 10}, {foo: 11}]); assert.deepEqual(scale.domain(), [10, 11], "scale was still listening to dataset after one perspective deregistered"); - renderer2.project("x", "foo", otherScale); + renderer2.attr("x", (d) => d.foo, otherScale); // "scale not listening to the dataset after all perspectives removed" dataset.data([{foo: 99}, {foo: 100}]); assert.deepEqual(scale.domain(), [0, 1], "scale shows default values when all perspectives removed"); @@ -94,46 +89,50 @@ describe("Scales", () => { svg2.remove(); }); - it("scale perspectives can be removed appropriately", () => { - assert.isTrue(( scale)._autoDomainAutomatically, "autoDomain enabled1"); - scale._updateExtent("1", "x", d3.extent(data, (e) => e.foo)); - scale._updateExtent("2", "x", d3.extent(data, (e) => e.bar)); - assert.isTrue(( scale)._autoDomainAutomatically, "autoDomain enabled2"); - assert.deepEqual(scale.domain(), [-20, 5], "scale domain includes both perspectives"); - assert.isTrue(( scale)._autoDomainAutomatically, "autoDomain enabled3"); - scale._removeExtent("1", "x"); - assert.isTrue(( scale)._autoDomainAutomatically, "autoDomain enabled4"); - assert.closeTo(scale.domain()[0], -20, 0.1, "only the bar accessor is active"); - assert.closeTo(scale.domain()[1], 1, 0.1, "only the bar accessor is active"); - scale._updateExtent("2", "x", d3.extent(data, (e) => e.foo)); - assert.isTrue(( scale)._autoDomainAutomatically, "autoDomain enabled5"); - assert.closeTo(scale.domain()[0], 0, 0.1, "the bar accessor was overwritten"); - assert.closeTo(scale.domain()[1], 5, 0.1, "the bar accessor was overwritten"); + it("addExtentsProvider()", () => { + scale.addExtentsProvider((scale: Plottable.Scale) => [[0, 10]]); + scale.autoDomain(); + assert.deepEqual(scale.domain(), [0, 10], "scale domain accounts for first provider"); + + scale.addExtentsProvider((scale: Plottable.Scale) => [[-10, 0]]); + scale.autoDomain(); + assert.deepEqual(scale.domain(), [-10, 10], "scale domain accounts for second provider"); + }); + + it("removeExtentsProvider()", () => { + var posProvider = (scale: Plottable.Scale) => [[0, 10]]; + scale.addExtentsProvider(posProvider); + var negProvider = (scale: Plottable.Scale) => [[-10, 0]]; + scale.addExtentsProvider(negProvider); + scale.autoDomain(); + assert.deepEqual(scale.domain(), [-10, 10], "scale domain accounts for both providers"); + + scale.removeExtentsProvider(negProvider); + scale.autoDomain(); + assert.deepEqual(scale.domain(), [0, 10], "scale domain only accounts for remaining provider"); }); it("should resize when a plot is removed", () => { - var svg = generateSVG(400, 400); - var ds1 = [{x: 0, y: 0}, {x: 1, y: 1}]; - var ds2 = [{x: 1, y: 1}, {x: 2, y: 2}]; - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); + var svg = TestMethods.generateSVG(400, 400); + var ds1 = new Plottable.Dataset([{x: 0, y: 0}, {x: 1, y: 1}]); + var ds2 = new Plottable.Dataset([{x: 1, y: 1}, {x: 2, y: 2}]); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); xScale.domainer(new Plottable.Domainer()); - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - var renderAreaD1 = new Plottable.Plot.Line(xScale, yScale); + var renderAreaD1 = new Plottable.Plots.Line(xScale, yScale); renderAreaD1.addDataset(ds1); - renderAreaD1.project("x", "x", xScale); - renderAreaD1.project("y", "y", yScale); - var renderAreaD2 = new Plottable.Plot.Line(xScale, yScale); + renderAreaD1.x((d) => d.x, xScale); + renderAreaD1.y((d) => d.y, yScale); + var renderAreaD2 = new Plottable.Plots.Line(xScale, yScale); renderAreaD2.addDataset(ds2); - renderAreaD2.project("x", "x", xScale); - renderAreaD2.project("y", "y", yScale); - var renderAreas = renderAreaD1.below(renderAreaD2); + renderAreaD2.x((d) => d.x, xScale); + renderAreaD2.y((d) => d.y, yScale); + var renderAreas = new Plottable.Components.Group([renderAreaD1, renderAreaD2]); renderAreas.renderTo(svg); assert.deepEqual(xScale.domain(), [0, 2]); renderAreaD1.detach(); assert.deepEqual(xScale.domain(), [1, 2], "resize on plot.detach()"); - renderAreas.below(renderAreaD1); + renderAreas.append(renderAreaD1); assert.deepEqual(xScale.domain(), [0, 2], "resize on plot.merge()"); svg.remove(); }); @@ -141,31 +140,15 @@ describe("Scales", () => { describe("Quantitative Scales", () => { it("autorange defaults to [0, 1] if no perspectives set", () => { - var scale = new Plottable.Scale.Linear(); + var scale = new Plottable.Scales.Linear(); scale.autoDomain(); var d = scale.domain(); - assert.equal(d[0], 0); - assert.equal(d[1], 1); - }); - - it("can change the number of ticks generated", () => { - var scale = new Plottable.Scale.Linear(); - var ticks10 = scale.ticks(); - assert.closeTo(ticks10.length, 10, 1, "defaults to (about) 10 ticks"); - - scale.numTicks(20); - var ticks20 = scale.ticks(); - assert.closeTo(ticks20.length, 20, 1, "can request a different number of ticks"); - }); - - it("autorange defaults to [1, 10] on log scale", () => { - var scale = new Plottable.Scale.Log(); - scale.autoDomain(); - assert.deepEqual(scale.domain(), [1, 10]); + assert.strictEqual(d[0], 0); + assert.strictEqual(d[1], 1); }); it("domain can't include NaN or Infinity", () => { - var scale = new Plottable.Scale.Linear(); + var scale = new Plottable.Scales.Linear(); scale.domain([0, 1]); scale.domain([5, Infinity]); assert.deepEqual(scale.domain(), [0, 1], "Infinity containing domain was ignored"); @@ -177,24 +160,8 @@ describe("Scales", () => { assert.deepEqual(scale.domain(), [-1, 5], "Regular domains still accepted"); }); - it("autoranges appropriately even if stringy numbers are projected", () => { - var sadTimesData = ["999", "10", "100", "1000", "2", "999"]; - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - var plot = new Plottable.Plot.Scatter(xScale, yScale); - plot.addDataset(sadTimesData); - var id = (d: any) => d; - xScale.domainer(new Plottable.Domainer()); // to disable padding, etc - plot.project("x", id, xScale); - plot.project("y", id, yScale); - var svg = generateSVG(); - plot.renderTo(svg); - assert.deepEqual(xScale.domain(), [2, 1000], "the domain was calculated appropriately"); - svg.remove(); - }); - it("custom tick generator", () => { - var scale = new Plottable.Scale.Linear(); + var scale = new Plottable.Scales.Linear(); scale.domain([0, 10]); var ticks = scale.ticks(); assert.closeTo(ticks.length, 10, 1, "ticks were generated correctly with default generator"); @@ -202,13 +169,12 @@ describe("Scales", () => { ticks = scale.ticks(); assert.deepEqual(ticks, [0, 3, 6, 9], "ticks were generated correctly with custom generator"); }); - }); describe("Category Scales", () => { it("rangeBand is updated when domain changes", () => { - var scale = new Plottable.Scale.Category(); + var scale = new Plottable.Scales.Category(); scale.range([0, 2679]); scale.domain(["1", "2", "3", "4"]); @@ -219,7 +185,7 @@ describe("Scales", () => { }); it("stepWidth operates normally", () => { - var scale = new Plottable.Scale.Category(); + var scale = new Plottable.Scales.Category(); scale.range([0, 3000]); scale.domain(["1", "2", "3", "4"]); @@ -230,16 +196,17 @@ describe("Scales", () => { it("CategoryScale + BarPlot combo works as expected when the data is swapped", () => { // This unit test taken from SLATE, see SLATE-163 a fix for SLATE-102 - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Linear(); + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Linear(); var dA = {x: "A", y: 2}; var dB = {x: "B", y: 2}; var dC = {x: "C", y: 2}; var dataset = new Plottable.Dataset([dA, dB]); - var barPlot = new Plottable.Plot.Bar(xScale, yScale).addDataset(dataset); - barPlot.project("x", "x", xScale); - barPlot.project("y", "y", yScale); - var svg = generateSVG(); + var barPlot = new Plottable.Plots.Bar(xScale, yScale); + barPlot.addDataset(dataset); + barPlot.x((d) => d.x, xScale); + barPlot.y((d) => d.y, yScale); + var svg = TestMethods.generateSVG(); assert.deepEqual(xScale.domain(), [], "before anchoring, the bar plot doesn't proxy data to the scale"); barPlot.renderTo(svg); assert.deepEqual(xScale.domain(), ["A", "B"], "after anchoring, the bar plot's data is on the scale"); @@ -262,15 +229,15 @@ describe("Scales", () => { describe("Color Scales", () => { it("accepts categorical string types and Category domain", () => { - var scale = new Plottable.Scale.Color("10"); + var scale = new Plottable.Scales.Color("10"); scale.domain(["yes", "no", "maybe"]); - assert.equal("#1f77b4", scale.scale("yes")); - assert.equal("#ff7f0e", scale.scale("no")); - assert.equal("#2ca02c", scale.scale("maybe")); + assert.strictEqual("#1f77b4", scale.scale("yes")); + assert.strictEqual("#ff7f0e", scale.scale("no")); + assert.strictEqual("#2ca02c", scale.scale("maybe")); }); it("default colors are generated", () => { - var scale = new Plottable.Scale.Color(); + var scale = new Plottable.Scales.Color(); var colorArray = ["#5279c7", "#fd373e", "#63c261", "#fad419", "#2c2b6f", "#ff7939", "#db2e65", "#99ce50", "#962565", "#06cccc"]; @@ -278,29 +245,29 @@ describe("Scales", () => { }); it("uses altered colors if size of domain exceeds size of range", () => { - var scale = new Plottable.Scale.Color(); + var scale = new Plottable.Scales.Color(); scale.range(["#5279c7", "#fd373e"]); scale.domain(["a", "b", "c"]); assert.notEqual(scale.scale("c"), "#5279c7"); }); it("interprets named color values correctly", () => { - var scale = new Plottable.Scale.Color(); + var scale = new Plottable.Scales.Color(); scale.range(["red", "blue"]); scale.domain(["a", "b"]); - assert.equal(scale.scale("a"), "#ff0000"); - assert.equal(scale.scale("b"), "#0000ff"); + assert.strictEqual(scale.scale("a"), "#ff0000"); + assert.strictEqual(scale.scale("b"), "#0000ff"); }); it("accepts CSS specified colors", () => { var style = d3.select("body").append("style"); style.html(".plottable-colors-0 {background-color: #ff0000 !important; }"); - var scale = new Plottable.Scale.Color(); + var scale = new Plottable.Scales.Color(); style.remove(); assert.strictEqual(scale.range()[0], "#ff0000", "User has specified red color for first color scale color"); assert.strictEqual(scale.range()[1], "#fd373e", "The second color of the color scale should be the same"); - var defaultScale = new Plottable.Scale.Color(); + var defaultScale = new Plottable.Scales.Color(); assert.strictEqual(scale.range()[0], "#ff0000", "Unloading the CSS should not modify the first scale color (this will not be the case if we support dynamic CSS"); assert.strictEqual(defaultScale.range()[0], "#5279c7", @@ -310,13 +277,13 @@ describe("Scales", () => { it("should try to recover from malicious CSS styleseets", () => { var defaultNumberOfColors = 10; - var initialScale = new Plottable.Scale.Color(); + var initialScale = new Plottable.Scales.Color(); assert.strictEqual(initialScale.range().length, defaultNumberOfColors, "there should initially be " + defaultNumberOfColors + " default colors"); var maliciousStyle = d3.select("body").append("style"); maliciousStyle.html("* {background-color: #fff000;}"); - var affectedScale = new Plottable.Scale.Color(); + var affectedScale = new Plottable.Scales.Color(); maliciousStyle.remove(); var colorRange = affectedScale.range(); assert.strictEqual(colorRange.length, defaultNumberOfColors + 1, @@ -331,14 +298,14 @@ describe("Scales", () => { }); it("does not crash by malicious CSS stylesheets", () => { - var initialScale = new Plottable.Scale.Color(); + var initialScale = new Plottable.Scales.Color(); assert.strictEqual(initialScale.range().length, 10, "there should initially be 10 default colors"); var maliciousStyle = d3.select("body").append("style"); maliciousStyle.html("[class^='plottable-'] {background-color: pink;}"); - var affectedScale = new Plottable.Scale.Color(); + var affectedScale = new Plottable.Scales.Color(); maliciousStyle.remove(); - var maximumColorsFromCss = ( Plottable.Scale.Color).MAXIMUM_COLORS_FROM_CSS; + var maximumColorsFromCss = ( Plottable.Scales.Color).MAXIMUM_COLORS_FROM_CSS; assert.strictEqual(affectedScale.range().length, maximumColorsFromCss, "current malicious CSS countermeasure is to cap maximum number of colors to 256"); }); @@ -347,164 +314,52 @@ describe("Scales", () => { describe("Interpolated Color Scales", () => { it("default scale uses reds and a linear scale type", () => { - var scale = new Plottable.Scale.InterpolatedColor(); + var scale = new Plottable.Scales.InterpolatedColor(); scale.domain([0, 16]); - assert.equal("#ffffff", scale.scale(0)); - assert.equal("#feb24c", scale.scale(8)); - assert.equal("#b10026", scale.scale(16)); + assert.strictEqual("#ffffff", scale.scale(0)); + assert.strictEqual("#feb24c", scale.scale(8)); + assert.strictEqual("#b10026", scale.scale(16)); }); it("linearly interpolates colors in L*a*b color space", () => { - var scale = new Plottable.Scale.InterpolatedColor("reds"); + var scale = new Plottable.Scales.InterpolatedColor(); scale.domain([0, 1]); - assert.equal("#b10026", scale.scale(1)); - assert.equal("#d9151f", scale.scale(0.9)); + assert.strictEqual("#b10026", scale.scale(1)); + assert.strictEqual("#d9151f", scale.scale(0.9)); }); it("accepts array types with color hex values", () => { - var scale = new Plottable.Scale.InterpolatedColor(["#000", "#FFF"]); + var scale = new Plottable.Scales.InterpolatedColor(["#000", "#FFF"]); scale.domain([0, 16]); - assert.equal("#000000", scale.scale(0)); - assert.equal("#ffffff", scale.scale(16)); - assert.equal("#777777", scale.scale(8)); + assert.strictEqual("#000000", scale.scale(0)); + assert.strictEqual("#ffffff", scale.scale(16)); + assert.strictEqual("#777777", scale.scale(8)); }); it("accepts array types with color names", () => { - var scale = new Plottable.Scale.InterpolatedColor(["black", "white"]); + var scale = new Plottable.Scales.InterpolatedColor(["black", "white"]); scale.domain([0, 16]); - assert.equal("#000000", scale.scale(0)); - assert.equal("#ffffff", scale.scale(16)); - assert.equal("#777777", scale.scale(8)); + assert.strictEqual("#000000", scale.scale(0)); + assert.strictEqual("#ffffff", scale.scale(16)); + assert.strictEqual("#777777", scale.scale(8)); }); it("overflow scale values clamp to range", () => { - var scale = new Plottable.Scale.InterpolatedColor(["black", "white"]); + var scale = new Plottable.Scales.InterpolatedColor(["black", "white"]); scale.domain([0, 16]); - assert.equal("#000000", scale.scale(0)); - assert.equal("#ffffff", scale.scale(16)); - assert.equal("#000000", scale.scale(-100)); - assert.equal("#ffffff", scale.scale(100)); + assert.strictEqual("#000000", scale.scale(0)); + assert.strictEqual("#ffffff", scale.scale(16)); + assert.strictEqual("#000000", scale.scale(-100)); + assert.strictEqual("#ffffff", scale.scale(100)); }); it("can be converted to a different range", () => { - var scale = new Plottable.Scale.InterpolatedColor(["black", "white"]); - scale.domain([0, 16]); - assert.equal("#000000", scale.scale(0)); - assert.equal("#ffffff", scale.scale(16)); - scale.colorRange("reds"); - assert.equal("#b10026", scale.scale(16)); - }); - - it("can be converted to a different scale type", () => { - var scale = new Plottable.Scale.InterpolatedColor(["black", "white"]); + var scale = new Plottable.Scales.InterpolatedColor(["black", "white"]); scale.domain([0, 16]); - assert.equal("#000000", scale.scale(0)); - assert.equal("#ffffff", scale.scale(16)); - assert.equal("#777777", scale.scale(8)); - - scale.scaleType("log"); - assert.equal("#000000", scale.scale(0)); - assert.equal("#ffffff", scale.scale(16)); - assert.equal("#e3e3e3", scale.scale(8)); + assert.strictEqual("#000000", scale.scale(0)); + assert.strictEqual("#ffffff", scale.scale(16)); + scale.colorRange(Plottable.Scales.InterpolatedColor.REDS); + assert.strictEqual("#b10026", scale.scale(16)); }); }); - describe("Modified Log Scale", () => { - var scale: Plottable.Scale.ModifiedLog; - var base = 10; - var epsilon = 0.00001; - beforeEach(() => { - scale = new Plottable.Scale.ModifiedLog(base); - }); - - it("is an increasing, continuous function that can go negative", () => { - d3.range(-base * 2, base * 2, base / 20).forEach((x: number) => { - // increasing - assert.operator(scale.scale(x - epsilon), "<", scale.scale(x)); - assert.operator(scale.scale(x), "<", scale.scale(x + epsilon)); - // continuous - assert.closeTo(scale.scale(x - epsilon), scale.scale(x), epsilon); - assert.closeTo(scale.scale(x), scale.scale(x + epsilon), epsilon); - }); - assert.closeTo(scale.scale(0), 0, epsilon); - }); - - it("is close to log() for large values", () => { - [10, 100, 23103.4, 5].forEach((x) => { - assert.closeTo(scale.scale(x), Math.log(x) / Math.log(10), 0.1); - }); - }); - - it("x = invert(scale(x))", () => { - [0, 1, base, 100, 0.001, -1, -0.3, -base, base - 0.001].forEach((x) => { - assert.closeTo(x, scale.invert(scale.scale(x)), epsilon); - assert.closeTo(x, scale.scale(scale.invert(x)), epsilon); - }); - }); - - it("domain defaults to [0, 1]", () => { - scale = new Plottable.Scale.ModifiedLog(base); - assert.deepEqual(scale.domain(), [0, 1]); - }); - - it("works with a domainer", () => { - scale._updateExtent("1", "x", [0, base * 2]); - var domain = scale.domain(); - scale.domainer(new Plottable.Domainer().pad(0.1)); - assert.operator(scale.domain()[0], "<", domain[0]); - assert.operator(domain[1], "<", scale.domain()[1]); - - scale.domainer(new Plottable.Domainer().nice()); - assert.operator(scale.domain()[0], "<=", domain[0]); - assert.operator(domain[1], "<=", scale.domain()[1]); - - scale = new Plottable.Scale.ModifiedLog(base); - scale.domainer(new Plottable.Domainer()); - assert.deepEqual(scale.domain(), [0, 1]); - }); - - it("gives reasonable values for ticks()", () => { - scale._updateExtent("1", "x", [0, base / 2]); - var ticks = scale.ticks(); - assert.operator(ticks.length, ">", 0); - - scale._updateExtent("1", "x", [-base * 2, base * 2]); - ticks = scale.ticks(); - var beforePivot = ticks.filter((x) => x <= -base); - var afterPivot = ticks.filter((x) => base <= x); - var betweenPivots = ticks.filter((x) => -base < x && x < base); - assert.operator(beforePivot.length, ">", 0, "should be ticks before -base"); - assert.operator(afterPivot.length, ">", 0, "should be ticks after base"); - assert.operator(betweenPivots.length, ">", 0, "should be ticks between -base and base"); - }); - - it("works on inverted domain", () => { - scale._updateExtent("1", "x", [200, -100]); - var range = scale.range(); - assert.closeTo(scale.scale(-100), range[1], epsilon); - assert.closeTo(scale.scale(200), range[0], epsilon); - var a = [-100, -10, -3, 0, 1, 3.64, 50, 60, 200]; - var b = a.map((x) => scale.scale(x)); - // should be decreasing function; reverse is sorted - assert.deepEqual(b.slice().reverse(), b.slice().sort((x, y) => x - y)); - - var ticks = scale.ticks(); - assert.deepEqual(ticks, ticks.slice().sort((x, y) => x - y), "ticks should be sorted"); - assert.deepEqual(ticks, Plottable._Util.Methods.uniq(ticks), "ticks should not be repeated"); - var beforePivot = ticks.filter((x) => x <= -base); - var afterPivot = ticks.filter((x) => base <= x); - var betweenPivots = ticks.filter((x) => -base < x && x < base); - assert.operator(beforePivot.length, ">", 0, "should be ticks before -base"); - assert.operator(afterPivot.length, ">", 0, "should be ticks after base"); - assert.operator(betweenPivots.length, ">", 0, "should be ticks between -base and base"); - }); - - it("ticks() is always non-empty", () => { - [[2, 9], [0, 1], [1, 2], [0.001, 0.01], [-0.1, 0.1], [-3, -2]].forEach((domain) => { - scale._updateExtent("1", "x", domain); - var ticks = scale.ticks(); - assert.operator(ticks.length, ">", 0); - }); - }); - }); - }); diff --git a/test/scales/tickGeneratorsTests.ts b/test/scales/tickGeneratorsTests.ts index 240045b868..399bb900d2 100644 --- a/test/scales/tickGeneratorsTests.ts +++ b/test/scales/tickGeneratorsTests.ts @@ -6,61 +6,60 @@ describe("Tick generators", () => { describe("interval", () => { it("generate ticks within domain", () => { var start = 0.5, end = 4.01, interval = 1; - var scale = new Plottable.Scale.Linear().domain([start, end]); - var ticks = Plottable.Scale.TickGenerators.intervalTickGenerator(interval)(scale); + var scale = new Plottable.Scales.Linear().domain([start, end]); + var ticks = Plottable.Scales.TickGenerators.intervalTickGenerator(interval)(scale); assert.deepEqual(ticks, [0.5, 1, 2, 3, 4, 4.01], "generated ticks contains all possible ticks within range"); }); it("domain crossing 0", () => { var start = -1.5, end = 1, interval = 0.5; - var scale = new Plottable.Scale.Linear().domain([start, end]); - var ticks = Plottable.Scale.TickGenerators.intervalTickGenerator(interval)(scale); + var scale = new Plottable.Scales.Linear().domain([start, end]); + var ticks = Plottable.Scales.TickGenerators.intervalTickGenerator(interval)(scale); assert.deepEqual(ticks, [-1.5, -1, -0.5, 0, 0.5, 1], "generated all number divisible by 0.5 in domain"); }); it("generate ticks with reversed domain", () => { var start = -2.2, end = -7.6, interval = 2.5; - var scale = new Plottable.Scale.Linear().domain([start, end]); - var ticks = Plottable.Scale.TickGenerators.intervalTickGenerator(interval)(scale); + var scale = new Plottable.Scales.Linear().domain([start, end]); + var ticks = Plottable.Scales.TickGenerators.intervalTickGenerator(interval)(scale); assert.deepEqual(ticks, [-7.6, -7.5, -5, -2.5, -2.2], "generated all ticks between lower and higher value"); }); it("passing big interval", () => { var start = 0.5, end = 10.01, interval = 11; - var scale = new Plottable.Scale.Linear().domain([start, end]); - var ticks = Plottable.Scale.TickGenerators.intervalTickGenerator(interval)(scale); + var scale = new Plottable.Scales.Linear().domain([start, end]); + var ticks = Plottable.Scales.TickGenerators.intervalTickGenerator(interval)(scale); assert.deepEqual(ticks, [0.5, 10.01], "no middle ticks were added"); }); it("passing non positive interval", () => { - var scale = new Plottable.Scale.Linear().domain([0, 1]); - assert.throws(() => Plottable.Scale.TickGenerators.intervalTickGenerator(0), "interval must be positive number"); - assert.throws(() => Plottable.Scale.TickGenerators.intervalTickGenerator(-2), "interval must be positive number"); + assert.throws(() => Plottable.Scales.TickGenerators.intervalTickGenerator(0), "interval must be positive number"); + assert.throws(() => Plottable.Scales.TickGenerators.intervalTickGenerator(-2), "interval must be positive number"); }); }); describe("integer", () => { it("normal case", () => { - var scale = new Plottable.Scale.Linear().domain([0, 4]); - var ticks = Plottable.Scale.TickGenerators.integerTickGenerator()(scale); + var scale = new Plottable.Scales.Linear().domain([0, 4]); + var ticks = Plottable.Scales.TickGenerators.integerTickGenerator()(scale); assert.deepEqual(ticks, [0, 1, 2, 3, 4], "only the integers are returned"); }); it("works across negative numbers", () => { - var scale = new Plottable.Scale.Linear().domain([-2, 1]); - var ticks = Plottable.Scale.TickGenerators.integerTickGenerator()(scale); + var scale = new Plottable.Scales.Linear().domain([-2, 1]); + var ticks = Plottable.Scales.TickGenerators.integerTickGenerator()(scale); assert.deepEqual(ticks, [-2, -1, 0, 1], "only the integers are returned"); }); it("includes endticks", () => { - var scale = new Plottable.Scale.Linear().domain([-2.7, 1.5]); - var ticks = Plottable.Scale.TickGenerators.integerTickGenerator()(scale); + var scale = new Plottable.Scales.Linear().domain([-2.7, 1.5]); + var ticks = Plottable.Scales.TickGenerators.integerTickGenerator()(scale); assert.deepEqual(ticks, [-2.5, -2, -1, 0, 1, 1.5], "end ticks are included"); }); it("all float ticks", () => { - var scale = new Plottable.Scale.Linear().domain([1.1, 1.5]); - var ticks = Plottable.Scale.TickGenerators.integerTickGenerator()(scale); + var scale = new Plottable.Scales.Linear().domain([1.1, 1.5]); + var ticks = Plottable.Scales.TickGenerators.integerTickGenerator()(scale); assert.deepEqual(ticks, [1.1, 1.5], "only the end ticks are returned"); }); }); diff --git a/test/scales/timeScaleTests.ts b/test/scales/timeScaleTests.ts index 05300992d4..3743dac0d7 100644 --- a/test/scales/timeScaleTests.ts +++ b/test/scales/timeScaleTests.ts @@ -3,67 +3,40 @@ var assert = chai.assert; describe("TimeScale tests", () => { - it("parses reasonable formats for dates", () => { - var scale = new Plottable.Scale.Time(); - var firstDate = new Date(2014, 9, 1, 0, 0, 0, 0).valueOf(); - var secondDate = new Date(2014, 10, 1, 0, 0, 0).valueOf(); - - function checkDomain(domain: any[]) { - scale.domain(domain); - var time1 = scale.domain()[0].valueOf(); - assert.equal(time1, firstDate, "first value of domain set correctly"); - var time2 = scale.domain()[1].valueOf(); - assert.equal(time2, secondDate, "first value of domain set correctly"); - } - checkDomain(["10/1/2014", "11/1/2014"]); - checkDomain(["October 1, 2014", "November 1, 2014"]); - checkDomain(["Oct 1, 2014", "Nov 1, 2014"]); - }); - it("can't set reversed domain", () => { - var scale = new Plottable.Scale.Time(); - assert.throws(() => scale.domain(["1985-10-26", "1955-11-05"]), "chronological"); - }); - - it("time coercer works as intended", () => { - var tc = new Plottable.Scale.Time()._typeCoercer; - assert.equal(tc(null).getMilliseconds(), 0, "null converted to Date(0)"); - // converting null to Date(0) is the correct behavior as it mirror's d3's semantics - assert.equal(tc("Wed Dec 31 1969 16:00:00 GMT-0800 (PST)").getMilliseconds(), 0, "string parsed to date"); - assert.equal(tc(0).getMilliseconds(), 0, "number parsed to date"); - var d = new Date(0); - assert.equal(tc(d), d, "date passed thru unchanged"); + var scale = new Plottable.Scales.Time(); + assert.throws(() => scale.domain([new Date("1985-10-26"), new Date("1955-11-05")]), "chronological"); }); it("tickInterval produces correct number of ticks", () => { - var scale = new Plottable.Scale.Time(); + var scale = new Plottable.Scales.Time(); // 100 year span scale.domain([new Date(2000, 0, 1, 0, 0, 0, 0), new Date(2100, 0, 1, 0, 0, 0, 0)]); - var ticks = scale.tickInterval(d3.time.year); - assert.equal(ticks.length, 101, "generated correct number of ticks"); + var ticks = scale.tickInterval(Plottable.TimeInterval.year); + assert.strictEqual(ticks.length, 101, "generated correct number of ticks"); // 1 year span scale.domain([new Date(2000, 0, 1, 0, 0, 0, 0), new Date(2000, 11, 31, 0, 0, 0, 0)]); - ticks = scale.tickInterval(d3.time.month); - assert.equal(ticks.length, 12, "generated correct number of ticks"); - ticks = scale.tickInterval(d3.time.month, 3); - assert.equal(ticks.length, 4, "generated correct number of ticks"); + ticks = scale.tickInterval(Plottable.TimeInterval.month); + assert.strictEqual(ticks.length, 12, "generated correct number of ticks"); + ticks = scale.tickInterval(Plottable.TimeInterval.month, 3); + assert.strictEqual(ticks.length, 4, "generated correct number of ticks"); // 1 month span scale.domain([new Date(2000, 0, 1, 0, 0, 0, 0), new Date(2000, 1, 1, 0, 0, 0, 0)]); - ticks = scale.tickInterval(d3.time.day); - assert.equal(ticks.length, 32, "generated correct number of ticks"); + ticks = scale.tickInterval(Plottable.TimeInterval.day); + assert.strictEqual(ticks.length, 32, "generated correct number of ticks"); // 1 day span scale.domain([new Date(2000, 0, 1, 0, 0, 0, 0), new Date(2000, 0, 1, 23, 0, 0, 0)]); - ticks = scale.tickInterval(d3.time.hour); - assert.equal(ticks.length, 24, "generated correct number of ticks"); + ticks = scale.tickInterval(Plottable.TimeInterval.hour); + assert.strictEqual(ticks.length, 24, "generated correct number of ticks"); // 1 hour span scale.domain([new Date(2000, 0, 1, 0, 0, 0, 0), new Date(2000, 0, 1, 1, 0, 0, 0)]); - ticks = scale.tickInterval(d3.time.minute); - assert.equal(ticks.length, 61, "generated correct number of ticks"); - ticks = scale.tickInterval(d3.time.minute, 10); - assert.equal(ticks.length, 7, "generated correct number of ticks"); + ticks = scale.tickInterval(Plottable.TimeInterval.minute); + assert.strictEqual(ticks.length, 61, "generated correct number of ticks"); + ticks = scale.tickInterval(Plottable.TimeInterval.minute, 10); + assert.strictEqual(ticks.length, 7, "generated correct number of ticks"); // 1 minute span scale.domain([new Date(2000, 0, 1, 0, 0, 0, 0), new Date(2000, 0, 1, 0, 1, 0, 0)]); - ticks = scale.tickInterval(d3.time.second); - assert.equal(ticks.length, 61, "generated correct number of ticks"); + ticks = scale.tickInterval(Plottable.TimeInterval.second); + assert.strictEqual(ticks.length, 61, "generated correct number of ticks"); }); }); diff --git a/test/testMethods.ts b/test/testMethods.ts new file mode 100644 index 0000000000..c52cf3f58d --- /dev/null +++ b/test/testMethods.ts @@ -0,0 +1,227 @@ +/// + +module TestMethods { + + export function generateSVG(width = 400, height = 400): D3.Selection { + var parent: D3.Selection = TestMethods.getSVGParent(); + return parent.append("svg").attr("width", width).attr("height", height).attr("class", "svg"); + } + + export function getSVGParent(): D3.Selection { + var mocha = d3.select("#mocha-report"); + if (mocha.node() != null) { + var suites = mocha.selectAll(".suite"); + var lastSuite = d3.select(suites[0][suites[0].length - 1]); + return lastSuite.selectAll("ul"); + } else { + return d3.select("body"); + } + } + + export function verifySpaceRequest(sr: Plottable.SpaceRequest, expectedMinWidth: number, expectedMinHeight: number, message: string) { + assert.strictEqual(sr.minWidth, expectedMinWidth, message + " (space request: minWidth)"); + assert.strictEqual(sr.minHeight, expectedMinHeight, message + " (space request: minHeight)"); + } + + export function fixComponentSize(c: Plottable.Component, fixedWidth?: number, fixedHeight?: number) { + c.requestedSpace = function(w, h) { + return { + minWidth: fixedWidth == null ? 0 : fixedWidth, + minHeight: fixedHeight == null ? 0 : fixedHeight + }; + }; + ( c).fixedWidth = () => fixedWidth == null ? false : true; + ( c).fixedHeight = () => fixedHeight == null ? false : true; + return c; + } + + export function makeFixedSizeComponent(fixedWidth?: number, fixedHeight?: number) { + return fixComponentSize(new Plottable.Component(), fixedWidth, fixedHeight); + } + + export function getTranslate(element: D3.Selection) { + return d3.transform(element.attr("transform")).translate; + } + + export function assertBBoxEquivalence(bbox: SVGRect, widthAndHeightPair: number[], message: string) { + var width = widthAndHeightPair[0]; + var height = widthAndHeightPair[1]; + assert.strictEqual(bbox.width, width, "width: " + message); + assert.strictEqual(bbox.height, height, "height: " + message); + } + + export function assertBBoxInclusion(outerEl: D3.Selection, innerEl: D3.Selection) { + var outerBox = outerEl.node().getBoundingClientRect(); + var innerBox = innerEl.node().getBoundingClientRect(); + assert.operator(Math.floor(outerBox.left), "<=", Math.ceil(innerBox.left) + window.Pixel_CloseTo_Requirement, + "bounding rect left included"); + assert.operator(Math.floor(outerBox.top), "<=", Math.ceil(innerBox.top) + window.Pixel_CloseTo_Requirement, + "bounding rect top included"); + assert.operator(Math.ceil(outerBox.right) + window.Pixel_CloseTo_Requirement, ">=", Math.floor(innerBox.right), + "bounding rect right included"); + assert.operator(Math.ceil(outerBox.bottom) + window.Pixel_CloseTo_Requirement, ">=", Math.floor(innerBox.bottom), + "bounding rect bottom included"); + } + + export function assertBBoxNonIntersection(firstEl: D3.Selection, secondEl: D3.Selection) { + var firstBox = firstEl.node().getBoundingClientRect(); + var secondBox = secondEl.node().getBoundingClientRect(); + + var intersectionBox = { + left: Math.max(firstBox.left, secondBox.left), + right: Math.min(firstBox.right, secondBox.right), + bottom: Math.min(firstBox.bottom, secondBox.bottom), + top: Math.max(firstBox.top, secondBox.top) + }; + + // +1 for inaccuracy in IE + assert.isTrue(intersectionBox.left + 1 >= intersectionBox.right || intersectionBox.bottom + 1 >= intersectionBox.top, + "bounding rects are not intersecting"); + } + + export function assertPointsClose(actual: Plottable.Point, expected: Plottable.Point, epsilon: number, message: String) { + assert.closeTo(actual.x, expected.x, epsilon, message + " (x)"); + assert.closeTo(actual.y, expected.y, epsilon, message + " (y)"); + }; + + export function assertWidthHeight(el: D3.Selection, widthExpected: number, heightExpected: number, message: string) { + var width = el.attr("width"); + var height = el.attr("height"); + assert.strictEqual(width, String(widthExpected), "width: " + message); + assert.strictEqual(height, String(heightExpected), "height: " + message); + } + + export function makeLinearSeries(n: number): { x: number; y: number }[] { + function makePoint(x: number) { + return { x: x, y: x }; + } + return d3.range(n).map(makePoint); + } + + export function makeQuadraticSeries(n: number): { x: number; y: number }[] { + function makeQuadraticPoint(x: number) { + return { x: x, y: x * x }; + } + return d3.range(n).map(makeQuadraticPoint); + } + + // for IE, whose paths look like "M 0 500 L" instead of "M0,500L" + export function normalizePath(pathString: string) { + return pathString.replace(/ *([A-Z]) */g, "$1").replace(/ /g, ","); + } + + export function numAttr(s: D3.Selection, a: string) { + return parseFloat(s.attr(a)); + } + + export function triggerFakeUIEvent(type: string, target: D3.Selection) { + var e = document.createEvent("UIEvents"); + e.initUIEvent(type, true, true, window, 1); + target.node().dispatchEvent(e); + } + + export function triggerFakeMouseEvent(type: string, target: D3.Selection, relativeX: number, relativeY: number, button = 0) { + var clientRect = target.node().getBoundingClientRect(); + var xPos = clientRect.left + relativeX; + var yPos = clientRect.top + relativeY; + var e = document.createEvent("MouseEvents"); + e.initMouseEvent(type, true, true, window, 1, + xPos, yPos, + xPos, yPos, + false, false, false, false, + button, null); + target.node().dispatchEvent(e); + } + + export function triggerFakeDragSequence(target: D3.Selection, start: Plottable.Point, end: Plottable.Point) { + triggerFakeMouseEvent("mousedown", target, start.x, start.y); + triggerFakeMouseEvent("mousemove", target, end.x, end.y); + triggerFakeMouseEvent("mouseup", target, end.x, end.y); + } + + export function triggerFakeWheelEvent(type: string, target: D3.Selection, relativeX: number, relativeY: number, deltaY: number) { + var clientRect = target.node().getBoundingClientRect(); + var xPos = clientRect.left + relativeX; + var yPos = clientRect.top + relativeY; + var event: WheelEvent; + if (Plottable.Utils.Methods.isIE()) { + event = document.createEvent("WheelEvent"); + event.initWheelEvent("wheel", true, true, window, 1, xPos, yPos, xPos, yPos, 0, null, null, 0, deltaY, 0, 0); + } else { + // HACKHACK anycasting constructor to allow for the dictionary argument + // https://github.com/Microsoft/TypeScript/issues/2416 + event = new ( WheelEvent)("wheel", { bubbles: true, clientX: xPos, clientY: yPos, deltaY: deltaY }); + } + + target.node().dispatchEvent(event); + } + + export function triggerFakeTouchEvent( type: string, target: D3.Selection, touchPoints: Plottable.Point[], ids: number[] = [] ) { + var targetNode = target.node(); + var clientRect = targetNode.getBoundingClientRect(); + var e = document.createEvent( "UIEvent" ); + e.initUIEvent( type, true, true, window, 1 ); + var fakeTouchList: any = []; + + touchPoints.forEach(( touchPoint, i ) => { + var xPos = clientRect.left + touchPoint.x; + var yPos = clientRect.top + touchPoint.y; + var identifier = ids[i] == null ? 0 : ids[i]; + fakeTouchList.push( { + identifier: identifier, + target: targetNode, + screenX: xPos, + screenY: yPos, + clientX: xPos, + clientY: yPos, + pageX: xPos, + pageY: yPos + }); + }); + fakeTouchList.item = ( index: number ) => fakeTouchList[index]; + e.touches = fakeTouchList; + e.targetTouches = fakeTouchList; + e.changedTouches = fakeTouchList; + + e.altKey = false; + e.metaKey = false; + e.ctrlKey = false; + e.shiftKey = false; + target.node().dispatchEvent( e ); + } + + export function assertAreaPathCloseTo(actualPath: string, expectedPath: string, precision: number, msg: string) { + var actualAreaPathStrings = actualPath.split("Z"); + var expectedAreaPathStrings = expectedPath.split("Z"); + + actualAreaPathStrings.pop(); + expectedAreaPathStrings.pop(); + + var actualAreaPathPoints = actualAreaPathStrings.map((path) => path.split(/[A-Z]/).map((point) => point.split(","))); + actualAreaPathPoints.forEach((areaPathPoint) => areaPathPoint.shift()); + var expectedAreaPathPoints = expectedAreaPathStrings.map((path) => path.split(/[A-Z]/).map((point) => point.split(","))); + expectedAreaPathPoints.forEach((areaPathPoint) => areaPathPoint.shift()); + + assert.lengthOf(actualAreaPathPoints, expectedAreaPathPoints.length, "number of broken area paths should be equal"); + actualAreaPathPoints.forEach((actualAreaPoints, i) => { + var expectedAreaPoints = expectedAreaPathPoints[i]; + assert.lengthOf(actualAreaPoints, expectedAreaPoints.length, "number of points in path should be equal"); + actualAreaPoints.forEach((actualAreaPoint, j) => { + var expectedAreaPoint = expectedAreaPoints[j]; + assert.closeTo(+actualAreaPoint[0], +expectedAreaPoint[0], 0.1, msg); + assert.closeTo(+actualAreaPoint[1], +expectedAreaPoint[1], 0.1, msg); + }); + }); + } + + export function verifyClipPath(c: Plottable.Component) { + var clipPathId = (c)._boxContainer[0][0].firstChild.id; + var expectedPrefix = /MSIE [5-9]/.test(navigator.userAgent) ? "" : document.location.href; + expectedPrefix = expectedPrefix.replace(/#.*/g, ""); + var expectedClipPathURL = "url(" + expectedPrefix + "#" + clipPathId + ")"; + // IE 9 has clipPath like 'url("#clipPath")', must accomodate + var normalizeClipPath = (s: string) => s.replace(/"/g, ""); + assert.isTrue(normalizeClipPath(( c)._element.attr("clip-path")) === expectedClipPathURL, + "the element has clip-path url attached"); + } +} diff --git a/test/testReference.ts b/test/testReference.ts index 2ed00a5c96..53bed109cc 100644 --- a/test/testReference.ts +++ b/test/testReference.ts @@ -5,7 +5,7 @@ /// /// /// -/// +/// /// /// @@ -26,8 +26,8 @@ /// /// +/// /// -/// /// /// /// @@ -39,38 +39,40 @@ /// /// -/// /// -/// +/// /// /// /// /// /// -/// /// +/// /// /// /// /// -/// +/// +/// /// /// +/// /// +/// /// -/// /// /// /// +/// /// /// /// /// -/// -/// -/// +/// +/// +/// diff --git a/test/testUtils.ts b/test/testUtils.ts deleted file mode 100644 index 90e5bcab29..0000000000 --- a/test/testUtils.ts +++ /dev/null @@ -1,241 +0,0 @@ -/// - -function generateSVG(width = 400, height = 400): D3.Selection { - var parent: D3.Selection = getSVGParent(); - return parent.append("svg").attr("width", width).attr("height", height).attr("class", "svg"); -} - -function getSVGParent(): D3.Selection { - var mocha = d3.select("#mocha-report"); - if (mocha.node() != null) { - var suites = mocha.selectAll(".suite"); - var lastSuite = d3.select(suites[0][suites[0].length - 1]); - return lastSuite.selectAll("ul"); - } else { - return d3.select("body"); - } -} - -function makeFakeEvent(x: number, y: number): D3.D3Event { - return { - dx: 0, - dy: 0, - clientX: x, - clientY: y, - translate: [x, y], - scale: 1, - sourceEvent: null, - x: x, - y: y, - keyCode: 0, - altKey: false - }; -} - -function verifySpaceRequest(sr: Plottable._SpaceRequest, w: number, h: number, ww: boolean, wh: boolean, message: string) { - assert.equal(sr.width, w, message + " (space request: width)"); - assert.equal(sr.height, h, message + " (space request: height)"); - assert.equal(sr.wantsWidth , ww, message + " (space request: wantsWidth)"); - assert.equal(sr.wantsHeight, wh, message + " (space request: wantsHeight)"); -} - -function fixComponentSize(c: Plottable.Component.AbstractComponent, fixedWidth?: number, fixedHeight?: number) { - c._requestedSpace = function(w, h) { - return { - width: fixedWidth == null ? 0 : fixedWidth, - height: fixedHeight == null ? 0 : fixedHeight, - wantsWidth : fixedWidth == null ? false : w < fixedWidth , - wantsHeight: fixedHeight == null ? false : h < fixedHeight - }; - }; - ( c)._fixedWidthFlag = fixedWidth == null ? false : true; - ( c)._fixedHeightFlag = fixedHeight == null ? false : true; - return c; -} - -function makeFixedSizeComponent(fixedWidth?: number, fixedHeight?: number) { - return fixComponentSize(new Plottable.Component.AbstractComponent(), fixedWidth, fixedHeight); -} - -function getTranslate(element: D3.Selection) { - return d3.transform(element.attr("transform")).translate; -} - -function assertBBoxEquivalence(bbox: SVGRect, widthAndHeightPair: number[], message: string) { - var width = widthAndHeightPair[0]; - var height = widthAndHeightPair[1]; - assert.equal(bbox.width, width, "width: " + message); - assert.equal(bbox.height, height, "height: " + message); -} - -function assertBBoxInclusion(outerEl: D3.Selection, innerEl: D3.Selection) { - var outerBox = outerEl.node().getBoundingClientRect(); - var innerBox = innerEl.node().getBoundingClientRect(); - assert.operator(Math.floor(outerBox.left), "<=", Math.ceil(innerBox.left) + window.Pixel_CloseTo_Requirement, - "bounding rect left included"); - assert.operator(Math.floor(outerBox.top), "<=", Math.ceil(innerBox.top) + window.Pixel_CloseTo_Requirement, - "bounding rect top included"); - assert.operator(Math.ceil(outerBox.right) + window.Pixel_CloseTo_Requirement, ">=", Math.floor(innerBox.right), - "bounding rect right included"); - assert.operator(Math.ceil(outerBox.bottom) + window.Pixel_CloseTo_Requirement, ">=", Math.floor(innerBox.bottom), - "bounding rect bottom included"); -} - -function assertBBoxNonIntersection(firstEl: D3.Selection, secondEl: D3.Selection) { - var firstBox = firstEl.node().getBoundingClientRect(); - var secondBox = secondEl.node().getBoundingClientRect(); - - var intersectionBox = { - left: Math.max(firstBox.left, secondBox.left), - right: Math.min(firstBox.right, secondBox.right), - bottom: Math.min(firstBox.bottom, secondBox.bottom), - top: Math.max(firstBox.top, secondBox.top) - }; - - // +1 for inaccuracy in IE - assert.isTrue(intersectionBox.left + 1 >= intersectionBox.right || intersectionBox.bottom + 1 >= intersectionBox.top, - "bounding rects are not intersecting"); -} - -function assertPointsClose(actual: Plottable.Point, expected: Plottable.Point, epsilon: number, message: String) { - assert.closeTo(actual.x, expected.x, epsilon, message + " (x)"); - assert.closeTo(actual.y, expected.y, epsilon, message + " (y)"); -}; - -function assertXY(el: D3.Selection, xExpected: number, yExpected: number, message: string) { - var x = el.attr("x"); - var y = el.attr("y"); - assert.equal(x, xExpected, "x: " + message); - assert.equal(y, yExpected, "y: " + message); -} - -function assertWidthHeight(el: D3.Selection, widthExpected: number, heightExpected: number, message: string) { - var width = el.attr("width"); - var height = el.attr("height"); - assert.equal(width, widthExpected, "width: " + message); - assert.equal(height, heightExpected, "height: " + message); -} - - -function makeLinearSeries(n: number): {x: number; y: number}[] { - function makePoint(x: number) { - return {x: x, y: x}; - } - return d3.range(n).map(makePoint); -} - -function makeQuadraticSeries(n: number): {x: number; y: number}[] { - function makeQuadraticPoint(x: number) { - return {x: x, y: x * x}; - } - return d3.range(n).map(makeQuadraticPoint); -} - -// for IE, whose paths look like "M 0 500 L" instead of "M0,500L" -function normalizePath(pathString: string) { - return pathString.replace(/ *([A-Z]) */g, "$1").replace(/ /g, ","); -} - -function numAttr(s: D3.Selection, a: string) { - return parseFloat(s.attr(a)); -} - -function triggerFakeUIEvent(type: string, target: D3.Selection) { - var e = document.createEvent("UIEvents"); - e.initUIEvent(type, true, true, window, 1); - target.node().dispatchEvent(e); -} - -function triggerFakeMouseEvent(type: string, target: D3.Selection, relativeX: number, relativeY: number, button = 0) { - var clientRect = target.node().getBoundingClientRect(); - var xPos = clientRect.left + relativeX; - var yPos = clientRect.top + relativeY; - var e = document.createEvent("MouseEvents"); - e.initMouseEvent(type, true, true, window, 1, - xPos, yPos, - xPos, yPos, - false, false, false, false, - button, null); - target.node().dispatchEvent(e); -} - -function triggerFakeDragSequence(target: D3.Selection, start: Plottable.Point, end: Plottable.Point) { - triggerFakeMouseEvent("mousedown", target, start.x , start.y); - triggerFakeMouseEvent("mousemove", target, end.x, end.y); - triggerFakeMouseEvent("mouseup", target, end.x, end.y); -} - -function triggerFakeWheelEvent(type: string, target: D3.Selection, relativeX: number, relativeY: number, deltaY: number) { - var clientRect = target.node().getBoundingClientRect(); - var xPos = clientRect.left + relativeX; - var yPos = clientRect.top + relativeY; - var event: WheelEvent; - if (Plottable._Util.Methods.isIE()) { - event = document.createEvent("WheelEvent"); - event.initWheelEvent("wheel", true, true, window, 1, xPos, yPos, xPos, yPos, 0, null, null, 0, deltaY, 0, 0); - } else { - // HACKHACK anycasting constructor to allow for the dictionary argument - // https://github.com/Microsoft/TypeScript/issues/2416 - event = new ( WheelEvent)("wheel", { bubbles: true, clientX: xPos, clientY: yPos, deltaY: deltaY }); - } - - target.node().dispatchEvent(event); -} - -function triggerFakeTouchEvent(type: string, target: D3.Selection, touchPoints: Plottable.Point[], ids: number[] = []) { - var targetNode = target.node(); - var clientRect = targetNode.getBoundingClientRect(); - var e = document.createEvent("UIEvent"); - e.initUIEvent(type, true, true, window, 1); - var fakeTouchList: any = []; - - touchPoints.forEach((touchPoint, i) => { - var xPos = clientRect.left + touchPoint.x; - var yPos = clientRect.top + touchPoint.y; - var identifier = ids[i] == null ? 0 : ids[i]; - fakeTouchList.push( { - identifier: identifier, - target: targetNode, - screenX: xPos, - screenY: yPos, - clientX: xPos, - clientY: yPos, - pageX: xPos, - pageY: yPos - }); - }); - fakeTouchList.item = (index: number) => fakeTouchList[index]; - e.touches = fakeTouchList; - e.targetTouches = fakeTouchList; - e.changedTouches = fakeTouchList; - - e.altKey = false; - e.metaKey = false; - e.ctrlKey = false; - e.shiftKey = false; - target.node().dispatchEvent(e); -} - -function assertAreaPathCloseTo(actualPath: string, expectedPath: string, precision: number, msg: string) { - var actualAreaPathStrings = actualPath.split("Z"); - var expectedAreaPathStrings = expectedPath.split("Z"); - - actualAreaPathStrings.pop(); - expectedAreaPathStrings.pop(); - - var actualAreaPathPoints = actualAreaPathStrings.map((path) => path.split(/[A-Z]/).map((point) => point.split(","))); - actualAreaPathPoints.forEach((areaPathPoint) => areaPathPoint.shift()); - var expectedAreaPathPoints = expectedAreaPathStrings.map((path) => path.split(/[A-Z]/).map((point) => point.split(","))); - expectedAreaPathPoints.forEach((areaPathPoint) => areaPathPoint.shift()); - - assert.lengthOf(actualAreaPathPoints, expectedAreaPathPoints.length, "number of broken area paths should be equal"); - actualAreaPathPoints.forEach((actualAreaPoints, i) => { - var expectedAreaPoints = expectedAreaPathPoints[i]; - assert.lengthOf(actualAreaPoints, expectedAreaPoints.length, "number of points in path should be equal"); - actualAreaPoints.forEach((actualAreaPoint, j) => { - var expectedAreaPoint = expectedAreaPoints[j]; - assert.closeTo(+actualAreaPoint[0], +expectedAreaPoint[0], 0.1, msg); - assert.closeTo(+actualAreaPoint[1], +expectedAreaPoint[1], 0.1, msg); - }); - }); -} diff --git a/test/tests.js b/test/tests.js index 14b6acc1d7..44372d7f46 100644 --- a/test/tests.js +++ b/test/tests.js @@ -1,210 +1,219 @@ /// -function generateSVG(width, height) { - if (width === void 0) { width = 400; } - if (height === void 0) { height = 400; } - var parent = getSVGParent(); - return parent.append("svg").attr("width", width).attr("height", height).attr("class", "svg"); -} -function getSVGParent() { - var mocha = d3.select("#mocha-report"); - if (mocha.node() != null) { - var suites = mocha.selectAll(".suite"); - var lastSuite = d3.select(suites[0][suites[0].length - 1]); - return lastSuite.selectAll("ul"); +var TestMethods; +(function (TestMethods) { + function generateSVG(width, height) { + if (width === void 0) { width = 400; } + if (height === void 0) { height = 400; } + var parent = TestMethods.getSVGParent(); + return parent.append("svg").attr("width", width).attr("height", height).attr("class", "svg"); } - else { - return d3.select("body"); + TestMethods.generateSVG = generateSVG; + function getSVGParent() { + var mocha = d3.select("#mocha-report"); + if (mocha.node() != null) { + var suites = mocha.selectAll(".suite"); + var lastSuite = d3.select(suites[0][suites[0].length - 1]); + return lastSuite.selectAll("ul"); + } + else { + return d3.select("body"); + } } -} -function makeFakeEvent(x, y) { - return { - dx: 0, - dy: 0, - clientX: x, - clientY: y, - translate: [x, y], - scale: 1, - sourceEvent: null, - x: x, - y: y, - keyCode: 0, - altKey: false - }; -} -function verifySpaceRequest(sr, w, h, ww, wh, message) { - assert.equal(sr.width, w, message + " (space request: width)"); - assert.equal(sr.height, h, message + " (space request: height)"); - assert.equal(sr.wantsWidth, ww, message + " (space request: wantsWidth)"); - assert.equal(sr.wantsHeight, wh, message + " (space request: wantsHeight)"); -} -function fixComponentSize(c, fixedWidth, fixedHeight) { - c._requestedSpace = function (w, h) { - return { - width: fixedWidth == null ? 0 : fixedWidth, - height: fixedHeight == null ? 0 : fixedHeight, - wantsWidth: fixedWidth == null ? false : w < fixedWidth, - wantsHeight: fixedHeight == null ? false : h < fixedHeight + TestMethods.getSVGParent = getSVGParent; + function verifySpaceRequest(sr, expectedMinWidth, expectedMinHeight, message) { + assert.strictEqual(sr.minWidth, expectedMinWidth, message + " (space request: minWidth)"); + assert.strictEqual(sr.minHeight, expectedMinHeight, message + " (space request: minHeight)"); + } + TestMethods.verifySpaceRequest = verifySpaceRequest; + function fixComponentSize(c, fixedWidth, fixedHeight) { + c.requestedSpace = function (w, h) { + return { + minWidth: fixedWidth == null ? 0 : fixedWidth, + minHeight: fixedHeight == null ? 0 : fixedHeight + }; }; - }; - c._fixedWidthFlag = fixedWidth == null ? false : true; - c._fixedHeightFlag = fixedHeight == null ? false : true; - return c; -} -function makeFixedSizeComponent(fixedWidth, fixedHeight) { - return fixComponentSize(new Plottable.Component.AbstractComponent(), fixedWidth, fixedHeight); -} -function getTranslate(element) { - return d3.transform(element.attr("transform")).translate; -} -function assertBBoxEquivalence(bbox, widthAndHeightPair, message) { - var width = widthAndHeightPair[0]; - var height = widthAndHeightPair[1]; - assert.equal(bbox.width, width, "width: " + message); - assert.equal(bbox.height, height, "height: " + message); -} -function assertBBoxInclusion(outerEl, innerEl) { - var outerBox = outerEl.node().getBoundingClientRect(); - var innerBox = innerEl.node().getBoundingClientRect(); - assert.operator(Math.floor(outerBox.left), "<=", Math.ceil(innerBox.left) + window.Pixel_CloseTo_Requirement, "bounding rect left included"); - assert.operator(Math.floor(outerBox.top), "<=", Math.ceil(innerBox.top) + window.Pixel_CloseTo_Requirement, "bounding rect top included"); - assert.operator(Math.ceil(outerBox.right) + window.Pixel_CloseTo_Requirement, ">=", Math.floor(innerBox.right), "bounding rect right included"); - assert.operator(Math.ceil(outerBox.bottom) + window.Pixel_CloseTo_Requirement, ">=", Math.floor(innerBox.bottom), "bounding rect bottom included"); -} -function assertBBoxNonIntersection(firstEl, secondEl) { - var firstBox = firstEl.node().getBoundingClientRect(); - var secondBox = secondEl.node().getBoundingClientRect(); - var intersectionBox = { - left: Math.max(firstBox.left, secondBox.left), - right: Math.min(firstBox.right, secondBox.right), - bottom: Math.min(firstBox.bottom, secondBox.bottom), - top: Math.max(firstBox.top, secondBox.top) - }; - // +1 for inaccuracy in IE - assert.isTrue(intersectionBox.left + 1 >= intersectionBox.right || intersectionBox.bottom + 1 >= intersectionBox.top, "bounding rects are not intersecting"); -} -function assertPointsClose(actual, expected, epsilon, message) { - assert.closeTo(actual.x, expected.x, epsilon, message + " (x)"); - assert.closeTo(actual.y, expected.y, epsilon, message + " (y)"); -} -; -function assertXY(el, xExpected, yExpected, message) { - var x = el.attr("x"); - var y = el.attr("y"); - assert.equal(x, xExpected, "x: " + message); - assert.equal(y, yExpected, "y: " + message); -} -function assertWidthHeight(el, widthExpected, heightExpected, message) { - var width = el.attr("width"); - var height = el.attr("height"); - assert.equal(width, widthExpected, "width: " + message); - assert.equal(height, heightExpected, "height: " + message); -} -function makeLinearSeries(n) { - function makePoint(x) { - return { x: x, y: x }; + c.fixedWidth = function () { return fixedWidth == null ? false : true; }; + c.fixedHeight = function () { return fixedHeight == null ? false : true; }; + return c; } - return d3.range(n).map(makePoint); -} -function makeQuadraticSeries(n) { - function makeQuadraticPoint(x) { - return { x: x, y: x * x }; + TestMethods.fixComponentSize = fixComponentSize; + function makeFixedSizeComponent(fixedWidth, fixedHeight) { + return fixComponentSize(new Plottable.Component(), fixedWidth, fixedHeight); } - return d3.range(n).map(makeQuadraticPoint); -} -// for IE, whose paths look like "M 0 500 L" instead of "M0,500L" -function normalizePath(pathString) { - return pathString.replace(/ *([A-Z]) */g, "$1").replace(/ /g, ","); -} -function numAttr(s, a) { - return parseFloat(s.attr(a)); -} -function triggerFakeUIEvent(type, target) { - var e = document.createEvent("UIEvents"); - e.initUIEvent(type, true, true, window, 1); - target.node().dispatchEvent(e); -} -function triggerFakeMouseEvent(type, target, relativeX, relativeY, button) { - if (button === void 0) { button = 0; } - var clientRect = target.node().getBoundingClientRect(); - var xPos = clientRect.left + relativeX; - var yPos = clientRect.top + relativeY; - var e = document.createEvent("MouseEvents"); - e.initMouseEvent(type, true, true, window, 1, xPos, yPos, xPos, yPos, false, false, false, false, button, null); - target.node().dispatchEvent(e); -} -function triggerFakeDragSequence(target, start, end) { - triggerFakeMouseEvent("mousedown", target, start.x, start.y); - triggerFakeMouseEvent("mousemove", target, end.x, end.y); - triggerFakeMouseEvent("mouseup", target, end.x, end.y); -} -function triggerFakeWheelEvent(type, target, relativeX, relativeY, deltaY) { - var clientRect = target.node().getBoundingClientRect(); - var xPos = clientRect.left + relativeX; - var yPos = clientRect.top + relativeY; - var event; - if (Plottable._Util.Methods.isIE()) { - event = document.createEvent("WheelEvent"); - event.initWheelEvent("wheel", true, true, window, 1, xPos, yPos, xPos, yPos, 0, null, null, 0, deltaY, 0, 0); + TestMethods.makeFixedSizeComponent = makeFixedSizeComponent; + function getTranslate(element) { + return d3.transform(element.attr("transform")).translate; } - else { - // HACKHACK anycasting constructor to allow for the dictionary argument - // https://github.com/Microsoft/TypeScript/issues/2416 - event = new WheelEvent("wheel", { bubbles: true, clientX: xPos, clientY: yPos, deltaY: deltaY }); + TestMethods.getTranslate = getTranslate; + function assertBBoxEquivalence(bbox, widthAndHeightPair, message) { + var width = widthAndHeightPair[0]; + var height = widthAndHeightPair[1]; + assert.strictEqual(bbox.width, width, "width: " + message); + assert.strictEqual(bbox.height, height, "height: " + message); } - target.node().dispatchEvent(event); -} -function triggerFakeTouchEvent(type, target, touchPoints, ids) { - if (ids === void 0) { ids = []; } - var targetNode = target.node(); - var clientRect = targetNode.getBoundingClientRect(); - var e = document.createEvent("UIEvent"); - e.initUIEvent(type, true, true, window, 1); - var fakeTouchList = []; - touchPoints.forEach(function (touchPoint, i) { - var xPos = clientRect.left + touchPoint.x; - var yPos = clientRect.top + touchPoint.y; - var identifier = ids[i] == null ? 0 : ids[i]; - fakeTouchList.push({ - identifier: identifier, - target: targetNode, - screenX: xPos, - screenY: yPos, - clientX: xPos, - clientY: yPos, - pageX: xPos, - pageY: yPos - }); - }); - fakeTouchList.item = function (index) { return fakeTouchList[index]; }; - e.touches = fakeTouchList; - e.targetTouches = fakeTouchList; - e.changedTouches = fakeTouchList; - e.altKey = false; - e.metaKey = false; - e.ctrlKey = false; - e.shiftKey = false; - target.node().dispatchEvent(e); -} -function assertAreaPathCloseTo(actualPath, expectedPath, precision, msg) { - var actualAreaPathStrings = actualPath.split("Z"); - var expectedAreaPathStrings = expectedPath.split("Z"); - actualAreaPathStrings.pop(); - expectedAreaPathStrings.pop(); - var actualAreaPathPoints = actualAreaPathStrings.map(function (path) { return path.split(/[A-Z]/).map(function (point) { return point.split(","); }); }); - actualAreaPathPoints.forEach(function (areaPathPoint) { return areaPathPoint.shift(); }); - var expectedAreaPathPoints = expectedAreaPathStrings.map(function (path) { return path.split(/[A-Z]/).map(function (point) { return point.split(","); }); }); - expectedAreaPathPoints.forEach(function (areaPathPoint) { return areaPathPoint.shift(); }); - assert.lengthOf(actualAreaPathPoints, expectedAreaPathPoints.length, "number of broken area paths should be equal"); - actualAreaPathPoints.forEach(function (actualAreaPoints, i) { - var expectedAreaPoints = expectedAreaPathPoints[i]; - assert.lengthOf(actualAreaPoints, expectedAreaPoints.length, "number of points in path should be equal"); - actualAreaPoints.forEach(function (actualAreaPoint, j) { - var expectedAreaPoint = expectedAreaPoints[j]; - assert.closeTo(+actualAreaPoint[0], +expectedAreaPoint[0], 0.1, msg); - assert.closeTo(+actualAreaPoint[1], +expectedAreaPoint[1], 0.1, msg); + TestMethods.assertBBoxEquivalence = assertBBoxEquivalence; + function assertBBoxInclusion(outerEl, innerEl) { + var outerBox = outerEl.node().getBoundingClientRect(); + var innerBox = innerEl.node().getBoundingClientRect(); + assert.operator(Math.floor(outerBox.left), "<=", Math.ceil(innerBox.left) + window.Pixel_CloseTo_Requirement, "bounding rect left included"); + assert.operator(Math.floor(outerBox.top), "<=", Math.ceil(innerBox.top) + window.Pixel_CloseTo_Requirement, "bounding rect top included"); + assert.operator(Math.ceil(outerBox.right) + window.Pixel_CloseTo_Requirement, ">=", Math.floor(innerBox.right), "bounding rect right included"); + assert.operator(Math.ceil(outerBox.bottom) + window.Pixel_CloseTo_Requirement, ">=", Math.floor(innerBox.bottom), "bounding rect bottom included"); + } + TestMethods.assertBBoxInclusion = assertBBoxInclusion; + function assertBBoxNonIntersection(firstEl, secondEl) { + var firstBox = firstEl.node().getBoundingClientRect(); + var secondBox = secondEl.node().getBoundingClientRect(); + var intersectionBox = { + left: Math.max(firstBox.left, secondBox.left), + right: Math.min(firstBox.right, secondBox.right), + bottom: Math.min(firstBox.bottom, secondBox.bottom), + top: Math.max(firstBox.top, secondBox.top) + }; + // +1 for inaccuracy in IE + assert.isTrue(intersectionBox.left + 1 >= intersectionBox.right || intersectionBox.bottom + 1 >= intersectionBox.top, "bounding rects are not intersecting"); + } + TestMethods.assertBBoxNonIntersection = assertBBoxNonIntersection; + function assertPointsClose(actual, expected, epsilon, message) { + assert.closeTo(actual.x, expected.x, epsilon, message + " (x)"); + assert.closeTo(actual.y, expected.y, epsilon, message + " (y)"); + } + TestMethods.assertPointsClose = assertPointsClose; + ; + function assertWidthHeight(el, widthExpected, heightExpected, message) { + var width = el.attr("width"); + var height = el.attr("height"); + assert.strictEqual(width, String(widthExpected), "width: " + message); + assert.strictEqual(height, String(heightExpected), "height: " + message); + } + TestMethods.assertWidthHeight = assertWidthHeight; + function makeLinearSeries(n) { + function makePoint(x) { + return { x: x, y: x }; + } + return d3.range(n).map(makePoint); + } + TestMethods.makeLinearSeries = makeLinearSeries; + function makeQuadraticSeries(n) { + function makeQuadraticPoint(x) { + return { x: x, y: x * x }; + } + return d3.range(n).map(makeQuadraticPoint); + } + TestMethods.makeQuadraticSeries = makeQuadraticSeries; + // for IE, whose paths look like "M 0 500 L" instead of "M0,500L" + function normalizePath(pathString) { + return pathString.replace(/ *([A-Z]) */g, "$1").replace(/ /g, ","); + } + TestMethods.normalizePath = normalizePath; + function numAttr(s, a) { + return parseFloat(s.attr(a)); + } + TestMethods.numAttr = numAttr; + function triggerFakeUIEvent(type, target) { + var e = document.createEvent("UIEvents"); + e.initUIEvent(type, true, true, window, 1); + target.node().dispatchEvent(e); + } + TestMethods.triggerFakeUIEvent = triggerFakeUIEvent; + function triggerFakeMouseEvent(type, target, relativeX, relativeY, button) { + if (button === void 0) { button = 0; } + var clientRect = target.node().getBoundingClientRect(); + var xPos = clientRect.left + relativeX; + var yPos = clientRect.top + relativeY; + var e = document.createEvent("MouseEvents"); + e.initMouseEvent(type, true, true, window, 1, xPos, yPos, xPos, yPos, false, false, false, false, button, null); + target.node().dispatchEvent(e); + } + TestMethods.triggerFakeMouseEvent = triggerFakeMouseEvent; + function triggerFakeDragSequence(target, start, end) { + triggerFakeMouseEvent("mousedown", target, start.x, start.y); + triggerFakeMouseEvent("mousemove", target, end.x, end.y); + triggerFakeMouseEvent("mouseup", target, end.x, end.y); + } + TestMethods.triggerFakeDragSequence = triggerFakeDragSequence; + function triggerFakeWheelEvent(type, target, relativeX, relativeY, deltaY) { + var clientRect = target.node().getBoundingClientRect(); + var xPos = clientRect.left + relativeX; + var yPos = clientRect.top + relativeY; + var event; + if (Plottable.Utils.Methods.isIE()) { + event = document.createEvent("WheelEvent"); + event.initWheelEvent("wheel", true, true, window, 1, xPos, yPos, xPos, yPos, 0, null, null, 0, deltaY, 0, 0); + } + else { + // HACKHACK anycasting constructor to allow for the dictionary argument + // https://github.com/Microsoft/TypeScript/issues/2416 + event = new WheelEvent("wheel", { bubbles: true, clientX: xPos, clientY: yPos, deltaY: deltaY }); + } + target.node().dispatchEvent(event); + } + TestMethods.triggerFakeWheelEvent = triggerFakeWheelEvent; + function triggerFakeTouchEvent(type, target, touchPoints, ids) { + if (ids === void 0) { ids = []; } + var targetNode = target.node(); + var clientRect = targetNode.getBoundingClientRect(); + var e = document.createEvent("UIEvent"); + e.initUIEvent(type, true, true, window, 1); + var fakeTouchList = []; + touchPoints.forEach(function (touchPoint, i) { + var xPos = clientRect.left + touchPoint.x; + var yPos = clientRect.top + touchPoint.y; + var identifier = ids[i] == null ? 0 : ids[i]; + fakeTouchList.push({ + identifier: identifier, + target: targetNode, + screenX: xPos, + screenY: yPos, + clientX: xPos, + clientY: yPos, + pageX: xPos, + pageY: yPos + }); + }); + fakeTouchList.item = function (index) { return fakeTouchList[index]; }; + e.touches = fakeTouchList; + e.targetTouches = fakeTouchList; + e.changedTouches = fakeTouchList; + e.altKey = false; + e.metaKey = false; + e.ctrlKey = false; + e.shiftKey = false; + target.node().dispatchEvent(e); + } + TestMethods.triggerFakeTouchEvent = triggerFakeTouchEvent; + function assertAreaPathCloseTo(actualPath, expectedPath, precision, msg) { + var actualAreaPathStrings = actualPath.split("Z"); + var expectedAreaPathStrings = expectedPath.split("Z"); + actualAreaPathStrings.pop(); + expectedAreaPathStrings.pop(); + var actualAreaPathPoints = actualAreaPathStrings.map(function (path) { return path.split(/[A-Z]/).map(function (point) { return point.split(","); }); }); + actualAreaPathPoints.forEach(function (areaPathPoint) { return areaPathPoint.shift(); }); + var expectedAreaPathPoints = expectedAreaPathStrings.map(function (path) { return path.split(/[A-Z]/).map(function (point) { return point.split(","); }); }); + expectedAreaPathPoints.forEach(function (areaPathPoint) { return areaPathPoint.shift(); }); + assert.lengthOf(actualAreaPathPoints, expectedAreaPathPoints.length, "number of broken area paths should be equal"); + actualAreaPathPoints.forEach(function (actualAreaPoints, i) { + var expectedAreaPoints = expectedAreaPathPoints[i]; + assert.lengthOf(actualAreaPoints, expectedAreaPoints.length, "number of points in path should be equal"); + actualAreaPoints.forEach(function (actualAreaPoint, j) { + var expectedAreaPoint = expectedAreaPoints[j]; + assert.closeTo(+actualAreaPoint[0], +expectedAreaPoint[0], 0.1, msg); + assert.closeTo(+actualAreaPoint[1], +expectedAreaPoint[1], 0.1, msg); + }); }); - }); -} + } + TestMethods.assertAreaPathCloseTo = assertAreaPathCloseTo; + function verifyClipPath(c) { + var clipPathId = c._boxContainer[0][0].firstChild.id; + var expectedPrefix = /MSIE [5-9]/.test(navigator.userAgent) ? "" : document.location.href; + expectedPrefix = expectedPrefix.replace(/#.*/g, ""); + var expectedClipPathURL = "url(" + expectedPrefix + "#" + clipPathId + ")"; + // IE 9 has clipPath like 'url("#clipPath")', must accomodate + var normalizeClipPath = function (s) { return s.replace(/"/g, ""); }; + assert.isTrue(normalizeClipPath(c._element.attr("clip-path")) === expectedClipPathURL, "the element has clip-path url attached"); + } + TestMethods.verifyClipPath = verifyClipPath; +})(TestMethods || (TestMethods = {})); /// var __extends = this.__extends || function (d, b) { @@ -221,28 +230,30 @@ var Mocks; if (width === void 0) { width = 0; } if (height === void 0) { height = 0; } _super.call(this); - this.fixedWidth = width; - this.fixedHeight = height; - this._fixedWidthFlag = true; - this._fixedHeightFlag = true; + this.fsWidth = width; + this.fsHeight = height; } - FixedSizeComponent.prototype._requestedSpace = function (availableWidth, availableHeight) { + FixedSizeComponent.prototype.requestedSpace = function (availableWidth, availableHeight) { return { - width: this.fixedWidth, - height: this.fixedHeight, - wantsWidth: availableWidth < this.fixedWidth, - wantsHeight: availableHeight < this.fixedHeight + minWidth: this.fsWidth, + minHeight: this.fsHeight }; }; + FixedSizeComponent.prototype.fixedWidth = function () { + return true; + }; + FixedSizeComponent.prototype.fixedHeight = function () { + return true; + }; return FixedSizeComponent; - })(Plottable.Component.AbstractComponent); + })(Plottable.Component); Mocks.FixedSizeComponent = FixedSizeComponent; })(Mocks || (Mocks = {})); /// before(function () { // Set the render policy to immediate to make sure ETE tests can check DOM change immediately - Plottable.Core.RenderController.setRenderPolicy("immediate"); + Plottable.RenderController.setRenderPolicy("immediate"); // Taken from https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser var isFirefox = navigator.userAgent.indexOf("Firefox") !== -1; if (window.PHANTOMJS) { @@ -256,7 +267,6 @@ before(function () { } }); after(function () { - var parent = getSVGParent(); var mocha = d3.select("#mocha-report"); if (mocha.node() != null) { var suites = mocha.selectAll(".suite"); @@ -302,7 +312,7 @@ var MockDrawer = (function (_super) { step.animator.animate(this._getRenderArea(), step.attrToProjector); }; return MockDrawer; -})(Plottable._Drawer.AbstractDrawer); +})(Plottable.Drawers.AbstractDrawer); describe("Drawers", function () { describe("Abstract Drawer", function () { var oldTimeout; @@ -310,8 +320,8 @@ describe("Drawers", function () { var svg; var drawer; before(function () { - oldTimeout = Plottable._Util.Methods.setTimeout; - Plottable._Util.Methods.setTimeout = function (f, time) { + oldTimeout = Plottable.Utils.Methods.setTimeout; + Plottable.Utils.Methods.setTimeout = function (f, time) { var args = []; for (var _i = 2; _i < arguments.length; _i++) { args[_i - 2] = arguments[_i]; @@ -321,11 +331,11 @@ describe("Drawers", function () { }; }); after(function () { - Plottable._Util.Methods.setTimeout = oldTimeout; + Plottable.Utils.Methods.setTimeout = oldTimeout; }); beforeEach(function () { timings = []; - svg = generateSVG(); + svg = TestMethods.generateSVG(); drawer = new MockDrawer("foo"); drawer.setup(svg); }); @@ -333,8 +343,8 @@ describe("Drawers", function () { svg.remove(); // no point keeping it around since we don't draw anything in it anyway }); it("drawer timing works as expected for null animators", function () { - var a1 = new Plottable.Animator.Null(); - var a2 = new Plottable.Animator.Null(); + var a1 = new Plottable.Animators.Null(); + var a2 = new Plottable.Animators.Null(); var ds1 = { attrToProjector: {}, animator: a1 }; var ds2 = { attrToProjector: {}, animator: a2 }; var steps = [ds1, ds2]; @@ -366,8 +376,8 @@ describe("Drawers", function () { assert.deepEqual(timings, [0, 20, 30], "setTimeout called with appropriate times"); }); it("_getSelection", function () { - var svg = generateSVG(300, 300); - var drawer = new Plottable._Drawer.AbstractDrawer("test"); + var svg = TestMethods.generateSVG(300, 300); + var drawer = new Plottable.Drawers.AbstractDrawer("test"); drawer.setup(svg.append("g")); drawer._getSelector = function () { return "circle"; }; var data = [{ one: 2, two: 1 }, { one: 33, two: 21 }, { one: 11, two: 10 }]; @@ -384,16 +394,15 @@ describe("Drawers", function () { describe("Drawers", function () { describe("Arc Drawer", function () { it("getPixelPoint", function () { - var svg = generateSVG(300, 300); + var svg = TestMethods.generateSVG(300, 300); var data = [{ value: 10 }, { value: 10 }, { value: 10 }, { value: 10 }]; - var piePlot = new Plottable.Plot.Pie(); - var drawer = new Plottable._Drawer.Arc("one"); + var piePlot = new Plottable.Plots.Pie(); + var drawer = new Plottable.Drawers.Arc("_0"); // HACKHACK #1984: Dataset keys are being removed, so this is the internal key piePlot._getDrawer = function () { return drawer; }; - piePlot.addDataset("one", data); - piePlot.project("value", "value"); + piePlot.addDataset(new Plottable.Dataset(data)); + piePlot.sectorValue(function (d) { return d.value; }); piePlot.renderTo(svg); piePlot.getAllSelections().each(function (datum, index) { - var selection = d3.select(this); var pixelPoint = drawer._getPixelPoint(datum, index); var radius = 75; var angle = Math.PI / 4 + ((Math.PI * index) / 2); @@ -411,16 +420,16 @@ describe("Drawers", function () { describe("Drawers", function () { describe("Rect Drawer", function () { it("getPixelPoint vertical", function () { - var svg = generateSVG(300, 300); + var svg = TestMethods.generateSVG(300, 300); var data = [{ a: "foo", b: 10 }, { a: "bar", b: 24 }]; - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Linear(); - var barPlot = new Plottable.Plot.Bar(xScale, yScale); - var drawer = new Plottable._Drawer.Rect("one", true); + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Linear(); + var barPlot = new Plottable.Plots.Bar(xScale, yScale); + var drawer = new Plottable.Drawers.Rect("_0", true); // HACKHACK #1984: Dataset keys are being removed, so this is the internal key barPlot._getDrawer = function () { return drawer; }; - barPlot.addDataset("one", data); - barPlot.project("x", "a", xScale); - barPlot.project("y", "b", yScale); + barPlot.addDataset(new Plottable.Dataset(data)); + barPlot.x(function (d) { return d.a; }, xScale); + barPlot.y(function (d) { return d.b; }, yScale); barPlot.renderTo(svg); barPlot.getAllSelections().each(function (datum, index) { var selection = d3.select(this); @@ -431,16 +440,16 @@ describe("Drawers", function () { svg.remove(); }); it("getPixelPoint horizontal", function () { - var svg = generateSVG(300, 300); + var svg = TestMethods.generateSVG(300, 300); var data = [{ a: "foo", b: 10 }, { a: "bar", b: 24 }]; - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Category(); - var barPlot = new Plottable.Plot.Bar(xScale, yScale, false); - var drawer = new Plottable._Drawer.Rect("one", false); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Category(); + var barPlot = new Plottable.Plots.Bar(xScale, yScale, false); + var drawer = new Plottable.Drawers.Rect("_0", false); // HACKHACK #1984: Dataset keys are being removed, so this is the internal key barPlot._getDrawer = function () { return drawer; }; - barPlot.addDataset("one", data); - barPlot.project("x", "b", xScale); - barPlot.project("y", "a", yScale); + barPlot.addDataset(new Plottable.Dataset(data)); + barPlot.x(function (d) { return d.x; }, xScale); + barPlot.y(function (d) { return d.y; }, yScale); barPlot.renderTo(svg); barPlot.getAllSelections().each(function (datum, index) { var selection = d3.select(this); @@ -457,16 +466,16 @@ describe("Drawers", function () { describe("Drawers", function () { describe("Line Drawer", function () { it("getPixelPoint", function () { - var svg = generateSVG(300, 300); + var svg = TestMethods.generateSVG(300, 300); var data = [{ a: 12, b: 10 }, { a: 13, b: 24 }, { a: 14, b: 21 }, { a: 15, b: 14 }]; - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - var linePlot = new Plottable.Plot.Line(xScale, yScale); - var drawer = new Plottable._Drawer.Line("one"); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); + var linePlot = new Plottable.Plots.Line(xScale, yScale); + var drawer = new Plottable.Drawers.Line("_0"); // HACKHACK #1984: Dataset keys are being removed, so this is the internal key linePlot._getDrawer = function () { return drawer; }; - linePlot.addDataset("one", data); - linePlot.project("x", "a", xScale); - linePlot.project("y", "b", yScale); + linePlot.addDataset(new Plottable.Dataset(data)); + linePlot.x(function (d) { return d.a; }, xScale); + linePlot.y(function (d) { return d.b; }, yScale); linePlot.renderTo(svg); data.forEach(function (datum, index) { var pixelPoint = drawer._getPixelPoint(datum, index); @@ -476,16 +485,16 @@ describe("Drawers", function () { svg.remove(); }); it("getSelection", function () { - var svg = generateSVG(300, 300); + var svg = TestMethods.generateSVG(300, 300); var data = [{ a: 12, b: 10 }, { a: 13, b: 24 }, { a: 14, b: 21 }, { a: 15, b: 14 }]; - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - var linePlot = new Plottable.Plot.Line(xScale, yScale); - var drawer = new Plottable._Drawer.Line("one"); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); + var linePlot = new Plottable.Plots.Line(xScale, yScale); + var drawer = new Plottable.Drawers.Line("_0"); // HACKHACK #1984: Dataset keys are being removed, so this is the internal key linePlot._getDrawer = function () { return drawer; }; - linePlot.addDataset("one", data); - linePlot.project("x", "a", xScale); - linePlot.project("y", "b", yScale); + linePlot.addDataset(new Plottable.Dataset(data)); + linePlot.x(function (d) { return d.a; }, xScale); + linePlot.y(function (d) { return d.b; }, yScale); linePlot.renderTo(svg); var lineSelection = linePlot.getAllSelections(); data.forEach(function (datum, index) { @@ -501,25 +510,25 @@ describe("Drawers", function () { var assert = chai.assert; describe("BaseAxis", function () { it("orientation", function () { - var scale = new Plottable.Scale.Linear(); - assert.throws(function () { return new Plottable.Axis.AbstractAxis(scale, "blargh"); }, "unsupported"); + var scale = new Plottable.Scales.Linear(); + assert.throws(function () { return new Plottable.Axis(scale, "blargh"); }, "unsupported"); }); it("tickLabelPadding() rejects negative values", function () { - var scale = new Plottable.Scale.Linear(); - var baseAxis = new Plottable.Axis.AbstractAxis(scale, "bottom"); + var scale = new Plottable.Scales.Linear(); + var baseAxis = new Plottable.Axis(scale, "bottom"); assert.throws(function () { return baseAxis.tickLabelPadding(-1); }, "must be positive"); }); it("gutter() rejects negative values", function () { - var scale = new Plottable.Scale.Linear(); - var axis = new Plottable.Axis.AbstractAxis(scale, "right"); + var scale = new Plottable.Scales.Linear(); + var axis = new Plottable.Axis(scale, "right"); assert.throws(function () { return axis.gutter(-1); }, "must be positive"); }); it("width() + gutter()", function () { var SVG_WIDTH = 100; var SVG_HEIGHT = 500; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var scale = new Plottable.Scale.Linear(); - var verticalAxis = new Plottable.Axis.AbstractAxis(scale, "right"); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var scale = new Plottable.Scales.Linear(); + var verticalAxis = new Plottable.Axis(scale, "right"); verticalAxis.renderTo(svg); var expectedWidth = verticalAxis.tickLength() + verticalAxis.gutter(); // tick length and gutter by default assert.strictEqual(verticalAxis.width(), expectedWidth, "calling width() with no arguments returns currently used width"); @@ -531,9 +540,9 @@ describe("BaseAxis", function () { it("height() + gutter()", function () { var SVG_WIDTH = 500; var SVG_HEIGHT = 100; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var scale = new Plottable.Scale.Linear(); - var horizontalAxis = new Plottable.Axis.AbstractAxis(scale, "bottom"); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var scale = new Plottable.Scales.Linear(); + var horizontalAxis = new Plottable.Axis(scale, "bottom"); horizontalAxis.renderTo(svg); var expectedHeight = horizontalAxis.tickLength() + horizontalAxis.gutter(); // tick length and gutter by default assert.strictEqual(horizontalAxis.height(), expectedHeight, "calling height() with no arguments returns currently used height"); @@ -545,17 +554,17 @@ describe("BaseAxis", function () { it("draws ticks and baseline (horizontal)", function () { var SVG_WIDTH = 500; var SVG_HEIGHT = 100; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var scale = new Plottable.Scale.Linear(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var scale = new Plottable.Scales.Linear(); scale.domain([0, 10]); scale.range([0, SVG_WIDTH]); - var baseAxis = new Plottable.Axis.AbstractAxis(scale, "bottom"); + var baseAxis = new Plottable.Axis(scale, "bottom"); var tickValues = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; baseAxis._getTickValues = function () { return tickValues; }; baseAxis.renderTo(svg); - var tickMarks = svg.selectAll("." + Plottable.Axis.AbstractAxis.TICK_MARK_CLASS); + var tickMarks = svg.selectAll("." + Plottable.Axis.TICK_MARK_CLASS); assert.strictEqual(tickMarks[0].length, tickValues.length, "A tick mark was created for each value"); var baseline = svg.select(".baseline"); assert.isNotNull(baseline.node(), "baseline was drawn"); @@ -563,7 +572,7 @@ describe("BaseAxis", function () { assert.strictEqual(baseline.attr("x2"), String(SVG_WIDTH)); assert.strictEqual(baseline.attr("y1"), "0"); assert.strictEqual(baseline.attr("y2"), "0"); - baseAxis.orient("top"); + baseAxis.orientation("top"); assert.isNotNull(baseline.node(), "baseline was drawn"); assert.strictEqual(baseline.attr("x1"), "0"); assert.strictEqual(baseline.attr("x2"), String(SVG_WIDTH)); @@ -574,17 +583,17 @@ describe("BaseAxis", function () { it("draws ticks and baseline (vertical)", function () { var SVG_WIDTH = 100; var SVG_HEIGHT = 500; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var scale = new Plottable.Scale.Linear(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var scale = new Plottable.Scales.Linear(); scale.domain([0, 10]); scale.range([0, SVG_HEIGHT]); - var baseAxis = new Plottable.Axis.AbstractAxis(scale, "left"); + var baseAxis = new Plottable.Axis(scale, "left"); var tickValues = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; baseAxis._getTickValues = function () { return tickValues; }; baseAxis.renderTo(svg); - var tickMarks = svg.selectAll("." + Plottable.Axis.AbstractAxis.TICK_MARK_CLASS); + var tickMarks = svg.selectAll("." + Plottable.Axis.TICK_MARK_CLASS); assert.strictEqual(tickMarks[0].length, tickValues.length, "A tick mark was created for each value"); var baseline = svg.select(".baseline"); assert.isNotNull(baseline.node(), "baseline was drawn"); @@ -592,7 +601,7 @@ describe("BaseAxis", function () { assert.strictEqual(baseline.attr("x2"), String(baseAxis.width())); assert.strictEqual(baseline.attr("y1"), "0"); assert.strictEqual(baseline.attr("y2"), String(SVG_HEIGHT)); - baseAxis.orient("right"); + baseAxis.orientation("right"); assert.isNotNull(baseline.node(), "baseline was drawn"); assert.strictEqual(baseline.attr("x1"), "0"); assert.strictEqual(baseline.attr("x2"), "0"); @@ -603,17 +612,17 @@ describe("BaseAxis", function () { it("tickLength()", function () { var SVG_WIDTH = 500; var SVG_HEIGHT = 100; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var scale = new Plottable.Scale.Linear(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var scale = new Plottable.Scales.Linear(); scale.domain([0, 10]); scale.range([0, SVG_WIDTH]); - var baseAxis = new Plottable.Axis.AbstractAxis(scale, "bottom"); + var baseAxis = new Plottable.Axis(scale, "bottom"); var tickValues = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; baseAxis._getTickValues = function () { return tickValues; }; baseAxis.renderTo(svg); - var secondTickMark = svg.selectAll("." + Plottable.Axis.AbstractAxis.TICK_MARK_CLASS + ":nth-child(2)"); + var secondTickMark = svg.selectAll("." + Plottable.Axis.TICK_MARK_CLASS + ":nth-child(2)"); assert.strictEqual(secondTickMark.attr("x1"), "50"); assert.strictEqual(secondTickMark.attr("x2"), "50"); assert.strictEqual(secondTickMark.attr("y1"), "0"); @@ -626,15 +635,15 @@ describe("BaseAxis", function () { it("endTickLength()", function () { var SVG_WIDTH = 500; var SVG_HEIGHT = 100; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var scale = new Plottable.Scale.Linear(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var scale = new Plottable.Scales.Linear(); scale.domain([0, 10]); scale.range([0, SVG_WIDTH]); - var baseAxis = new Plottable.Axis.AbstractAxis(scale, "bottom"); + var baseAxis = new Plottable.Axis(scale, "bottom"); var tickValues = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; baseAxis._getTickValues = function () { return tickValues; }; baseAxis.renderTo(svg); - var firstTickMark = svg.selectAll("." + Plottable.Axis.AbstractAxis.END_TICK_MARK_CLASS); + var firstTickMark = svg.selectAll("." + Plottable.Axis.END_TICK_MARK_CLASS); assert.strictEqual(firstTickMark.attr("x1"), "0"); assert.strictEqual(firstTickMark.attr("x2"), "0"); assert.strictEqual(firstTickMark.attr("y1"), "0"); @@ -647,9 +656,9 @@ describe("BaseAxis", function () { it("height is adjusted to greater of tickLength or endTickLength", function () { var SVG_WIDTH = 500; var SVG_HEIGHT = 100; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var scale = new Plottable.Scale.Linear(); - var baseAxis = new Plottable.Axis.AbstractAxis(scale, "bottom"); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var scale = new Plottable.Scales.Linear(); + var baseAxis = new Plottable.Axis(scale, "bottom"); baseAxis.showEndTickLabels(true); baseAxis.renderTo(svg); var expectedHeight = Math.max(baseAxis.tickLength(), baseAxis.endTickLength()) + baseAxis.gutter(); @@ -663,15 +672,15 @@ describe("BaseAxis", function () { svg.remove(); }); it("default alignment based on orientation", function () { - var scale = new Plottable.Scale.Linear(); - var baseAxis = new Plottable.Axis.AbstractAxis(scale, "bottom"); - assert.equal(baseAxis._yAlignProportion, 0, "yAlignProportion defaults to 0 for bottom axis"); - baseAxis = new Plottable.Axis.AbstractAxis(scale, "top"); - assert.equal(baseAxis._yAlignProportion, 1, "yAlignProportion defaults to 1 for top axis"); - baseAxis = new Plottable.Axis.AbstractAxis(scale, "left"); - assert.equal(baseAxis._xAlignProportion, 1, "xAlignProportion defaults to 1 for left axis"); - baseAxis = new Plottable.Axis.AbstractAxis(scale, "right"); - assert.equal(baseAxis._xAlignProportion, 0, "xAlignProportion defaults to 0 for right axis"); + var scale = new Plottable.Scales.Linear(); + var baseAxis = new Plottable.Axis(scale, "bottom"); + assert.strictEqual(baseAxis.yAlignment(), "top", "y alignment defaults to \"top\" for bottom axis"); + baseAxis = new Plottable.Axis(scale, "top"); + assert.strictEqual(baseAxis.yAlignment(), "bottom", "y alignment defaults to \"bottom\" for top axis"); + baseAxis = new Plottable.Axis(scale, "left"); + assert.strictEqual(baseAxis.xAlignment(), "right", "x alignment defaults to \"right\" for left axis"); + baseAxis = new Plottable.Axis(scale, "right"); + assert.strictEqual(baseAxis.xAlignment(), "left", "x alignment defaults to \"left\" for right axis"); }); }); @@ -681,20 +690,20 @@ describe("TimeAxis", function () { var scale; var axis; beforeEach(function () { - scale = new Plottable.Scale.Time(); - axis = new Plottable.Axis.Time(scale, "bottom"); + scale = new Plottable.Scales.Time(); + axis = new Plottable.Axes.Time(scale, "bottom"); }); it("can not initialize vertical time axis", function () { - assert.throws(function () { return new Plottable.Axis.Time(scale, "left"); }, "horizontal"); - assert.throws(function () { return new Plottable.Axis.Time(scale, "right"); }, "horizontal"); + assert.throws(function () { return new Plottable.Axes.Time(scale, "left"); }, "horizontal"); + assert.throws(function () { return new Plottable.Axes.Time(scale, "right"); }, "horizontal"); }); it("cannot change time axis orientation to vertical", function () { - assert.throws(function () { return axis.orient("left"); }, "horizontal"); - assert.throws(function () { return axis.orient("right"); }, "horizontal"); - assert.equal(axis.orient(), "bottom", "orientation unchanged"); + assert.throws(function () { return axis.orientation("left"); }, "horizontal"); + assert.throws(function () { return axis.orientation("right"); }, "horizontal"); + assert.strictEqual(axis.orientation(), "bottom", "orientation unchanged"); }); it("Computing the default ticks doesn't error out for edge cases", function () { - var svg = generateSVG(400, 100); + var svg = TestMethods.generateSVG(400, 100); scale.range([0, 400]); // very large time span assert.doesNotThrow(function () { return scale.domain([new Date(0, 0, 1, 0, 0, 0, 0), new Date(50000, 0, 1, 0, 0, 0, 0)]); }); @@ -705,13 +714,13 @@ describe("TimeAxis", function () { svg.remove(); }); it("Tick labels don't overlap", function () { - var svg = generateSVG(400, 100); + var svg = TestMethods.generateSVG(400, 100); scale.range([0, 400]); function checkDomain(domain) { scale.domain(domain); axis.renderTo(svg); function checkLabelsForContainer(container) { - var visibleTickLabels = container.selectAll("." + Plottable.Axis.AbstractAxis.TICK_LABEL_CLASS).filter(function (d, i) { + var visibleTickLabels = container.selectAll("." + Plottable.Axis.TICK_LABEL_CLASS).filter(function (d, i) { return d3.select(this).style("visibility") === "visible"; }); var numLabels = visibleTickLabels[0].length; @@ -721,7 +730,7 @@ describe("TimeAxis", function () { for (var j = i + 1; j < numLabels; j++) { box1 = visibleTickLabels[0][i].getBoundingClientRect(); box2 = visibleTickLabels[0][j].getBoundingClientRect(); - assert.isFalse(Plottable._Util.DOM.boxesOverlap(box1, box2), "tick labels don't overlap"); + assert.isFalse(Plottable.Utils.DOM.boxesOverlap(box1, box2), "tick labels don't overlap"); } } } @@ -744,13 +753,13 @@ describe("TimeAxis", function () { svg.remove(); }); it("custom possible axis configurations", function () { - var svg = generateSVG(800, 100); - var scale = new Plottable.Scale.Time(); - var axis = new Plottable.Axis.Time(scale, "bottom"); + var svg = TestMethods.generateSVG(800, 100); + var scale = new Plottable.Scales.Time(); + var axis = new Plottable.Axes.Time(scale, "bottom"); var configurations = axis.axisConfigurations(); var newPossibleConfigurations = configurations.slice(0, 3); newPossibleConfigurations.forEach(function (axisConfig) { return axisConfig.forEach(function (tierConfig) { - tierConfig.interval = d3.time.minute; + tierConfig.interval = Plottable.TimeInterval.minute; tierConfig.step += 3; }); }); axis.axisConfigurations(newPossibleConfigurations); @@ -761,75 +770,75 @@ describe("TimeAxis", function () { scale.range([0, 800]); axis.renderTo(svg); var configs = newPossibleConfigurations[axis._mostPreciseConfigIndex]; - assert.deepEqual(configs[0].interval, d3.time.minute, "axis used new time unit"); + assert.deepEqual(configs[0].interval, Plottable.TimeInterval.minute, "axis used new time unit"); assert.deepEqual(configs[0].step, 4, "axis used new step"); svg.remove(); }); it("renders end ticks on either side", function () { var width = 500; - var svg = generateSVG(width, 100); - scale.domain(["2010", "2014"]); + var svg = TestMethods.generateSVG(width, 100); + scale.domain([new Date("2010-01-01"), new Date("2014-01-01")]); axis.renderTo(svg); var firstTick = d3.select(".tick-mark"); - assert.equal(firstTick.attr("x1"), 0, "xPos (x1) of first end tick is at the beginning of the axis container"); - assert.equal(firstTick.attr("x2"), 0, "xPos (x2) of first end tick is at the beginning of the axis container"); + assert.strictEqual(firstTick.attr("x1"), "0", "xPos (x1) of first end tick is at the beginning of the axis container"); + assert.strictEqual(firstTick.attr("x2"), "0", "xPos (x2) of first end tick is at the beginning of the axis container"); var lastTick = d3.select(d3.selectAll(".tick-mark")[0].pop()); - assert.equal(lastTick.attr("x1"), width, "xPos (x1) of last end tick is at the end of the axis container"); - assert.equal(lastTick.attr("x2"), width, "xPos (x2) of last end tick is at the end of the axis container"); + assert.strictEqual(lastTick.attr("x1"), String(width), "xPos (x1) of last end tick is at the end of the axis container"); + assert.strictEqual(lastTick.attr("x2"), String(width), "xPos (x2) of last end tick is at the end of the axis container"); svg.remove(); }); it("adds a class corresponding to the end-tick for the first and last ticks", function () { var width = 500; - var svg = generateSVG(width, 100); - scale.domain(["2010", "2014"]); + var svg = TestMethods.generateSVG(width, 100); + scale.domain([new Date("2010-01-01"), new Date("2014-01-01")]); axis.renderTo(svg); - var firstTick = d3.select("." + Plottable.Axis.AbstractAxis.TICK_MARK_CLASS); - assert.isTrue(firstTick.classed(Plottable.Axis.AbstractAxis.END_TICK_MARK_CLASS), "first end tick has the end-tick-mark class"); - var lastTick = d3.select(d3.selectAll("." + Plottable.Axis.AbstractAxis.TICK_MARK_CLASS)[0].pop()); - assert.isTrue(lastTick.classed(Plottable.Axis.AbstractAxis.END_TICK_MARK_CLASS), "last end tick has the end-tick-mark class"); + var firstTick = d3.select("." + Plottable.Axis.TICK_MARK_CLASS); + assert.isTrue(firstTick.classed(Plottable.Axis.END_TICK_MARK_CLASS), "first end tick has the end-tick-mark class"); + var lastTick = d3.select(d3.selectAll("." + Plottable.Axis.TICK_MARK_CLASS)[0].pop()); + assert.isTrue(lastTick.classed(Plottable.Axis.END_TICK_MARK_CLASS), "last end tick has the end-tick-mark class"); svg.remove(); }); it("tick labels do not overlap with tick marks", function () { - var svg = generateSVG(400, 100); - scale = new Plottable.Scale.Time(); + var svg = TestMethods.generateSVG(400, 100); + scale = new Plottable.Scales.Time(); scale.domain([new Date("2009-12-20"), new Date("2011-01-01")]); - axis = new Plottable.Axis.Time(scale, "bottom"); + axis = new Plottable.Axes.Time(scale, "bottom"); axis.renderTo(svg); - var tickRects = d3.selectAll("." + Plottable.Axis.AbstractAxis.TICK_MARK_CLASS)[0].map(function (mark) { return mark.getBoundingClientRect(); }); - var labelRects = d3.selectAll("." + Plottable.Axis.AbstractAxis.TICK_LABEL_CLASS).filter(function (d, i) { + var tickRects = d3.selectAll("." + Plottable.Axis.TICK_MARK_CLASS)[0].map(function (mark) { return mark.getBoundingClientRect(); }); + var labelRects = d3.selectAll("." + Plottable.Axis.TICK_LABEL_CLASS).filter(function (d, i) { return d3.select(this).style("visibility") === "visible"; })[0].map(function (label) { return label.getBoundingClientRect(); }); labelRects.forEach(function (labelRect) { tickRects.forEach(function (tickRect) { - assert.isFalse(Plottable._Util.DOM.boxesOverlap(labelRect, tickRect), "visible label does not overlap with a tick"); + assert.isFalse(Plottable.Utils.DOM.boxesOverlap(labelRect, tickRect), "visible label does not overlap with a tick"); }); }); svg.remove(); }); it("if the time only uses one tier, there should be no space left for the second tier", function () { - var svg = generateSVG(); - var xScale = new Plottable.Scale.Time(); + var svg = TestMethods.generateSVG(); + var xScale = new Plottable.Scales.Time(); xScale.domain([new Date("2013-03-23 12:00"), new Date("2013-04-03 0:00")]); - var xAxis = new Plottable.Axis.Time(xScale, "bottom"); + var xAxis = new Plottable.Axes.Time(xScale, "bottom"); xAxis.gutter(0); xAxis.axisConfigurations([ [ - { interval: d3.time.day, step: 2, formatter: Plottable.Formatters.time("%a %e") } + { interval: Plottable.TimeInterval.day, step: 2, formatter: Plottable.Formatters.time("%a %e") } ], ]); xAxis.renderTo(svg); var oneTierSize = xAxis.height(); xAxis.axisConfigurations([ [ - { interval: d3.time.day, step: 2, formatter: Plottable.Formatters.time("%a %e") }, - { interval: d3.time.day, step: 2, formatter: Plottable.Formatters.time("%a %e") } + { interval: Plottable.TimeInterval.day, step: 2, formatter: Plottable.Formatters.time("%a %e") }, + { interval: Plottable.TimeInterval.day, step: 2, formatter: Plottable.Formatters.time("%a %e") } ], ]); var twoTierSize = xAxis.height(); assert.strictEqual(twoTierSize, oneTierSize * 2, "two-tier axis is twice as tall as one-tier axis"); xAxis.axisConfigurations([ [ - { interval: d3.time.day, step: 2, formatter: Plottable.Formatters.time("%a %e") } + { interval: Plottable.TimeInterval.day, step: 2, formatter: Plottable.Formatters.time("%a %e") } ], ]); var initialTierSize = xAxis.height(); @@ -837,24 +846,24 @@ describe("TimeAxis", function () { svg.remove(); }); it("three tier time axis should be possible", function () { - var svg = generateSVG(); - var xScale = new Plottable.Scale.Time(); + var svg = TestMethods.generateSVG(); + var xScale = new Plottable.Scales.Time(); xScale.domain([new Date("2013-03-23 12:00"), new Date("2013-04-03 0:00")]); - var xAxis = new Plottable.Axis.Time(xScale, "bottom"); + var xAxis = new Plottable.Axes.Time(xScale, "bottom"); xAxis.gutter(0); xAxis.renderTo(svg); xAxis.axisConfigurations([ [ - { interval: d3.time.day, step: 2, formatter: Plottable.Formatters.time("%a %e") }, - { interval: d3.time.day, step: 2, formatter: Plottable.Formatters.time("%a %e") }, + { interval: Plottable.TimeInterval.day, step: 2, formatter: Plottable.Formatters.time("%a %e") }, + { interval: Plottable.TimeInterval.day, step: 2, formatter: Plottable.Formatters.time("%a %e") }, ], ]); var twoTierAxisHeight = xAxis.height(); xAxis.axisConfigurations([ [ - { interval: d3.time.day, step: 2, formatter: Plottable.Formatters.time("%a %e") }, - { interval: d3.time.day, step: 2, formatter: Plottable.Formatters.time("%a %e") }, - { interval: d3.time.day, step: 2, formatter: Plottable.Formatters.time("%a %e") }, + { interval: Plottable.TimeInterval.day, step: 2, formatter: Plottable.Formatters.time("%a %e") }, + { interval: Plottable.TimeInterval.day, step: 2, formatter: Plottable.Formatters.time("%a %e") }, + { interval: Plottable.TimeInterval.day, step: 2, formatter: Plottable.Formatters.time("%a %e") }, ], ]); var threeTierAxisHeight = xAxis.height(); @@ -862,13 +871,13 @@ describe("TimeAxis", function () { svg.remove(); }); it("many tier Axis.Time should not exceed the drawing area", function () { - var svg = generateSVG(400, 50); - var xScale = new Plottable.Scale.Time(); + var svg = TestMethods.generateSVG(400, 50); + var xScale = new Plottable.Scales.Time(); xScale.domain([new Date("2013-03-23 12:00"), new Date("2013-04-03 0:00")]); - var xAxis = new Plottable.Axis.Time(xScale, "bottom"); + var xAxis = new Plottable.Axes.Time(xScale, "bottom"); var tiersToCreate = 15; var configuration = Array.apply(null, Array(tiersToCreate)).map(function () { - return { interval: d3.time.day, step: 2, formatter: Plottable.Formatters.time("%a %e") }; + return { interval: Plottable.TimeInterval.day, step: 2, formatter: Plottable.Formatters.time("%a %e") }; }); xAxis.axisConfigurations([configuration]); xAxis.renderTo(svg); @@ -876,10 +885,10 @@ describe("TimeAxis", function () { var isInsideAxisBoundingRect = function (innerRect) { return Math.floor(innerRect.bottom) <= Math.ceil(axisBoundingRect.bottom) + window.Pixel_CloseTo_Requirement && Math.floor(axisBoundingRect.top) <= Math.ceil(innerRect.top) + window.Pixel_CloseTo_Requirement; }; - var numberOfVisibleTiers = xAxis._element.selectAll("." + Plottable.Axis.Time.TIME_AXIS_TIER_CLASS).each(function (e, i) { + xAxis._element.selectAll("." + Plottable.Axes.Time.TIME_AXIS_TIER_CLASS).each(function (e, i) { var sel = d3.select(this); var visibility = sel.style("visibility"); - //HACKHACK window.getComputedStyle() is behaving weirdly in IE9. Further investigation required + // HACKHACK window.getComputedStyle() is behaving weirdly in IE9. Further investigation required if (visibility === "inherit") { visibility = getStyleInIE9(sel[0][0]); } @@ -907,21 +916,6 @@ describe("TimeAxis", function () { /// var assert = chai.assert; describe("NumericAxis", function () { - function boxesOverlap(boxA, boxB) { - if (boxA.right < boxB.left) { - return false; - } - if (boxA.left > boxB.right) { - return false; - } - if (boxA.bottom < boxB.top) { - return false; - } - if (boxA.top > boxB.bottom) { - return false; - } - return true; - } function boxIsInside(inner, outer, epsilon) { if (epsilon === void 0) { epsilon = 0; } if (inner.left < outer.left - epsilon) { @@ -947,25 +941,25 @@ describe("NumericAxis", function () { assert.operator(inner.bottom, "<", outer.bottom + epsilon, message + " (box inside (bottom))"); } it("tickLabelPosition() input validation", function () { - var scale = new Plottable.Scale.Linear(); - var horizontalAxis = new Plottable.Axis.Numeric(scale, "bottom"); + var scale = new Plottable.Scales.Linear(); + var horizontalAxis = new Plottable.Axes.Numeric(scale, "bottom"); assert.throws(function () { return horizontalAxis.tickLabelPosition("top"); }, "horizontal"); assert.throws(function () { return horizontalAxis.tickLabelPosition("bottom"); }, "horizontal"); - var verticalAxis = new Plottable.Axis.Numeric(scale, "left"); + var verticalAxis = new Plottable.Axes.Numeric(scale, "left"); assert.throws(function () { return verticalAxis.tickLabelPosition("left"); }, "vertical"); assert.throws(function () { return verticalAxis.tickLabelPosition("right"); }, "vertical"); }); it("draws tick labels correctly (horizontal)", function () { var SVG_WIDTH = 500; var SVG_HEIGHT = 100; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var scale = new Plottable.Scale.Linear(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var scale = new Plottable.Scales.Linear(); scale.range([0, SVG_WIDTH]); - var numericAxis = new Plottable.Axis.Numeric(scale, "bottom"); + var numericAxis = new Plottable.Axes.Numeric(scale, "bottom"); numericAxis.renderTo(svg); - var tickLabels = numericAxis._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_LABEL_CLASS); + var tickLabels = numericAxis._element.selectAll("." + Plottable.Axis.TICK_LABEL_CLASS); assert.operator(tickLabels[0].length, ">=", 2, "at least two tick labels were drawn"); - var tickMarks = numericAxis._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_MARK_CLASS); + var tickMarks = numericAxis._element.selectAll("." + Plottable.Axis.TICK_MARK_CLASS); assert.strictEqual(tickLabels[0].length, tickMarks[0].length, "there is one label per mark"); var i; var markBB; @@ -979,8 +973,8 @@ describe("NumericAxis", function () { } // labels to left numericAxis.tickLabelPosition("left"); - tickLabels = numericAxis._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_LABEL_CLASS); - tickMarks = numericAxis._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_MARK_CLASS); + tickLabels = numericAxis._element.selectAll("." + Plottable.Axis.TICK_LABEL_CLASS); + tickMarks = numericAxis._element.selectAll("." + Plottable.Axis.TICK_MARK_CLASS); for (i = 0; i < tickLabels[0].length; i++) { markBB = tickMarks[0][i].getBoundingClientRect(); labelBB = tickLabels[0][i].getBoundingClientRect(); @@ -988,8 +982,8 @@ describe("NumericAxis", function () { } // labels to right numericAxis.tickLabelPosition("right"); - tickLabels = numericAxis._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_LABEL_CLASS); - tickMarks = numericAxis._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_MARK_CLASS); + tickLabels = numericAxis._element.selectAll("." + Plottable.Axis.TICK_LABEL_CLASS); + tickMarks = numericAxis._element.selectAll("." + Plottable.Axis.TICK_MARK_CLASS); for (i = 0; i < tickLabels[0].length; i++) { markBB = tickMarks[0][i].getBoundingClientRect(); labelBB = tickLabels[0][i].getBoundingClientRect(); @@ -1000,14 +994,14 @@ describe("NumericAxis", function () { it("draws ticks correctly (vertical)", function () { var SVG_WIDTH = 100; var SVG_HEIGHT = 500; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var scale = new Plottable.Scale.Linear(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var scale = new Plottable.Scales.Linear(); scale.range([0, SVG_HEIGHT]); - var numericAxis = new Plottable.Axis.Numeric(scale, "left"); + var numericAxis = new Plottable.Axes.Numeric(scale, "left"); numericAxis.renderTo(svg); - var tickLabels = numericAxis._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_LABEL_CLASS); + var tickLabels = numericAxis._element.selectAll("." + Plottable.Axis.TICK_LABEL_CLASS); assert.operator(tickLabels[0].length, ">=", 2, "at least two tick labels were drawn"); - var tickMarks = numericAxis._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_MARK_CLASS); + var tickMarks = numericAxis._element.selectAll("." + Plottable.Axis.TICK_MARK_CLASS); assert.strictEqual(tickLabels[0].length, tickMarks[0].length, "there is one label per mark"); var i; var markBB; @@ -1021,8 +1015,8 @@ describe("NumericAxis", function () { } // labels to top numericAxis.tickLabelPosition("top"); - tickLabels = numericAxis._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_LABEL_CLASS); - tickMarks = numericAxis._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_MARK_CLASS); + tickLabels = numericAxis._element.selectAll("." + Plottable.Axis.TICK_LABEL_CLASS); + tickMarks = numericAxis._element.selectAll("." + Plottable.Axis.TICK_MARK_CLASS); for (i = 0; i < tickLabels[0].length; i++) { markBB = tickMarks[0][i].getBoundingClientRect(); labelBB = tickLabels[0][i].getBoundingClientRect(); @@ -1030,8 +1024,8 @@ describe("NumericAxis", function () { } // labels to bottom numericAxis.tickLabelPosition("bottom"); - tickLabels = numericAxis._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_LABEL_CLASS); - tickMarks = numericAxis._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_MARK_CLASS); + tickLabels = numericAxis._element.selectAll("." + Plottable.Axis.TICK_LABEL_CLASS); + tickMarks = numericAxis._element.selectAll("." + Plottable.Axis.TICK_MARK_CLASS); for (i = 0; i < tickLabels[0].length; i++) { markBB = tickMarks[0][i].getBoundingClientRect(); labelBB = tickLabels[0][i].getBoundingClientRect(); @@ -1042,13 +1036,13 @@ describe("NumericAxis", function () { it("uses the supplied Formatter", function () { var SVG_WIDTH = 100; var SVG_HEIGHT = 500; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var scale = new Plottable.Scale.Linear(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var scale = new Plottable.Scales.Linear(); scale.range([0, SVG_HEIGHT]); var formatter = Plottable.Formatters.fixed(2); - var numericAxis = new Plottable.Axis.Numeric(scale, "left", formatter); + var numericAxis = new Plottable.Axes.Numeric(scale, "left", formatter); numericAxis.renderTo(svg); - var tickLabels = numericAxis._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_LABEL_CLASS); + var tickLabels = numericAxis._element.selectAll("." + Plottable.Axis.TICK_LABEL_CLASS); tickLabels.each(function (d, i) { var labelText = d3.select(this).text(); var formattedValue = formatter(d); @@ -1059,10 +1053,10 @@ describe("NumericAxis", function () { it("can hide tick labels that don't fit", function () { var SVG_WIDTH = 500; var SVG_HEIGHT = 100; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var scale = new Plottable.Scale.Linear(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var scale = new Plottable.Scales.Linear(); scale.range([0, SVG_WIDTH]); - var numericAxis = new Plottable.Axis.Numeric(scale, "bottom"); + var numericAxis = new Plottable.Axes.Numeric(scale, "bottom"); numericAxis.showEndTickLabel("left", false); assert.isFalse(numericAxis.showEndTickLabel("left"), "retrieve showEndTickLabel setting"); numericAxis.showEndTickLabel("right", true); @@ -1070,7 +1064,7 @@ describe("NumericAxis", function () { assert.throws(function () { return numericAxis.showEndTickLabel("top", true); }, Error); assert.throws(function () { return numericAxis.showEndTickLabel("bottom", true); }, Error); numericAxis.renderTo(svg); - var tickLabels = numericAxis._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_LABEL_CLASS); + var tickLabels = numericAxis._element.selectAll("." + Plottable.Axis.TICK_LABEL_CLASS); var firstLabel = d3.select(tickLabels[0][0]); assert.strictEqual(firstLabel.style("visibility"), "hidden", "first label is hidden"); var lastLabel = d3.select(tickLabels[0][tickLabels[0].length - 1]); @@ -1080,13 +1074,13 @@ describe("NumericAxis", function () { it("tick labels don't overlap in a constrained space", function () { var SVG_WIDTH = 100; var SVG_HEIGHT = 100; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var scale = new Plottable.Scale.Linear(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var scale = new Plottable.Scales.Linear(); scale.range([0, SVG_WIDTH]); - var numericAxis = new Plottable.Axis.Numeric(scale, "bottom"); + var numericAxis = new Plottable.Axes.Numeric(scale, "bottom"); numericAxis.showEndTickLabel("left", false).showEndTickLabel("right", false); numericAxis.renderTo(svg); - var visibleTickLabels = numericAxis._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_LABEL_CLASS).filter(function (d, i) { + var visibleTickLabels = numericAxis._element.selectAll("." + Plottable.Axis.TICK_LABEL_CLASS).filter(function (d, i) { return d3.select(this).style("visibility") === "visible"; }); var numLabels = visibleTickLabels[0].length; @@ -1096,11 +1090,11 @@ describe("NumericAxis", function () { for (var j = i + 1; j < numLabels; j++) { box1 = visibleTickLabels[0][i].getBoundingClientRect(); box2 = visibleTickLabels[0][j].getBoundingClientRect(); - assert.isFalse(Plottable._Util.DOM.boxesOverlap(box1, box2), "tick labels don't overlap"); + assert.isFalse(Plottable.Utils.DOM.boxesOverlap(box1, box2), "tick labels don't overlap"); } } - numericAxis.orient("bottom"); - visibleTickLabels = numericAxis._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_LABEL_CLASS).filter(function (d, i) { + numericAxis.orientation("bottom"); + visibleTickLabels = numericAxis._element.selectAll("." + Plottable.Axis.TICK_LABEL_CLASS).filter(function (d, i) { return d3.select(this).style("visibility") === "visible"; }); numLabels = visibleTickLabels[0].length; @@ -1108,7 +1102,7 @@ describe("NumericAxis", function () { for (j = i + 1; j < numLabels; j++) { box1 = visibleTickLabels[0][i].getBoundingClientRect(); box2 = visibleTickLabels[0][j].getBoundingClientRect(); - assert.isFalse(Plottable._Util.DOM.boxesOverlap(box1, box2), "tick labels don't overlap"); + assert.isFalse(Plottable.Utils.DOM.boxesOverlap(box1, box2), "tick labels don't overlap"); } } svg.remove(); @@ -1116,8 +1110,8 @@ describe("NumericAxis", function () { it("allocates enough width to show all tick labels when vertical", function () { var SVG_WIDTH = 150; var SVG_HEIGHT = 500; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var scale = new Plottable.Scale.Linear(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var scale = new Plottable.Scales.Linear(); scale.domain([5, -5]); scale.range([0, SVG_HEIGHT]); var formatter = function (d) { @@ -1126,9 +1120,9 @@ describe("NumericAxis", function () { } return String(d); }; - var numericAxis = new Plottable.Axis.Numeric(scale, "left", formatter); + var numericAxis = new Plottable.Axes.Numeric(scale, "left", formatter); numericAxis.renderTo(svg); - var visibleTickLabels = numericAxis._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_LABEL_CLASS).filter(function (d, i) { + var visibleTickLabels = numericAxis._element.selectAll("." + Plottable.Axis.TICK_LABEL_CLASS).filter(function (d, i) { return d3.select(this).style("visibility") === "visible"; }); var boundingBox = numericAxis._element.select(".bounding-box").node().getBoundingClientRect(); @@ -1138,7 +1132,7 @@ describe("NumericAxis", function () { assert.isTrue(boxIsInside(labelBox, boundingBox), "tick labels don't extend outside the bounding box"); }); scale.domain([50000000000, -50000000000]); - visibleTickLabels = numericAxis._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_LABEL_CLASS).filter(function (d, i) { + visibleTickLabels = numericAxis._element.selectAll("." + Plottable.Axis.TICK_LABEL_CLASS).filter(function (d, i) { return d3.select(this).style("visibility") === "visible"; }); boundingBox = numericAxis._element.select(".bounding-box").node().getBoundingClientRect(); @@ -1151,14 +1145,14 @@ describe("NumericAxis", function () { it("allocates enough height to show all tick labels when horizontal", function () { var SVG_WIDTH = 500; var SVG_HEIGHT = 100; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var scale = new Plottable.Scale.Linear(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var scale = new Plottable.Scales.Linear(); scale.domain([5, -5]); scale.range([0, SVG_WIDTH]); var formatter = Plottable.Formatters.fixed(2); - var numericAxis = new Plottable.Axis.Numeric(scale, "bottom", formatter); + var numericAxis = new Plottable.Axes.Numeric(scale, "bottom", formatter); numericAxis.renderTo(svg); - var visibleTickLabels = numericAxis._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_LABEL_CLASS).filter(function (d, i) { + var visibleTickLabels = numericAxis._element.selectAll("." + Plottable.Axis.TICK_LABEL_CLASS).filter(function (d, i) { return d3.select(this).style("visibility") === "visible"; }); var boundingBox = numericAxis._element.select(".bounding-box").node().getBoundingClientRect(); @@ -1170,44 +1164,45 @@ describe("NumericAxis", function () { svg.remove(); }); it("truncates long labels", function () { - var data = [ + var dataset = new Plottable.Dataset([ { x: "A", y: 500000000 }, { x: "B", y: 400000000 } - ]; + ]); var SVG_WIDTH = 120; var SVG_HEIGHT = 300; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Linear(); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - var yLabel = new Plottable.Component.AxisLabel("LABEL", "left"); - var barPlot = new Plottable.Plot.Bar(xScale, yScale); - barPlot.project("x", "x", xScale); - barPlot.project("y", "y", yScale); - barPlot.addDataset(data); - var chart = new Plottable.Component.Table([ + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Linear(); + var yAxis = new Plottable.Axes.Numeric(yScale, "left"); + var yLabel = new Plottable.Components.Label("LABEL", "left"); + yLabel.classed(Plottable.Components.Label.AXIS_LABEL_CLASS, true); + var barPlot = new Plottable.Plots.Bar(xScale, yScale); + barPlot.x(function (d) { return d.x; }, xScale); + barPlot.y(function (d) { return d.y; }, yScale); + barPlot.addDataset(dataset); + var chart = new Plottable.Components.Table([ [yLabel, yAxis, barPlot] ]); chart.renderTo(svg); var labelContainer = d3.select(".tick-label-container"); d3.selectAll(".tick-label").each(function () { - assertBBoxInclusion(labelContainer, d3.select(this)); + TestMethods.assertBBoxInclusion(labelContainer, d3.select(this)); }); svg.remove(); }); it("confines labels to the bounding box for the axis", function () { var SVG_WIDTH = 500; var SVG_HEIGHT = 100; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var scale = new Plottable.Scale.Linear(); - var axis = new Plottable.Axis.Numeric(scale, "bottom"); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var scale = new Plottable.Scales.Linear(); + var axis = new Plottable.Axes.Numeric(scale, "bottom"); axis.formatter(function (d) { return "longstringsareverylong"; }); axis.renderTo(svg); var boundingBox = d3.select(".x-axis .bounding-box"); d3.selectAll(".x-axis .tick-label").each(function () { var tickLabel = d3.select(this); if (tickLabel.style("visibility") === "inherit") { - assertBBoxInclusion(boundingBox, tickLabel); + TestMethods.assertBBoxInclusion(boundingBox, tickLabel); } }); svg.remove(); @@ -1218,10 +1213,10 @@ describe("NumericAxis", function () { it("tick labels follow a sensible interval", function () { var SVG_WIDTH = 500; var SVG_HEIGHT = 100; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var scale = new Plottable.Scale.Linear(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var scale = new Plottable.Scales.Linear(); scale.domain([-2500000, 2500000]); - var baseAxis = new Plottable.Axis.Numeric(scale, "bottom"); + var baseAxis = new Plottable.Axes.Numeric(scale, "bottom"); baseAxis.renderTo(svg); var visibleTickLabels = baseAxis._element.selectAll(".tick-label").filter(function (d, i) { var visibility = d3.select(this).style("visibility"); @@ -1237,13 +1232,13 @@ describe("NumericAxis", function () { it("does not draw ticks marks outside of the svg", function () { var SVG_WIDTH = 300; var SVG_HEIGHT = 100; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var scale = new Plottable.Scale.Linear(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var scale = new Plottable.Scales.Linear(); scale.domain([0, 3]); scale.tickGenerator(function (s) { return [0, 1, 2, 3, 4]; }); - var baseAxis = new Plottable.Axis.Numeric(scale, "bottom"); + var baseAxis = new Plottable.Axes.Numeric(scale, "bottom"); baseAxis.renderTo(svg); var tickMarks = baseAxis._element.selectAll(".tick-mark"); tickMarks.each(function () { @@ -1256,10 +1251,10 @@ describe("NumericAxis", function () { it("renders tick labels properly when the domain is reversed", function () { var SVG_WIDTH = 300; var SVG_HEIGHT = 100; - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var scale = new Plottable.Scale.Linear(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var scale = new Plottable.Scales.Linear(); scale.domain([3, 0]); - var baseAxis = new Plottable.Axis.Numeric(scale, "bottom"); + var baseAxis = new Plottable.Axes.Numeric(scale, "bottom"); baseAxis.renderTo(svg); var tickLabels = baseAxis._element.selectAll(".tick-label").filter(function (d, i) { var visibility = d3.select(this).style("visibility"); @@ -1274,19 +1269,16 @@ describe("NumericAxis", function () { svg.remove(); }); it("constrained tick labels do not overlap tick marks", function () { - var svg = generateSVG(300, 400); - var yScale = new Plottable.Scale.Linear().numTicks(100); + var svg = TestMethods.generateSVG(100, 50); + var yScale = new Plottable.Scales.Linear(); yScale.domain([175, 185]); - var yAxis = new Plottable.Axis.Numeric(yScale, "left").tickLabelPosition("top").tickLength(50); - var chartTable = new Plottable.Component.Table([ - [yAxis], - ]); - chartTable.renderTo(svg); - var tickLabels = yAxis._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_LABEL_CLASS).filter(function (d, i) { + var yAxis = new Plottable.Axes.Numeric(yScale, "left").tickLabelPosition("top").tickLength(50); + yAxis.renderTo(svg); + var tickLabels = yAxis._element.selectAll("." + Plottable.Axis.TICK_LABEL_CLASS).filter(function (d, i) { var visibility = d3.select(this).style("visibility"); return (visibility === "visible") || (visibility === "inherit"); }); - var tickMarks = yAxis._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_MARK_CLASS).filter(function (d, i) { + var tickMarks = yAxis._element.selectAll("." + Plottable.Axis.TICK_MARK_CLASS).filter(function (d, i) { var visibility = d3.select(this).style("visibility"); return (visibility === "visible") || (visibility === "inherit"); }); @@ -1294,7 +1286,7 @@ describe("NumericAxis", function () { var tickLabelBox = this.getBoundingClientRect(); tickMarks.each(function () { var tickMarkBox = this.getBoundingClientRect(); - assert.isFalse(Plottable._Util.DOM.boxesOverlap(tickLabelBox, tickMarkBox), "tickMarks and tickLabels should not overlap when top/bottom/left/right position is used for the tickLabel"); + assert.isFalse(Plottable.Utils.DOM.boxesOverlap(tickLabelBox, tickMarkBox), "tickMarks and tickLabels should not overlap when top/bottom/left/right position is used for the tickLabel"); }); }); svg.remove(); @@ -1305,9 +1297,9 @@ describe("NumericAxis", function () { var assert = chai.assert; describe("Category Axes", function () { it("re-renders appropriately when data is changed", function () { - var svg = generateSVG(400, 400); - var xScale = new Plottable.Scale.Category().domain(["foo", "bar", "baz"]).range([400, 0]); - var ca = new Plottable.Axis.Category(xScale, "left"); + var svg = TestMethods.generateSVG(400, 400); + var xScale = new Plottable.Scales.Category().domain(["foo", "bar", "baz"]).range([400, 0]); + var ca = new Plottable.Axes.Category(xScale, "left"); ca.renderTo(svg); assert.deepEqual(ca._tickLabelContainer.selectAll(".tick-label").data(), xScale.domain(), "tick labels render domain"); assert.doesNotThrow(function () { return xScale.domain(["bar", "baz", "bam"]); }); @@ -1315,32 +1307,30 @@ describe("Category Axes", function () { svg.remove(); }); it("requests appropriate space when the scale has no domain", function () { - var svg = generateSVG(400, 400); - var scale = new Plottable.Scale.Category(); - var ca = new Plottable.Axis.Category(scale); - ca._anchor(svg); - var s = ca._requestedSpace(400, 400); - assert.operator(s.width, ">=", 0, "it requested 0 or more width"); - assert.operator(s.height, ">=", 0, "it requested 0 or more height"); - assert.isFalse(s.wantsWidth, "it doesn't want width"); - assert.isFalse(s.wantsHeight, "it doesn't want height"); + var svg = TestMethods.generateSVG(400, 400); + var scale = new Plottable.Scales.Category(); + var ca = new Plottable.Axes.Category(scale); + ca.anchor(svg); + var s = ca.requestedSpace(400, 400); + assert.operator(s.minWidth, ">=", 0, "it requested 0 or more width"); + assert.operator(s.minHeight, ">=", 0, "it requested 0 or more height"); svg.remove(); }); it("doesnt blow up for non-string data", function () { - var svg = generateSVG(1000, 400); + var svg = TestMethods.generateSVG(1000, 400); var domain = [null, undefined, true, 2, "foo"]; - var scale = new Plottable.Scale.Category().domain(domain); - var axis = new Plottable.Axis.Category(scale); + var scale = new Plottable.Scales.Category().domain(domain); + var axis = new Plottable.Axes.Category(scale); axis.renderTo(svg); var texts = svg.selectAll("text")[0].map(function (s) { return d3.select(s).text(); }); assert.deepEqual(texts, ["null", "undefined", "true", "2", "foo"]); svg.remove(); }); it("uses the formatter if supplied", function () { - var svg = generateSVG(400, 400); + var svg = TestMethods.generateSVG(400, 400); var domain = ["Air", "Bi", "Sea"]; - var scale = new Plottable.Scale.Category().domain(domain); - var axis = new Plottable.Axis.Category(scale, "bottom"); + var scale = new Plottable.Scales.Category().domain(domain); + var axis = new Plottable.Axes.Category(scale, "bottom"); var addPlane = function (l) { return l + "plane"; }; axis.formatter(addPlane); axis.renderTo(svg); @@ -1352,9 +1342,9 @@ describe("Category Axes", function () { svg.remove(); }); it("width accounts for gutter. ticklength, and padding on vertical axes", function () { - var svg = generateSVG(400, 400); - var xScale = new Plottable.Scale.Category().domain(["foo", "bar", "baz"]).range([400, 0]); - var ca = new Plottable.Axis.Category(xScale, "left"); + var svg = TestMethods.generateSVG(400, 400); + var xScale = new Plottable.Scales.Category().domain(["foo", "bar", "baz"]).range([400, 0]); + var ca = new Plottable.Axes.Category(xScale, "left"); ca.renderTo(svg); var axisWidth = ca.width(); ca.tickLabelPadding(ca.tickLabelPadding() + 5); @@ -1368,9 +1358,9 @@ describe("Category Axes", function () { svg.remove(); }); it("height accounts for gutter. ticklength, and padding on horizontal axes", function () { - var svg = generateSVG(400, 400); - var xScale = new Plottable.Scale.Category().domain(["foo", "bar", "baz"]).range([400, 0]); - var ca = new Plottable.Axis.Category(xScale, "bottom"); + var svg = TestMethods.generateSVG(400, 400); + var xScale = new Plottable.Scales.Category().domain(["foo", "bar", "baz"]).range([400, 0]); + var ca = new Plottable.Axes.Category(xScale, "bottom"); ca.renderTo(svg); var axisHeight = ca.height(); ca.tickLabelPadding(ca.tickLabelPadding() + 5); @@ -1385,10 +1375,10 @@ describe("Category Axes", function () { }); it("vertically aligns short words properly", function () { var SVG_WIDTH = 400; - var svg = generateSVG(SVG_WIDTH, 100); + var svg = TestMethods.generateSVG(SVG_WIDTH, 100); var years = ["2000", "2001", "2002", "2003"]; - var scale = new Plottable.Scale.Category().domain(years).range([0, SVG_WIDTH]); - var axis = new Plottable.Axis.Category(scale, "bottom"); + var scale = new Plottable.Scales.Category().domain(years).range([0, SVG_WIDTH]); + var axis = new Plottable.Axes.Category(scale, "bottom"); axis.renderTo(svg); var ticks = axis._content.selectAll("text"); var text = ticks[0].map(function (d) { return d3.select(d).text(); }); @@ -1408,16 +1398,17 @@ describe("Category Axes", function () { svg.remove(); }); it("axis should request more space if there's not enough space to fit the text", function () { - var svg = generateSVG(300, 300); + var svg = TestMethods.generateSVG(300, 300); var years = ["2000", "2001", "2002", "2003"]; - var scale = new Plottable.Scale.Category().domain(years); - var axis = new Plottable.Axis.Category(scale, "bottom"); + var scale = new Plottable.Scales.Category().domain(years); + var axis = new Plottable.Axes.Category(scale, "bottom"); axis.renderTo(svg); - var requestedSpace = axis._requestedSpace(300, 10); - assert.isTrue(requestedSpace.wantsHeight, "axis should ask for more space (horizontal orientation)"); - axis.orient("left"); - requestedSpace = axis._requestedSpace(10, 300); - assert.isTrue(requestedSpace.wantsWidth, "axis should ask for more space (vertical orientation)"); + var smallDimension = 10; + var spaceRequest = axis.requestedSpace(300, smallDimension); + assert.operator(spaceRequest.minHeight, ">", smallDimension, "horizontal axis requested more height if constrained"); + axis.orientation("left"); + spaceRequest = axis.requestedSpace(smallDimension, 300); + assert.operator(spaceRequest.minWidth, ">", smallDimension, "vertical axis requested more width if constrained"); svg.remove(); }); it("axis labels respect tick labels", function () { @@ -1425,32 +1416,32 @@ describe("Category Axes", function () { for (var i = 0; i < tickLabels[0].length; i++) { var tickLabelBox = tickLabels[0][i].getBoundingClientRect(); var tickMarkBox = tickMarks[0][i].getBoundingClientRect(); - assert.isFalse(Plottable._Util.DOM.boxesOverlap(tickLabelBox, tickMarkBox), "tick label and box do not overlap"); + assert.isFalse(Plottable.Utils.DOM.boxesOverlap(tickLabelBox, tickMarkBox), "tick label and box do not overlap"); } } - var svg = generateSVG(400, 300); - var yScale = new Plottable.Scale.Category(); - var axis = new Plottable.Axis.Category(yScale, "left"); + var svg = TestMethods.generateSVG(400, 300); + var yScale = new Plottable.Scales.Category(); + var axis = new Plottable.Axes.Category(yScale, "left"); yScale.domain(["A", "B", "C"]); axis.renderTo(svg); var tickLabels = axis._content.selectAll(".tick-label"); var tickMarks = axis._content.selectAll(".tick-mark"); verifyTickLabelOverlaps(tickLabels, tickMarks); - axis.orient("right"); + axis.orientation("right"); verifyTickLabelOverlaps(tickLabels, tickMarks); svg.remove(); }); it("axis should request more space when rotated than not rotated", function () { - var svg = generateSVG(300, 300); + var svg = TestMethods.generateSVG(300, 300); var labels = ["label1", "label2", "label100"]; - var scale = new Plottable.Scale.Category().domain(labels); - var axis = new Plottable.Axis.Category(scale, "bottom"); + var scale = new Plottable.Scales.Category().domain(labels); + var axis = new Plottable.Axes.Category(scale, "bottom"); axis.renderTo(svg); - var requestedSpace = axis._requestedSpace(300, 50); - var flatHeight = requestedSpace.height; + var requestedSpace = axis.requestedSpace(300, 50); + var flatHeight = requestedSpace.minHeight; axis.tickLabelAngle(-90); - requestedSpace = axis._requestedSpace(300, 50); - assert.isTrue(flatHeight < requestedSpace.height, "axis should request more height when tick labels are rotated"); + requestedSpace = axis.requestedSpace(300, 50); + assert.isTrue(flatHeight < requestedSpace.minHeight, "axis should request more height when tick labels are rotated"); svg.remove(); }); }); @@ -1459,31 +1450,31 @@ describe("Category Axes", function () { var assert = chai.assert; describe("Gridlines", function () { it("Gridlines and axis tick marks align", function () { - var svg = generateSVG(640, 480); - var xScale = new Plottable.Scale.Linear(); + var svg = TestMethods.generateSVG(640, 480); + var xScale = new Plottable.Scales.Linear(); xScale.domain([0, 10]); // manually set domain since we won't have a renderer - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); - var yScale = new Plottable.Scale.Linear(); + var xAxis = new Plottable.Axes.Numeric(xScale, "bottom"); + var yScale = new Plottable.Scales.Linear(); yScale.domain([0, 10]); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - var gridlines = new Plottable.Component.Gridlines(xScale, yScale); - var basicTable = new Plottable.Component.Table().addComponent(0, 0, yAxis).addComponent(0, 1, gridlines).addComponent(1, 1, xAxis); - basicTable._anchor(svg); - basicTable._computeLayout(); + var yAxis = new Plottable.Axes.Numeric(yScale, "left"); + var gridlines = new Plottable.Components.Gridlines(xScale, yScale); + var basicTable = new Plottable.Components.Table().add(yAxis, 0, 0).add(gridlines, 0, 1).add(xAxis, 1, 1); + basicTable.anchor(svg); + basicTable.computeLayout(); xScale.range([0, xAxis.width()]); // manually set range since we don't have a renderer yScale.range([yAxis.height(), 0]); - basicTable._render(); - var xAxisTickMarks = xAxis._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_MARK_CLASS)[0]; + basicTable.render(); + var xAxisTickMarks = xAxis._element.selectAll("." + Plottable.Axis.TICK_MARK_CLASS)[0]; var xGridlines = gridlines._element.select(".x-gridlines").selectAll("line")[0]; - assert.equal(xAxisTickMarks.length, xGridlines.length, "There is an x gridline for each x tick"); + assert.strictEqual(xAxisTickMarks.length, xGridlines.length, "There is an x gridline for each x tick"); for (var i = 0; i < xAxisTickMarks.length; i++) { var xTickMarkRect = xAxisTickMarks[i].getBoundingClientRect(); var xGridlineRect = xGridlines[i].getBoundingClientRect(); assert.closeTo(xTickMarkRect.left, xGridlineRect.left, 1, "x tick and gridline align"); } - var yAxisTickMarks = yAxis._element.selectAll("." + Plottable.Axis.AbstractAxis.TICK_MARK_CLASS)[0]; + var yAxisTickMarks = yAxis._element.selectAll("." + Plottable.Axis.TICK_MARK_CLASS)[0]; var yGridlines = gridlines._element.select(".y-gridlines").selectAll("line")[0]; - assert.equal(yAxisTickMarks.length, yGridlines.length, "There is an x gridline for each x tick"); + assert.strictEqual(yAxisTickMarks.length, yGridlines.length, "There is an x gridline for each x tick"); for (var j = 0; j < yAxisTickMarks.length; j++) { var yTickMarkRect = yAxisTickMarks[j].getBoundingClientRect(); var yGridlineRect = yGridlines[j].getBoundingClientRect(); @@ -1491,20 +1482,15 @@ describe("Gridlines", function () { } svg.remove(); }); - it("Unanchored Gridlines don't throw an error when scale updates", function () { - var xScale = new Plottable.Scale.Linear(); - var gridlines = new Plottable.Component.Gridlines(xScale, null); - xScale.domain([0, 1]); - // test passes if error is not thrown. - }); }); /// var assert = chai.assert; describe("Labels", function () { it("Standard text title label generates properly", function () { - var svg = generateSVG(400, 80); - var label = new Plottable.Component.TitleLabel("A CHART TITLE"); + var svg = TestMethods.generateSVG(400, 80); + var label = new Plottable.Components.Label("A CHART TITLE"); + label.classed(Plottable.Components.Label.TITLE_LABEL_CLASS, true); label.renderTo(svg); var content = label._content; assert.isTrue(label._element.classed("label"), "title element has label css class"); @@ -1512,132 +1498,138 @@ describe("Labels", function () { var textChildren = content.selectAll("text"); assert.lengthOf(textChildren, 1, "There is one text node in the parent element"); var text = content.select("text"); - var bbox = Plottable._Util.DOM.getBBox(text); + var bbox = Plottable.Utils.DOM.getBBox(text); assert.closeTo(bbox.height, label.height(), 0.5, "text height === label.minimumHeight()"); - assert.equal(text.node().textContent, "A CHART TITLE", "node's text content is as expected"); + assert.strictEqual(text.node().textContent, "A CHART TITLE", "node's text content is as expected"); svg.remove(); }); // Skipping due to FF odd client bounding rect computation - #1470. it.skip("Left-rotated text is handled properly", function () { - var svg = generateSVG(100, 400); - var label = new Plottable.Component.AxisLabel("LEFT-ROTATED LABEL", "left"); + var svg = TestMethods.generateSVG(100, 400); + var label = new Plottable.Components.Label("LEFT-ROTATED LABEL", "left"); + label.classed(Plottable.Components.Label.AXIS_LABEL_CLASS, true); label.renderTo(svg); var content = label._content; var text = content.select("text"); - var textBBox = Plottable._Util.DOM.getBBox(text); - assertBBoxInclusion(label._element.select(".bounding-box"), text); + var textBBox = Plottable.Utils.DOM.getBBox(text); + TestMethods.assertBBoxInclusion(label._element.select(".bounding-box"), text); assert.closeTo(textBBox.height, label.width(), window.Pixel_CloseTo_Requirement, "text height"); svg.remove(); }); // Skipping due to FF odd client bounding rect computation - #1470. it.skip("Right-rotated text is handled properly", function () { - var svg = generateSVG(100, 400); - var label = new Plottable.Component.AxisLabel("RIGHT-ROTATED LABEL", "right"); + var svg = TestMethods.generateSVG(100, 400); + var label = new Plottable.Components.Label("RIGHT-ROTATED LABEL", "right"); + label.classed(Plottable.Components.Label.AXIS_LABEL_CLASS, true); label.renderTo(svg); var content = label._content; var text = content.select("text"); - var textBBox = Plottable._Util.DOM.getBBox(text); - assertBBoxInclusion(label._element.select(".bounding-box"), text); + var textBBox = Plottable.Utils.DOM.getBBox(text); + TestMethods.assertBBoxInclusion(label._element.select(".bounding-box"), text); assert.closeTo(textBBox.height, label.width(), window.Pixel_CloseTo_Requirement, "text height"); svg.remove(); }); it("Label text can be changed after label is created", function () { - var svg = generateSVG(400, 80); - var label = new Plottable.Component.TitleLabel("a"); + var svg = TestMethods.generateSVG(400, 80); + var label = new Plottable.Components.Label("a"); + label.classed(Plottable.Components.Label.TITLE_LABEL_CLASS, true); label.renderTo(svg); - assert.equal(label._content.select("text").text(), "a", "the text starts at the specified string"); + assert.strictEqual(label._content.select("text").text(), "a", "the text starts at the specified string"); assert.operator(label.height(), ">", 0, "rowMin is > 0 for non-empty string"); label.text("hello world"); label.renderTo(svg); - assert.equal(label._content.select("text").text(), "hello world", "the label text updated properly"); + assert.strictEqual(label._content.select("text").text(), "hello world", "the label text updated properly"); assert.operator(label.height(), ">", 0, "rowMin is > 0 for non-empty string"); svg.remove(); }); // skipping because Dan is rewriting labels and the height test fails it.skip("Superlong text is handled in a sane fashion", function () { var svgWidth = 400; - var svg = generateSVG(svgWidth, 80); - var label = new Plottable.Component.TitleLabel("THIS LABEL IS SO LONG WHOEVER WROTE IT WAS PROBABLY DERANGED"); + var svg = TestMethods.generateSVG(svgWidth, 80); + var label = new Plottable.Components.Label("THIS LABEL IS SO LONG WHOEVER WROTE IT WAS PROBABLY DERANGED"); + label.classed(Plottable.Components.Label.TITLE_LABEL_CLASS, true); label.renderTo(svg); var content = label._content; var text = content.select("text"); - var bbox = Plottable._Util.DOM.getBBox(text); - assert.equal(bbox.height, label.height(), "text height === label.minimumHeight()"); + var bbox = Plottable.Utils.DOM.getBBox(text); + assert.strictEqual(bbox.height, label.height(), "text height === label.minimumHeight()"); assert.operator(bbox.width, "<=", svgWidth, "the text is not wider than the SVG width"); svg.remove(); }); it("text in a tiny box is truncated to empty string", function () { - var svg = generateSVG(10, 10); - var label = new Plottable.Component.TitleLabel("Yeah, not gonna fit..."); + var svg = TestMethods.generateSVG(10, 10); + var label = new Plottable.Components.Label("Yeah, not gonna fit..."); + label.classed(Plottable.Components.Label.TITLE_LABEL_CLASS, true); label.renderTo(svg); var text = label._content.select("text"); - assert.equal(text.text(), "", "text was truncated to empty string"); + assert.strictEqual(text.text(), "", "text was truncated to empty string"); svg.remove(); }); it("centered text in a table is positioned properly", function () { - var svg = generateSVG(400, 400); - var label = new Plottable.Component.TitleLabel("X"); - var t = new Plottable.Component.Table().addComponent(0, 0, label).addComponent(1, 0, new Plottable.Component.AbstractComponent()); + var svg = TestMethods.generateSVG(400, 400); + var label = new Plottable.Components.Label("X"); + var t = new Plottable.Components.Table().add(label, 0, 0).add(new Plottable.Component(), 1, 0); t.renderTo(svg); var textTranslate = d3.transform(label._content.select("g").attr("transform")).translate; var eleTranslate = d3.transform(label._element.attr("transform")).translate; - var textWidth = Plottable._Util.DOM.getBBox(label._content.select("text")).width; + var textWidth = Plottable.Utils.DOM.getBBox(label._content.select("text")).width; assert.closeTo(eleTranslate[0] + textTranslate[0] + textWidth / 2, 200, 5, "label is centered"); svg.remove(); }); it("if a label text is changed to empty string, width updates to 0", function () { - var svg = generateSVG(400, 400); - var label = new Plottable.Component.TitleLabel("foo"); + var svg = TestMethods.generateSVG(400, 400); + var label = new Plottable.Components.Label("foo"); label.renderTo(svg); label.text(""); - assert.equal(label.width(), 0, "width updated to 0"); + assert.strictEqual(label.width(), 0, "width updated to 0"); svg.remove(); }); it("unsupported alignments and orientations are unsupported", function () { - assert.throws(function () { return new Plottable.Component.Label("foo", "bar"); }, Error, "not a valid orientation"); + assert.throws(function () { return new Plottable.Components.Label("foo", "bar"); }, Error, "not a valid orientation"); }); // Skipping due to FF odd client bounding rect computation - #1470. it.skip("Label orientation can be changed after label is created", function () { - var svg = generateSVG(400, 400); - var label = new Plottable.Component.AxisLabel("CHANGING ORIENTATION"); + var svg = TestMethods.generateSVG(400, 400); + var label = new Plottable.Components.Label("CHANGING ORIENTATION"); + label.classed(Plottable.Components.Label.AXIS_LABEL_CLASS, true); label.renderTo(svg); var content = label._content; var text = content.select("text"); - var bbox = Plottable._Util.DOM.getBBox(text); + var bbox = Plottable.Utils.DOM.getBBox(text); assert.closeTo(bbox.height, label.height(), 1, "label is in horizontal position"); - label.orient("right"); + label.orientation("right"); text = content.select("text"); - bbox = Plottable._Util.DOM.getBBox(text); - assertBBoxInclusion(label._element.select(".bounding-box"), text); + bbox = Plottable.Utils.DOM.getBBox(text); + TestMethods.assertBBoxInclusion(label._element.select(".bounding-box"), text); assert.closeTo(bbox.height, label.width(), window.Pixel_CloseTo_Requirement, "label is in vertical position"); svg.remove(); }); it("padding reacts well under align", function () { - var svg = generateSVG(400, 200); - var testLabel = new Plottable.Component.Label("testing label").padding(30).xAlign("left"); - var longLabel = new Plottable.Component.Label("LONG LABELLLLLLLLLLLLLLLLL").xAlign("left"); - var topLabel = new Plottable.Component.Label("label").yAlign("bottom"); - new Plottable.Component.Table([[topLabel], [testLabel], [longLabel]]).renderTo(svg); + var svg = TestMethods.generateSVG(400, 200); + var testLabel = new Plottable.Components.Label("testing label").padding(30).xAlignment("left"); + var longLabel = new Plottable.Components.Label("LONG LABELLLLLLLLLLLLLLLLL").xAlignment("left"); + var topLabel = new Plottable.Components.Label("label").yAlignment("bottom"); + new Plottable.Components.Table([[topLabel], [testLabel], [longLabel]]).renderTo(svg); var testTextRect = testLabel._element.select("text").node().getBoundingClientRect(); var longTextRect = longLabel._element.select("text").node().getBoundingClientRect(); assert.closeTo(testTextRect.left, longTextRect.left + 30, 2, "left difference by padding amount"); - testLabel.xAlign("right"); + testLabel.xAlignment("right"); testTextRect = testLabel._element.select("text").node().getBoundingClientRect(); longTextRect = longLabel._element.select("text").node().getBoundingClientRect(); assert.closeTo(testTextRect.right, longTextRect.right - 30, 2, "right difference by padding amount"); - testLabel.yAlign("bottom"); + testLabel.yAlignment("bottom"); testTextRect = testLabel._element.select("text").node().getBoundingClientRect(); longTextRect = longLabel._element.select("text").node().getBoundingClientRect(); assert.closeTo(testTextRect.bottom, longTextRect.top - 30, 2, "vertical difference by padding amount"); - testLabel.yAlign("top"); + testLabel.yAlignment("top"); testTextRect = testLabel._element.select("text").node().getBoundingClientRect(); var topTextRect = topLabel._element.select("text").node().getBoundingClientRect(); assert.closeTo(testTextRect.top, topTextRect.bottom + 30, 2, "vertical difference by padding amount"); svg.remove(); }); it("padding puts space around the label", function () { - var svg = generateSVG(400, 200); - var testLabel = new Plottable.Component.Label("testing label").padding(30); + var svg = TestMethods.generateSVG(400, 200); + var testLabel = new Plottable.Components.Label("testing label").padding(30); testLabel.renderTo(svg); var measurer = new SVGTypewriter.Measurers.Measurer(svg); var measure = measurer.measure("testing label"); @@ -1648,7 +1640,7 @@ describe("Labels", function () { svg.remove(); }); it("negative padding throws an error", function () { - var testLabel = new Plottable.Component.Label("testing label"); + var testLabel = new Plottable.Components.Label("testing label"); assert.throws(function () { return testLabel.padding(-10); }, Error, "Cannot be less than 0"); }); }); @@ -1659,12 +1651,12 @@ describe("Legend", function () { var svg; var color; var legend; - var entrySelector = "." + Plottable.Component.Legend.LEGEND_ENTRY_CLASS; - var rowSelector = "." + Plottable.Component.Legend.LEGEND_ROW_CLASS; + var entrySelector = "." + Plottable.Components.Legend.LEGEND_ENTRY_CLASS; + var rowSelector = "." + Plottable.Components.Legend.LEGEND_ROW_CLASS; beforeEach(function () { - svg = generateSVG(400, 400); - color = new Plottable.Scale.Color(); - legend = new Plottable.Component.Legend(color); + svg = TestMethods.generateSVG(400, 400); + color = new Plottable.Scales.Color(); + legend = new Plottable.Components.Legend(color); }); it("a basic legend renders", function () { color.domain(["foo", "bar", "baz"]); @@ -1672,37 +1664,37 @@ describe("Legend", function () { var rows = legend._content.selectAll(entrySelector); assert.lengthOf(rows[0], color.domain().length, "one entry is created for each item in the domain"); rows.each(function (d, i) { - assert.equal(d, color.domain()[i], "the data is set properly"); + assert.strictEqual(d, color.domain()[i], "the data is set properly"); var d3this = d3.select(this); var text = d3this.select("text").text(); - assert.equal(text, d, "the text node has correct text"); - var symbol = d3this.select("." + Plottable.Component.Legend.LEGEND_SYMBOL_CLASS); - assert.equal(symbol.attr("fill"), color.scale(d), "the symbol's fill is set properly"); + assert.strictEqual(text, d, "the text node has correct text"); + var symbol = d3this.select("." + Plottable.Components.Legend.LEGEND_SYMBOL_CLASS); + assert.strictEqual(symbol.attr("fill"), color.scale(d), "the symbol's fill is set properly"); }); svg.remove(); }); it("legend domain can be updated after initialization, and height updates as well", function () { legend.renderTo(svg); legend.scale(color); - assert.equal(legend._requestedSpace(200, 200).height, 10, "there is a padding requested height when domain is empty"); + assert.strictEqual(legend.requestedSpace(200, 200).minHeight, 10, "there is a padding requested height when domain is empty"); color.domain(["foo", "bar"]); - var height1 = legend._requestedSpace(400, 400).height; + var height1 = legend.requestedSpace(400, 400).minHeight; var actualHeight1 = legend.height(); assert.operator(height1, ">", 0, "changing the domain gives a positive height"); color.domain(["foo", "bar", "baz"]); - assert.operator(legend._requestedSpace(400, 400).height, ">", height1, "adding to the domain increases the height requested"); + assert.operator(legend.requestedSpace(400, 400).minHeight, ">", height1, "adding to the domain increases the height requested"); var actualHeight2 = legend.height(); assert.operator(actualHeight1, "<", actualHeight2, "Changing the domain caused the legend to re-layout with more height"); var numRows = legend._content.selectAll(rowSelector)[0].length; - assert.equal(numRows, 3, "there are 3 rows"); + assert.strictEqual(numRows, 3, "there are 3 rows"); svg.remove(); }); it("a legend with many labels does not overflow vertically", function () { color.domain(["alpha", "beta", "gamma", "delta", "omega", "omicron", "persei", "eight"]); legend.renderTo(svg); - var contentBBox = Plottable._Util.DOM.getBBox(legend._content); + var contentBBox = Plottable.Utils.DOM.getBBox(legend._content); var contentBottomEdge = contentBBox.y + contentBBox.height; - var bboxBBox = Plottable._Util.DOM.getBBox(legend._element.select(".bounding-box")); + var bboxBBox = Plottable.Utils.DOM.getBBox(legend._element.select(".bounding-box")); var bboxBottomEdge = bboxBBox.y + bboxBBox.height; assert.operator(contentBottomEdge, "<=", bboxBottomEdge, "content does not extend past bounding box"); svg.remove(); @@ -1724,10 +1716,10 @@ describe("Legend", function () { color.domain(["foo", "bar", "baz"]); legend.renderTo(svg); var numRows = legend._content.selectAll(rowSelector)[0].length; - assert.equal(numRows, 3, "there are 3 legend rows initially"); - legend._render(); + assert.strictEqual(numRows, 3, "there are 3 legend rows initially"); + legend.render(); numRows = legend._content.selectAll(rowSelector)[0].length; - assert.equal(numRows, 3, "there are 3 legend rows after second render"); + assert.strictEqual(numRows, 3, "there are 3 legend rows after second render"); svg.remove(); }); it("re-rendering the legend with a new domain will do the right thing", function () { @@ -1736,11 +1728,11 @@ describe("Legend", function () { var newDomain = ["mushu", "foo", "persei", "baz", "eight"]; color.domain(newDomain); legend._content.selectAll(entrySelector).each(function (d, i) { - assert.equal(d, newDomain[i], "the data is set correctly"); + assert.strictEqual(d, newDomain[i], "the data is set correctly"); var text = d3.select(this).select("text").text(); - assert.equal(text, d, "the text was set properly"); - var fill = d3.select(this).select("." + Plottable.Component.Legend.LEGEND_SYMBOL_CLASS).attr("fill"); - assert.equal(fill, color.scale(d), "the fill was set properly"); + assert.strictEqual(text, d, "the text was set properly"); + var fill = d3.select(this).select("." + Plottable.Components.Legend.LEGEND_SYMBOL_CLASS).attr("fill"); + assert.strictEqual(fill, color.scale(d), "the fill was set properly"); }); assert.lengthOf(legend._content.selectAll(rowSelector)[0], 5, "there are the right number of legend elements"); svg.remove(); @@ -1749,15 +1741,15 @@ describe("Legend", function () { color.domain(["foo", "bar", "baz"]); legend.renderTo(svg); var newDomain = ["a", "b", "c"]; - var newColorScale = new Plottable.Scale.Color("20"); + var newColorScale = new Plottable.Scales.Color("20"); newColorScale.domain(newDomain); legend.scale(newColorScale); legend._content.selectAll(entrySelector).each(function (d, i) { - assert.equal(d, newDomain[i], "the data is set correctly"); + assert.strictEqual(d, newDomain[i], "the data is set correctly"); var text = d3.select(this).select("text").text(); - assert.equal(text, d, "the text was set properly"); - var fill = d3.select(this).select("." + Plottable.Component.Legend.LEGEND_SYMBOL_CLASS).attr("fill"); - assert.equal(fill, newColorScale.scale(d), "the fill was set properly"); + assert.strictEqual(text, d, "the text was set properly"); + var fill = d3.select(this).select("." + Plottable.Components.Legend.LEGEND_SYMBOL_CLASS).attr("fill"); + assert.strictEqual(fill, newColorScale.scale(d), "the fill was set properly"); }); svg.remove(); }); @@ -1765,17 +1757,17 @@ describe("Legend", function () { color.domain(["foo", "bar", "baz"]); legend.renderTo(svg); var tempDomain = ["a", "b", "c"]; - var newColorScale = new Plottable.Scale.Color("20"); + var newColorScale = new Plottable.Scales.Color("20"); newColorScale.domain(tempDomain); legend.scale(newColorScale); var newDomain = ["a", "foo", "d"]; newColorScale.domain(newDomain); legend._content.selectAll(entrySelector).each(function (d, i) { - assert.equal(d, newDomain[i], "the data is set correctly"); + assert.strictEqual(d, newDomain[i], "the data is set correctly"); var text = d3.select(this).select("text").text(); - assert.equal(text, d, "the text was set properly"); - var fill = d3.select(this).select("." + Plottable.Component.Legend.LEGEND_SYMBOL_CLASS).attr("fill"); - assert.equal(fill, newColorScale.scale(d), "the fill was set properly"); + assert.strictEqual(text, d, "the text was set properly"); + var fill = d3.select(this).select("." + Plottable.Components.Legend.LEGEND_SYMBOL_CLASS).attr("fill"); + assert.strictEqual(fill, newColorScale.scale(d), "the fill was set properly"); }); svg.remove(); }); @@ -1786,20 +1778,20 @@ describe("Legend", function () { style.attr("type", "text/css"); function verifySymbolHeight() { var text = legend._content.select("text"); - var icon = legend._content.select("." + Plottable.Component.Legend.LEGEND_SYMBOL_CLASS); - var textHeight = Plottable._Util.DOM.getBBox(text).height; + var icon = legend._content.select("." + Plottable.Components.Legend.LEGEND_SYMBOL_CLASS); + var textHeight = Plottable.Utils.DOM.getBBox(text).height; var symbolHeight = icon.node().getBoundingClientRect().height; assert.operator(symbolHeight, "<", textHeight, "icons too small: symbolHeight < textHeight"); assert.operator(symbolHeight, ">", textHeight / 2, "icons too big: textHeight / 2 > symbolHeight"); } verifySymbolHeight(); style.text(".plottable .legend text { font-size: 60px; }"); - legend._computeLayout(); - legend._render(); + legend.computeLayout(); + legend.render(); verifySymbolHeight(); style.text(".plottable .legend text { font-size: 10px; }"); - legend._computeLayout(); - legend._render(); + legend.computeLayout(); + legend.render(); verifySymbolHeight(); svg.remove(); }); @@ -1829,12 +1821,20 @@ describe("Legend", function () { assert.lengthOf(rows[0], 2, "Wrapped text on to two rows when space is constrained"); legend.detach(); svg.remove(); - svg = generateSVG(100, 100); + svg = TestMethods.generateSVG(100, 100); legend.renderTo(svg); rows = legend._element.selectAll(rowSelector); assert.lengthOf(rows[0], 3, "Wrapped text on to three rows when further constrained"); svg.remove(); }); + it("requests more width if entries would be truncated", function () { + color.domain(["George Waaaaaashington", "John Adaaaams", "Thomaaaaas Jefferson"]); + legend.renderTo(svg); // have to be in DOM to measure + var idealSpaceRequest = legend.requestedSpace(Infinity, Infinity); + var constrainedRequest = legend.requestedSpace(idealSpaceRequest.minWidth * 0.9, Infinity); + assert.strictEqual(idealSpaceRequest.minWidth, constrainedRequest.minWidth, "won't settle for less width if entries would be truncated"); + svg.remove(); + }); it("getEntry() retrieves the correct entry for vertical legends", function () { color.domain(["AA", "BB", "CC"]); legend.maxEntriesPerRow(1); @@ -1870,22 +1870,22 @@ describe("Legend", function () { }); it("truncates and hides entries if space is constrained for a horizontal legend", function () { svg.remove(); - svg = generateSVG(70, 400); + svg = TestMethods.generateSVG(70, 400); legend.maxEntriesPerRow(Infinity); legend.renderTo(svg); var textEls = legend._element.selectAll("text"); textEls.each(function (d) { var textEl = d3.select(this); - assertBBoxInclusion(legend._element, textEl); + TestMethods.assertBBoxInclusion(legend._element, textEl); }); legend.detach(); svg.remove(); - svg = generateSVG(100, 50); + svg = TestMethods.generateSVG(100, 50); legend.renderTo(svg); textEls = legend._element.selectAll("text"); textEls.each(function (d) { var textEl = d3.select(this); - assertBBoxInclusion(legend._element, textEl); + TestMethods.assertBBoxInclusion(legend._element, textEl); }); svg.remove(); }); @@ -1897,8 +1897,8 @@ describe("InterpolatedColorLegend", function () { var svg; var colorScale; beforeEach(function () { - svg = generateSVG(400, 400); - colorScale = new Plottable.Scale.InterpolatedColor(); + svg = TestMethods.generateSVG(400, 400); + colorScale = new Plottable.Scales.InterpolatedColor(); }); function assertBasicRendering(legend) { var scaleDomain = colorScale.domain(); @@ -1910,16 +1910,16 @@ describe("InterpolatedColorLegend", function () { var swatchContainerBCR = swatchContainer.node().getBoundingClientRect(); var swatchBoundingBox = legendElement.select(".swatch-bounding-box"); var boundingBoxBCR = swatchBoundingBox.node().getBoundingClientRect(); - assert.isTrue(Plottable._Util.DOM.boxIsInside(swatchContainerBCR, boundingBoxBCR), "bounding box contains all swatches"); + assert.isTrue(Plottable.Utils.DOM.boxIsInside(swatchContainerBCR, boundingBoxBCR), "bounding box contains all swatches"); var elementBCR = legendElement.node().getBoundingClientRect(); - assert.isTrue(Plottable._Util.DOM.boxIsInside(swatchContainerBCR, elementBCR), "swatches are drawn within the legend's element"); + assert.isTrue(Plottable.Utils.DOM.boxIsInside(swatchContainerBCR, elementBCR), "swatches are drawn within the legend's element"); var formattedDomainValues = scaleDomain.map(legend._formatter); var labels = legendElement.selectAll("text"); var labelTexts = labels[0].map(function (textNode) { return textNode.textContent; }); assert.deepEqual(labelTexts, formattedDomainValues, "formatter is used to format label text"); } it("renders correctly (orientation: horizontal)", function () { - var legend = new Plottable.Component.InterpolatedColorLegend(colorScale, "horizontal"); + var legend = new Plottable.Components.InterpolatedColorLegend(colorScale, "horizontal"); legend.renderTo(svg); assertBasicRendering(legend); var legendElement = legend._element; @@ -1933,7 +1933,7 @@ describe("InterpolatedColorLegend", function () { svg.remove(); }); it("renders correctly (orientation: right)", function () { - var legend = new Plottable.Component.InterpolatedColorLegend(colorScale, "right"); + var legend = new Plottable.Components.InterpolatedColorLegend(colorScale, "right"); legend.renderTo(svg); assertBasicRendering(legend); var legendElement = legend._element; @@ -1948,7 +1948,7 @@ describe("InterpolatedColorLegend", function () { svg.remove(); }); it("renders correctly (orientation: left)", function () { - var legend = new Plottable.Component.InterpolatedColorLegend(colorScale, "left"); + var legend = new Plottable.Components.InterpolatedColorLegend(colorScale, "left"); legend.renderTo(svg); assertBasicRendering(legend); var legendElement = legend._element; @@ -1963,68 +1963,68 @@ describe("InterpolatedColorLegend", function () { svg.remove(); }); it("re-renders when scale domain updates", function () { - var legend = new Plottable.Component.InterpolatedColorLegend(colorScale, "horizontal"); + var legend = new Plottable.Components.InterpolatedColorLegend(colorScale, "horizontal"); legend.renderTo(svg); colorScale.domain([0, 85]); assertBasicRendering(legend); svg.remove(); }); - it("orient() input-checking", function () { - var legend = new Plottable.Component.InterpolatedColorLegend(colorScale, "horizontal"); - legend.orient("horizontal"); // should work - legend.orient("right"); // should work - legend.orient("left"); // should work - assert.throws(function () { return legend.orient("blargh"); }, "not a valid orientation"); + it("orientation() input-checking", function () { + var legend = new Plottable.Components.InterpolatedColorLegend(colorScale, "horizontal"); + legend.orientation("horizontal"); // should work + legend.orientation("right"); // should work + legend.orientation("left"); // should work + assert.throws(function () { return legend.orientation("blargh"); }, "not a valid orientation"); svg.remove(); }); it("orient() triggers layout computation", function () { - var legend = new Plottable.Component.InterpolatedColorLegend(colorScale, "horizontal"); + var legend = new Plottable.Components.InterpolatedColorLegend(colorScale, "horizontal"); legend.renderTo(svg); var widthBefore = legend.width(); var heightBefore = legend.height(); - legend.orient("right"); + legend.orientation("right"); assert.notEqual(legend.width(), widthBefore, "proportions changed (width)"); assert.notEqual(legend.height(), heightBefore, "proportions changed (height)"); svg.remove(); }); it("renders correctly when width is constrained (orientation: horizontal)", function () { svg.attr("width", 100); - var legend = new Plottable.Component.InterpolatedColorLegend(colorScale, "horizontal"); + var legend = new Plottable.Components.InterpolatedColorLegend(colorScale, "horizontal"); legend.renderTo(svg); assertBasicRendering(legend); svg.remove(); }); it("renders correctly when height is constrained (orientation: horizontal)", function () { svg.attr("height", 20); - var legend = new Plottable.Component.InterpolatedColorLegend(colorScale, "horizontal"); + var legend = new Plottable.Components.InterpolatedColorLegend(colorScale, "horizontal"); legend.renderTo(svg); assertBasicRendering(legend); svg.remove(); }); it("renders correctly when width is constrained (orientation: right)", function () { svg.attr("width", 30); - var legend = new Plottable.Component.InterpolatedColorLegend(colorScale, "right"); + var legend = new Plottable.Components.InterpolatedColorLegend(colorScale, "right"); legend.renderTo(svg); assertBasicRendering(legend); svg.remove(); }); it("renders correctly when height is constrained (orientation: right)", function () { svg.attr("height", 100); - var legend = new Plottable.Component.InterpolatedColorLegend(colorScale, "right"); + var legend = new Plottable.Components.InterpolatedColorLegend(colorScale, "right"); legend.renderTo(svg); assertBasicRendering(legend); svg.remove(); }); it("renders correctly when width is constrained (orientation: left)", function () { svg.attr("width", 30); - var legend = new Plottable.Component.InterpolatedColorLegend(colorScale, "left"); + var legend = new Plottable.Components.InterpolatedColorLegend(colorScale, "left"); legend.renderTo(svg); assertBasicRendering(legend); svg.remove(); }); it("renders correctly when height is constrained (orientation: left)", function () { svg.attr("height", 100); - var legend = new Plottable.Component.InterpolatedColorLegend(colorScale, "left"); + var legend = new Plottable.Components.InterpolatedColorLegend(colorScale, "left"); legend.renderTo(svg); assertBasicRendering(legend); svg.remove(); @@ -2035,8 +2035,8 @@ describe("InterpolatedColorLegend", function () { var assert = chai.assert; describe("SelectionBoxLayer", function () { it("boxVisible()", function () { - var svg = generateSVG(); - var sbl = new Plottable.Component.SelectionBoxLayer(); + var svg = TestMethods.generateSVG(); + var sbl = new Plottable.Components.SelectionBoxLayer(); sbl.renderTo(svg); var selectionBox = svg.select(".selection-box"); assert.isTrue(selectionBox.empty(), "initilizes without box in DOM"); @@ -2049,8 +2049,8 @@ describe("SelectionBoxLayer", function () { svg.remove(); }); it("bounds()", function () { - var svg = generateSVG(); - var sbl = new Plottable.Component.SelectionBoxLayer(); + var svg = TestMethods.generateSVG(); + var sbl = new Plottable.Components.SelectionBoxLayer(); var topLeft = { x: 100, y: 100 @@ -2067,7 +2067,7 @@ describe("SelectionBoxLayer", function () { sbl.renderTo(svg); function assertCorrectRendering(expectedTL, expectedBR, msg) { var selectionBox = svg.select(".selection-box"); - var bbox = Plottable._Util.DOM.getBBox(selectionBox); + var bbox = Plottable.Utils.DOM.getBBox(selectionBox); assert.strictEqual(bbox.x, expectedTL.x, msg + " (x-origin)"); assert.strictEqual(bbox.x, expectedTL.y, msg + " (y-origin)"); assert.strictEqual(bbox.width, expectedBR.x - expectedTL.x, msg + " (width)"); @@ -2088,11 +2088,11 @@ describe("SelectionBoxLayer", function () { svg.remove(); }); it("has an effective size of 0, but will occupy all offered space", function () { - var sbl = new Plottable.Component.SelectionBoxLayer(); - var request = sbl._requestedSpace(400, 400); - verifySpaceRequest(request, 0, 0, false, false, "occupies and asks for no space"); - assert.isTrue(sbl._isFixedWidth(), "fixed width"); - assert.isTrue(sbl._isFixedHeight(), "fixed height"); + var sbl = new Plottable.Components.SelectionBoxLayer(); + var request = sbl.requestedSpace(400, 400); + TestMethods.verifySpaceRequest(request, 0, 0, "does not request any space"); + assert.isTrue(sbl.fixedWidth(), "fixed width"); + assert.isTrue(sbl.fixedHeight(), "fixed height"); }); }); @@ -2110,23 +2110,26 @@ var CountingPlot = (function (_super) { _super.apply(this, arguments); this.renders = 0; } - CountingPlot.prototype._render = function () { + CountingPlot.prototype.render = function () { ++this.renders; - return _super.prototype._render.call(this); + return _super.prototype.render.call(this); }; return CountingPlot; -})(Plottable.Plot.AbstractPlot); +})(Plottable.Plot); describe("Plots", function () { - describe("Abstract Plot", function () { + describe("Plot", function () { it("Plots default correctly", function () { - var r = new Plottable.Plot.AbstractPlot(); - assert.isTrue(r.clipPathEnabled, "clipPathEnabled defaults to true"); + var svg = TestMethods.generateSVG(400, 300); + var r = new Plottable.Plot(); + r.renderTo(svg); + TestMethods.verifyClipPath(r); + svg.remove(); }); it("Base Plot functionality works", function () { - var svg = generateSVG(400, 300); - var r = new Plottable.Plot.AbstractPlot(); - r._anchor(svg); - r._computeLayout(); + var svg = TestMethods.generateSVG(400, 300); + var r = new Plottable.Plot(); + r.anchor(svg); + r.computeLayout(); var renderArea = r._content.select(".render-area"); assert.isNotNull(renderArea.node(), "there is a render-area"); svg.remove(); @@ -2135,81 +2138,89 @@ describe("Plots", function () { var dFoo = new Plottable.Dataset(["foo"], { cssClass: "bar" }); var dBar = new Plottable.Dataset(["bar"], { cssClass: "boo" }); var r = new CountingPlot(); - r.addDataset("foo", dFoo); - assert.equal(1, r.renders, "initial render due to addDataset"); - dFoo.broadcaster.broadcast(); - assert.equal(2, r.renders, "we re-render when our dataset changes"); - r.addDataset("bar", dBar); - assert.equal(3, r.renders, "we should redraw when we add a dataset"); - dFoo.broadcaster.broadcast(); - assert.equal(4, r.renders, "we should still listen to the first dataset"); - dBar.broadcaster.broadcast(); - assert.equal(5, r.renders, "we should listen to the new dataset"); - r.removeDataset("foo"); - assert.equal(6, r.renders, "we re-render on dataset removal"); - dFoo.broadcaster.broadcast(); - assert.equal(6, r.renders, "we don't listen to removed datasets"); + r.addDataset(dFoo); + assert.strictEqual(1, r.renders, "initial render due to addDataset"); + dFoo.data(dFoo.data()); + assert.strictEqual(2, r.renders, "we re-render when our dataset changes"); + r.addDataset(dBar); + assert.strictEqual(3, r.renders, "we should redraw when we add a dataset"); + dFoo.data(dFoo.data()); + assert.strictEqual(4, r.renders, "we should still listen to the first dataset"); + dFoo.data(dFoo.data()); + assert.strictEqual(5, r.renders, "we should listen to the new dataset"); + r.removeDataset(dFoo); + assert.strictEqual(6, r.renders, "we re-render on dataset removal"); + dFoo.data(dFoo.data()); + assert.strictEqual(6, r.renders, "we don't listen to removed datasets"); + }); + it("datasets()", function () { + var dataset1 = new Plottable.Dataset([]); + var dataset2 = new Plottable.Dataset([]); + var plot = new Plottable.Plot(); + plot.addDataset(dataset1); + plot.addDataset(dataset2); + assert.deepEqual(plot.datasets(), [dataset1, dataset2], "retrieved Datasets in order they were added"); + plot.datasets([dataset2, dataset1]); + assert.deepEqual(plot.datasets(), [dataset2, dataset1], "order of Datasets was changed"); + var dataset3 = new Plottable.Dataset([]); + plot.addDataset(dataset3); + assert.deepEqual(plot.datasets(), [dataset2, dataset1, dataset3], "adding further Datasets respects the order"); + plot.removeDataset(dataset1); + assert.deepEqual(plot.datasets(), [dataset2, dataset3], "removing a Dataset leaves the remainder in the same order"); }); it("Updates its projectors when the Dataset is changed", function () { var d1 = new Plottable.Dataset([{ x: 5, y: 6 }], { cssClass: "bar" }); - var r = new Plottable.Plot.AbstractPlot(); - r.addDataset("d1", d1); + var r = new Plottable.Plot(); + r.addDataset(d1); var xScaleCalls = 0; var yScaleCalls = 0; - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); var metadataProjector = function (d, i, m) { return m.cssClass; }; - r.project("x", "x", xScale); - r.project("y", "y", yScale); - r.project("meta", metadataProjector); - xScale.broadcaster.registerListener("unitTest", function (listenable) { - assert.equal(listenable, xScale, "Callback received the calling scale as the first argument"); + r.attr("x", function (d) { return d.x; }, xScale); + r.attr("y", function (d) { return d.y; }, yScale); + r.attr("meta", metadataProjector); + xScale.onUpdate(function (listenable) { + assert.strictEqual(listenable, xScale, "Callback received the calling scale as the first argument"); ++xScaleCalls; }); - yScale.broadcaster.registerListener("unitTest", function (listenable) { - assert.equal(listenable, yScale, "Callback received the calling scale as the first argument"); + yScale.onUpdate(function (listenable) { + assert.strictEqual(listenable, yScale, "Callback received the calling scale as the first argument"); ++yScaleCalls; }); - assert.equal(0, xScaleCalls, "initially hasn't made any X callbacks"); - assert.equal(0, yScaleCalls, "initially hasn't made any Y callbacks"); - d1.broadcaster.broadcast(); - assert.equal(1, xScaleCalls, "X scale was wired up to datasource correctly"); - assert.equal(1, yScaleCalls, "Y scale was wired up to datasource correctly"); + assert.strictEqual(0, xScaleCalls, "initially hasn't made any X callbacks"); + assert.strictEqual(0, yScaleCalls, "initially hasn't made any Y callbacks"); + d1.data(d1.data()); + assert.strictEqual(1, xScaleCalls, "X scale was wired up to datasource correctly"); + assert.strictEqual(1, yScaleCalls, "Y scale was wired up to datasource correctly"); var d2 = new Plottable.Dataset([{ x: 7, y: 8 }], { cssClass: "boo" }); - r.removeDataset("d1"); + r.removeDataset(d1); r.addDataset(d2); - assert.equal(3, xScaleCalls, "Changing datasource fires X scale listeners (but doesn't coalesce callbacks)"); - assert.equal(3, yScaleCalls, "Changing datasource fires Y scale listeners (but doesn't coalesce callbacks)"); - d1.broadcaster.broadcast(); - assert.equal(3, xScaleCalls, "X scale was unhooked from old datasource"); - assert.equal(3, yScaleCalls, "Y scale was unhooked from old datasource"); - d2.broadcaster.broadcast(); - assert.equal(4, xScaleCalls, "X scale was hooked into new datasource"); - assert.equal(4, yScaleCalls, "Y scale was hooked into new datasource"); - }); - it("Plot automatically generates a Dataset if only data is provided", function () { - var data = ["foo", "bar"]; - var r = new Plottable.Plot.AbstractPlot().addDataset("foo", data); - var dataset = r.datasets()[0]; - assert.isNotNull(dataset, "A Dataset was automatically generated"); - assert.deepEqual(dataset.data(), data, "The generated Dataset has the correct data"); + assert.strictEqual(3, xScaleCalls, "Changing datasource fires X scale listeners (but doesn't coalesce callbacks)"); + assert.strictEqual(3, yScaleCalls, "Changing datasource fires Y scale listeners (but doesn't coalesce callbacks)"); + d1.data(d1.data()); + assert.strictEqual(3, xScaleCalls, "X scale was unhooked from old datasource"); + assert.strictEqual(3, yScaleCalls, "Y scale was unhooked from old datasource"); + d2.data(d2.data()); + assert.strictEqual(4, xScaleCalls, "X scale was hooked into new datasource"); + assert.strictEqual(4, yScaleCalls, "Y scale was hooked into new datasource"); }); it("Plot.project works as intended", function () { - var r = new Plottable.Plot.AbstractPlot(); - var s = new Plottable.Scale.Linear().domain([0, 1]).range([0, 10]); - r.project("attr", "a", s); + var r = new Plottable.Plot(); + var s = new Plottable.Scales.Linear().domain([0, 1]).range([0, 10]); + r.attr("attr", function (d) { return d.a; }, s); var attrToProjector = r._generateAttrToProjector(); var projector = attrToProjector["attr"]; - assert.equal(projector({ "a": 0.5 }, 0, null, null), 5, "projector works as intended"); + assert.strictEqual(projector({ "a": 0.5 }, 0, null, null), 5, "projector works as intended"); }); it("Changing Plot.dataset().data to [] causes scale to contract", function () { var ds1 = new Plottable.Dataset([0, 1, 2]); var ds2 = new Plottable.Dataset([1, 2, 3]); - var s = new Plottable.Scale.Linear(); - var svg1 = generateSVG(100, 100); - var svg2 = generateSVG(100, 100); - var r1 = new Plottable.Plot.AbstractPlot().addDataset(ds1).project("x", function (x) { return x; }, s).renderTo(svg1); - var r2 = new Plottable.Plot.AbstractPlot().addDataset(ds2).project("x", function (x) { return x; }, s).renderTo(svg2); + var s = new Plottable.Scales.Linear(); + var svg1 = TestMethods.generateSVG(100, 100); + var svg2 = TestMethods.generateSVG(100, 100); + new Plottable.Plot().addDataset(ds1).attr("x", function (x) { return x; }, s).renderTo(svg1); + new Plottable.Plot().addDataset(ds2).attr("x", function (x) { return x; }, s).renderTo(svg2); assert.deepEqual(s.domain(), [0, 3], "Simple domain combining"); ds1.data([]); assert.deepEqual(s.domain(), [1, 3], "Contracting domain due to projection becoming empty"); @@ -2217,47 +2228,51 @@ describe("Plots", function () { svg2.remove(); }); it("getAllSelections() with dataset retrieval", function () { - var svg = generateSVG(400, 400); - var plot = new Plottable.Plot.AbstractPlot(); + var svg = TestMethods.generateSVG(400, 400); + var plot = new Plottable.Plot(); // Create mock drawers with already drawn items - var mockDrawer1 = new Plottable._Drawer.AbstractDrawer("ds1"); + // HACKHACK #1984: Dataset keys are being removed, so this is the internal key + var mockDrawer1 = new Plottable.Drawers.AbstractDrawer("_0"); var renderArea1 = svg.append("g"); renderArea1.append("circle").attr("cx", 100).attr("cy", 100).attr("r", 10); mockDrawer1.setup = function () { return mockDrawer1._renderArea = renderArea1; }; mockDrawer1._getSelector = function () { return "circle"; }; var renderArea2 = svg.append("g"); renderArea2.append("circle").attr("cx", 10).attr("cy", 10).attr("r", 10); - var mockDrawer2 = new Plottable._Drawer.AbstractDrawer("ds2"); + // HACKHACK #1984: Dataset keys are being removed, so this is the internal key + var mockDrawer2 = new Plottable.Drawers.AbstractDrawer("_1"); mockDrawer2.setup = function () { return mockDrawer2._renderArea = renderArea2; }; mockDrawer2._getSelector = function () { return "circle"; }; // Mock _getDrawer to return the mock drawers plot._getDrawer = function (key) { - if (key === "ds1") { + if (key === "_0") { return mockDrawer1; } else { return mockDrawer2; } }; - plot.addDataset("ds1", [{ value: 0 }, { value: 1 }, { value: 2 }]); - plot.addDataset("ds2", [{ value: 1 }, { value: 2 }, { value: 3 }]); + var dataset1 = new Plottable.Dataset([{ value: 0 }, { value: 1 }, { value: 2 }]); + plot.addDataset(dataset1); + var dataset2 = new Plottable.Dataset([{ value: 1 }, { value: 2 }, { value: 3 }]); + plot.addDataset(dataset2); plot.renderTo(svg); var selections = plot.getAllSelections(); assert.strictEqual(selections.size(), 2, "all circle selections gotten"); - var oneSelection = plot.getAllSelections("ds1"); + var oneSelection = plot.getAllSelections([dataset1]); assert.strictEqual(oneSelection.size(), 1); - assert.strictEqual(numAttr(oneSelection, "cx"), 100, "retrieved selection in renderArea1"); - var oneElementSelection = plot.getAllSelections(["ds2"]); + assert.strictEqual(TestMethods.numAttr(oneSelection, "cx"), 100, "retrieved selection in renderArea1"); + var oneElementSelection = plot.getAllSelections([dataset2]); assert.strictEqual(oneElementSelection.size(), 1); - assert.strictEqual(numAttr(oneElementSelection, "cy"), 10, "retreived selection in renderArea2"); - var nonExcludedSelection = plot.getAllSelections(["ds1"], true); + assert.strictEqual(TestMethods.numAttr(oneElementSelection, "cy"), 10, "retreived selection in renderArea2"); + var nonExcludedSelection = plot.getAllSelections([dataset1], true); assert.strictEqual(nonExcludedSelection.size(), 1); - assert.strictEqual(numAttr(nonExcludedSelection, "cy"), 10, "retreived non-excluded selection in renderArea2"); + assert.strictEqual(TestMethods.numAttr(nonExcludedSelection, "cy"), 10, "retreived non-excluded selection in renderArea2"); svg.remove(); }); it("getAllPlotData() with dataset retrieval", function () { - var svg = generateSVG(400, 400); - var plot = new Plottable.Plot.AbstractPlot(); + var svg = TestMethods.generateSVG(400, 400); + var plot = new Plottable.Plot(); var data1 = [{ value: 0 }, { value: 1 }, { value: 2 }]; var data2 = [{ value: 0 }, { value: 1 }, { value: 2 }]; var data1Points = data1.map(function (datum) { @@ -2269,7 +2284,8 @@ describe("Plots", function () { var data1PointConverter = function (datum, index) { return data1Points[index]; }; var data2PointConverter = function (datum, index) { return data2Points[index]; }; // Create mock drawers with already drawn items - var mockDrawer1 = new Plottable._Drawer.AbstractDrawer("ds1"); + // HACKHACK #1984: Dataset keys are being removed, so this is the internal key + var mockDrawer1 = new Plottable.Drawers.AbstractDrawer("_0"); var renderArea1 = svg.append("g"); renderArea1.append("circle").attr("cx", 100).attr("cy", 100).attr("r", 10); mockDrawer1.setup = function () { return mockDrawer1._renderArea = renderArea1; }; @@ -2277,21 +2293,24 @@ describe("Plots", function () { mockDrawer1._getPixelPoint = data1PointConverter; var renderArea2 = svg.append("g"); renderArea2.append("circle").attr("cx", 10).attr("cy", 10).attr("r", 10); - var mockDrawer2 = new Plottable._Drawer.AbstractDrawer("ds2"); + // HACKHACK #1984: Dataset keys are being removed, so this is the internal key + var mockDrawer2 = new Plottable.Drawers.AbstractDrawer("_1"); mockDrawer2.setup = function () { return mockDrawer2._renderArea = renderArea2; }; mockDrawer2._getSelector = function () { return "circle"; }; mockDrawer2._getPixelPoint = data2PointConverter; // Mock _getDrawer to return the mock drawers plot._getDrawer = function (key) { - if (key === "ds1") { + if (key === "_0") { return mockDrawer1; } else { return mockDrawer2; } }; - plot.addDataset("ds1", data1); - plot.addDataset("ds2", data2); + var dataset1 = new Plottable.Dataset(data1); + plot.addDataset(dataset1); + var dataset2 = new Plottable.Dataset(data2); + plot.addDataset(dataset2); plot.renderTo(svg); var allPlotData = plot.getAllPlotData(); assert.strictEqual(allPlotData.selection.size(), 2, "all circle selections gotten"); @@ -2299,30 +2318,30 @@ describe("Plots", function () { assert.includeMembers(allPlotData.data, data2, "includes data2 members"); assert.includeMembers(allPlotData.pixelPoints, data1.map(data1PointConverter), "includes data1 points"); assert.includeMembers(allPlotData.pixelPoints, data2.map(data2PointConverter), "includes data2 points"); - var singlePlotData = plot.getAllPlotData("ds1"); + var singlePlotData = plot.getAllPlotData([dataset1]); var oneSelection = singlePlotData.selection; assert.strictEqual(oneSelection.size(), 1); - assert.strictEqual(numAttr(oneSelection, "cx"), 100, "retrieved selection in renderArea1"); + assert.strictEqual(TestMethods.numAttr(oneSelection, "cx"), 100, "retrieved selection in renderArea1"); assert.includeMembers(singlePlotData.data, data1, "includes data1 members"); assert.includeMembers(singlePlotData.pixelPoints, data1.map(data1PointConverter), "includes data1 points"); - var oneElementPlotData = plot.getAllPlotData(["ds2"]); + var oneElementPlotData = plot.getAllPlotData([dataset2]); var oneElementSelection = oneElementPlotData.selection; assert.strictEqual(oneElementSelection.size(), 1); - assert.strictEqual(numAttr(oneElementSelection, "cy"), 10, "retreieved selection in renderArea2"); + assert.strictEqual(TestMethods.numAttr(oneElementSelection, "cy"), 10, "retreieved selection in renderArea2"); assert.includeMembers(oneElementPlotData.data, data2, "includes data2 members"); assert.includeMembers(oneElementPlotData.pixelPoints, data2.map(data2PointConverter), "includes data2 points"); svg.remove(); }); it("getAllPlotData() with NaN pixel points", function () { - var svg = generateSVG(400, 400); - var plot = new Plottable.Plot.AbstractPlot(); + var svg = TestMethods.generateSVG(400, 400); + var plot = new Plottable.Plot(); var data = [{ value: NaN }, { value: 1 }, { value: 2 }]; var dataPoints = data.map(function (datum) { return { x: datum.value, y: 10 }; }); var dataPointConverter = function (datum, index) { return dataPoints[index]; }; // Create mock drawer with already drawn items - var mockDrawer = new Plottable._Drawer.AbstractDrawer("ds"); + var mockDrawer = new Plottable.Drawers.AbstractDrawer("ds"); var renderArea = svg.append("g"); var circles = renderArea.selectAll("circles").data(data); circles.enter().append("circle").attr("cx", 100).attr("cy", 100).attr("r", 10); @@ -2332,7 +2351,8 @@ describe("Plots", function () { mockDrawer._getPixelPoint = dataPointConverter; // Mock _getDrawer to return the mock drawer plot._getDrawer = function () { return mockDrawer; }; - plot.addDataset("ds", data); + var dataset = new Plottable.Dataset(data); + plot.addDataset(dataset); plot.renderTo(svg); var oneElementPlotData = plot.getAllPlotData(); var oneElementSelection = oneElementPlotData.selection; @@ -2346,8 +2366,8 @@ describe("Plots", function () { svg.remove(); }); it("getClosestPlotData", function () { - var svg = generateSVG(400, 400); - var plot = new Plottable.Plot.AbstractPlot(); + var svg = TestMethods.generateSVG(400, 400); + var plot = new Plottable.Plot(); var data1 = [{ value: 0 }, { value: 1 }, { value: 2 }]; var data2 = [{ value: 0 }, { value: 1 }, { value: 2 }]; var data1Points = data1.map(function (datum) { @@ -2359,7 +2379,7 @@ describe("Plots", function () { var data1PointConverter = function (datum, index) { return data1Points[index]; }; var data2PointConverter = function (datum, index) { return data2Points[index]; }; // Create mock drawers with already drawn items - var mockDrawer1 = new Plottable._Drawer.AbstractDrawer("ds1"); + var mockDrawer1 = new Plottable.Drawers.AbstractDrawer("ds1"); var renderArea1 = svg.append("g"); renderArea1.append("circle").attr("cx", 100).attr("cy", 100).attr("r", 10); mockDrawer1.setup = function () { return mockDrawer1._renderArea = renderArea1; }; @@ -2367,7 +2387,7 @@ describe("Plots", function () { mockDrawer1._getPixelPoint = data1PointConverter; var renderArea2 = svg.append("g"); renderArea2.append("circle").attr("cx", 10).attr("cy", 10).attr("r", 10); - var mockDrawer2 = new Plottable._Drawer.AbstractDrawer("ds2"); + var mockDrawer2 = new Plottable.Drawers.AbstractDrawer("ds2"); mockDrawer2.setup = function () { return mockDrawer2._renderArea = renderArea2; }; mockDrawer2._getSelector = function () { return "circle"; }; mockDrawer2._getPixelPoint = data2PointConverter; @@ -2380,8 +2400,8 @@ describe("Plots", function () { return mockDrawer2; } }; - plot.addDataset("ds1", data1); - plot.addDataset("ds2", data2); + plot.addDataset(new Plottable.Dataset(data1)); + plot.addDataset(new Plottable.Dataset(data2)); plot.renderTo(svg); var queryPoint = { x: 1, y: 11 }; var closestPlotData = plot.getClosestPlotData(queryPoint); @@ -2393,81 +2413,49 @@ describe("Plots", function () { var d1; var d2; beforeEach(function () { - plot = new Plottable.Plot.AbstractPlot(); + plot = new Plottable.Plot(); d1 = new Plottable.Dataset(); d2 = new Plottable.Dataset(); - plot.addDataset("foo", d1); - plot.addDataset("bar", d2); + plot.addDataset(d1); + plot.addDataset(d2); assert.deepEqual(plot.datasets(), [d1, d2], "datasets as expected"); }); - it("removeDataset can work on keys", function () { - plot.removeDataset("bar"); - assert.deepEqual(plot.datasets(), [d1], "second dataset removed"); - plot.removeDataset("foo"); - assert.deepEqual(plot.datasets(), [], "all datasets removed"); - }); - it("removeDataset can work on datasets", function () { + it("removeDataset()", function () { plot.removeDataset(d2); assert.deepEqual(plot.datasets(), [d1], "second dataset removed"); plot.removeDataset(d1); assert.deepEqual(plot.datasets(), [], "all datasets removed"); }); - it("removeDataset ignores inputs that do not correspond to a dataset", function () { + it("removeDataset ignores Datasets not in the Plot", function () { var d3 = new Plottable.Dataset(); plot.removeDataset(d3); - plot.removeDataset("bad key"); assert.deepEqual(plot.datasets(), [d1, d2], "datasets as expected"); }); - it("removeDataset functions on inputs that are data arrays, not datasets", function () { - var a1 = ["foo", "bar"]; - var a2 = [1, 2, 3]; - plot.addDataset(a1); - plot.addDataset(a2); - assert.lengthOf(plot.datasets(), 4, "there are four datasets"); - assert.equal(plot.datasets()[3].data(), a2, "second array dataset correct"); - assert.equal(plot.datasets()[2].data(), a1, "first array dataset correct"); - plot.removeDataset(a2); - plot.removeDataset(a1); - assert.deepEqual(plot.datasets(), [d1, d2], "datasets as expected"); - }); - it("removeDataset behaves appropriately when the key 'undefined' is used", function () { - var a = [1, 2, 3]; - plot.addDataset("undefined", a); - assert.lengthOf(plot.datasets(), 3, "there are three datasets initially"); - plot.removeDataset("foofoofoofoofoofoofoofoo"); - assert.lengthOf(plot.datasets(), 3, "there are three datasets after bad key removal"); - plot.removeDataset(undefined); - assert.lengthOf(plot.datasets(), 3, "there are three datasets after removing `undefined`"); - plot.removeDataset([94, 93, 92]); - assert.lengthOf(plot.datasets(), 3, "there are three datasets after removing random dataset"); - plot.removeDataset("undefined"); - assert.lengthOf(plot.datasets(), 2, "the dataset called 'undefined' could be removed"); - }); }); - it("remove() disconnects plots from its scales", function () { - var r = new Plottable.Plot.AbstractPlot(); - var s = new Plottable.Scale.Linear(); - r.project("attr", "a", s); - r.remove(); - var key2callback = s.broadcaster._key2callback; - assert.isUndefined(key2callback.get(r), "the plot is no longer attached to the scale"); + it("destroy() disconnects plots from its scales", function () { + var plot2 = new Plottable.Plot(); + var scale = new Plottable.Scales.Linear(); + plot2.attr("attr", function (d) { return d.a; }, scale); + plot2.destroy(); + var scaleCallbacks = scale._callbacks.values(); + assert.strictEqual(scaleCallbacks.length, 0, "the plot is no longer attached to the scale"); }); it("extent registration works as intended", function () { - var scale1 = new Plottable.Scale.Linear(); - var scale2 = new Plottable.Scale.Linear(); + var scale1 = new Plottable.Scales.Linear(); + var scale2 = new Plottable.Scales.Linear(); var d1 = new Plottable.Dataset([1, 2, 3]); var d2 = new Plottable.Dataset([4, 99, 999]); var d3 = new Plottable.Dataset([-1, -2, -3]); var id = function (d) { return d; }; - var plot1 = new Plottable.Plot.AbstractPlot(); - var plot2 = new Plottable.Plot.AbstractPlot(); - var svg = generateSVG(400, 400); + var plot1 = new Plottable.Plot(); + var plot2 = new Plottable.Plot(); + var svg = TestMethods.generateSVG(400, 400); plot1.attr("null", id, scale1); plot2.attr("null", id, scale1); plot1.renderTo(svg); plot2.renderTo(svg); function assertDomainIsClose(actualDomain, expectedDomain, msg) { - // to avoid floating point issues :/ + // to avoid floating point issues:/ assert.closeTo(actualDomain[0], expectedDomain[0], 0.01, msg); assert.closeTo(actualDomain[1], expectedDomain[1], 0.01, msg); } @@ -2484,87 +2472,116 @@ describe("Plots", function () { svg.remove(); }); it("additionalPaint timing works properly", function () { - var animator = new Plottable.Animator.Base().delay(10).duration(10).maxIterativeDelay(0); - var x = new Plottable.Scale.Linear(); - var y = new Plottable.Scale.Linear(); - var plot = new Plottable.Plot.Bar(x, y).addDataset([]).animate(true); + var animator = new Plottable.Animators.Base().delay(10).duration(10).maxIterativeDelay(0); + var x = new Plottable.Scales.Linear(); + var y = new Plottable.Scales.Linear(); + var plot = new Plottable.Plots.Bar(x, y); + plot.addDataset(new Plottable.Dataset([])).animate(true); var recordedTime = -1; var additionalPaint = function (x) { recordedTime = Math.max(x, recordedTime); }; plot._additionalPaint = additionalPaint; plot.animator("bars", animator); - var svg = generateSVG(); - plot.project("x", "x", x); - plot.project("y", "y", y); + var svg = TestMethods.generateSVG(); + plot.x(function (d) { return d.x; }, x); + plot.y(function (d) { return d.y; }, y); plot.renderTo(svg); + assert.strictEqual(recordedTime, 20, "additionalPaint passed appropriate time argument"); svg.remove(); - assert.equal(recordedTime, 20, "additionalPaint passed appropriate time argument"); }); it("extent calculation done in correct dataset order", function () { - var animator = new Plottable.Animator.Base().delay(10).duration(10).maxIterativeDelay(0); - var CategoryScale = new Plottable.Scale.Category(); - var dataset1 = [{ key: "A" }]; - var dataset2 = [{ key: "B" }]; - var plot = new Plottable.Plot.AbstractPlot().addDataset("b", dataset2).addDataset("a", dataset1); - plot.project("key", "key", CategoryScale); - plot.datasetOrder(["a", "b"]); - var svg = generateSVG(); + var categoryScale = new Plottable.Scales.Category(); + var dataset1 = new Plottable.Dataset([{ key: "A" }]); + var dataset2 = new Plottable.Dataset([{ key: "B" }]); + var plot = new Plottable.Plot(); + plot.addDataset(dataset2); + plot.addDataset(dataset1); + plot.attr("key", function (d) { return d.key; }, categoryScale); + var svg = TestMethods.generateSVG(); plot.renderTo(svg); - assert.deepEqual(CategoryScale.domain(), ["A", "B"], "extent is in the right order"); + assert.deepEqual(categoryScale.domain(), ["B", "A"], "extent is in the right order"); svg.remove(); }); }); - describe("Abstract XY Plot", function () { +}); + +/// +var assert = chai.assert; +describe("Plots", function () { + describe("XY Plot", function () { var svg; var xScale; var yScale; - var xAccessor; - var yAccessor; - var simpleDataset; var plot; - before(function () { - xAccessor = function (d, i, u) { return d.a + u.foo; }; - yAccessor = function (d, i, u) { return d.b + u.foo; }; - }); + var simpleDataset = new Plottable.Dataset([ + { a: -6, b: 6 }, + { a: -2, b: 2 }, + { a: 2, b: -2 }, + { a: 6, b: -6 } + ]); + var xAccessor = function (d) { return d.a; }; + var yAccessor = function (d) { return d.b; }; beforeEach(function () { - svg = generateSVG(500, 500); - simpleDataset = new Plottable.Dataset([{ a: -5, b: 6 }, { a: -2, b: 2 }, { a: 2, b: -2 }, { a: 5, b: -6 }], { foo: 0 }); - xScale = new Plottable.Scale.Linear(); - yScale = new Plottable.Scale.Linear(); - plot = new Plottable.Plot.AbstractXYPlot(xScale, yScale); - plot.addDataset(simpleDataset).project("x", xAccessor, xScale).project("y", yAccessor, yScale).renderTo(svg); - }); - it("plot auto domain scale to visible points", function () { + svg = TestMethods.generateSVG(500, 500); + xScale = new Plottable.Scales.Linear(); + yScale = new Plottable.Scales.Linear(); + plot = new Plottable.XYPlot(xScale, yScale); + plot.addDataset(simpleDataset); + plot.x(xAccessor, xScale).y(yAccessor, yScale).renderTo(svg); + }); + it("automatically adjusting Y domain over visible points", function () { xScale.domain([-3, 3]); assert.deepEqual(yScale.domain(), [-7, 7], "domain has not been adjusted to visible points"); plot.automaticallyAdjustYScaleOverVisiblePoints(true); assert.deepEqual(yScale.domain(), [-2.5, 2.5], "domain has been adjusted to visible points"); plot.automaticallyAdjustYScaleOverVisiblePoints(false); - plot.automaticallyAdjustXScaleOverVisiblePoints(true); - yScale.domain([-6, 6]); - assert.deepEqual(xScale.domain(), [-6, 6], "domain has been adjusted to visible points"); svg.remove(); }); - it("no visible points", function () { + it("automatically adjusting Y domain when no points are visible", function () { plot.automaticallyAdjustYScaleOverVisiblePoints(true); xScale.domain([-0.5, 0.5]); - assert.deepEqual(yScale.domain(), [-7, 7], "domain has been not been adjusted"); + assert.deepEqual(yScale.domain(), [-1, 1], "domain equivalent to that with empty dataset"); svg.remove(); }); - it("automaticallyAdjustYScaleOverVisiblePoints disables autoDomain", function () { - xScale.domain([-2, 2]); + it("automatically adjusting Y domain when X scale is replaced", function () { plot.automaticallyAdjustYScaleOverVisiblePoints(true); - plot.renderTo(svg); - assert.deepEqual(yScale.domain(), [-2.5, 2.5], "domain has been been adjusted"); + var newXScale = new Plottable.Scales.Linear().domain([-3, 3]); + plot.x(xAccessor, newXScale); + assert.deepEqual(yScale.domain(), [-2.5, 2.5], "domain has been adjusted to visible points on new X scale domain"); + xScale.domain([-2, 2]); + assert.deepEqual(yScale.domain(), [-2.5, 2.5], "changing domain of original X scale doesn't affect Y scale's domain"); + svg.remove(); + }); + it("automatically adjusting X domain over visible points", function () { + yScale.domain([-3, 3]); + assert.deepEqual(xScale.domain(), [-7, 7], "domain has not been adjusted to visible points"); + plot.automaticallyAdjustXScaleOverVisiblePoints(true); + assert.deepEqual(xScale.domain(), [-2.5, 2.5], "domain has been adjusted to visible points"); + plot.automaticallyAdjustXScaleOverVisiblePoints(false); + svg.remove(); + }); + it("automatically adjusting X domain when no points are visible", function () { + plot.automaticallyAdjustXScaleOverVisiblePoints(true); + yScale.domain([-0.5, 0.5]); + assert.deepEqual(xScale.domain(), [-1, 1], "domain equivalent to that with empty dataset"); svg.remove(); }); - it("show all data", function () { + it("automatically adjusting X domain when Y scale is replaced", function () { + plot.automaticallyAdjustXScaleOverVisiblePoints(true); + var newYScale = new Plottable.Scales.Linear().domain([-3, 3]); + plot.y(yAccessor, newYScale); + assert.deepEqual(xScale.domain(), [-2.5, 2.5], "domain has been adjusted to visible points on new Y scale domain"); + yScale.domain([-2, 2]); + assert.deepEqual(xScale.domain(), [-2.5, 2.5], "changing domain of original Y scale doesn't affect X scale's domain"); + svg.remove(); + }); + it("showAllData()", function () { plot.automaticallyAdjustYScaleOverVisiblePoints(true); xScale.domain([-0.5, 0.5]); plot.showAllData(); assert.deepEqual(yScale.domain(), [-7, 7], "domain has been adjusted to show all data"); - assert.deepEqual(xScale.domain(), [-6, 6], "domain has been adjusted to show all data"); + assert.deepEqual(xScale.domain(), [-7, 7], "domain has been adjusted to show all data"); svg.remove(); }); it("show all data without auto adjust domain", function () { @@ -2573,37 +2590,16 @@ describe("Plots", function () { plot.automaticallyAdjustYScaleOverVisiblePoints(false); plot.showAllData(); assert.deepEqual(yScale.domain(), [-7, 7], "domain has been adjusted to show all data"); - assert.deepEqual(xScale.domain(), [-6, 6], "domain has been adjusted to show all data"); - svg.remove(); - }); - it("no cycle in auto domain on plot", function () { - var zScale = new Plottable.Scale.Linear().domain([-10, 10]); - plot.automaticallyAdjustYScaleOverVisiblePoints(true); - var plot2 = new Plottable.Plot.AbstractXYPlot(zScale, yScale).automaticallyAdjustXScaleOverVisiblePoints(true).project("x", xAccessor, zScale).project("y", yAccessor, yScale).addDataset(simpleDataset); - var plot3 = new Plottable.Plot.AbstractXYPlot(zScale, xScale).automaticallyAdjustYScaleOverVisiblePoints(true).project("x", xAccessor, zScale).project("y", yAccessor, xScale).addDataset(simpleDataset); - plot2.renderTo(svg); - plot3.renderTo(svg); - xScale.domain([-2, 2]); - assert.deepEqual(yScale.domain(), [-2.5, 2.5], "y domain is adjusted by x domain using custom algorithm and domainer"); - assert.deepEqual(zScale.domain(), [-2.5, 2.5], "z domain is adjusted by y domain using custom algorithm and domainer"); - assert.deepEqual(xScale.domain(), [-2, 2], "x domain is not adjusted using custom algorithm and domainer"); + assert.deepEqual(xScale.domain(), [-7, 7], "domain has been adjusted to show all data"); svg.remove(); }); it("listeners are deregistered after removal", function () { plot.automaticallyAdjustYScaleOverVisiblePoints(true); - plot.remove(); - var key2callback = xScale.broadcaster._key2callback; - assert.isUndefined(key2callback.get("yDomainAdjustment" + plot.getID()), "the plot is no longer attached to the xScale"); - key2callback = yScale.broadcaster._key2callback; - assert.isUndefined(key2callback.get("xDomainAdjustment" + plot.getID()), "the plot is no longer attached to the yScale"); - svg.remove(); - }); - it("listeners are deregistered for changed scale", function () { - plot.automaticallyAdjustYScaleOverVisiblePoints(true); - var newScale = new Plottable.Scale.Linear().domain([-10, 10]); - plot.project("x", xAccessor, newScale); - xScale.domain([-2, 2]); - assert.deepEqual(yScale.domain(), [-7, 7], "replaced xScale didn't adjust yScale"); + plot.destroy(); + var xScaleCallbacks = xScale._callbacks.values(); + assert.strictEqual(xScaleCallbacks.length, 0, "the plot is no longer attached to xScale"); + var yScaleCallbacks = yScale._callbacks.values(); + assert.strictEqual(yScaleCallbacks.length, 0, "the plot is no longer attached to yScale"); svg.remove(); }); }); @@ -2615,9 +2611,9 @@ describe("Plots", function () { describe("PiePlot", function () { // HACKHACK #1798: beforeEach being used below it("renders correctly with no data", function () { - var svg = generateSVG(400, 400); - var plot = new Plottable.Plot.Pie(); - plot.project("value", function (d) { return d.value; }); + var svg = TestMethods.generateSVG(400, 400); + var plot = new Plottable.Plots.Pie(); + plot.sectorValue(function (d) { return d.value; }); assert.doesNotThrow(function () { return plot.renderTo(svg); }, Error); assert.strictEqual(plot.width(), 400, "was allocated width"); assert.strictEqual(plot.height(), 400, "was allocated height"); @@ -2631,12 +2627,12 @@ describe("Plots", function () { var piePlot; var renderArea; beforeEach(function () { - svg = generateSVG(500, 500); + svg = TestMethods.generateSVG(500, 500); simpleData = [{ value: 5, value2: 10, type: "A" }, { value: 15, value2: 10, type: "B" }]; simpleDataset = new Plottable.Dataset(simpleData); - piePlot = new Plottable.Plot.Pie(); - piePlot.addDataset("simpleDataset", simpleDataset); - piePlot.project("value", "value"); + piePlot = new Plottable.Plots.Pie(); + piePlot.addDataset(simpleDataset); + piePlot.sectorValue(function (d) { return d.value; }); piePlot.renderTo(svg); renderArea = piePlot._renderArea; }); @@ -2644,7 +2640,7 @@ describe("Plots", function () { var arcPaths = renderArea.selectAll(".arc"); assert.lengthOf(arcPaths[0], 2, "only has two sectors"); var arcPath0 = d3.select(arcPaths[0][0]); - var pathPoints0 = normalizePath(arcPath0.attr("d")).split(/[A-Z]/).slice(1, 4); + var pathPoints0 = TestMethods.normalizePath(arcPath0.attr("d")).split(/[A-Z]/).slice(1, 4); var firstPathPoints0 = pathPoints0[0].split(","); assert.closeTo(parseFloat(firstPathPoints0[0]), 0, 1, "draws line vertically at beginning"); assert.operator(parseFloat(firstPathPoints0[1]), "<", 0, "draws line upwards"); @@ -2655,7 +2651,7 @@ describe("Plots", function () { assert.closeTo(parseFloat(secondPathPoints0[0]), 0, 1, "draws line to origin"); assert.closeTo(parseFloat(secondPathPoints0[1]), 0, 1, "draws line to origin"); var arcPath1 = d3.select(arcPaths[0][1]); - var pathPoints1 = normalizePath(arcPath1.attr("d")).split(/[A-Z]/).slice(1, 4); + var pathPoints1 = TestMethods.normalizePath(arcPath1.attr("d")).split(/[A-Z]/).slice(1, 4); var firstPathPoints1 = pathPoints1[0].split(","); assert.operator(parseFloat(firstPathPoints1[0]), ">", 0, "draws line to the right"); assert.closeTo(parseFloat(firstPathPoints1[1]), 0, 1, "draws line horizontally"); @@ -2668,11 +2664,11 @@ describe("Plots", function () { svg.remove(); }); it("project value onto different attribute", function () { - piePlot.project("value", "value2"); + piePlot.sectorValue(function (d) { return d.value2; }); var arcPaths = renderArea.selectAll(".arc"); assert.lengthOf(arcPaths[0], 2, "only has two sectors"); var arcPath0 = d3.select(arcPaths[0][0]); - var pathPoints0 = normalizePath(arcPath0.attr("d")).split(/[A-Z]/).slice(1, 4); + var pathPoints0 = TestMethods.normalizePath(arcPath0.attr("d")).split(/[A-Z]/).slice(1, 4); var firstPathPoints0 = pathPoints0[0].split(","); assert.closeTo(parseFloat(firstPathPoints0[0]), 0, 1, "draws line vertically at beginning"); assert.operator(parseFloat(firstPathPoints0[1]), "<", 0, "draws line upwards"); @@ -2680,21 +2676,21 @@ describe("Plots", function () { assert.closeTo(parseFloat(arcDestPoint0[0]), 0, 1, "ends on a line vertically from beginning"); assert.operator(parseFloat(arcDestPoint0[1]), ">", 0, "ends below the center"); var arcPath1 = d3.select(arcPaths[0][1]); - var pathPoints1 = normalizePath(arcPath1.attr("d")).split(/[A-Z]/).slice(1, 4); + var pathPoints1 = TestMethods.normalizePath(arcPath1.attr("d")).split(/[A-Z]/).slice(1, 4); var firstPathPoints1 = pathPoints1[0].split(","); assert.closeTo(parseFloat(firstPathPoints1[0]), 0, 1, "draws line vertically at beginning"); assert.operator(parseFloat(firstPathPoints1[1]), ">", 0, "draws line downwards"); var arcDestPoint1 = pathPoints1[1].split(",").slice(5); assert.closeTo(parseFloat(arcDestPoint1[0]), 0, 1, "ends on a line vertically from beginning"); assert.operator(parseFloat(arcDestPoint1[1]), "<", 0, "ends above the center"); - piePlot.project("value", "value"); + piePlot.sectorValue(function (d) { return d.value; }); svg.remove(); }); it("innerRadius project", function () { - piePlot.project("inner-radius", function () { return 5; }); + piePlot.innerRadius(5); var arcPaths = renderArea.selectAll(".arc"); assert.lengthOf(arcPaths[0], 2, "only has two sectors"); - var pathPoints0 = normalizePath(d3.select(arcPaths[0][0]).attr("d")).split(/[A-Z]/).slice(1, 5); + var pathPoints0 = TestMethods.normalizePath(d3.select(arcPaths[0][0]).attr("d")).split(/[A-Z]/).slice(1, 5); var radiusPath0 = pathPoints0[2].split(",").map(function (coordinate) { return parseFloat(coordinate); }); assert.closeTo(radiusPath0[0], 5, 1, "stops line at innerRadius point"); assert.closeTo(radiusPath0[1], 0, 1, "stops line at innerRadius point"); @@ -2703,14 +2699,14 @@ describe("Plots", function () { assert.closeTo(innerArcPath0[1], 5, 1, "makes inner arc of radius 5"); assert.closeTo(innerArcPath0[5], 0, 1, "make inner arc to center"); assert.closeTo(innerArcPath0[6], -5, 1, "makes inner arc to top of inner circle"); - piePlot.project("inner-radius", function () { return 0; }); + piePlot.innerRadius(0); svg.remove(); }); it("outerRadius project", function () { - piePlot.project("outer-radius", function () { return 150; }); + piePlot.outerRadius(function () { return 150; }); var arcPaths = renderArea.selectAll(".arc"); assert.lengthOf(arcPaths[0], 2, "only has two sectors"); - var pathPoints0 = normalizePath(d3.select(arcPaths[0][0]).attr("d")).split(/[A-Z]/).slice(1, 5); + var pathPoints0 = TestMethods.normalizePath(d3.select(arcPaths[0][0]).attr("d")).split(/[A-Z]/).slice(1, 5); var radiusPath0 = pathPoints0[0].split(",").map(function (coordinate) { return parseFloat(coordinate); }); assert.closeTo(radiusPath0[0], 0, 1, "starts at outerRadius point"); assert.closeTo(radiusPath0[1], -150, 1, "starts at outerRadius point"); @@ -2719,33 +2715,25 @@ describe("Plots", function () { assert.closeTo(outerArcPath0[1], 150, 1, "makes outer arc of radius 150"); assert.closeTo(outerArcPath0[5], 150, 1, "makes outer arc to right edge"); assert.closeTo(outerArcPath0[6], 0, 1, "makes outer arc to right edge"); - piePlot.project("outer-radius", function () { return 250; }); + piePlot.outerRadius(function () { return 250; }); svg.remove(); }); describe("getAllSelections", function () { it("retrieves all dataset selections with no args", function () { var allSectors = piePlot.getAllSelections(); - var allSectors2 = piePlot.getAllSelections(piePlot._datasetKeysInOrder); - assert.deepEqual(allSectors, allSectors2, "all sectors retrieved"); - svg.remove(); - }); - it("retrieves correct selections (array arg)", function () { - var allSectors = piePlot.getAllSelections(["simpleDataset"]); assert.strictEqual(allSectors.size(), 2, "all sectors retrieved"); - var selectionData = allSectors.data(); - assert.includeMembers(selectionData.map(function (datum) { return datum.data; }), simpleData, "dataset data in selection data"); svg.remove(); }); - it("retrieves correct selections (string arg)", function () { - var allSectors = piePlot.getAllSelections("simpleDataset"); + it("retrieves correct selections", function () { + var allSectors = piePlot.getAllSelections([simpleDataset]); assert.strictEqual(allSectors.size(), 2, "all sectors retrieved"); var selectionData = allSectors.data(); assert.includeMembers(selectionData.map(function (datum) { return datum.data; }), simpleData, "dataset data in selection data"); svg.remove(); }); - it("skips invalid keys", function () { - var allSectors = piePlot.getAllSelections(["whoo"]); - assert.strictEqual(allSectors.size(), 0, "all sectors retrieved"); + it("skips invalid Datsets", function () { + var allSectors = piePlot.getAllSelections([new Plottable.Dataset([])]); + assert.strictEqual(allSectors.size(), 0, "no sectors retrieved"); svg.remove(); }); }); @@ -2759,13 +2747,13 @@ describe("Plots", function () { svg.remove(); }); it("project fill", function () { - piePlot.project("fill", function (d, i) { return String(i); }, new Plottable.Scale.Color("10")); + piePlot.attr("fill", function (d, i) { return String(i); }, new Plottable.Scales.Color("10")); var arcPaths = renderArea.selectAll(".arc"); var arcPath0 = d3.select(arcPaths[0][0]); assert.strictEqual(arcPath0.attr("fill"), "#1f77b4", "first sector filled appropriately"); var arcPath1 = d3.select(arcPaths[0][1]); assert.strictEqual(arcPath1.attr("fill"), "#ff7f0e", "second sector filled appropriately"); - piePlot.project("fill", "type", new Plottable.Scale.Color("20")); + piePlot.attr("fill", function (d) { return d.type; }, new Plottable.Scales.Color("20")); arcPaths = renderArea.selectAll(".arc"); arcPath0 = d3.select(arcPaths[0][0]); assert.strictEqual(arcPath0.attr("fill"), "#1f77b4", "first sector filled appropriately"); @@ -2776,19 +2764,19 @@ describe("Plots", function () { }); it("throws warnings on negative data", function () { var message; - var oldWarn = Plottable._Util.Methods.warn; - Plottable._Util.Methods.warn = function (warn) { return message = warn; }; - piePlot.removeDataset("simpleDataset"); + var oldWarn = Plottable.Utils.Methods.warn; + Plottable.Utils.Methods.warn = function (warn) { return message = warn; }; + piePlot.removeDataset(simpleDataset); var negativeDataset = new Plottable.Dataset([{ value: -5 }, { value: 15 }]); - piePlot.addDataset("negativeDataset", negativeDataset); - assert.equal(message, "Negative values will not render correctly in a pie chart."); - Plottable._Util.Methods.warn = oldWarn; + piePlot.addDataset(negativeDataset); + assert.strictEqual(message, "Negative values will not render correctly in a pie chart."); + Plottable.Utils.Methods.warn = oldWarn; svg.remove(); }); }); describe("fail safe tests", function () { it("undefined, NaN and non-numeric strings not be represented in a Pie Chart", function () { - var svg = generateSVG(); + var svg = TestMethods.generateSVG(); var data1 = [ { v: 1 }, { v: undefined }, @@ -2798,25 +2786,25 @@ describe("Plots", function () { { v: "Bad String" }, { v: 1 }, ]; - var plot = new Plottable.Plot.Pie(); - plot.addDataset(data1); - plot.project("value", "v"); + var plot = new Plottable.Plots.Pie(); + plot.addDataset(new Plottable.Dataset(data1)); + plot.sectorValue(function (d) { return d.v; }); plot.renderTo(svg); var elementsDrawnSel = plot._element.selectAll(".arc"); assert.strictEqual(elementsDrawnSel.size(), 4, "There should be exactly 4 slices in the pie chart, representing the valid values"); svg.remove(); }); it("nulls and 0s should be represented in a Pie Chart as DOM elements, but have radius 0", function () { - var svg = generateSVG(); + var svg = TestMethods.generateSVG(); var data1 = [ { v: 1 }, { v: 0 }, { v: null }, { v: 1 }, ]; - var plot = new Plottable.Plot.Pie(); - plot.addDataset(data1); - plot.project("value", "v"); + var plot = new Plottable.Plots.Pie(); + plot.addDataset(new Plottable.Dataset(data1)); + plot.sectorValue(function (d) { return d.v; }); plot.renderTo(svg); var elementsDrawnSel = plot._element.selectAll(".arc"); assert.strictEqual(elementsDrawnSel.size(), 4, "All 4 elements of the pie chart should have a DOM node"); @@ -2827,86 +2815,13 @@ describe("Plots", function () { }); }); -/// -var assert = chai.assert; -describe("Plots", function () { - describe("New Style Plots", function () { - var p; - var oldWarn = Plottable._Util.Methods.warn; - beforeEach(function () { - p = new Plottable.Plot.AbstractPlot(); - p._getDrawer = function (k) { return new Plottable._Drawer.Element(k).svgElement("rect"); }; - }); - afterEach(function () { - Plottable._Util.Methods.warn = oldWarn; - }); - it("Datasets can be added and removed as expected", function () { - p.addDataset("foo", [1, 2, 3]); - var d2 = new Plottable.Dataset([4, 5, 6]); - p.addDataset("bar", d2); - p.addDataset([7, 8, 9]); - var d4 = new Plottable.Dataset([10, 11, 12]); - p.addDataset(d4); - assert.deepEqual(p._datasetKeysInOrder, ["foo", "bar", "_0", "_1"], "dataset keys as expected"); - var datasets = p.datasets(); - assert.deepEqual(datasets[0].data(), [1, 2, 3]); - assert.equal(datasets[1], d2); - assert.deepEqual(datasets[2].data(), [7, 8, 9]); - assert.equal(datasets[3], d4); - p.removeDataset("foo"); - p.removeDataset("_0"); - assert.deepEqual(p._datasetKeysInOrder, ["bar", "_1"]); - assert.lengthOf(p.datasets(), 2); - }); - it("Datasets are listened to appropriately", function () { - var callbackCounter = 0; - var callback = function () { return callbackCounter++; }; - p._onDatasetUpdate = callback; - var d = new Plottable.Dataset([1, 2, 3]); - p.addDataset("foo", d); - assert.equal(callbackCounter, 1, "adding dataset triggers listener"); - d.data([1, 2, 3, 4]); - assert.equal(callbackCounter, 2, "modifying data triggers listener"); - p.removeDataset("foo"); - assert.equal(callbackCounter, 3, "removing dataset triggers listener"); - }); - it("Datasets can be reordered", function () { - p.addDataset("foo", [1]); - p.addDataset("bar", [2]); - p.addDataset("baz", [3]); - assert.deepEqual(p.datasetOrder(), ["foo", "bar", "baz"]); - p.datasetOrder(["bar", "baz", "foo"]); - assert.deepEqual(p.datasetOrder(), ["bar", "baz", "foo"]); - var warned = 0; - Plottable._Util.Methods.warn = function () { return warned++; }; // suppress expected warnings - p.datasetOrder(["blah", "blee", "bar", "baz", "foo"]); - assert.equal(warned, 1); - assert.deepEqual(p.datasetOrder(), ["bar", "baz", "foo"]); - }); - it("Has proper warnings", function () { - var warned = 0; - Plottable._Util.Methods.warn = function () { return warned++; }; - p.addDataset("_foo", []); - assert.equal(warned, 1); - p.addDataset("2", []); - p.addDataset("4", []); - // get warning for not a permutation - p.datasetOrder(["_bar", "4", "2"]); - assert.equal(warned, 2); - // do not get warning for a permutation - p.datasetOrder(["2", "_foo", "4"]); - assert.equal(warned, 2); - }); - }); -}); - /// var assert = chai.assert; describe("Plots", function () { // HACKHACK #1798: beforeEach being used below describe("LinePlot", function () { it("getAllPlotData with NaNs", function () { - var svg = generateSVG(500, 500); + var svg = TestMethods.generateSVG(500, 500); var dataWithNaN = [ { foo: 0.0, bar: 0.0 }, { foo: 0.2, bar: 0.2 }, @@ -2914,12 +2829,12 @@ describe("Plots", function () { { foo: 0.6, bar: 0.6 }, { foo: 0.8, bar: 0.8 } ]; - var xScale = new Plottable.Scale.Linear().domain([0, 1]); - var yScale = new Plottable.Scale.Linear().domain([0, 1]); - var linePlot = new Plottable.Plot.Line(xScale, yScale); - linePlot.addDataset(dataWithNaN); - linePlot.project("x", function (d) { return d.foo; }, xScale); - linePlot.project("y", function (d) { return d.bar; }, yScale); + var xScale = new Plottable.Scales.Linear().domain([0, 1]); + var yScale = new Plottable.Scales.Linear().domain([0, 1]); + var linePlot = new Plottable.Plots.Line(xScale, yScale); + linePlot.addDataset(new Plottable.Dataset(dataWithNaN)); + linePlot.x(function (d) { return d.foo; }, xScale); + linePlot.y(function (d) { return d.bar; }, yScale); linePlot.renderTo(svg); var apd = linePlot.getAllPlotData(); var expectedLength = dataWithNaN.length - 1; @@ -2931,12 +2846,12 @@ describe("Plots", function () { describe("LinePlot", function () { // HACKHACK #1798: beforeEach being used below it("renders correctly with no data", function () { - var svg = generateSVG(400, 400); - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - var plot = new Plottable.Plot.Line(xScale, yScale); - plot.project("x", function (d) { return d.x; }, xScale); - plot.project("y", function (d) { return d.y; }, yScale); + var svg = TestMethods.generateSVG(400, 400); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); + var plot = new Plottable.Plots.Line(xScale, yScale); + plot.x(function (d) { return d.x; }, xScale); + plot.y(function (d) { return d.y; }, yScale); assert.doesNotThrow(function () { return plot.renderTo(svg); }, Error); assert.strictEqual(plot.width(), 400, "was allocated width"); assert.strictEqual(plot.height(), 400, "was allocated height"); @@ -2955,37 +2870,38 @@ describe("Plots", function () { var linePlot; var renderArea; before(function () { - xScale = new Plottable.Scale.Linear().domain([0, 1]); - yScale = new Plottable.Scale.Linear().domain([0, 1]); + xScale = new Plottable.Scales.Linear().domain([0, 1]); + yScale = new Plottable.Scales.Linear().domain([0, 1]); xAccessor = function (d) { return d.foo; }; yAccessor = function (d) { return d.bar; }; colorAccessor = function (d, i, m) { return d3.rgb(d.foo, d.bar, i).toString(); }; }); beforeEach(function () { - svg = generateSVG(500, 500); + svg = TestMethods.generateSVG(500, 500); simpleDataset = new Plottable.Dataset(twoPointData); - linePlot = new Plottable.Plot.Line(xScale, yScale); - linePlot.addDataset("s1", simpleDataset).project("x", xAccessor, xScale).project("y", yAccessor, yScale).project("stroke", colorAccessor).addDataset("s2", simpleDataset).renderTo(svg); + linePlot = new Plottable.Plots.Line(xScale, yScale); + linePlot.addDataset(simpleDataset); + linePlot.x(xAccessor, xScale).y(yAccessor, yScale).attr("stroke", colorAccessor).renderTo(svg); renderArea = linePlot._renderArea; }); it("draws a line correctly", function () { var linePath = renderArea.select(".line"); - assert.strictEqual(normalizePath(linePath.attr("d")), "M0,500L500,0", "line d was set correctly"); + assert.strictEqual(TestMethods.normalizePath(linePath.attr("d")), "M0,500L500,0", "line d was set correctly"); var lineComputedStyle = window.getComputedStyle(linePath.node()); assert.strictEqual(lineComputedStyle.fill, "none", "line fill renders as \"none\""); svg.remove(); }); it("attributes set appropriately from accessor", function () { var areaPath = renderArea.select(".line"); - assert.equal(areaPath.attr("stroke"), "#000000", "stroke set correctly"); + assert.strictEqual(areaPath.attr("stroke"), "#000000", "stroke set correctly"); svg.remove(); }); it("attributes can be changed by projecting new accessor and re-render appropriately", function () { var newColorAccessor = function () { return "pink"; }; - linePlot.project("stroke", newColorAccessor); + linePlot.attr("stroke", newColorAccessor); linePlot.renderTo(svg); var linePath = renderArea.select(".line"); - assert.equal(linePath.attr("stroke"), "pink", "stroke changed correctly"); + assert.strictEqual(linePath.attr("stroke"), "pink", "stroke changed correctly"); svg.remove(); }); it("attributes can be changed by projecting attribute accessor (sets to first datum attribute)", function () { @@ -2994,12 +2910,12 @@ describe("Plots", function () { d.stroke = "pink"; }); simpleDataset.data(data); - linePlot.project("stroke", "stroke"); + linePlot.attr("stroke", function (d) { return d.stroke; }); var areaPath = renderArea.select(".line"); - assert.equal(areaPath.attr("stroke"), "pink", "stroke set to uniform stroke color"); + assert.strictEqual(areaPath.attr("stroke"), "pink", "stroke set to uniform stroke color"); data[0].stroke = "green"; simpleDataset.data(data); - assert.equal(areaPath.attr("stroke"), "green", "stroke set to first datum stroke color"); + assert.strictEqual(areaPath.attr("stroke"), "green", "stroke set to first datum stroke color"); svg.remove(); }); it("correctly handles NaN and undefined x and y values", function () { @@ -3012,15 +2928,15 @@ describe("Plots", function () { ]; simpleDataset.data(lineData); var linePath = renderArea.select(".line"); - var d_original = normalizePath(linePath.attr("d")); + var d_original = TestMethods.normalizePath(linePath.attr("d")); function assertCorrectPathSplitting(msgPrefix) { - var d = normalizePath(linePath.attr("d")); + var d = TestMethods.normalizePath(linePath.attr("d")); var pathSegements = d.split("M").filter(function (segment) { return segment !== ""; }); assert.lengthOf(pathSegements, 2, msgPrefix + " split path into two segments"); var firstSegmentContained = d_original.indexOf(pathSegements[0]) >= 0; assert.isTrue(firstSegmentContained, "first path segment is a subpath of the original path"); var secondSegmentContained = d_original.indexOf(pathSegements[1]) >= 0; - assert.isTrue(firstSegmentContained, "second path segment is a subpath of the original path"); + assert.isTrue(secondSegmentContained, "second path segment is a subpath of the original path"); } var dataWithNaN = lineData.slice(); dataWithNaN[2] = { foo: 0.4, bar: NaN }; @@ -3038,100 +2954,50 @@ describe("Plots", function () { assertCorrectPathSplitting("x=undefined"); svg.remove(); }); - it("_getClosestWithinRange", function () { - var dataset2 = [ - { foo: 0, bar: 1 }, - { foo: 1, bar: 0.95 } - ]; - linePlot.addDataset(dataset2); - var closestData = linePlot._getClosestWithinRange({ x: 500, y: 0 }, 5); - assert.strictEqual(closestData.closestValue, twoPointData[1], "got closest point from first dataset"); - closestData = linePlot._getClosestWithinRange({ x: 500, y: 25 }, 5); - assert.strictEqual(closestData.closestValue, dataset2[1], "got closest point from second dataset"); - closestData = linePlot._getClosestWithinRange({ x: 500, y: 10 }, 5); - assert.isUndefined(closestData.closestValue, "returns nothing if no points are within range"); - closestData = linePlot._getClosestWithinRange({ x: 500, y: 10 }, 25); - assert.strictEqual(closestData.closestValue, twoPointData[1], "returns the closest point within range"); - closestData = linePlot._getClosestWithinRange({ x: 500, y: 20 }, 25); - assert.strictEqual(closestData.closestValue, dataset2[1], "returns the closest point within range"); - svg.remove(); - }); - it("_doHover()", function () { - var dataset2 = [ - { foo: 0, bar: 1 }, - { foo: 1, bar: 0.95 } - ]; - linePlot.addDataset(dataset2); - var hoverData = linePlot._doHover({ x: 495, y: 0 }); - var expectedDatum = twoPointData[1]; - assert.strictEqual(hoverData.data[0], expectedDatum, "returned the closest point within range"); - var hoverTarget = hoverData.selection; - assert.strictEqual(parseFloat(hoverTarget.attr("cx")), xScale.scale(expectedDatum.foo), "hover target was positioned correctly (x)"); - assert.strictEqual(parseFloat(hoverTarget.attr("cy")), yScale.scale(expectedDatum.bar), "hover target was positioned correctly (y)"); - hoverData = linePlot._doHover({ x: 0, y: 0 }); - expectedDatum = dataset2[0]; - assert.strictEqual(hoverData.data[0], expectedDatum, "returned the closest point within range"); - hoverTarget = hoverData.selection; - assert.strictEqual(parseFloat(hoverTarget.attr("cx")), xScale.scale(expectedDatum.foo), "hover target was positioned correctly (x)"); - assert.strictEqual(parseFloat(hoverTarget.attr("cy")), yScale.scale(expectedDatum.bar), "hover target was positioned correctly (y)"); - svg.remove(); - }); describe("getAllSelections()", function () { it("retrieves all dataset selections with no args", function () { - var dataset3 = [ + var dataset3 = new Plottable.Dataset([ { foo: 0, bar: 1 }, { foo: 1, bar: 0.95 } - ]; - linePlot.addDataset("d3", dataset3); + ]); + linePlot.addDataset(dataset3); var allLines = linePlot.getAllSelections(); - var allLines2 = linePlot.getAllSelections(linePlot._datasetKeysInOrder); - assert.deepEqual(allLines, allLines2, "all lines retrieved"); - svg.remove(); - }); - it("retrieves correct selections (string arg)", function () { - var dataset3 = [ - { foo: 0, bar: 1 }, - { foo: 1, bar: 0.95 } - ]; - linePlot.addDataset("d3", dataset3); - var allLines = linePlot.getAllSelections("d3"); - assert.strictEqual(allLines.size(), 1, "all lines retrieved"); - var selectionData = allLines.data(); - assert.include(selectionData, dataset3, "third dataset data in selection data"); + assert.strictEqual(allLines.size(), 2, "all lines retrieved"); svg.remove(); }); - it("retrieves correct selections (array arg)", function () { - var dataset3 = [ + it("retrieves correct selections", function () { + var dataset3 = new Plottable.Dataset([ { foo: 0, bar: 1 }, { foo: 1, bar: 0.95 } - ]; - linePlot.addDataset("d3", dataset3); - var allLines = linePlot.getAllSelections(["d3"]); + ]); + linePlot.addDataset(dataset3); + var allLines = linePlot.getAllSelections([dataset3]); assert.strictEqual(allLines.size(), 1, "all lines retrieved"); var selectionData = allLines.data(); - assert.include(selectionData, dataset3, "third dataset data in selection data"); + assert.include(selectionData, dataset3.data(), "third dataset data in selection data"); svg.remove(); }); - it("skips invalid keys", function () { - var dataset3 = [ + it("skips invalid Dataset", function () { + var dataset3 = new Plottable.Dataset([ { foo: 0, bar: 1 }, { foo: 1, bar: 0.95 } - ]; - linePlot.addDataset("d3", dataset3); - var allLines = linePlot.getAllSelections(["d3", "test"]); + ]); + linePlot.addDataset(dataset3); + var dummyDataset = new Plottable.Dataset([]); + var allLines = linePlot.getAllSelections([dataset3, dummyDataset]); assert.strictEqual(allLines.size(), 1, "all lines retrieved"); var selectionData = allLines.data(); - assert.include(selectionData, dataset3, "third dataset data in selection data"); + assert.include(selectionData, dataset3.data(), "third dataset data in selection data"); svg.remove(); }); }); describe("getAllPlotData()", function () { it("retrieves correct data", function () { - var dataset3 = [ + var dataset3 = new Plottable.Dataset([ { foo: 0, bar: 1 }, { foo: 1, bar: 0.95 } - ]; - linePlot.addDataset("d3", dataset3); + ]); + linePlot.addDataset(dataset3); var allLines = linePlot.getAllPlotData().selection; assert.strictEqual(allLines.size(), linePlot.datasets().length, "single line per dataset"); svg.remove(); @@ -3141,26 +3007,26 @@ describe("Plots", function () { var lines; var d0, d1; var d0Px, d1Px; - var dataset3; - function assertPlotDataEqual(expected, actual, msg) { - assert.deepEqual(expected.data, actual.data, msg); - assert.closeTo(expected.pixelPoints[0].x, actual.pixelPoints[0].x, 0.01, msg); - assert.closeTo(expected.pixelPoints[0].y, actual.pixelPoints[0].y, 0.01, msg); - assert.deepEqual(expected.selection, actual.selection, msg); + var dataset2; + function assertPlotDataEqual(actual, expected, msg) { + assert.deepEqual(actual.data, expected.data, msg); + assert.closeTo(actual.pixelPoints[0].x, expected.pixelPoints[0].x, 0.01, msg); + assert.closeTo(actual.pixelPoints[0].y, expected.pixelPoints[0].y, 0.01, msg); + assert.deepEqual(actual.selection, expected.selection, msg); } beforeEach(function () { - dataset3 = [ + dataset2 = new Plottable.Dataset([ { foo: 0, bar: 0.75 }, { foo: 1, bar: 0.25 } - ]; - linePlot.addDataset("d3", dataset3); + ]); + linePlot.addDataset(dataset2); lines = d3.selectAll(".line-plot .line"); - d0 = dataset3[0]; + d0 = dataset2.data()[0]; d0Px = { x: xScale.scale(xAccessor(d0)), y: yScale.scale(yAccessor(d0)) }; - d1 = dataset3[1]; + d1 = dataset2.data()[1]; d1Px = { x: xScale.scale(xAccessor(d1)), y: yScale.scale(yAccessor(d1)) @@ -3170,21 +3036,21 @@ describe("Plots", function () { var expected = { data: [d0], pixelPoints: [d0Px], - selection: d3.selectAll([lines[0][2]]) + selection: d3.selectAll([lines[0][1]]) }; var closest = linePlot.getClosestPlotData({ x: d0Px.x, y: d0Px.y - 1 }); - assertPlotDataEqual(expected, closest, "if above a point, it is closest"); + assertPlotDataEqual(closest, expected, "if above a point, it is closest"); closest = linePlot.getClosestPlotData({ x: d0Px.x, y: d0Px.y + 1 }); - assertPlotDataEqual(expected, closest, "if below a point, it is closest"); + assertPlotDataEqual(closest, expected, "if below a point, it is closest"); closest = linePlot.getClosestPlotData({ x: d0Px.x + 1, y: d0Px.y + 1 }); - assertPlotDataEqual(expected, closest, "if right of a point, it is closest"); + assertPlotDataEqual(closest, expected, "if right of a point, it is closest"); expected = { data: [d1], pixelPoints: [d1Px], - selection: d3.selectAll([lines[0][2]]) + selection: d3.selectAll([lines[0][1]]) }; closest = linePlot.getClosestPlotData({ x: d1Px.x - 1, y: d1Px.y }); - assertPlotDataEqual(expected, closest, "if left of a point, it is closest"); + assertPlotDataEqual(closest, expected, "if left of a point, it is closest"); svg.remove(); }); it("considers only in-view points", function () { @@ -3195,14 +3061,14 @@ describe("Plots", function () { x: xScale.scale(xAccessor(d1)), y: yScale.scale(yAccessor(d1)) }], - selection: d3.selectAll([lines[0][2]]) + selection: d3.selectAll([lines[0][1]]) }; var closest = linePlot.getClosestPlotData({ x: xScale.scale(0.25), y: d1Px.y }); - assertPlotDataEqual(expected, closest, "only in-view points are considered"); + assertPlotDataEqual(closest, expected, "only in-view points are considered"); svg.remove(); }); it("handles empty plots gracefully", function () { - linePlot = new Plottable.Plot.Line(xScale, yScale); + linePlot = new Plottable.Plots.Line(xScale, yScale); var closest = linePlot.getClosestPlotData({ x: d0Px.x, y: d0Px.y }); assert.lengthOf(closest.data, 0); assert.lengthOf(closest.pixelPoints, 0); @@ -3212,11 +3078,11 @@ describe("Plots", function () { }); it("retains original classes when class is projected", function () { var newClassProjector = function () { return "pink"; }; - linePlot.project("class", newClassProjector); + linePlot.attr("class", newClassProjector); linePlot.renderTo(svg); - var linePath = renderArea.select("." + Plottable._Drawer.Line.LINE_CLASS); + var linePath = renderArea.select("." + Plottable.Drawers.Line.LINE_CLASS); assert.isTrue(linePath.classed("pink")); - assert.isTrue(linePath.classed(Plottable._Drawer.Line.LINE_CLASS)); + assert.isTrue(linePath.classed(Plottable.Drawers.Line.LINE_CLASS)); svg.remove(); }); }); @@ -3228,12 +3094,12 @@ describe("Plots", function () { describe("AreaPlot", function () { // HACKHACK #1798: beforeEach being used below it("renders correctly with no data", function () { - var svg = generateSVG(400, 400); - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - var plot = new Plottable.Plot.Area(xScale, yScale); - plot.project("x", function (d) { return d.x; }, xScale); - plot.project("y", function (d) { return d.y; }, yScale); + var svg = TestMethods.generateSVG(400, 400); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); + var plot = new Plottable.Plots.Area(xScale, yScale); + plot.x(function (d) { return d.x; }, xScale); + plot.y(function (d) { return d.y; }, yScale); assert.doesNotThrow(function () { return plot.renderTo(svg); }, Error); assert.strictEqual(plot.width(), 400, "was allocated width"); assert.strictEqual(plot.height(), 400, "was allocated height"); @@ -3254,8 +3120,8 @@ describe("Plots", function () { var areaPlot; var renderArea; before(function () { - xScale = new Plottable.Scale.Linear().domain([0, 1]); - yScale = new Plottable.Scale.Linear().domain([0, 1]); + xScale = new Plottable.Scales.Linear().domain([0, 1]); + yScale = new Plottable.Scales.Linear().domain([0, 1]); xAccessor = function (d) { return d.foo; }; yAccessor = function (d) { return d.bar; }; y0Accessor = function () { return 0; }; @@ -3263,31 +3129,33 @@ describe("Plots", function () { fillAccessor = function () { return "steelblue"; }; }); beforeEach(function () { - svg = generateSVG(500, 500); + svg = TestMethods.generateSVG(500, 500); simpleDataset = new Plottable.Dataset(twoPointData); - areaPlot = new Plottable.Plot.Area(xScale, yScale); - areaPlot.addDataset("sd", simpleDataset).project("x", xAccessor, xScale).project("y", yAccessor, yScale).project("y0", y0Accessor, yScale).project("fill", fillAccessor).project("stroke", colorAccessor).renderTo(svg); + areaPlot = new Plottable.Plots.Area(xScale, yScale); + areaPlot.addDataset(simpleDataset); + areaPlot.x(xAccessor, xScale).y(yAccessor, yScale); + areaPlot.y0(y0Accessor, yScale).attr("fill", fillAccessor).attr("stroke", colorAccessor).renderTo(svg); renderArea = areaPlot._renderArea; }); it("draws area and line correctly", function () { var areaPath = renderArea.select(".area"); - assert.strictEqual(normalizePath(areaPath.attr("d")), "M0,500L500,0L500,500L0,500Z", "area d was set correctly"); + assert.strictEqual(TestMethods.normalizePath(areaPath.attr("d")), "M0,500L500,0L500,500L0,500Z", "area d was set correctly"); assert.strictEqual(areaPath.attr("fill"), "steelblue", "area fill was set correctly"); var areaComputedStyle = window.getComputedStyle(areaPath.node()); assert.strictEqual(areaComputedStyle.stroke, "none", "area stroke renders as \"none\""); var linePath = renderArea.select(".line"); - assert.strictEqual(normalizePath(linePath.attr("d")), "M0,500L500,0", "line d was set correctly"); + assert.strictEqual(TestMethods.normalizePath(linePath.attr("d")), "M0,500L500,0", "line d was set correctly"); assert.strictEqual(linePath.attr("stroke"), "#000000", "line stroke was set correctly"); var lineComputedStyle = window.getComputedStyle(linePath.node()); assert.strictEqual(lineComputedStyle.fill, "none", "line fill renders as \"none\""); svg.remove(); }); it("area fill works for non-zero floor values appropriately, e.g. half the height of the line", function () { - areaPlot.project("y0", function (d) { return d.bar / 2; }, yScale); + areaPlot.y0(function (d) { return d.bar / 2; }, yScale); areaPlot.renderTo(svg); renderArea = areaPlot._renderArea; var areaPath = renderArea.select(".area"); - assert.equal(normalizePath(areaPath.attr("d")), "M0,500L500,0L500,250L0,500Z"); + assert.strictEqual(TestMethods.normalizePath(areaPath.attr("d")), "M0,500L500,0L500,250L0,500Z"); svg.remove(); }); it("area is appended before line", function () { @@ -3310,69 +3178,59 @@ describe("Plots", function () { var dataWithNaN = areaData.slice(); dataWithNaN[2] = { foo: 0.4, bar: NaN }; simpleDataset.data(dataWithNaN); - var areaPathString = normalizePath(areaPath.attr("d")); - assertAreaPathCloseTo(areaPathString, expectedPath, 0.1, "area d was set correctly (y=NaN case)"); + var areaPathString = TestMethods.normalizePath(areaPath.attr("d")); + TestMethods.assertAreaPathCloseTo(areaPathString, expectedPath, 0.1, "area d was set correctly (y=NaN case)"); dataWithNaN[2] = { foo: NaN, bar: 0.4 }; simpleDataset.data(dataWithNaN); - areaPathString = normalizePath(areaPath.attr("d")); - assertAreaPathCloseTo(areaPathString, expectedPath, 0.1, "area d was set correctly (x=NaN case)"); + areaPathString = TestMethods.normalizePath(areaPath.attr("d")); + TestMethods.assertAreaPathCloseTo(areaPathString, expectedPath, 0.1, "area d was set correctly (x=NaN case)"); var dataWithUndefined = areaData.slice(); dataWithUndefined[2] = { foo: 0.4, bar: undefined }; simpleDataset.data(dataWithUndefined); - areaPathString = normalizePath(areaPath.attr("d")); - assertAreaPathCloseTo(areaPathString, expectedPath, 0.1, "area d was set correctly (y=undefined case)"); + areaPathString = TestMethods.normalizePath(areaPath.attr("d")); + TestMethods.assertAreaPathCloseTo(areaPathString, expectedPath, 0.1, "area d was set correctly (y=undefined case)"); dataWithUndefined[2] = { foo: undefined, bar: 0.4 }; simpleDataset.data(dataWithUndefined); - areaPathString = normalizePath(areaPath.attr("d")); - assertAreaPathCloseTo(areaPathString, expectedPath, 0.1, "area d was set correctly (x=undefined case)"); + areaPathString = TestMethods.normalizePath(areaPath.attr("d")); + TestMethods.assertAreaPathCloseTo(areaPathString, expectedPath, 0.1, "area d was set correctly (x=undefined case)"); svg.remove(); }); describe("getAllSelections()", function () { it("retrieves all selections with no args", function () { var newTwoPointData = [{ foo: 2, bar: 1 }, { foo: 3, bar: 2 }]; - areaPlot.addDataset("newTwo", new Plottable.Dataset(newTwoPointData)); + areaPlot.addDataset(new Plottable.Dataset(newTwoPointData)); var allAreas = areaPlot.getAllSelections(); - var allAreas2 = areaPlot.getAllSelections(areaPlot._datasetKeysInOrder); - assert.deepEqual(allAreas, allAreas2, "all areas/lines retrieved"); assert.strictEqual(allAreas.filter(".line").size(), 2, "2 lines retrieved"); assert.strictEqual(allAreas.filter(".area").size(), 2, "2 areas retrieved"); svg.remove(); }); - it("retrieves correct selections (string arg)", function () { - var newTwoPointData = [{ foo: 2, bar: 1 }, { foo: 3, bar: 2 }]; - areaPlot.addDataset("newTwo", new Plottable.Dataset(newTwoPointData)); - var allAreas = areaPlot.getAllSelections("newTwo"); - assert.strictEqual(allAreas.size(), 2, "areas/lines retrieved"); - var selectionData = allAreas.data(); - assert.include(selectionData, newTwoPointData, "new dataset data in selection data"); - svg.remove(); - }); - it("retrieves correct selections (array arg)", function () { - var newTwoPointData = [{ foo: 2, bar: 1 }, { foo: 3, bar: 2 }]; - areaPlot.addDataset("newTwo", new Plottable.Dataset(newTwoPointData)); - var allAreas = areaPlot.getAllSelections(["newTwo"]); + it("retrieves correct selections", function () { + var twoPointDataset = new Plottable.Dataset([{ foo: 2, bar: 1 }, { foo: 3, bar: 2 }]); + areaPlot.addDataset(twoPointDataset); + var allAreas = areaPlot.getAllSelections([twoPointDataset]); assert.strictEqual(allAreas.size(), 2, "areas/lines retrieved"); var selectionData = allAreas.data(); - assert.include(selectionData, newTwoPointData, "new dataset data in selection data"); + assert.include(selectionData, twoPointDataset.data(), "new dataset data in selection data"); svg.remove(); }); - it("skips invalid keys", function () { - var newTwoPointData = [{ foo: 2, bar: 1 }, { foo: 3, bar: 2 }]; - areaPlot.addDataset("newTwo", new Plottable.Dataset(newTwoPointData)); - var allAreas = areaPlot.getAllSelections(["newTwo", "test"]); + it("skips invalid Datasets", function () { + var twoPointDataset = new Plottable.Dataset([{ foo: 2, bar: 1 }, { foo: 3, bar: 2 }]); + areaPlot.addDataset(twoPointDataset); + var dummyDataset = new Plottable.Dataset([]); + var allAreas = areaPlot.getAllSelections([twoPointDataset, dummyDataset]); assert.strictEqual(allAreas.size(), 2, "areas/lines retrieved"); var selectionData = allAreas.data(); - assert.include(selectionData, newTwoPointData, "new dataset data in selection data"); + assert.include(selectionData, twoPointDataset.data(), "new dataset data in selection data"); svg.remove(); }); }); it("retains original classes when class is projected", function () { var newClassProjector = function () { return "pink"; }; - areaPlot.project("class", newClassProjector); + areaPlot.attr("class", newClassProjector); areaPlot.renderTo(svg); - var areaPath = renderArea.select("." + Plottable._Drawer.Area.AREA_CLASS); + var areaPath = renderArea.select("." + Plottable.Drawers.Area.AREA_CLASS); assert.isTrue(areaPath.classed("pink")); - assert.isTrue(areaPath.classed(Plottable._Drawer.Area.AREA_CLASS)); + assert.isTrue(areaPath.classed(Plottable.Drawers.Area.AREA_CLASS)); svg.remove(); }); }); @@ -3384,12 +3242,12 @@ describe("Plots", function () { describe("Bar Plot", function () { // HACKHACK #1798: beforeEach being used below it("renders correctly with no data", function () { - var svg = generateSVG(400, 400); - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - var plot = new Plottable.Plot.Bar(xScale, yScale); - plot.project("x", function (d) { return d.x; }, xScale); - plot.project("y", function (d) { return d.y; }, yScale); + var svg = TestMethods.generateSVG(400, 400); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); + var plot = new Plottable.Plots.Bar(xScale, yScale); + plot.x(function (d) { return d.x; }, xScale); + plot.y(function (d) { return d.y; }, yScale); assert.doesNotThrow(function () { return plot.renderTo(svg); }, Error); assert.strictEqual(plot.width(), 400, "was allocated width"); assert.strictEqual(plot.height(), 400, "was allocated height"); @@ -3410,22 +3268,22 @@ describe("Plots", function () { var SVG_WIDTH = 600; var SVG_HEIGHT = 400; beforeEach(function () { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - xScale = new Plottable.Scale.Category().domain(["A", "B"]); - yScale = new Plottable.Scale.Linear(); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + xScale = new Plottable.Scales.Category().domain(["A", "B"]); + yScale = new Plottable.Scales.Linear(); var data = [ { x: "A", y: 1 }, { x: "B", y: -1.5 }, { x: "B", y: 1 } ]; dataset = new Plottable.Dataset(data); - barPlot = new Plottable.Plot.Bar(xScale, yScale); + barPlot = new Plottable.Plots.Bar(xScale, yScale); barPlot.addDataset(dataset); barPlot.animate(false); barPlot.baseline(0); yScale.domain([-2, 2]); - barPlot.project("x", "x", xScale); - barPlot.project("y", "y", yScale); + barPlot.x(function (d) { return d.x; }, xScale); + barPlot.y(function (d) { return d.y; }, yScale); barPlot.renderTo(svg); }); it("renders correctly", function () { @@ -3434,19 +3292,19 @@ describe("Plots", function () { assert.lengthOf(bars[0], 3, "One bar was created per data point"); var bar0 = d3.select(bars[0][0]); var bar1 = d3.select(bars[0][1]); - assert.closeTo(numAttr(bar0, "width"), xScale.rangeBand(), 1, "bar0 width is correct"); - assert.closeTo(numAttr(bar1, "width"), xScale.rangeBand(), 1, "bar1 width is correct"); - assert.equal(bar0.attr("height"), "100", "bar0 height is correct"); - assert.equal(bar1.attr("height"), "150", "bar1 height is correct"); - assert.closeTo(numAttr(bar0, "x"), 111, 1, "bar0 x is correct"); - assert.closeTo(numAttr(bar1, "x"), 333, 1, "bar1 x is correct"); - assert.equal(bar0.attr("y"), "100", "bar0 y is correct"); - assert.equal(bar1.attr("y"), "200", "bar1 y is correct"); + assert.closeTo(TestMethods.numAttr(bar0, "width"), xScale.rangeBand(), 1, "bar0 width is correct"); + assert.closeTo(TestMethods.numAttr(bar1, "width"), xScale.rangeBand(), 1, "bar1 width is correct"); + assert.strictEqual(bar0.attr("height"), "100", "bar0 height is correct"); + assert.strictEqual(bar1.attr("height"), "150", "bar1 height is correct"); + assert.closeTo(TestMethods.numAttr(bar0, "x"), 111, 1, "bar0 x is correct"); + assert.closeTo(TestMethods.numAttr(bar1, "x"), 333, 1, "bar1 x is correct"); + assert.strictEqual(bar0.attr("y"), "100", "bar0 y is correct"); + assert.strictEqual(bar1.attr("y"), "200", "bar1 y is correct"); var baseline = renderArea.select(".baseline"); - assert.equal(baseline.attr("y1"), "200", "the baseline is in the correct vertical position"); - assert.equal(baseline.attr("y2"), "200", "the baseline is in the correct vertical position"); - assert.equal(baseline.attr("x1"), "0", "the baseline starts at the edge of the chart"); - assert.equal(baseline.attr("x2"), SVG_WIDTH, "the baseline ends at the edge of the chart"); + assert.strictEqual(baseline.attr("y1"), "200", "the baseline is in the correct vertical position"); + assert.strictEqual(baseline.attr("y2"), "200", "the baseline is in the correct vertical position"); + assert.strictEqual(baseline.attr("x1"), "0", "the baseline starts at the edge of the chart"); + assert.strictEqual(baseline.attr("x2"), String(SVG_WIDTH), "the baseline ends at the edge of the chart"); svg.remove(); }); it("baseline value can be changed; barPlot updates appropriately", function () { @@ -3455,21 +3313,21 @@ describe("Plots", function () { var bars = renderArea.selectAll("rect"); var bar0 = d3.select(bars[0][0]); var bar1 = d3.select(bars[0][1]); - assert.equal(bar0.attr("height"), "200", "bar0 height is correct"); - assert.equal(bar1.attr("height"), "50", "bar1 height is correct"); - assert.equal(bar0.attr("y"), "100", "bar0 y is correct"); - assert.equal(bar1.attr("y"), "300", "bar1 y is correct"); + assert.strictEqual(bar0.attr("height"), "200", "bar0 height is correct"); + assert.strictEqual(bar1.attr("height"), "50", "bar1 height is correct"); + assert.strictEqual(bar0.attr("y"), "100", "bar0 y is correct"); + assert.strictEqual(bar1.attr("y"), "300", "bar1 y is correct"); var baseline = renderArea.select(".baseline"); - assert.equal(baseline.attr("y1"), "300", "the baseline is in the correct vertical position"); - assert.equal(baseline.attr("y2"), "300", "the baseline is in the correct vertical position"); - assert.equal(baseline.attr("x1"), "0", "the baseline starts at the edge of the chart"); - assert.equal(baseline.attr("x2"), SVG_WIDTH, "the baseline ends at the edge of the chart"); + assert.strictEqual(baseline.attr("y1"), "300", "the baseline is in the correct vertical position"); + assert.strictEqual(baseline.attr("y2"), "300", "the baseline is in the correct vertical position"); + assert.strictEqual(baseline.attr("x1"), "0", "the baseline starts at the edge of the chart"); + assert.strictEqual(baseline.attr("x2"), String(SVG_WIDTH), "the baseline ends at the edge of the chart"); svg.remove(); }); - it("getBar()", function () { + it("getBars()", function () { var bar = barPlot.getBars(155, 150); // in the middle of bar 0 assert.lengthOf(bar[0], 1, "getBar returns a bar"); - assert.equal(bar.data()[0], dataset.data()[0], "the data in the bar matches the datasource"); + assert.strictEqual(bar.data()[0], dataset.data()[0], "the data in the bar matches the datasource"); bar = barPlot.getBars(-1, -1); // no bars here assert.isTrue(bar.empty(), "returns empty selection if no bar was selected"); bar = barPlot.getBars(200, 50); // between the two bars @@ -3480,13 +3338,13 @@ describe("Plots", function () { // origin is at the top left! bar = barPlot.getBars({ min: 155, max: 455 }, { min: 150, max: 150 }); assert.lengthOf(bar.data(), 2, "selected 2 bars (not the negative one)"); - assert.equal(bar.data()[0], dataset.data()[0], "the data in bar 0 matches the datasource"); - assert.equal(bar.data()[1], dataset.data()[2], "the data in bar 1 matches the datasource"); + assert.strictEqual(bar.data()[0], dataset.data()[0], "the data in bar 0 matches the datasource"); + assert.strictEqual(bar.data()[1], dataset.data()[2], "the data in bar 1 matches the datasource"); bar = barPlot.getBars({ min: 155, max: 455 }, { min: 150, max: 350 }); assert.lengthOf(bar.data(), 3, "selected all the bars"); - assert.equal(bar.data()[0], dataset.data()[0], "the data in bar 0 matches the datasource"); - assert.equal(bar.data()[1], dataset.data()[1], "the data in bar 1 matches the datasource"); - assert.equal(bar.data()[2], dataset.data()[2], "the data in bar 2 matches the datasource"); + assert.strictEqual(bar.data()[0], dataset.data()[0], "the data in bar 0 matches the datasource"); + assert.strictEqual(bar.data()[1], dataset.data()[1], "the data in bar 1 matches the datasource"); + assert.strictEqual(bar.data()[2], dataset.data()[2], "the data in bar 2 matches the datasource"); svg.remove(); }); it("don't show points from outside of domain", function () { @@ -3595,7 +3453,7 @@ describe("Plots", function () { svg.remove(); }); it("handles empty plots gracefully", function () { - barPlot = new Plottable.Plot.Bar(xScale, yScale); + barPlot = new Plottable.Plots.Bar(xScale, yScale); var closest = barPlot.getClosestPlotData({ x: d0Px.x, y: d0Px.y }); assert.lengthOf(closest.data, 0, "empty plots return empty data"); assert.lengthOf(closest.pixelPoints, 0, "empty plots return empty pixelPoints"); @@ -3613,22 +3471,22 @@ describe("Plots", function () { var SVG_WIDTH = 600; var SVG_HEIGHT = 400; beforeEach(function () { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - xScale = new Plottable.Scale.ModifiedLog(); - yScale = new Plottable.Scale.Linear(); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + xScale = new Plottable.Scales.ModifiedLog(); + yScale = new Plottable.Scales.Linear(); var data = [ { x: 2, y: 1 }, { x: 10, y: -1.5 }, { x: 100, y: 1 } ]; dataset = new Plottable.Dataset(data); - barPlot = new Plottable.Plot.Bar(xScale, yScale); + barPlot = new Plottable.Plots.Bar(xScale, yScale); barPlot.addDataset(dataset); barPlot.animate(false); barPlot.baseline(0); yScale.domain([-2, 2]); - barPlot.project("x", "x", xScale); - barPlot.project("y", "y", yScale); + barPlot.x(function (d) { return d.x; }, xScale); + barPlot.y(function (d) { return d.y; }, yScale); barPlot.renderTo(svg); }); it("barPixelWidth calculated appropriately", function () { @@ -3643,9 +3501,9 @@ describe("Plots", function () { var bar0 = d3.select(bars[0][0]); var bar1 = d3.select(bars[0][1]); var bar2 = d3.select(bars[0][2]); - assert.closeTo(numAttr(bar0, "width"), barPixelWidth, 0.1, "bar0 width is correct"); - assert.closeTo(numAttr(bar1, "width"), barPixelWidth, 0.1, "bar1 width is correct"); - assert.closeTo(numAttr(bar2, "width"), barPixelWidth, 0.1, "bar2 width is correct"); + assert.closeTo(TestMethods.numAttr(bar0, "width"), barPixelWidth, 0.1, "bar0 width is correct"); + assert.closeTo(TestMethods.numAttr(bar1, "width"), barPixelWidth, 0.1, "bar1 width is correct"); + assert.closeTo(TestMethods.numAttr(bar2, "width"), barPixelWidth, 0.1, "bar2 width is correct"); svg.remove(); }); }); @@ -3658,20 +3516,20 @@ describe("Plots", function () { var SVG_WIDTH = 600; var SVG_HEIGHT = 400; beforeEach(function () { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - xScale = new Plottable.Scale.Linear(); - yScale = new Plottable.Scale.Linear(); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + xScale = new Plottable.Scales.Linear(); + yScale = new Plottable.Scales.Linear(); var data = [ { x: 2, y: 1 }, { x: 10, y: -1.5 }, { x: 100, y: 1 } ]; + barPlot = new Plottable.Plots.Bar(xScale, yScale); dataset = new Plottable.Dataset(data); - barPlot = new Plottable.Plot.Bar(xScale, yScale); barPlot.addDataset(dataset); barPlot.baseline(0); - barPlot.project("x", "x", xScale); - barPlot.project("y", "y", yScale); + barPlot.x(function (d) { return d.x; }, xScale); + barPlot.y(function (d) { return d.y; }, yScale); barPlot.renderTo(svg); }); it("bar width takes an appropriate value", function () { @@ -3686,26 +3544,26 @@ describe("Plots", function () { var bar0 = d3.select(bars[0][0]); var bar1 = d3.select(bars[0][1]); var bar2 = d3.select(bars[0][2]); - assert.closeTo(numAttr(bar0, "width"), barPixelWidth, 0.1, "bar0 width is correct"); - assert.closeTo(numAttr(bar1, "width"), barPixelWidth, 0.1, "bar1 width is correct"); - assert.closeTo(numAttr(bar2, "width"), barPixelWidth, 0.1, "bar2 width is correct"); + assert.closeTo(TestMethods.numAttr(bar0, "width"), barPixelWidth, 0.1, "bar0 width is correct"); + assert.closeTo(TestMethods.numAttr(bar1, "width"), barPixelWidth, 0.1, "bar1 width is correct"); + assert.closeTo(TestMethods.numAttr(bar2, "width"), barPixelWidth, 0.1, "bar2 width is correct"); svg.remove(); }); it("sensible bar width one datum", function () { barPlot.removeDataset(dataset); - barPlot.addDataset([{ x: 10, y: 2 }]); + barPlot.addDataset(new Plottable.Dataset([{ x: 10, y: 2 }])); assert.closeTo(barPlot._getBarPixelWidth(), 228, 0.1, "sensible bar width for only one datum"); svg.remove(); }); it("sensible bar width same datum", function () { barPlot.removeDataset(dataset); - barPlot.addDataset([{ x: 10, y: 2 }, { x: 10, y: 2 }]); + barPlot.addDataset(new Plottable.Dataset([{ x: 10, y: 2 }, { x: 10, y: 2 }])); assert.closeTo(barPlot._getBarPixelWidth(), 228, 0.1, "uses the width sensible for one datum"); svg.remove(); }); it("sensible bar width unsorted data", function () { barPlot.removeDataset(dataset); - barPlot.addDataset([{ x: 2, y: 2 }, { x: 20, y: 2 }, { x: 5, y: 2 }]); + barPlot.addDataset(new Plottable.Dataset([{ x: 2, y: 2 }, { x: 20, y: 2 }, { x: 5, y: 2 }])); var expectedBarPixelWidth = (xScale.scale(5) - xScale.scale(2)) * 0.95; assert.closeTo(barPlot._getBarPixelWidth(), expectedBarPixelWidth, 0.1, "bar width uses closest sorted x values"); svg.remove(); @@ -3716,12 +3574,13 @@ describe("Plots", function () { var barPlot; var xScale; beforeEach(function () { - svg = generateSVG(600, 400); + svg = TestMethods.generateSVG(600, 400); var data = [{ x: "12/01/92", y: 0, type: "a" }, { x: "12/01/93", y: 1, type: "a" }, { x: "12/01/94", y: 1, type: "a" }, { x: "12/01/95", y: 2, type: "a" }, { x: "12/01/96", y: 2, type: "a" }, { x: "12/01/97", y: 2, type: "a" }]; - xScale = new Plottable.Scale.Time(); - var yScale = new Plottable.Scale.Linear(); - barPlot = new Plottable.Plot.Bar(xScale, yScale); - barPlot.addDataset(data).project("x", function (d) { return d3.time.format("%m/%d/%y").parse(d.x); }, xScale).project("y", "y", yScale).renderTo(svg); + xScale = new Plottable.Scales.Time(); + var yScale = new Plottable.Scales.Linear(); + barPlot = new Plottable.Plots.Bar(xScale, yScale); + barPlot.addDataset(new Plottable.Dataset(data)); + barPlot.x(function (d) { return d3.time.format("%m/%d/%y").parse(d.x); }, xScale).y(function (d) { return d.y; }, yScale).renderTo(svg); }); it("bar width takes an appropriate value", function () { var timeFormatter = d3.time.format("%m/%d/%y"); @@ -3739,9 +3598,9 @@ describe("Plots", function () { var SVG_WIDTH = 600; var SVG_HEIGHT = 400; beforeEach(function () { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - yScale = new Plottable.Scale.Category().domain(["A", "B"]); - xScale = new Plottable.Scale.Linear(); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + yScale = new Plottable.Scales.Category().domain(["A", "B"]); + xScale = new Plottable.Scales.Linear(); xScale.domain([-3, 3]); var data = [ { y: "A", x: 1 }, @@ -3749,12 +3608,12 @@ describe("Plots", function () { { y: "B", x: 1 } ]; dataset = new Plottable.Dataset(data); - barPlot = new Plottable.Plot.Bar(xScale, yScale, false); + barPlot = new Plottable.Plots.Bar(xScale, yScale, false); barPlot.addDataset(dataset); barPlot.animate(false); barPlot.baseline(0); - barPlot.project("x", "x", xScale); - barPlot.project("y", "y", yScale); + barPlot.x(function (d) { return d.x; }, xScale); + barPlot.y(function (d) { return d.y; }, yScale); barPlot.renderTo(svg); }); it("renders correctly", function () { @@ -3763,19 +3622,19 @@ describe("Plots", function () { assert.lengthOf(bars[0], 3, "One bar was created per data point"); var bar0 = d3.select(bars[0][0]); var bar1 = d3.select(bars[0][1]); - assert.closeTo(numAttr(bar0, "height"), yScale.rangeBand(), 1, "bar0 height is correct"); - assert.closeTo(numAttr(bar1, "height"), yScale.rangeBand(), 1, "bar1 height is correct"); - assert.equal(bar0.attr("width"), "100", "bar0 width is correct"); - assert.equal(bar1.attr("width"), "150", "bar1 width is correct"); - assert.closeTo(numAttr(bar0, "y"), 74, 1, "bar0 y is correct"); - assert.closeTo(numAttr(bar1, "y"), 222, 1, "bar1 y is correct"); - assert.equal(bar0.attr("x"), "300", "bar0 x is correct"); - assert.equal(bar1.attr("x"), "150", "bar1 x is correct"); + assert.closeTo(TestMethods.numAttr(bar0, "height"), yScale.rangeBand(), 1, "bar0 height is correct"); + assert.closeTo(TestMethods.numAttr(bar1, "height"), yScale.rangeBand(), 1, "bar1 height is correct"); + assert.strictEqual(bar0.attr("width"), "100", "bar0 width is correct"); + assert.strictEqual(bar1.attr("width"), "150", "bar1 width is correct"); + assert.closeTo(TestMethods.numAttr(bar0, "y"), 74, 1, "bar0 y is correct"); + assert.closeTo(TestMethods.numAttr(bar1, "y"), 222, 1, "bar1 y is correct"); + assert.strictEqual(bar0.attr("x"), "300", "bar0 x is correct"); + assert.strictEqual(bar1.attr("x"), "150", "bar1 x is correct"); var baseline = renderArea.select(".baseline"); - assert.equal(baseline.attr("x1"), "300", "the baseline is in the correct horizontal position"); - assert.equal(baseline.attr("x2"), "300", "the baseline is in the correct horizontal position"); - assert.equal(baseline.attr("y1"), "0", "the baseline starts at the top of the chart"); - assert.equal(baseline.attr("y2"), SVG_HEIGHT, "the baseline ends at the bottom of the chart"); + assert.strictEqual(baseline.attr("x1"), "300", "the baseline is in the correct horizontal position"); + assert.strictEqual(baseline.attr("x2"), "300", "the baseline is in the correct horizontal position"); + assert.strictEqual(baseline.attr("y1"), "0", "the baseline starts at the top of the chart"); + assert.strictEqual(baseline.attr("y2"), String(SVG_HEIGHT), "the baseline ends at the bottom of the chart"); svg.remove(); }); it("baseline value can be changed; barPlot updates appropriately", function () { @@ -3784,15 +3643,15 @@ describe("Plots", function () { var bars = renderArea.selectAll("rect"); var bar0 = d3.select(bars[0][0]); var bar1 = d3.select(bars[0][1]); - assert.equal(bar0.attr("width"), "200", "bar0 width is correct"); - assert.equal(bar1.attr("width"), "50", "bar1 width is correct"); - assert.equal(bar0.attr("x"), "200", "bar0 x is correct"); - assert.equal(bar1.attr("x"), "150", "bar1 x is correct"); + assert.strictEqual(bar0.attr("width"), "200", "bar0 width is correct"); + assert.strictEqual(bar1.attr("width"), "50", "bar1 width is correct"); + assert.strictEqual(bar0.attr("x"), "200", "bar0 x is correct"); + assert.strictEqual(bar1.attr("x"), "150", "bar1 x is correct"); var baseline = renderArea.select(".baseline"); - assert.equal(baseline.attr("x1"), "200", "the baseline is in the correct horizontal position"); - assert.equal(baseline.attr("x2"), "200", "the baseline is in the correct horizontal position"); - assert.equal(baseline.attr("y1"), "0", "the baseline starts at the top of the chart"); - assert.equal(baseline.attr("y2"), SVG_HEIGHT, "the baseline ends at the bottom of the chart"); + assert.strictEqual(baseline.attr("x1"), "200", "the baseline is in the correct horizontal position"); + assert.strictEqual(baseline.attr("x2"), "200", "the baseline is in the correct horizontal position"); + assert.strictEqual(baseline.attr("y1"), "0", "the baseline starts at the top of the chart"); + assert.strictEqual(baseline.attr("y2"), String(SVG_HEIGHT), "the baseline ends at the bottom of the chart"); svg.remove(); }); it("width projector may be overwritten, and calling project queues rerender", function () { @@ -3801,13 +3660,13 @@ describe("Plots", function () { var bar1 = d3.select(bars[0][1]); var bar0y = bar0.data()[0].y; var bar1y = bar1.data()[0].y; - barPlot.project("width", 10); - assert.closeTo(numAttr(bar0, "height"), 10, 0.01, "bar0 height"); - assert.closeTo(numAttr(bar1, "height"), 10, 0.01, "bar1 height"); - assert.closeTo(numAttr(bar0, "width"), 100, 0.01, "bar0 width"); - assert.closeTo(numAttr(bar1, "width"), 150, 0.01, "bar1 width"); - assert.closeTo(numAttr(bar0, "y"), yScale.scale(bar0y) - numAttr(bar0, "height") / 2, 0.01, "bar0 ypos"); - assert.closeTo(numAttr(bar1, "y"), yScale.scale(bar1y) - numAttr(bar1, "height") / 2, 0.01, "bar1 ypos"); + barPlot.attr("width", 10); + assert.closeTo(TestMethods.numAttr(bar0, "height"), 10, 0.01, "bar0 height"); + assert.closeTo(TestMethods.numAttr(bar1, "height"), 10, 0.01, "bar1 height"); + assert.closeTo(TestMethods.numAttr(bar0, "width"), 100, 0.01, "bar0 width"); + assert.closeTo(TestMethods.numAttr(bar1, "width"), 150, 0.01, "bar1 width"); + assert.closeTo(TestMethods.numAttr(bar0, "y"), yScale.scale(bar0y) - TestMethods.numAttr(bar0, "height") / 2, 0.01, "bar0 ypos"); + assert.closeTo(TestMethods.numAttr(bar1, "y"), yScale.scale(bar1y) - TestMethods.numAttr(bar1, "height") / 2, 0.01, "bar1 ypos"); svg.remove(); }); describe("getAllPlotData()", function () { @@ -3919,15 +3778,15 @@ describe("Plots", function () { var yScale; var svg; beforeEach(function () { - svg = generateSVG(); + svg = TestMethods.generateSVG(); data = [{ x: "foo", y: 5 }, { x: "bar", y: 640 }, { x: "zoo", y: 12345 }]; + xScale = new Plottable.Scales.Category(); + yScale = new Plottable.Scales.Linear(); dataset = new Plottable.Dataset(data); - xScale = new Plottable.Scale.Category(); - yScale = new Plottable.Scale.Linear(); - plot = new Plottable.Plot.Bar(xScale, yScale); + plot = new Plottable.Plots.Bar(xScale, yScale); plot.addDataset(dataset); - plot.project("x", "x", xScale); - plot.project("y", "y", yScale); + plot.x(function (d) { return d.x; }, xScale); + plot.y(function (d) { return d.y; }, yScale); }); it("bar labels disabled by default", function () { plot.renderTo(svg); @@ -3937,33 +3796,33 @@ describe("Plots", function () { }); it("bar labels render properly", function () { plot.renderTo(svg); - plot.barLabelsEnabled(true); + plot.labelsEnabled(true); var texts = svg.selectAll("text")[0].map(function (n) { return d3.select(n).text(); }); assert.lengthOf(texts, 2, "both texts drawn"); - assert.equal(texts[0], "640", "first label is 640"); - assert.equal(texts[1], "12345", "first label is 12345"); + assert.strictEqual(texts[0], "640", "first label is 640"); + assert.strictEqual(texts[1], "12345", "first label is 12345"); svg.remove(); }); it("bar labels hide if bars too skinny", function () { - plot.barLabelsEnabled(true); + plot.labelsEnabled(true); plot.renderTo(svg); - plot.barLabelFormatter(function (n) { return n.toString() + (n === 12345 ? "looong" : ""); }); + plot.labelFormatter(function (n) { return n.toString() + (n === 12345 ? "looong" : ""); }); var texts = svg.selectAll("text")[0].map(function (n) { return d3.select(n).text(); }); assert.lengthOf(texts, 0, "no text drawn"); svg.remove(); }); it("formatters are used properly", function () { - plot.barLabelsEnabled(true); - plot.barLabelFormatter(function (n) { return n.toString() + "%"; }); + plot.labelsEnabled(true); + plot.labelFormatter(function (n) { return n.toString() + "%"; }); plot.renderTo(svg); var texts = svg.selectAll("text")[0].map(function (n) { return d3.select(n).text(); }); assert.lengthOf(texts, 2, "both texts drawn"); - assert.equal(texts[0], "640%", "first label is 640%"); - assert.equal(texts[1], "12345%", "first label is 12345%"); + assert.strictEqual(texts[0], "640%", "first label is 640%"); + assert.strictEqual(texts[1], "12345%", "first label is 12345%"); svg.remove(); }); it("bar labels are removed instantly on dataset change", function (done) { - plot.barLabelsEnabled(true); + plot.labelsEnabled(true); plot.renderTo(svg); var texts = svg.selectAll("text")[0].map(function (n) { return d3.select(n).text(); }); assert.lengthOf(texts, 2, "both texts drawn"); @@ -3989,72 +3848,56 @@ describe("Plots", function () { var dataset; var svg; beforeEach(function () { - svg = generateSVG(); + svg = TestMethods.generateSVG(); dataset = new Plottable.Dataset(); - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Linear(); - verticalBarPlot = new Plottable.Plot.Bar(xScale, yScale); - verticalBarPlot.project("x", "x", xScale); - verticalBarPlot.project("y", "y", yScale); + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Linear(); + verticalBarPlot = new Plottable.Plots.Bar(xScale, yScale); + verticalBarPlot.x(function (d) { return d.x; }, xScale); + verticalBarPlot.y(function (d) { return d.y; }, yScale); }); it("retrieves all dataset selections with no args", function () { var barData = [{ x: "foo", y: 5 }, { x: "bar", y: 640 }, { x: "zoo", y: 12345 }]; - var barData2 = [{ x: "one", y: 5 }, { x: "two", y: 640 }, { x: "three", y: 12345 }]; - verticalBarPlot.addDataset("a", barData); - verticalBarPlot.addDataset("b", barData2); + verticalBarPlot.addDataset(new Plottable.Dataset(barData)); verticalBarPlot.renderTo(svg); var allBars = verticalBarPlot.getAllSelections(); - var allBars2 = verticalBarPlot.getAllSelections(verticalBarPlot._datasetKeysInOrder); - assert.deepEqual(allBars, allBars2, "both ways of getting all selections work"); + assert.strictEqual(allBars.size(), 3, "retrieved all bars"); svg.remove(); }); - it("retrieves correct selections (string arg)", function () { - var barData = [{ x: "foo", y: 5 }, { x: "bar", y: 640 }, { x: "zoo", y: 12345 }]; - var barData2 = [{ x: "one", y: 5 }, { x: "two", y: 640 }, { x: "three", y: 12345 }]; - verticalBarPlot.addDataset("a", barData); - verticalBarPlot.addDataset(barData2); + it("retrieves correct selections for supplied Datasets", function () { + var dataset1 = new Plottable.Dataset([{ x: "foo", y: 5 }, { x: "bar", y: 640 }, { x: "zoo", y: 12345 }]); + var dataset2 = new Plottable.Dataset([{ x: "one", y: 5 }, { x: "two", y: 640 }, { x: "three", y: 12345 }]); + verticalBarPlot.addDataset(dataset1); + verticalBarPlot.addDataset(dataset2); verticalBarPlot.renderTo(svg); - var allBars = verticalBarPlot.getAllSelections("a"); + var allBars = verticalBarPlot.getAllSelections([dataset1]); assert.strictEqual(allBars.size(), 3, "all bars retrieved"); var selectionData = allBars.data(); - assert.includeMembers(selectionData, barData, "first dataset data in selection data"); - svg.remove(); - }); - it("retrieves correct selections (array arg)", function () { - var barData = [{ x: "foo", y: 5 }, { x: "bar", y: 640 }, { x: "zoo", y: 12345 }]; - var barData2 = [{ x: "one", y: 5 }, { x: "two", y: 640 }, { x: "three", y: 12345 }]; - verticalBarPlot.addDataset("a", barData); - verticalBarPlot.addDataset("b", barData2); - verticalBarPlot.renderTo(svg); - var allBars = verticalBarPlot.getAllSelections(["a", "b"]); - assert.strictEqual(allBars.size(), 6, "all bars retrieved"); - var selectionData = allBars.data(); - assert.includeMembers(selectionData, barData, "first dataset data in selection data"); - assert.includeMembers(selectionData, barData2, "second dataset data in selection data"); + assert.includeMembers(selectionData, dataset1.data(), "first dataset data in selection data"); svg.remove(); }); - it("skips invalid keys", function () { - var barData = [{ x: "foo", y: 5 }, { x: "bar", y: 640 }, { x: "zoo", y: 12345 }]; - var barData2 = [{ x: "one", y: 5 }, { x: "two", y: 640 }, { x: "three", y: 12345 }]; - verticalBarPlot.addDataset("a", barData); - verticalBarPlot.addDataset("b", barData2); + it("skips invalid Datasets", function () { + var dataset1 = new Plottable.Dataset([{ x: "foo", y: 5 }, { x: "bar", y: 640 }, { x: "zoo", y: 12345 }]); + var notAddedDataset = new Plottable.Dataset([{ x: "one", y: 5 }, { x: "two", y: 640 }, { x: "three", y: 12345 }]); + verticalBarPlot.addDataset(dataset1); verticalBarPlot.renderTo(svg); - var allBars = verticalBarPlot.getAllSelections(["a", "c"]); + var allBars = verticalBarPlot.getAllSelections([dataset1, notAddedDataset]); assert.strictEqual(allBars.size(), 3, "all bars retrieved"); var selectionData = allBars.data(); - assert.includeMembers(selectionData, barData, "first dataset data in selection data"); + assert.includeMembers(selectionData, dataset1.data(), "first dataset data in selection data"); svg.remove(); }); }); it("plot auto domain scale to visible points on Category scale", function () { - var svg = generateSVG(500, 500); - var xAccessor = function (d, i, u) { return d.a; }; - var yAccessor = function (d, i, u) { return d.b + u.foo; }; + var svg = TestMethods.generateSVG(500, 500); + var xAccessor = function (d, i, dataset) { return d.a; }; + var yAccessor = function (d, i, dataset) { return d.b + dataset.metadata().foo; }; var simpleDataset = new Plottable.Dataset([{ a: "a", b: 6 }, { a: "b", b: 2 }, { a: "c", b: -2 }, { a: "d", b: -6 }], { foo: 0 }); - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Linear(); - var plot = new Plottable.Plot.Bar(xScale, yScale); - plot.addDataset(simpleDataset).project("x", xAccessor, xScale).project("y", yAccessor, yScale).renderTo(svg); + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Linear(); + var plot = new Plottable.Plots.Bar(xScale, yScale); + plot.addDataset(simpleDataset); + plot.x(xAccessor, xScale).y(yAccessor, yScale).renderTo(svg); xScale.domain(["b", "c"]); assert.deepEqual(yScale.domain(), [-7, 7], "domain has not been adjusted to visible points"); plot.automaticallyAdjustYScaleOverVisiblePoints(true); @@ -4077,51 +3920,53 @@ describe("Plots", function () { { x: "B", y: "V", magnitude: 8 }, ]; var VERIFY_CELLS = function (cells) { - assert.equal(cells.length, 4); + assert.strictEqual(cells.length, 4); var cellAU = d3.select(cells[0]); var cellBU = d3.select(cells[1]); var cellAV = d3.select(cells[2]); var cellBV = d3.select(cells[3]); - assert.equal(cellAU.attr("height"), "100", "cell 'AU' height is correct"); - assert.equal(cellAU.attr("width"), "200", "cell 'AU' width is correct"); - assert.equal(cellAU.attr("x"), "0", "cell 'AU' x coord is correct"); - assert.equal(cellAU.attr("y"), "0", "cell 'AU' y coord is correct"); - assert.equal(cellAU.attr("fill"), "#000000", "cell 'AU' color is correct"); - assert.equal(cellBU.attr("height"), "100", "cell 'BU' height is correct"); - assert.equal(cellBU.attr("width"), "200", "cell 'BU' width is correct"); - assert.equal(cellBU.attr("x"), "200", "cell 'BU' x coord is correct"); - assert.equal(cellBU.attr("y"), "0", "cell 'BU' y coord is correct"); - assert.equal(cellBU.attr("fill"), "#212121", "cell 'BU' color is correct"); - assert.equal(cellAV.attr("height"), "100", "cell 'AV' height is correct"); - assert.equal(cellAV.attr("width"), "200", "cell 'AV' width is correct"); - assert.equal(cellAV.attr("x"), "0", "cell 'AV' x coord is correct"); - assert.equal(cellAV.attr("y"), "100", "cell 'AV' y coord is correct"); - assert.equal(cellAV.attr("fill"), "#ffffff", "cell 'AV' color is correct"); - assert.equal(cellBV.attr("height"), "100", "cell 'BV' height is correct"); - assert.equal(cellBV.attr("width"), "200", "cell 'BV' width is correct"); - assert.equal(cellBV.attr("x"), "200", "cell 'BV' x coord is correct"); - assert.equal(cellBV.attr("y"), "100", "cell 'BV' y coord is correct"); - assert.equal(cellBV.attr("fill"), "#777777", "cell 'BV' color is correct"); + assert.strictEqual(cellAU.attr("height"), "100", "cell 'AU' height is correct"); + assert.strictEqual(cellAU.attr("width"), "200", "cell 'AU' width is correct"); + assert.strictEqual(cellAU.attr("x"), "0", "cell 'AU' x coord is correct"); + assert.strictEqual(cellAU.attr("y"), "0", "cell 'AU' y coord is correct"); + assert.strictEqual(cellAU.attr("fill"), "#000000", "cell 'AU' color is correct"); + assert.strictEqual(cellBU.attr("height"), "100", "cell 'BU' height is correct"); + assert.strictEqual(cellBU.attr("width"), "200", "cell 'BU' width is correct"); + assert.strictEqual(cellBU.attr("x"), "200", "cell 'BU' x coord is correct"); + assert.strictEqual(cellBU.attr("y"), "0", "cell 'BU' y coord is correct"); + assert.strictEqual(cellBU.attr("fill"), "#212121", "cell 'BU' color is correct"); + assert.strictEqual(cellAV.attr("height"), "100", "cell 'AV' height is correct"); + assert.strictEqual(cellAV.attr("width"), "200", "cell 'AV' width is correct"); + assert.strictEqual(cellAV.attr("x"), "0", "cell 'AV' x coord is correct"); + assert.strictEqual(cellAV.attr("y"), "100", "cell 'AV' y coord is correct"); + assert.strictEqual(cellAV.attr("fill"), "#ffffff", "cell 'AV' color is correct"); + assert.strictEqual(cellBV.attr("height"), "100", "cell 'BV' height is correct"); + assert.strictEqual(cellBV.attr("width"), "200", "cell 'BV' width is correct"); + assert.strictEqual(cellBV.attr("x"), "200", "cell 'BV' x coord is correct"); + assert.strictEqual(cellBV.attr("y"), "100", "cell 'BV' y coord is correct"); + assert.strictEqual(cellBV.attr("fill"), "#777777", "cell 'BV' color is correct"); }; it("renders correctly", function () { - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Category(); - var colorScale = new Plottable.Scale.InterpolatedColor(["black", "white"]); - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var gridPlot = new Plottable.Plot.Grid(xScale, yScale, colorScale); - gridPlot.addDataset(DATA).project("fill", "magnitude", colorScale).project("x", "x", xScale).project("y", "y", yScale); + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Category(); + var colorScale = new Plottable.Scales.InterpolatedColor(["black", "white"]); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var gridPlot = new Plottable.Plots.Grid(xScale, yScale); + gridPlot.addDataset(new Plottable.Dataset(DATA)).attr("fill", function (d) { return d.magnitude; }, colorScale); + gridPlot.x(function (d) { return d.x; }, xScale).y(function (d) { return d.y; }, yScale); gridPlot.renderTo(svg); VERIFY_CELLS(gridPlot._renderArea.selectAll("rect")[0]); svg.remove(); }); it("renders correctly when data is set after construction", function () { - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Category(); - var colorScale = new Plottable.Scale.InterpolatedColor(["black", "white"]); - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Category(); + var colorScale = new Plottable.Scales.InterpolatedColor(["black", "white"]); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); var dataset = new Plottable.Dataset(); - var gridPlot = new Plottable.Plot.Grid(xScale, yScale, colorScale); - gridPlot.addDataset(dataset).project("fill", "magnitude", colorScale).project("x", "x", xScale).project("y", "y", yScale).renderTo(svg); + var gridPlot = new Plottable.Plots.Grid(xScale, yScale); + gridPlot.addDataset(dataset).attr("fill", function (d) { return d.magnitude; }, colorScale); + gridPlot.x(function (d) { return d.x; }, xScale).y(function (d) { return d.y; }, yScale).renderTo(svg); dataset.data(DATA); VERIFY_CELLS(gridPlot._renderArea.selectAll("rect")[0]); svg.remove(); @@ -4129,13 +3974,14 @@ describe("Plots", function () { it("renders correctly when there isn't data for every spot", function () { var CELL_HEIGHT = 50; var CELL_WIDTH = 100; - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Category(); - var colorScale = new Plottable.Scale.InterpolatedColor(["black", "white"]); - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Category(); + var colorScale = new Plottable.Scales.InterpolatedColor(["black", "white"]); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); var dataset = new Plottable.Dataset(); - var gridPlot = new Plottable.Plot.Grid(xScale, yScale, colorScale); - gridPlot.addDataset(dataset).project("fill", "magnitude", colorScale).project("x", "x", xScale).project("y", "y", yScale).renderTo(svg); + var gridPlot = new Plottable.Plots.Grid(xScale, yScale); + gridPlot.addDataset(dataset).attr("fill", function (d) { return d.magnitude; }, colorScale); + gridPlot.x(function (d) { return d.x; }, xScale).y(function (d) { return d.y; }, yScale).renderTo(svg); var data = [ { x: "A", y: "W", magnitude: 0 }, { x: "B", y: "X", magnitude: 8 }, @@ -4144,23 +3990,24 @@ describe("Plots", function () { ]; dataset.data(data); var cells = gridPlot._renderArea.selectAll("rect")[0]; - assert.equal(cells.length, data.length); + assert.strictEqual(cells.length, data.length); for (var i = 0; i < cells.length; i++) { var cell = d3.select(cells[i]); - assert.equal(cell.attr("x"), i * CELL_WIDTH, "Cell x coord is correct"); - assert.equal(cell.attr("y"), i * CELL_HEIGHT, "Cell y coord is correct"); - assert.equal(cell.attr("width"), CELL_WIDTH, "Cell width is correct"); - assert.equal(cell.attr("height"), CELL_HEIGHT, "Cell height is correct"); + assert.strictEqual(cell.attr("x"), String(i * CELL_WIDTH), "Cell x coord is correct"); + assert.strictEqual(cell.attr("y"), String(i * CELL_HEIGHT), "Cell y coord is correct"); + assert.strictEqual(cell.attr("width"), String(CELL_WIDTH), "Cell width is correct"); + assert.strictEqual(cell.attr("height"), String(CELL_HEIGHT), "Cell height is correct"); } svg.remove(); }); it("can invert y axis correctly", function () { - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Category(); - var colorScale = new Plottable.Scale.InterpolatedColor(["black", "white"]); - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var gridPlot = new Plottable.Plot.Grid(xScale, yScale, colorScale); - gridPlot.addDataset(DATA).project("fill", "magnitude").project("x", "x", xScale).project("y", "y", yScale).renderTo(svg); + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Category(); + var colorScale = new Plottable.Scales.InterpolatedColor(["black", "white"]); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var gridPlot = new Plottable.Plots.Grid(xScale, yScale); + gridPlot.addDataset(new Plottable.Dataset(DATA)).attr("fill", function (d) { return d.magnitude; }, colorScale); + gridPlot.x(function (d) { return d.x; }, xScale).y(function (d) { return d.y; }, yScale).renderTo(svg); yScale.domain(["U", "V"]); var cells = gridPlot._renderArea.selectAll("rect")[0]; var cellAU = d3.select(cells[0]); @@ -4185,55 +4032,47 @@ describe("Plots", function () { }); describe("getAllSelections()", function () { it("retrieves all selections with no args", function () { - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Category(); - var colorScale = new Plottable.Scale.InterpolatedColor(["black", "white"]); - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var gridPlot = new Plottable.Plot.Grid(xScale, yScale, colorScale); - gridPlot.addDataset("a", DATA).project("fill", "magnitude", colorScale).project("x", "x", xScale).project("y", "y", yScale); + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Category(); + var colorScale = new Plottable.Scales.InterpolatedColor(["black", "white"]); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var gridPlot = new Plottable.Plots.Grid(xScale, yScale); + var dataset = new Plottable.Dataset(DATA); + gridPlot.addDataset(dataset).attr("fill", function (d) { return d.magnitude; }, colorScale); + gridPlot.x(function (d) { return d.x; }, xScale).y(function (d) { return d.y; }, yScale); gridPlot.renderTo(svg); var allCells = gridPlot.getAllSelections(); - var allCells2 = gridPlot.getAllSelections(gridPlot._datasetKeysInOrder); - assert.deepEqual(allCells, allCells2, "all cells retrieved"); - svg.remove(); - }); - it("retrieves correct selections (string arg)", function () { - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Category(); - var colorScale = new Plottable.Scale.InterpolatedColor(["black", "white"]); - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var gridPlot = new Plottable.Plot.Grid(xScale, yScale, colorScale); - gridPlot.addDataset("a", DATA).project("fill", "magnitude", colorScale).project("x", "x", xScale).project("y", "y", yScale); - gridPlot.renderTo(svg); - var allCells = gridPlot.getAllSelections("a"); assert.strictEqual(allCells.size(), 4, "all cells retrieved"); - var selectionData = allCells.data(); - assert.includeMembers(selectionData, DATA, "data in selection data"); svg.remove(); }); - it("retrieves correct selections (array arg)", function () { - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Category(); - var colorScale = new Plottable.Scale.InterpolatedColor(["black", "white"]); - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var gridPlot = new Plottable.Plot.Grid(xScale, yScale, colorScale); - gridPlot.addDataset("a", DATA).project("fill", "magnitude", colorScale).project("x", "x", xScale).project("y", "y", yScale); + it("retrieves correct selections", function () { + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Category(); + var colorScale = new Plottable.Scales.InterpolatedColor(["black", "white"]); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var gridPlot = new Plottable.Plots.Grid(xScale, yScale); + var dataset = new Plottable.Dataset(DATA); + gridPlot.addDataset(dataset).attr("fill", function (d) { return d.magnitude; }, colorScale); + gridPlot.x(function (d) { return d.x; }, xScale).y(function (d) { return d.y; }, yScale); gridPlot.renderTo(svg); - var allCells = gridPlot.getAllSelections(["a"]); + var allCells = gridPlot.getAllSelections([dataset]); assert.strictEqual(allCells.size(), 4, "all cells retrieved"); var selectionData = allCells.data(); assert.includeMembers(selectionData, DATA, "data in selection data"); svg.remove(); }); - it("skips invalid keys", function () { - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Category(); - var colorScale = new Plottable.Scale.InterpolatedColor(["black", "white"]); - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var gridPlot = new Plottable.Plot.Grid(xScale, yScale, colorScale); - gridPlot.addDataset("a", DATA).project("fill", "magnitude", colorScale).project("x", "x", xScale).project("y", "y", yScale); + it("skips invalid Datasets", function () { + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Category(); + var colorScale = new Plottable.Scales.InterpolatedColor(["black", "white"]); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var gridPlot = new Plottable.Plots.Grid(xScale, yScale); + var dataset = new Plottable.Dataset(DATA); + gridPlot.addDataset(dataset).attr("fill", function (d) { return d.magnitude; }, colorScale); + gridPlot.x(function (d) { return d.x; }, xScale).y(function (d) { return d.y; }, yScale); gridPlot.renderTo(svg); - var allCells = gridPlot.getAllSelections(["a", "b"]); + var dummyDataset = new Plottable.Dataset([]); + var allCells = gridPlot.getAllSelections([dataset, dummyDataset]); assert.strictEqual(allCells.size(), 4, "all cells retrieved"); var selectionData = allCells.data(); assert.includeMembers(selectionData, DATA, "data in selection data"); @@ -4257,7 +4096,7 @@ describe("Plots", function () { { x: 4, y: 4, x2: 5, y2: 5 } ]; var VERIFY_CELLS = function (cells) { - assert.equal(cells[0].length, 5); + assert.strictEqual(cells[0].length, 5); cells.each(function (d, i) { var cell = d3.select(this); assert.closeTo(+cell.attr("height"), 50, 0.5, "Cell height is correct"); @@ -4267,18 +4106,20 @@ describe("Plots", function () { }); }; it("renders correctly", function () { - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var rectanglePlot = new Plottable.Plot.Rectangle(xScale, yScale); - rectanglePlot.addDataset(DATA).project("x", "x", xScale).project("y", "y", yScale).project("x1", "x", xScale).project("y1", "y", yScale).project("x2", "x2", xScale).project("y2", "y2", yScale).renderTo(svg); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var rectanglePlot = new Plottable.Plots.Rectangle(xScale, yScale); + rectanglePlot.addDataset(new Plottable.Dataset(DATA)); + rectanglePlot.x(function (d) { return d.x; }, xScale).y(function (d) { return d.y; }, yScale); + rectanglePlot.x1(function (d) { return d.x; }, xScale).y1(function (d) { return d.y; }, yScale).x2(function (d) { return d.x2; }, xScale).y2(function (d) { return d.y2; }, yScale).renderTo(svg); VERIFY_CELLS(rectanglePlot._renderArea.selectAll("rect")); svg.remove(); }); }); describe("fail safe tests", function () { it("illegal rectangles don't get displayed", function () { - var svg = generateSVG(); + var svg = TestMethods.generateSVG(); var data1 = [ { x: "A", y1: 1, y2: 2, v: 1 }, { x: "B", y1: 2, y2: 3, v: 2 }, @@ -4287,21 +4128,20 @@ describe("Plots", function () { { x: "E", y1: 5, y2: 6, v: 5 }, { x: "F", y1: 6, y2: 7, v: 6 } ]; - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Linear(); - var cScale = new Plottable.Scale.Color(); - var plot = new Plottable.Plot.Grid(xScale, yScale, cScale); - plot.project("x", "x", xScale).project("y", "y1", yScale).project("y2", "y2", yScale); - plot.addDataset(data1); + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Linear(); + var plot = new Plottable.Plots.Grid(xScale, yScale); + plot.x(function (d) { return d.x; }, xScale).y(function (d) { return d.y1; }, yScale).y2(function (d) { return d.y2; }, yScale); + plot.addDataset(new Plottable.Dataset(data1)); plot.renderTo(svg); var rectanglesSelection = plot._element.selectAll(".bar-area rect"); assert.strictEqual(rectanglesSelection.size(), 5, "only 5 rectangles should be displayed"); rectanglesSelection.each(function (d, i) { var sel = d3.select(this); - assert.isFalse(Plottable._Util.Methods.isNaN(+sel.attr("x")), "x attribute should be valid for rectangle # " + i + ". Currently " + sel.attr("x")); - assert.isFalse(Plottable._Util.Methods.isNaN(+sel.attr("y")), "y attribute should be valid for rectangle # " + i + ". Currently " + sel.attr("y")); - assert.isFalse(Plottable._Util.Methods.isNaN(+sel.attr("height")), "height attribute should be valid for rectangle # " + i + ". Currently " + sel.attr("height")); - assert.isFalse(Plottable._Util.Methods.isNaN(+sel.attr("width")), "width attribute should be valid for rectangle # " + i + ". Currently " + sel.attr("width")); + assert.isFalse(Plottable.Utils.Methods.isNaN(+sel.attr("x")), "x attribute should be valid for rectangle # " + i + ". Currently " + sel.attr("x")); + assert.isFalse(Plottable.Utils.Methods.isNaN(+sel.attr("y")), "y attribute should be valid for rectangle # " + i + ". Currently " + sel.attr("y")); + assert.isFalse(Plottable.Utils.Methods.isNaN(+sel.attr("height")), "height attribute should be valid for rectangle # " + i + ". Currently " + sel.attr("height")); + assert.isFalse(Plottable.Utils.Methods.isNaN(+sel.attr("width")), "width attribute should be valid for rectangle # " + i + ". Currently " + sel.attr("width")); }); svg.remove(); }); @@ -4313,29 +4153,29 @@ var assert = chai.assert; describe("Plots", function () { describe("ScatterPlot", function () { it("renders correctly with no data", function () { - var svg = generateSVG(400, 400); - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - var plot = new Plottable.Plot.Scatter(xScale, yScale); - plot.project("x", function (d) { return d.x; }, xScale); - plot.project("y", function (d) { return d.y; }, yScale); + var svg = TestMethods.generateSVG(400, 400); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); + var plot = new Plottable.Plots.Scatter(xScale, yScale); + plot.x(function (d) { return d.x; }, xScale); + plot.y(function (d) { return d.y; }, yScale); assert.doesNotThrow(function () { return plot.renderTo(svg); }, Error); assert.strictEqual(plot.width(), 400, "was allocated width"); assert.strictEqual(plot.height(), 400, "was allocated height"); svg.remove(); }); - it("the accessors properly access data, index, and metadata", function () { - var svg = generateSVG(400, 400); - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); + it("the accessors properly access data, index and Dataset", function () { + var svg = TestMethods.generateSVG(400, 400); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); xScale.domain([0, 400]); yScale.domain([400, 0]); var data = [{ x: 0, y: 0 }, { x: 1, y: 1 }]; var metadata = { foo: 10, bar: 20 }; - var xAccessor = function (d, i, m) { return d.x + i * m.foo; }; - var yAccessor = function (d, i, m) { return m.bar; }; + var xAccessor = function (d, i, dataset) { return d.x + i * dataset.metadata().foo; }; + var yAccessor = function (d, i, dataset) { return dataset.metadata().bar; }; var dataset = new Plottable.Dataset(data, metadata); - var plot = new Plottable.Plot.Scatter(xScale, yScale).project("x", xAccessor).project("y", yAccessor); + var plot = new Plottable.Plots.Scatter(xScale, yScale).x(xAccessor).y(yAccessor); plot.addDataset(dataset); plot.renderTo(svg); var symbols = plot.getAllSelections(); @@ -4366,12 +4206,12 @@ describe("Plots", function () { svg.remove(); }); it("getAllSelections()", function () { - var svg = generateSVG(400, 400); - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); + var svg = TestMethods.generateSVG(400, 400); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); var data = [{ x: 0, y: 0 }, { x: 1, y: 1 }]; var data2 = [{ x: 1, y: 2 }, { x: 3, y: 4 }]; - var plot = new Plottable.Plot.Scatter(xScale, yScale).project("x", "x", xScale).project("y", "y", yScale).addDataset(data).addDataset(data2); + var plot = new Plottable.Plots.Scatter(xScale, yScale).x(function (d) { return d.x; }, xScale).y(function (d) { return d.y; }, yScale).addDataset(new Plottable.Dataset(data)).addDataset(new Plottable.Dataset(data2)); plot.renderTo(svg); var allCircles = plot.getAllSelections(); assert.strictEqual(allCircles.size(), 4, "all circles retrieved"); @@ -4387,12 +4227,12 @@ describe("Plots", function () { assert.closeTo(expected.pixelPoints[0].y, actual.pixelPoints[0].y, 0.01, msg); assert.deepEqual(expected.selection, actual.selection, msg); } - var svg = generateSVG(400, 400); - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); + var svg = TestMethods.generateSVG(400, 400); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); var data = [{ x: 0, y: 0 }, { x: 1, y: 1 }]; var data2 = [{ x: 1, y: 2 }, { x: 3, y: 4 }]; - var plot = new Plottable.Plot.Scatter(xScale, yScale).project("x", "x", xScale).project("y", "y", yScale).addDataset(data).addDataset(data2); + var plot = new Plottable.Plots.Scatter(xScale, yScale).x(function (d) { return d.x; }, xScale).y(function (d) { return d.y; }, yScale).addDataset(new Plottable.Dataset(data)).addDataset(new Plottable.Dataset(data2)); plot.renderTo(svg); var points = d3.selectAll(".scatter-plot path"); var d0 = data[0]; @@ -4422,34 +4262,8 @@ describe("Plots", function () { assertPlotDataEqual(expected, closest, "it ignores off-plot data points"); svg.remove(); }); - it("_getClosestStruckPoint()", function () { - var svg = generateSVG(400, 400); - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - xScale.domain([0, 400]); - yScale.domain([400, 0]); - var data1 = [ - { x: 80, y: 200, size: 40 }, - { x: 100, y: 200, size: 40 }, - { x: 125, y: 200, size: 10 }, - { x: 138, y: 200, size: 10 } - ]; - var plot = new Plottable.Plot.Scatter(xScale, yScale); - plot.addDataset(data1); - plot.project("x", "x").project("y", "y").project("size", "size"); - plot.renderTo(svg); - var twoOverlappingCirclesResult = plot._getClosestStruckPoint({ x: 85, y: 200 }, 10); - assert.strictEqual(twoOverlappingCirclesResult.data[0], data1[0], "returns closest circle among circles that the test point touches"); - var overlapAndCloseToPointResult = plot._getClosestStruckPoint({ x: 118, y: 200 }, 10); - assert.strictEqual(overlapAndCloseToPointResult.data[0], data1[1], "returns closest circle that test point touches, even if non-touched circles are closer"); - var twoPointsInRangeResult = plot._getClosestStruckPoint({ x: 130, y: 200 }, 10); - assert.strictEqual(twoPointsInRangeResult.data[0], data1[2], "returns closest circle within range if test point does not touch any circles"); - var farFromAnyPointsResult = plot._getClosestStruckPoint({ x: 400, y: 400 }, 10); - assert.isNull(farFromAnyPointsResult.data, "returns no data if no circle were within range and test point does not touch any circles"); - svg.remove(); - }); it("correctly handles NaN and undefined x and y values", function () { - var svg = generateSVG(400, 400); + var svg = TestMethods.generateSVG(400, 400); var data = [ { foo: 0.0, bar: 0.0 }, { foo: 0.2, bar: 0.2 }, @@ -4458,10 +4272,11 @@ describe("Plots", function () { { foo: 0.8, bar: 0.8 } ]; var dataset = new Plottable.Dataset(data); - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - var plot = new Plottable.Plot.Scatter(xScale, yScale); - plot.addDataset(dataset).project("x", "foo", xScale).project("y", "bar", yScale); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); + var plot = new Plottable.Plots.Scatter(xScale, yScale); + plot.addDataset(dataset); + plot.x(function (d) { return d.foo; }, xScale).y(function (d) { return d.bar; }, yScale); plot.renderTo(svg); var dataWithNaN = data.slice(); dataWithNaN[2] = { foo: 0.4, bar: NaN }; @@ -4483,13 +4298,11 @@ describe("Plots", function () { var circlePlot; var SVG_WIDTH = 600; var SVG_HEIGHT = 300; - var pixelAreaFull = { xMin: 0, xMax: SVG_WIDTH, yMin: 0, yMax: SVG_HEIGHT }; - var pixelAreaPart = { xMin: 200, xMax: 600, yMin: 100, yMax: 200 }; var dataAreaFull = { xMin: 0, xMax: 9, yMin: 81, yMax: 0 }; var dataAreaPart = { xMin: 3, xMax: 9, yMin: 54, yMax: 27 }; var colorAccessor = function (d, i, m) { return d3.rgb(d.x, d.y, i).toString(); }; var circlesInArea; - var quadraticDataset = makeQuadraticSeries(10); + var quadraticDataset = new Plottable.Dataset(TestMethods.makeQuadraticSeries(10)); function getCirclePlotVerifier() { // creates a function that verifies that circles are drawn properly after accounting for svg transform // and then modifies circlesInArea to contain the number of circles that were discovered in the plot area @@ -4509,35 +4322,35 @@ describe("Plots", function () { circlesInArea++; assert.closeTo(x, xScale.scale(datum.x), 0.01, "the scaled/translated x is correct"); assert.closeTo(y, yScale.scale(datum.y), 0.01, "the scaled/translated y is correct"); - assert.equal(selection.attr("fill"), colorAccessor(datum, index, null), "fill is correct"); + assert.strictEqual(selection.attr("fill"), colorAccessor(datum, index, null), "fill is correct"); } ; }; } ; beforeEach(function () { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - xScale = new Plottable.Scale.Linear().domain([0, 9]); - yScale = new Plottable.Scale.Linear().domain([0, 81]); - circlePlot = new Plottable.Plot.Scatter(xScale, yScale); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + xScale = new Plottable.Scales.Linear().domain([0, 9]); + yScale = new Plottable.Scales.Linear().domain([0, 81]); + circlePlot = new Plottable.Plots.Scatter(xScale, yScale); circlePlot.addDataset(quadraticDataset); - circlePlot.project("fill", colorAccessor); - circlePlot.project("x", "x", xScale); - circlePlot.project("y", "y", yScale); + circlePlot.attr("fill", colorAccessor); + circlePlot.x(function (d) { return d.x; }, xScale); + circlePlot.y(function (d) { return d.y; }, yScale); circlePlot.renderTo(svg); }); it("setup is handled properly", function () { assert.deepEqual(xScale.range(), [0, SVG_WIDTH], "xScale range was set by the renderer"); assert.deepEqual(yScale.range(), [SVG_HEIGHT, 0], "yScale range was set by the renderer"); circlePlot.getAllSelections().each(getCirclePlotVerifier()); - assert.equal(circlesInArea, 10, "10 circles were drawn"); + assert.strictEqual(circlesInArea, 10, "10 circles were drawn"); svg.remove(); }); it("rendering is idempotent", function () { - circlePlot._render(); - circlePlot._render(); + circlePlot.render(); + circlePlot.render(); circlePlot.getAllSelections().each(getCirclePlotVerifier()); - assert.equal(circlesInArea, 10, "10 circles were drawn"); + assert.strictEqual(circlesInArea, 10, "10 circles were drawn"); svg.remove(); }); describe("after the scale has changed", function () { @@ -4550,7 +4363,7 @@ describe("Plots", function () { it("the circles re-rendered properly", function () { var circles = circlePlot.getAllSelections(); circles.each(getCirclePlotVerifier()); - assert.equal(circlesInArea, 4, "four circles were found in the render area"); + assert.strictEqual(circlesInArea, 4, "four circles were found in the render area"); svg.remove(); }); }); @@ -4563,137 +4376,139 @@ var assert = chai.assert; describe("Plots", function () { describe("Stacked Plot Stacking", function () { var stackedPlot; - var SVG_WIDTH = 600; - var SVG_HEIGHT = 400; beforeEach(function () { - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - stackedPlot = new Plottable.Plot.AbstractStacked(xScale, yScale); - stackedPlot.project("x", "x", xScale); - stackedPlot.project("y", "y", yScale); - stackedPlot._getDrawer = function (key) { return new Plottable._Drawer.AbstractDrawer(key); }; + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); + stackedPlot = new Plottable.Stacked(xScale, yScale); + stackedPlot.x(function (d) { return d.x; }, xScale); + stackedPlot.y(function (d) { return d.y; }, yScale); + stackedPlot._getDrawer = function (key) { return new Plottable.Drawers.AbstractDrawer(key); }; stackedPlot._isVertical = true; }); it("uses positive offset on stacking the 0 value", function () { - var data1 = [ + var data0 = [ { x: 1, y: 1 }, { x: 3, y: 1 } ]; - var data2 = [ + var data1 = [ { x: 1, y: 0 }, { x: 3, y: 1 } ]; - var data3 = [ + var data2 = [ { x: 1, y: -1 }, { x: 3, y: 1 } ]; - var data4 = [ + var data3 = [ { x: 1, y: 1 }, { x: 3, y: 1 } ]; - var data5 = [ + var data4 = [ { x: 1, y: 0 }, { x: 3, y: 1 } ]; - stackedPlot.addDataset("d1", data1); - stackedPlot.addDataset("d2", data2); - stackedPlot.addDataset("d3", data3); - stackedPlot.addDataset("d4", data4); - stackedPlot.addDataset("d5", data5); - var ds2PlotMetadata = stackedPlot._key2PlotDatasetKey.get("d2").plotMetadata; - var ds5PlotMetadata = stackedPlot._key2PlotDatasetKey.get("d5").plotMetadata; - assert.strictEqual(ds2PlotMetadata.offsets.get("1"), 1, "positive offset was used"); - assert.strictEqual(ds5PlotMetadata.offsets.get("1"), 2, "positive offset was used"); + stackedPlot.addDataset(new Plottable.Dataset(data0)); + stackedPlot.addDataset(new Plottable.Dataset(data1)); + stackedPlot.addDataset(new Plottable.Dataset(data2)); + stackedPlot.addDataset(new Plottable.Dataset(data3)); + stackedPlot.addDataset(new Plottable.Dataset(data4)); + // HACKHACK #1984: Dataset keys are being removed, so these are internal keys + var keys = stackedPlot._key2PlotDatasetKey.keys(); + var ds1PlotMetadata = stackedPlot._key2PlotDatasetKey.get(keys[1]).plotMetadata; + var ds4PlotMetadata = stackedPlot._key2PlotDatasetKey.get(keys[4]).plotMetadata; + assert.strictEqual(ds1PlotMetadata.offsets.get("1"), 1, "positive offset was used"); + assert.strictEqual(ds4PlotMetadata.offsets.get("1"), 2, "positive offset was used"); }); it("uses negative offset on stacking the 0 value on all negative/0 valued data", function () { - var data1 = [ + var data0 = [ { x: 1, y: -2 } ]; - var data2 = [ + var data1 = [ { x: 1, y: 0 } ]; - var data3 = [ + var data2 = [ { x: 1, y: -1 } ]; - var data4 = [ + var data3 = [ { x: 1, y: 0 } ]; - stackedPlot.addDataset("d1", data1); - stackedPlot.addDataset("d2", data2); - stackedPlot.addDataset("d3", data3); - stackedPlot.addDataset("d4", data4); - var ds2PlotMetadata = stackedPlot._key2PlotDatasetKey.get("d2").plotMetadata; - var ds4PlotMetadata = stackedPlot._key2PlotDatasetKey.get("d4").plotMetadata; - assert.strictEqual(ds2PlotMetadata.offsets.get("1"), -2, "positive offset was used"); - assert.strictEqual(ds4PlotMetadata.offsets.get("1"), -3, "positive offset was used"); + stackedPlot.addDataset(new Plottable.Dataset(data0)); + stackedPlot.addDataset(new Plottable.Dataset(data1)); + stackedPlot.addDataset(new Plottable.Dataset(data2)); + stackedPlot.addDataset(new Plottable.Dataset(data3)); + // HACKHACK #1984: Dataset keys are being removed, so these are internal keys + var keys = stackedPlot._key2PlotDatasetKey.keys(); + var ds1PlotMetadata = stackedPlot._key2PlotDatasetKey.get(keys[1]).plotMetadata; + var ds3PlotMetadata = stackedPlot._key2PlotDatasetKey.get(keys[3]).plotMetadata; + assert.strictEqual(ds1PlotMetadata.offsets.get("1"), -2, "positive offset was used"); + assert.strictEqual(ds3PlotMetadata.offsets.get("1"), -3, "positive offset was used"); }); it("project can be called after addDataset", function () { - var data1 = [ + var data0 = [ { a: 1, b: 2 } ]; - var data2 = [ + var data1 = [ { a: 1, b: 4 } ]; - stackedPlot.addDataset("d1", data1); - stackedPlot.addDataset("d2", data2); - var ds1PlotMetadata = stackedPlot._key2PlotDatasetKey.get("d1").plotMetadata; - var ds2PlotMetadata = stackedPlot._key2PlotDatasetKey.get("d2").plotMetadata; - assert.isTrue(isNaN(ds1PlotMetadata.offsets.get("1")), "stacking is initially incorrect"); - stackedPlot.project("x", "a"); - stackedPlot.project("y", "b"); - assert.strictEqual(ds2PlotMetadata.offsets.get("1"), 2, "stacking was done correctly"); + stackedPlot.addDataset(new Plottable.Dataset(data0)); + stackedPlot.addDataset(new Plottable.Dataset(data1)); + // HACKHACK #1984: Dataset keys are being removed, so these are internal keys + var keys = stackedPlot._key2PlotDatasetKey.keys(); + var ds0PlotMetadata = stackedPlot._key2PlotDatasetKey.get(keys[0]).plotMetadata; + var ds1PlotMetadata = stackedPlot._key2PlotDatasetKey.get(keys[1]).plotMetadata; + assert.isTrue(isNaN(ds0PlotMetadata.offsets.get("1")), "stacking is initially incorrect"); + stackedPlot.x(function (d) { return d.a; }); + stackedPlot.y(function (d) { return d.b; }); + assert.strictEqual(ds1PlotMetadata.offsets.get("1"), 2, "stacking was done correctly"); }); it("strings are coerced to numbers for stacking", function () { - var data1 = [ + var data0 = [ { x: 1, y: "-2" } ]; - var data2 = [ + var data1 = [ { x: 1, y: "3" } ]; - var data3 = [ + var data2 = [ { x: 1, y: "-1" } ]; - var data4 = [ + var data3 = [ { x: 1, y: "5" } ]; - var data5 = [ + var data4 = [ { x: 1, y: "1" } ]; - var data6 = [ + var data5 = [ { x: 1, y: "-1" } ]; - stackedPlot.addDataset("d1", data1); - stackedPlot.addDataset("d2", data2); - stackedPlot.addDataset("d3", data3); - stackedPlot.addDataset("d4", data4); - stackedPlot.addDataset("d5", data5); - stackedPlot.addDataset("d6", data6); - var ds3PlotMetadata = stackedPlot._key2PlotDatasetKey.get("d3").plotMetadata; - var ds4PlotMetadata = stackedPlot._key2PlotDatasetKey.get("d4").plotMetadata; - var ds5PlotMetadata = stackedPlot._key2PlotDatasetKey.get("d5").plotMetadata; - var ds6PlotMetadata = stackedPlot._key2PlotDatasetKey.get("d6").plotMetadata; - assert.strictEqual(ds3PlotMetadata.offsets.get("1"), -2, "stacking on data1 numerical y value"); - assert.strictEqual(ds4PlotMetadata.offsets.get("1"), 3, "stacking on data2 numerical y value"); - assert.strictEqual(ds5PlotMetadata.offsets.get("1"), 8, "stacking on data1 + data3 numerical y values"); - assert.strictEqual(ds6PlotMetadata.offsets.get("1"), -3, "stacking on data2 + data4 numerical y values"); + stackedPlot.addDataset(new Plottable.Dataset(data0)); + stackedPlot.addDataset(new Plottable.Dataset(data1)); + stackedPlot.addDataset(new Plottable.Dataset(data2)); + stackedPlot.addDataset(new Plottable.Dataset(data3)); + stackedPlot.addDataset(new Plottable.Dataset(data4)); + stackedPlot.addDataset(new Plottable.Dataset(data5)); + // HACKHACK #1984: Dataset keys are being removed, so these are internal keys + var keys = stackedPlot._key2PlotDatasetKey.keys(); + var ds2PlotMetadata = stackedPlot._key2PlotDatasetKey.get(keys[2]).plotMetadata; + var ds3PlotMetadata = stackedPlot._key2PlotDatasetKey.get(keys[3]).plotMetadata; + var ds4PlotMetadata = stackedPlot._key2PlotDatasetKey.get(keys[4]).plotMetadata; + var ds5PlotMetadata = stackedPlot._key2PlotDatasetKey.get(keys[5]).plotMetadata; + assert.strictEqual(ds2PlotMetadata.offsets.get("1"), -2, "stacking on data1 numerical y value"); + assert.strictEqual(ds3PlotMetadata.offsets.get("1"), 3, "stacking on data2 numerical y value"); + assert.strictEqual(ds4PlotMetadata.offsets.get("1"), 8, "stacking on data1 + data3 numerical y values"); + assert.strictEqual(ds5PlotMetadata.offsets.get("1"), -3, "stacking on data2 + data4 numerical y values"); assert.deepEqual(stackedPlot._stackedExtent, [-4, 9], "stacked extent is as normal"); }); it("stacks correctly on empty data", function () { - var data1 = [ - ]; - var data2 = [ - ]; - stackedPlot.addDataset(data1); - stackedPlot.addDataset(data2); - assert.deepEqual(data1, [], "empty data causes no stacking to happen"); - assert.deepEqual(data2, [], "empty data causes no stacking to happen"); + var dataset1 = new Plottable.Dataset([]); + var dataset2 = new Plottable.Dataset([]); + assert.doesNotThrow(function () { return stackedPlot.addDataset(dataset1); }, Error); + assert.doesNotThrow(function () { return stackedPlot.addDataset(dataset2); }, Error); }); it("does not crash on stacking no datasets", function () { - var data1 = [ + var dataset1 = new Plottable.Dataset([ { x: 1, y: -2 } - ]; - stackedPlot.addDataset("a", data1); - assert.doesNotThrow(function () { return stackedPlot.removeDataset("a"); }, Error); + ]); + stackedPlot.addDataset(dataset1); + assert.doesNotThrow(function () { return stackedPlot.removeDataset(dataset1); }, Error); }); }); describe("auto scale domain on numeric", function () { @@ -4702,32 +4517,36 @@ describe("Plots", function () { var SVG_HEIGHT = 400; var yScale; var xScale; - var data1; - var data2; + var dataset1; + var dataset2; beforeEach(function () { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - xScale = new Plottable.Scale.Linear().domain([1, 2]); - yScale = new Plottable.Scale.Linear(); - data1 = [ + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + xScale = new Plottable.Scales.Linear().domain([1, 2]); + yScale = new Plottable.Scales.Linear(); + dataset1 = new Plottable.Dataset([ { x: 1, y: 1 }, { x: 2, y: 2 }, { x: 3, y: 8 } - ]; - data2 = [ + ]); + dataset2 = new Plottable.Dataset([ { x: 1, y: 2 }, { x: 2, y: 2 }, { x: 3, y: 3 } - ]; + ]); }); it("auto scales correctly on stacked area", function () { - var plot = new Plottable.Plot.StackedArea(xScale, yScale).addDataset(data1).addDataset(data2).project("x", "x", xScale).project("y", "y", yScale); + var plot = new Plottable.Plots.StackedArea(xScale, yScale); + plot.addDataset(dataset1).addDataset(dataset2); + plot.x(function (d) { return d.x; }, xScale).y(function (d) { return d.y; }, yScale); plot.automaticallyAdjustYScaleOverVisiblePoints(true); plot.renderTo(svg); assert.deepEqual(yScale.domain(), [0, 4.5], "auto scales takes stacking into account"); svg.remove(); }); it("auto scales correctly on stacked bar", function () { - var plot = new Plottable.Plot.StackedBar(xScale, yScale).addDataset(data1).addDataset(data2).project("x", "x", xScale).project("y", "y", yScale); + var plot = new Plottable.Plots.StackedBar(xScale, yScale); + plot.addDataset(dataset1).addDataset(dataset2); + plot.x(function (d) { return d.x; }, xScale).y(function (d) { return d.y; }, yScale); plot.automaticallyAdjustYScaleOverVisiblePoints(true); plot.renderTo(svg); assert.deepEqual(yScale.domain(), [0, 4.5], "auto scales takes stacking into account"); @@ -4740,32 +4559,37 @@ describe("Plots", function () { var SVG_HEIGHT = 400; var yScale; var xScale; - var data1; - var data2; + var dataset1; + var dataset2; beforeEach(function () { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - xScale = new Plottable.Scale.Category().domain(["a", "b"]); - yScale = new Plottable.Scale.Linear(); - data1 = [ + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + xScale = new Plottable.Scales.Category().domain(["a", "b"]); + yScale = new Plottable.Scales.Linear(); + dataset1 = new Plottable.Dataset([ { x: "a", y: 1 }, { x: "b", y: 2 }, { x: "c", y: 8 } - ]; - data2 = [ + ]); + dataset2 = new Plottable.Dataset([ { x: "a", y: 2 }, { x: "b", y: 2 }, { x: "c", y: 3 } - ]; + ]); }); - it("auto scales correctly on stacked area", function () { - var plot = new Plottable.Plot.StackedArea(yScale, yScale).addDataset(data1).addDataset(data2).project("x", "x", xScale).project("y", "y", yScale); + // TODO: #2003 - The test should be taking in xScales but the StackedArea signature disallows category scales + it.skip("auto scales correctly on stacked area", function () { + var plot = new Plottable.Plots.StackedArea(yScale, yScale); + plot.addDataset(dataset1).addDataset(dataset2); + plot.x(function (d) { return d.x; }, yScale).y(function (d) { return d.y; }, yScale); plot.automaticallyAdjustYScaleOverVisiblePoints(true); plot.renderTo(svg); assert.deepEqual(yScale.domain(), [0, 4.5], "auto scales takes stacking into account"); svg.remove(); }); it("auto scales correctly on stacked bar", function () { - var plot = new Plottable.Plot.StackedBar(xScale, yScale).addDataset(data1).addDataset(data2).project("x", "x", xScale).project("y", "y", yScale); + var plot = new Plottable.Plots.StackedBar(xScale, yScale); + plot.addDataset(dataset1).addDataset(dataset2); + plot.x(function (d) { return d.x; }, xScale).y(function (d) { return d.y; }, yScale); plot.automaticallyAdjustYScaleOverVisiblePoints(true); plot.renderTo(svg); assert.deepEqual(yScale.domain(), [0, 4.5], "auto scales takes stacking into account"); @@ -4778,12 +4602,12 @@ describe("Plots", function () { var yScale; var stackedBarPlot; beforeEach(function () { - svg = generateSVG(600, 400); - xScale = new Plottable.Scale.Category(); - yScale = new Plottable.Scale.Linear(); - stackedBarPlot = new Plottable.Plot.StackedBar(xScale, yScale); - stackedBarPlot.project("x", "key", xScale); - stackedBarPlot.project("y", "value", yScale); + svg = TestMethods.generateSVG(600, 400); + xScale = new Plottable.Scales.Category(); + yScale = new Plottable.Scales.Linear(); + stackedBarPlot = new Plottable.Plots.StackedBar(xScale, yScale); + stackedBarPlot.x(function (d) { return d.key; }, xScale); + stackedBarPlot.y(function (d) { return d.value; }, yScale); stackedBarPlot.renderTo(svg); }); afterEach(function () { @@ -4802,9 +4626,10 @@ describe("Plots", function () { { key: "a", value: 1 }, { key: "b", value: -2 } ]; + var dataset1 = new Plottable.Dataset(data1); var dataset2 = new Plottable.Dataset(data2); - stackedBarPlot.addDataset("d1", data1); - stackedBarPlot.addDataset("d2", dataset2); + stackedBarPlot.addDataset(dataset1); + stackedBarPlot.addDataset(dataset2); assert.closeTo(yScale.domain()[0], -6, 1, "min stacked extent is as normal"); assert.closeTo(yScale.domain()[1], 4, 1, "max stacked extent is as normal"); dataset2.data(data2_b); @@ -4827,10 +4652,10 @@ describe("Plots", function () { var SVG_WIDTH = 600; var SVG_HEIGHT = 400; beforeEach(function () { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - xScale = new Plottable.Scale.Linear().domain([1, 3]); - yScale = new Plottable.Scale.Linear().domain([0, 4]); - var colorScale = new Plottable.Scale.Color("10").domain(["a", "b"]); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + xScale = new Plottable.Scales.Linear().domain([1, 3]); + yScale = new Plottable.Scales.Linear().domain([0, 4]); + var colorScale = new Plottable.Scales.Color("10").domain(["a", "b"]); var data1 = [ { x: 1, y: 1, type: "a" }, { x: 3, y: 2, type: "a" } @@ -4841,23 +4666,23 @@ describe("Plots", function () { ]; dataset1 = new Plottable.Dataset(data1); dataset2 = new Plottable.Dataset(data2); - renderer = new Plottable.Plot.StackedArea(xScale, yScale); - renderer.addDataset(data1); - renderer.addDataset(data2); - renderer.project("x", "x", xScale); - renderer.project("y", "y", yScale); - renderer.project("fill", "type", colorScale); - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); - var table = new Plottable.Component.Table([[renderer], [xAxis]]).renderTo(svg); + renderer = new Plottable.Plots.StackedArea(xScale, yScale); + renderer.addDataset(dataset1); + renderer.addDataset(dataset2); + renderer.x(function (d) { return d.x; }, xScale); + renderer.y(function (d) { return d.y; }, yScale); + renderer.attr("fill", "type", colorScale); + var xAxis = new Plottable.Axes.Numeric(xScale, "bottom"); + new Plottable.Components.Table([[renderer], [xAxis]]).renderTo(svg); }); it("renders correctly", function () { var areas = renderer._renderArea.selectAll(".area"); var area0 = d3.select(areas[0][0]); - var d0 = normalizePath(area0.attr("d")).split(/[a-zA-Z]/); + var d0 = TestMethods.normalizePath(area0.attr("d")).split(/[a-zA-Z]/); var d0Ys = d0.slice(1, d0.length - 1).map(function (s) { return parseFloat(s.split(",")[1]); }); assert.strictEqual(d0Ys.indexOf(0), -1, "bottom area never touches the top"); var area1 = d3.select(areas[0][1]); - var d1 = normalizePath(area1.attr("d")).split(/[a-zA-Z]/); + var d1 = TestMethods.normalizePath(area1.attr("d")).split(/[a-zA-Z]/); var d1Ys = d1.slice(1, d1.length - 1).map(function (s) { return parseFloat(s.split(",")[1]); }); assert.notEqual(d1Ys.indexOf(0), -1, "touches the top"); var domain = yScale.domain(); @@ -4872,23 +4697,23 @@ describe("Plots", function () { var SVG_WIDTH = 600; var SVG_HEIGHT = 400; beforeEach(function () { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var xScale = new Plottable.Scale.Linear().domain([1, 3]); - var yScale = new Plottable.Scale.Linear().domain([0, 4]); - var colorScale = new Plottable.Scale.Color("10"); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var xScale = new Plottable.Scales.Linear().domain([1, 3]); + var yScale = new Plottable.Scales.Linear().domain([0, 4]); + var colorScale = new Plottable.Scales.Color("10"); var data1 = [ ]; var data2 = [ { x: 1, y: 3, type: "b" }, { x: 3, y: 1, type: "b" } ]; - renderer = new Plottable.Plot.StackedArea(xScale, yScale); - renderer.addDataset(data1); - renderer.addDataset(data2); - renderer.project("fill", "type", colorScale); - renderer.project("x", "x", xScale); - renderer.project("y", "y", yScale); - new Plottable.Component.Table([[renderer]]).renderTo(svg); + renderer = new Plottable.Plots.StackedArea(xScale, yScale); + renderer.addDataset(new Plottable.Dataset(data1)); + renderer.addDataset(new Plottable.Dataset(data2)); + renderer.attr("fill", "type", colorScale); + renderer.x(function (d) { return d.x; }, xScale); + renderer.y(function (d) { return d.y; }, yScale); + new Plottable.Components.Table([[renderer]]).renderTo(svg); }); it("path elements rendered correctly", function () { var areas = renderer._renderArea.selectAll(".area"); @@ -4908,10 +4733,10 @@ describe("Plots", function () { var SVG_WIDTH = 600; var SVG_HEIGHT = 400; beforeEach(function () { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - xScale = new Plottable.Scale.Linear().domain([1, 3]); - yScale = new Plottable.Scale.Linear(); - var colorScale = new Plottable.Scale.Color("10").domain(["a", "b"]); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + xScale = new Plottable.Scales.Linear().domain([1, 3]); + yScale = new Plottable.Scales.Linear(); + var colorScale = new Plottable.Scales.Color("10").domain(["a", "b"]); var data1 = [ { x: 1, y: 1, type: "a" }, { x: 3, y: 2, type: "a" } @@ -4920,12 +4745,12 @@ describe("Plots", function () { { x: 1, y: 5, type: "b" }, { x: 3, y: 1, type: "b" } ]; - renderer = new Plottable.Plot.StackedArea(xScale, yScale); - renderer.addDataset(data1); - renderer.addDataset(data2); - renderer.project("fill", "type", colorScale); - renderer.project("x", "x", xScale); - renderer.project("y", "y", yScale); + renderer = new Plottable.Plots.StackedArea(xScale, yScale); + renderer.addDataset(new Plottable.Dataset(data1)); + renderer.addDataset(new Plottable.Dataset(data2)); + renderer.attr("fill", "type", colorScale); + renderer.x(function (d) { return d.x; }, xScale); + renderer.y(function (d) { return d.y; }, yScale); renderer.renderTo(svg); }); it("stacks correctly on adding datasets", function () { @@ -4938,7 +4763,8 @@ describe("Plots", function () { { x: 1, y: 0, type: "c" }, { x: 3, y: 0, type: "c" } ]; - renderer.addDataset("a", new Plottable.Dataset(data)); + var datasetA = new Plottable.Dataset(data); + renderer.addDataset(datasetA); renderer.renderTo(svg); assert.strictEqual(oldLowerBound, yScale.domain()[0], "lower bound doesn't change with 0 added"); assert.strictEqual(oldUpperBound, yScale.domain()[1], "upper bound doesn't change with 0 added"); @@ -4949,7 +4775,8 @@ describe("Plots", function () { { x: 1, y: 10, type: "d" }, { x: 3, y: 3, type: "d" } ]; - renderer.addDataset("b", new Plottable.Dataset(data)); + var datasetB = new Plottable.Dataset(data); + renderer.addDataset(datasetB); renderer.renderTo(svg); assert.closeTo(oldLowerBound, yScale.domain()[0], 2, "lower bound doesn't change on positive addition"); assert.closeTo(oldUpperBound + 10, yScale.domain()[1], 2, "upper bound increases"); @@ -4959,12 +4786,13 @@ describe("Plots", function () { { x: 1, y: 0, type: "e" }, { x: 3, y: 1, type: "e" } ]; - renderer.addDataset("c", new Plottable.Dataset(data)); + var datasetC = new Plottable.Dataset(data); + renderer.addDataset(datasetC); renderer.renderTo(svg); assert.strictEqual(oldUpperBound, yScale.domain()[1], "upper bound doesn't increase since maximum doesn't increase"); - renderer.removeDataset("a"); - renderer.removeDataset("b"); - renderer.removeDataset("c"); + renderer.removeDataset(datasetA); + renderer.removeDataset(datasetB); + renderer.removeDataset(datasetC); svg.remove(); }); it("stacks correctly on removing datasets", function () { @@ -4973,33 +4801,36 @@ describe("Plots", function () { { x: 1, y: 0, type: "c" }, { x: 3, y: 0, type: "c" } ]; - renderer.addDataset("a", new Plottable.Dataset(data)); + var datasetA = new Plottable.Dataset(data); + renderer.addDataset(datasetA); data = [ { x: 1, y: 10, type: "d" }, { x: 3, y: 3, type: "d" } ]; - renderer.addDataset("b", new Plottable.Dataset(data)); + var datasetB = new Plottable.Dataset(data); + renderer.addDataset(datasetB); data = [ { x: 1, y: 0, type: "e" }, { x: 3, y: 1, type: "e" } ]; - renderer.addDataset("c", new Plottable.Dataset(data)); - renderer.project("x", "x", xScale); - renderer.project("y", "y", yScale); + var datasetC = new Plottable.Dataset(data); + renderer.addDataset(datasetC); + renderer.x(function (d) { return d.x; }, xScale); + renderer.y(function (d) { return d.y; }, yScale); renderer.renderTo(svg); assert.closeTo(16, yScale.domain()[1], 2, "Initially starts with around 14 at highest extent"); renderer.detach(); - renderer.removeDataset("a"); + renderer.removeDataset(datasetA); renderer.renderTo(svg); assert.closeTo(16, yScale.domain()[1], 2, "Remains with around 14 at highest extent"); var oldUpperBound = yScale.domain()[1]; renderer.detach(); - renderer.removeDataset("b"); + renderer.removeDataset(datasetB); renderer.renderTo(svg); assert.closeTo(oldUpperBound - 10, yScale.domain()[1], 2, "Highest extent decreases by around 10"); oldUpperBound = yScale.domain()[1]; renderer.detach(); - renderer.removeDataset("c"); + renderer.removeDataset(datasetC); renderer.renderTo(svg); assert.strictEqual(oldUpperBound, yScale.domain()[1], "Extent doesn't change if maximum doesn't change"); svg.remove(); @@ -5016,8 +4847,8 @@ describe("Plots", function () { ]; var dataset = new Plottable.Dataset(data); renderer.addDataset(dataset); - renderer.project("x", "x", xScale); - renderer.project("y", "y", yScale); + renderer.x(function (d) { return d.x; }, xScale); + renderer.y(function (d) { return d.y; }, yScale); renderer.renderTo(svg); assert.strictEqual(oldLowerBound, yScale.domain()[0], "lower bound doesn't change with 0 added"); assert.strictEqual(oldUpperBound, yScale.domain()[1], "upper bound doesn't change with 0 added"); @@ -5054,8 +4885,8 @@ describe("Plots", function () { }); it("warning is thrown when datasets are updated with different domains", function () { var flag = false; - var oldWarn = Plottable._Util.Methods.warn; - Plottable._Util.Methods.warn = function (msg) { + var oldWarn = Plottable.Utils.Methods.warn; + Plottable.Utils.Methods.warn = function (msg) { if (msg.indexOf("domain") > -1) { flag = true; } @@ -5065,7 +4896,7 @@ describe("Plots", function () { ]; var dataset = new Plottable.Dataset(missingDomainData); renderer.addDataset(dataset); - Plottable._Util.Methods.warn = oldWarn; + Plottable.Utils.Methods.warn = oldWarn; assert.isTrue(flag, "warning has been issued about differing domains"); svg.remove(); }); @@ -5078,10 +4909,10 @@ describe("Plots", function () { var SVG_WIDTH = 600; var SVG_HEIGHT = 400; beforeEach(function () { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - xScale = new Plottable.Scale.Linear().domain([1, 3]); - yScale = new Plottable.Scale.Linear().domain([0, 4]); - var colorScale = new Plottable.Scale.Color("10").domain(["a", "b"]); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + xScale = new Plottable.Scales.Linear().domain([1, 3]); + yScale = new Plottable.Scales.Linear().domain([0, 4]); + var colorScale = new Plottable.Scales.Color("10").domain(["a", "b"]); var data1 = [ { x: 1, yTest: 1, type: "a" }, { x: 3, yTest: 2, type: "a" } @@ -5090,23 +4921,23 @@ describe("Plots", function () { { x: 1, yTest: 3, type: "b" }, { x: 3, yTest: 1, type: "b" } ]; - renderer = new Plottable.Plot.StackedArea(xScale, yScale); - renderer.project("y", "yTest", yScale); - renderer.project("x", "x", xScale); - renderer.addDataset(data1); - renderer.addDataset(data2); - renderer.project("fill", "type", colorScale); - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); - var table = new Plottable.Component.Table([[renderer], [xAxis]]).renderTo(svg); + renderer = new Plottable.Plots.StackedArea(xScale, yScale); + renderer.y(function (d) { return d.yTest; }, yScale); + renderer.x(function (d) { return d.x; }, xScale); + renderer.addDataset(new Plottable.Dataset(data1)); + renderer.addDataset(new Plottable.Dataset(data2)); + renderer.attr("fill", function (d) { return d.type; }, colorScale); + var xAxis = new Plottable.Axes.Numeric(xScale, "bottom"); + new Plottable.Components.Table([[renderer], [xAxis]]).renderTo(svg); }); it("renders correctly", function () { var areas = renderer._renderArea.selectAll(".area"); var area0 = d3.select(areas[0][0]); - var d0 = normalizePath(area0.attr("d")).split(/[a-zA-Z]/); + var d0 = TestMethods.normalizePath(area0.attr("d")).split(/[a-zA-Z]/); var d0Ys = d0.slice(1, d0.length - 1).map(function (s) { return parseFloat(s.split(",")[1]); }); assert.strictEqual(d0Ys.indexOf(0), -1, "bottom area never touches the top"); var area1 = d3.select(areas[0][1]); - var d1 = normalizePath(area1.attr("d")).split(/[a-zA-Z]/); + var d1 = TestMethods.normalizePath(area1.attr("d")).split(/[a-zA-Z]/); var d1Ys = d1.slice(1, d1.length - 1).map(function (s) { return parseFloat(s.split(",")[1]); }); assert.notEqual(d1Ys.indexOf(0), -1, "touches the top"); var domain = yScale.domain(); @@ -5115,7 +4946,7 @@ describe("Plots", function () { svg.remove(); }); it("project works correctly", function () { - renderer.project("check", "type"); + renderer.attr("check", function (d) { return d.type; }); var areas = renderer._renderArea.selectAll(".area"); var area0 = d3.select(areas[0][0]); assert.strictEqual(area0.attr("check"), "a", "projector has been applied to first area"); @@ -5126,66 +4957,72 @@ describe("Plots", function () { }); describe("fail safe tests", function () { it("0 as a string coerces correctly and is not subject to off by one errors", function () { - var data1 = [ + var data0 = [ { x: 1, y: 1, fill: "blue" }, { x: 2, y: 2, fill: "blue" }, { x: 3, y: 3, fill: "blue" }, ]; - var data2 = [ + var data1 = [ { x: 1, y: 1, fill: "red" }, { x: 2, y: "0", fill: "red" }, { x: 3, y: 3, fill: "red" }, ]; - var data3 = [ + var data2 = [ { x: 1, y: 1, fill: "green" }, { x: 2, y: 2, fill: "green" }, { x: 3, y: 3, fill: "green" }, ]; - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - var plot = new Plottable.Plot.StackedArea(xScale, yScale); - plot.addDataset("d1", data1); - plot.addDataset("d2", data2); - plot.addDataset("d3", data3); - plot.project("fill", "fill"); - plot.project("x", "x", xScale).project("y", "y", yScale); - var ds1Point2Offset = plot._key2PlotDatasetKey.get("d1").plotMetadata.offsets.get(2); - var ds2Point2Offset = plot._key2PlotDatasetKey.get("d2").plotMetadata.offsets.get(2); - var ds3Point2Offset = plot._key2PlotDatasetKey.get("d3").plotMetadata.offsets.get(2); - assert.strictEqual(ds1Point2Offset, 0, "dataset1 (blue) should have no offset on middle point"); - assert.strictEqual(ds2Point2Offset, 2, "dataset2 (red) should have this offset and be on top of blue dataset"); - assert.strictEqual(ds3Point2Offset, 2, "dataset3 (green) should have this offset because the red dataset (ds2) has no height in this point"); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); + var plot = new Plottable.Plots.StackedArea(xScale, yScale); + var dataset0 = new Plottable.Dataset(data0); + plot.addDataset(dataset0); + var dataset1 = new Plottable.Dataset(data1); + plot.addDataset(dataset1); + var dataset2 = new Plottable.Dataset(data2); + plot.addDataset(dataset2); + plot.attr("fill", "fill"); + plot.x(function (d) { return d.x; }, xScale).y(function (d) { return d.y; }, yScale); + var ds0Point2Offset = plot._key2PlotDatasetKey.get("_0").plotMetadata.offsets.get(2); + var ds1Point2Offset = plot._key2PlotDatasetKey.get("_1").plotMetadata.offsets.get(2); + var ds2Point2Offset = plot._key2PlotDatasetKey.get("_2").plotMetadata.offsets.get(2); + assert.strictEqual(ds0Point2Offset, 0, "dataset0 (blue) sh1uld have no offset on middle point"); + assert.strictEqual(ds1Point2Offset, 2, "dataset1 (red) should have this offset and be on top of blue dataset"); + assert.strictEqual(ds2Point2Offset, 2, "dataset2 (green) should have this offset because the red dataset has no height in this point"); }); it("null defaults to 0", function () { - var data1 = [ + var data0 = [ { x: 1, y: 1, fill: "blue" }, { x: 2, y: 2, fill: "blue" }, { x: 3, y: 3, fill: "blue" }, ]; - var data2 = [ + var data1 = [ { x: 1, y: 1, fill: "red" }, { x: 2, y: "0", fill: "red" }, { x: 3, y: 3, fill: "red" }, ]; - var data3 = [ + var data2 = [ { x: 1, y: 1, fill: "green" }, { x: 2, y: 2, fill: "green" }, { x: 3, y: 3, fill: "green" }, ]; - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - var plot = new Plottable.Plot.StackedArea(xScale, yScale); - plot.addDataset("d1", data1); - plot.addDataset("d2", data2); - plot.addDataset("d3", data3); - plot.project("fill", "fill"); - plot.project("x", "x", xScale).project("y", "y", yScale); - var ds1Point2Offset = plot._key2PlotDatasetKey.get("d1").plotMetadata.offsets.get(2); - var ds2Point2Offset = plot._key2PlotDatasetKey.get("d2").plotMetadata.offsets.get(2); - var ds3Point2Offset = plot._key2PlotDatasetKey.get("d3").plotMetadata.offsets.get(2); - assert.strictEqual(ds1Point2Offset, 0, "dataset1 (blue) should have no offset on middle point"); - assert.strictEqual(ds2Point2Offset, 2, "dataset2 (red) should have this offset and be on top of blue dataset"); - assert.strictEqual(ds3Point2Offset, 2, "dataset3 (green) should have this offset because the red dataset (ds2) has no height in this point"); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); + var plot = new Plottable.Plots.StackedArea(xScale, yScale); + var dataset0 = new Plottable.Dataset(data0); + plot.addDataset(dataset0); + var dataset1 = new Plottable.Dataset(data1); + plot.addDataset(dataset1); + var dataset2 = new Plottable.Dataset(data2); + plot.addDataset(dataset2); + plot.attr("fill", "fill"); + plot.x(function (d) { return d.x; }, xScale).y(function (d) { return d.y; }, yScale); + var ds0Point2Offset = plot._key2PlotDatasetKey.get("_0").plotMetadata.offsets.get(2); + var ds1Point2Offset = plot._key2PlotDatasetKey.get("_1").plotMetadata.offsets.get(2); + var ds2Point2Offset = plot._key2PlotDatasetKey.get("_2").plotMetadata.offsets.get(2); + assert.strictEqual(ds0Point2Offset, 0, "dataset0 (blue) should have no offset on middle point"); + assert.strictEqual(ds1Point2Offset, 2, "dataset1 (red) should have this offset and be on top of blue dataset"); + assert.strictEqual(ds2Point2Offset, 2, "dataset2 (green) should have this offset because the red dataset has no height in this point"); }); }); }); @@ -5207,9 +5044,9 @@ describe("Plots", function () { var originalData1; var originalData2; beforeEach(function () { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - xScale = new Plottable.Scale.Category(); - yScale = new Plottable.Scale.Linear().domain([0, 3]); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + xScale = new Plottable.Scales.Category(); + yScale = new Plottable.Scales.Linear().domain([0, 3]); originalData1 = [ { x: "A", y: 1 }, { x: "B", y: 2 } @@ -5228,14 +5065,14 @@ describe("Plots", function () { ]; dataset1 = new Plottable.Dataset(data1); dataset2 = new Plottable.Dataset(data2); - renderer = new Plottable.Plot.StackedBar(xScale, yScale); + renderer = new Plottable.Plots.StackedBar(xScale, yScale); renderer.addDataset(dataset1); renderer.addDataset(dataset2); - renderer.project("x", "x", xScale); - renderer.project("y", "y", yScale); + renderer.x(function (d) { return d.x; }, xScale); + renderer.y(function (d) { return d.y; }, yScale); renderer.baseline(0); - var xAxis = new Plottable.Axis.Category(xScale, "bottom"); - var table = new Plottable.Component.Table([[renderer], [xAxis]]).renderTo(svg); + var xAxis = new Plottable.Axes.Category(xScale, "bottom"); + new Plottable.Components.Table([[renderer], [xAxis]]).renderTo(svg); axisHeight = xAxis.height(); bandWidth = xScale.rangeBand(); }); @@ -5250,25 +5087,26 @@ describe("Plots", function () { var bar2X = bar2.data()[0].x; var bar3X = bar3.data()[0].x; // check widths - assert.closeTo(numAttr(bar0, "width"), bandWidth, 2); - assert.closeTo(numAttr(bar1, "width"), bandWidth, 2); - assert.closeTo(numAttr(bar2, "width"), bandWidth, 2); - assert.closeTo(numAttr(bar3, "width"), bandWidth, 2); + assert.closeTo(TestMethods.numAttr(bar0, "width"), bandWidth, 2); + assert.closeTo(TestMethods.numAttr(bar1, "width"), bandWidth, 2); + assert.closeTo(TestMethods.numAttr(bar2, "width"), bandWidth, 2); + assert.closeTo(TestMethods.numAttr(bar3, "width"), bandWidth, 2); // check heights - assert.closeTo(numAttr(bar0, "height"), (400 - axisHeight) / 3, 0.01, "height is correct for bar0"); - assert.closeTo(numAttr(bar1, "height"), (400 - axisHeight) / 3 * 2, 0.01, "height is correct for bar1"); - assert.closeTo(numAttr(bar2, "height"), (400 - axisHeight) / 3 * 2, 0.01, "height is correct for bar2"); - assert.closeTo(numAttr(bar3, "height"), (400 - axisHeight) / 3, 0.01, "height is correct for bar3"); + assert.closeTo(TestMethods.numAttr(bar0, "height"), (400 - axisHeight) / 3, 0.01, "height is correct for bar0"); + assert.closeTo(TestMethods.numAttr(bar1, "height"), (400 - axisHeight) / 3 * 2, 0.01, "height is correct for bar1"); + assert.closeTo(TestMethods.numAttr(bar2, "height"), (400 - axisHeight) / 3 * 2, 0.01, "height is correct for bar2"); + assert.closeTo(TestMethods.numAttr(bar3, "height"), (400 - axisHeight) / 3, 0.01, "height is correct for bar3"); // check that bar is aligned on the center of the scale - assert.closeTo(numAttr(bar0, "x") + numAttr(bar0, "width") / 2, xScale.scale(bar0X), 0.01, "x pos correct for bar0"); - assert.closeTo(numAttr(bar1, "x") + numAttr(bar1, "width") / 2, xScale.scale(bar1X), 0.01, "x pos correct for bar1"); - assert.closeTo(numAttr(bar2, "x") + numAttr(bar2, "width") / 2, xScale.scale(bar2X), 0.01, "x pos correct for bar2"); - assert.closeTo(numAttr(bar3, "x") + numAttr(bar3, "width") / 2, xScale.scale(bar3X), 0.01, "x pos correct for bar3"); + var centerX = function (selection) { return TestMethods.numAttr(selection, "x") + TestMethods.numAttr(selection, "width") / 2; }; + assert.closeTo(centerX(bar0), xScale.scale(bar0X), 0.01, "x pos correct for bar0"); + assert.closeTo(centerX(bar1), xScale.scale(bar1X), 0.01, "x pos correct for bar1"); + assert.closeTo(centerX(bar2), xScale.scale(bar2X), 0.01, "x pos correct for bar2"); + assert.closeTo(centerX(bar3), xScale.scale(bar3X), 0.01, "x pos correct for bar3"); // now check y values to ensure they do indeed stack - assert.closeTo(numAttr(bar0, "y"), (400 - axisHeight) / 3 * 2, 0.01, "y is correct for bar0"); - assert.closeTo(numAttr(bar1, "y"), (400 - axisHeight) / 3, 0.01, "y is correct for bar1"); - assert.closeTo(numAttr(bar2, "y"), 0, 0.01, "y is correct for bar2"); - assert.closeTo(numAttr(bar3, "y"), 0, 0.01, "y is correct for bar3"); + assert.closeTo(TestMethods.numAttr(bar0, "y"), (400 - axisHeight) / 3 * 2, 0.01, "y is correct for bar0"); + assert.closeTo(TestMethods.numAttr(bar1, "y"), (400 - axisHeight) / 3, 0.01, "y is correct for bar1"); + assert.closeTo(TestMethods.numAttr(bar2, "y"), 0, 0.01, "y is correct for bar2"); + assert.closeTo(TestMethods.numAttr(bar3, "y"), 0, 0.01, "y is correct for bar3"); assert.deepEqual(dataset1.data(), originalData1, "underlying data is not modified"); assert.deepEqual(dataset2.data(), originalData2, "underlying data is not modified"); svg.remove(); @@ -5316,11 +5154,10 @@ describe("Plots", function () { var SVG_WIDTH = 600; var SVG_HEIGHT = 400; var axisHeight = 0; - var bandWidth = 0; beforeEach(function () { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - xScale = new Plottable.Scale.Category(); - yScale = new Plottable.Scale.Linear(); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + xScale = new Plottable.Scales.Category(); + yScale = new Plottable.Scales.Linear(); var data1 = [ { x: "A", y: -1 }, { x: "B", y: -4 } @@ -5337,16 +5174,16 @@ describe("Plots", function () { { x: "A", y: -3 }, { x: "B", y: 4 } ]; - plot = new Plottable.Plot.StackedBar(xScale, yScale); - plot.addDataset(data1); - plot.addDataset(data2); - plot.addDataset(data3); - plot.addDataset(data4); - plot.project("x", "x", xScale); - plot.project("y", "y", yScale); + plot = new Plottable.Plots.StackedBar(xScale, yScale); + plot.addDataset(new Plottable.Dataset(data1)); + plot.addDataset(new Plottable.Dataset(data2)); + plot.addDataset(new Plottable.Dataset(data3)); + plot.addDataset(new Plottable.Dataset(data4)); + plot.x(function (d) { return d.x; }, xScale); + plot.y(function (d) { return d.y; }, yScale); plot.baseline(0); - var xAxis = new Plottable.Axis.Category(xScale, "bottom"); - var table = new Plottable.Component.Table([[plot], [xAxis]]).renderTo(svg); + var xAxis = new Plottable.Axes.Category(xScale, "bottom"); + new Plottable.Components.Table([[plot], [xAxis]]).renderTo(svg); axisHeight = xAxis.height(); }); it("stacking done correctly for negative values", function () { @@ -5360,11 +5197,11 @@ describe("Plots", function () { var bar6 = d3.select(bars[0][6]); var bar7 = d3.select(bars[0][7]); // check stacking order - assert.operator(numAttr(bar0, "y"), "<", numAttr(bar2, "y"), "'A' bars added below the baseline in dataset order"); - assert.operator(numAttr(bar2, "y"), "<", numAttr(bar4, "y"), "'A' bars added below the baseline in dataset order"); - assert.operator(numAttr(bar4, "y"), "<", numAttr(bar6, "y"), "'A' bars added below the baseline in dataset order"); - assert.operator(numAttr(bar1, "y"), "<", numAttr(bar5, "y"), "'B' bars added below the baseline in dataset order"); - assert.operator(numAttr(bar3, "y"), ">", numAttr(bar7, "y"), "'B' bars added above the baseline in dataset order"); + assert.operator(TestMethods.numAttr(bar0, "y"), "<", TestMethods.numAttr(bar2, "y"), "'A' bars below baseline in dataset order"); + assert.operator(TestMethods.numAttr(bar2, "y"), "<", TestMethods.numAttr(bar4, "y"), "'A' bars below baseline in dataset order"); + assert.operator(TestMethods.numAttr(bar4, "y"), "<", TestMethods.numAttr(bar6, "y"), "'A' bars below baseline in dataset order"); + assert.operator(TestMethods.numAttr(bar1, "y"), "<", TestMethods.numAttr(bar5, "y"), "'B' bars below baseline in dataset order"); + assert.operator(TestMethods.numAttr(bar3, "y"), ">", TestMethods.numAttr(bar7, "y"), "'B' bars above baseline in dataset order"); svg.remove(); }); it("stacked extent is set correctly", function () { @@ -5384,9 +5221,9 @@ describe("Plots", function () { var rendererWidth; var bandWidth = 0; beforeEach(function () { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - xScale = new Plottable.Scale.Linear().domain([0, 6]); - yScale = new Plottable.Scale.Category(); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + xScale = new Plottable.Scales.Linear().domain([0, 6]); + yScale = new Plottable.Scales.Category(); var data1 = [ { name: "jon", y: 0, type: "q1" }, { name: "dan", y: 2, type: "q1" } @@ -5397,14 +5234,14 @@ describe("Plots", function () { ]; dataset1 = new Plottable.Dataset(data1); dataset2 = new Plottable.Dataset(data2); - renderer = new Plottable.Plot.StackedBar(xScale, yScale, false); - renderer.project("y", "name", yScale); - renderer.project("x", "y", xScale); - renderer.addDataset(data1); - renderer.addDataset(data2); + renderer = new Plottable.Plots.StackedBar(xScale, yScale, false); + renderer.y(function (d) { return d.name; }, yScale); + renderer.x(function (d) { return d.y; }, xScale); + renderer.addDataset(new Plottable.Dataset(data1)); + renderer.addDataset(new Plottable.Dataset(data2)); renderer.baseline(0); - var yAxis = new Plottable.Axis.Category(yScale, "left"); - var table = new Plottable.Component.Table([[yAxis, renderer]]).renderTo(svg); + var yAxis = new Plottable.Axes.Category(yScale, "left"); + new Plottable.Components.Table([[yAxis, renderer]]).renderTo(svg); rendererWidth = renderer.width(); bandWidth = yScale.rangeBand(); }); @@ -5415,29 +5252,30 @@ describe("Plots", function () { var bar2 = d3.select(bars[0][2]); var bar3 = d3.select(bars[0][3]); // check heights - assert.closeTo(numAttr(bar0, "height"), bandWidth, 2); - assert.closeTo(numAttr(bar1, "height"), bandWidth, 2); - assert.closeTo(numAttr(bar2, "height"), bandWidth, 2); - assert.closeTo(numAttr(bar3, "height"), bandWidth, 2); + assert.closeTo(TestMethods.numAttr(bar0, "height"), bandWidth, 2); + assert.closeTo(TestMethods.numAttr(bar1, "height"), bandWidth, 2); + assert.closeTo(TestMethods.numAttr(bar2, "height"), bandWidth, 2); + assert.closeTo(TestMethods.numAttr(bar3, "height"), bandWidth, 2); // check widths - assert.closeTo(numAttr(bar0, "width"), 0, 0.01, "width is correct for bar0"); - assert.closeTo(numAttr(bar1, "width"), rendererWidth / 3, 0.01, "width is correct for bar1"); - assert.closeTo(numAttr(bar2, "width"), rendererWidth / 3, 0.01, "width is correct for bar2"); - assert.closeTo(numAttr(bar3, "width"), rendererWidth / 3 * 2, 0.01, "width is correct for bar3"); + assert.closeTo(TestMethods.numAttr(bar0, "width"), 0, 0.01, "width is correct for bar0"); + assert.closeTo(TestMethods.numAttr(bar1, "width"), rendererWidth / 3, 0.01, "width is correct for bar1"); + assert.closeTo(TestMethods.numAttr(bar2, "width"), rendererWidth / 3, 0.01, "width is correct for bar2"); + assert.closeTo(TestMethods.numAttr(bar3, "width"), rendererWidth / 3 * 2, 0.01, "width is correct for bar3"); var bar0Y = bar0.data()[0].name; var bar1Y = bar1.data()[0].name; var bar2Y = bar2.data()[0].name; var bar3Y = bar3.data()[0].name; // check that bar is aligned on the center of the scale - assert.closeTo(numAttr(bar0, "y") + numAttr(bar0, "height") / 2, yScale.scale(bar0Y), 0.01, "y pos correct for bar0"); - assert.closeTo(numAttr(bar1, "y") + numAttr(bar1, "height") / 2, yScale.scale(bar1Y), 0.01, "y pos correct for bar1"); - assert.closeTo(numAttr(bar2, "y") + numAttr(bar2, "height") / 2, yScale.scale(bar2Y), 0.01, "y pos correct for bar2"); - assert.closeTo(numAttr(bar3, "y") + numAttr(bar3, "height") / 2, yScale.scale(bar3Y), 0.01, "y pos correct for bar3"); + var centerY = function (selection) { return TestMethods.numAttr(selection, "y") + TestMethods.numAttr(selection, "height") / 2; }; + assert.closeTo(centerY(bar0), yScale.scale(bar0Y), 0.01, "y pos correct for bar0"); + assert.closeTo(centerY(bar1), yScale.scale(bar1Y), 0.01, "y pos correct for bar1"); + assert.closeTo(centerY(bar2), yScale.scale(bar2Y), 0.01, "y pos correct for bar2"); + assert.closeTo(centerY(bar3), yScale.scale(bar3Y), 0.01, "y pos correct for bar3"); // now check x values to ensure they do indeed stack - assert.closeTo(numAttr(bar0, "x"), 0, 0.01, "x is correct for bar0"); - assert.closeTo(numAttr(bar1, "x"), 0, 0.01, "x is correct for bar1"); - assert.closeTo(numAttr(bar2, "x"), 0, 0.01, "x is correct for bar2"); - assert.closeTo(numAttr(bar3, "x"), rendererWidth / 3, 0.01, "x is correct for bar3"); + assert.closeTo(TestMethods.numAttr(bar0, "x"), 0, 0.01, "x is correct for bar0"); + assert.closeTo(TestMethods.numAttr(bar1, "x"), 0, 0.01, "x is correct for bar1"); + assert.closeTo(TestMethods.numAttr(bar2, "x"), 0, 0.01, "x is correct for bar2"); + assert.closeTo(TestMethods.numAttr(bar3, "x"), rendererWidth / 3, 0.01, "x is correct for bar3"); svg.remove(); }); }); @@ -5446,11 +5284,10 @@ describe("Plots", function () { var plot; var SVG_WIDTH = 600; var SVG_HEIGHT = 400; - var numAttr = function (s, a) { return parseFloat(s.attr(a)); }; beforeEach(function () { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Linear(); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Linear(); var data1 = [ { x: "A", y: 1, type: "a" }, { x: "B", y: 2, type: "a" }, @@ -5464,14 +5301,14 @@ describe("Plots", function () { { x: "B", y: 1, type: "c" }, { x: "C", y: 7, type: "c" } ]; - plot = new Plottable.Plot.StackedBar(xScale, yScale); - plot.addDataset(data1); - plot.addDataset(data2); - plot.addDataset(data3); - plot.project("x", "x", xScale); - plot.project("y", "y", yScale); - var xAxis = new Plottable.Axis.Category(xScale, "bottom"); - var table = new Plottable.Component.Table([[plot], [xAxis]]).renderTo(svg); + plot = new Plottable.Plots.StackedBar(xScale, yScale); + plot.addDataset(new Plottable.Dataset(data1)); + plot.addDataset(new Plottable.Dataset(data2)); + plot.addDataset(new Plottable.Dataset(data3)); + plot.x(function (d) { return d.x; }, xScale); + plot.y(function (d) { return d.y; }, yScale); + var xAxis = new Plottable.Axes.Category(xScale, "bottom"); + new Plottable.Components.Table([[plot], [xAxis]]).renderTo(svg); }); it("renders correctly", function () { var bars = plot._renderArea.selectAll("rect"); @@ -5479,14 +5316,14 @@ describe("Plots", function () { var aBars = [d3.select(bars[0][0]), d3.select(bars[0][3])]; var bBars = [d3.select(bars[0][1]), d3.select(bars[0][4]), d3.select(bars[0][5])]; var cBars = [d3.select(bars[0][2]), d3.select(bars[0][6])]; - assert.closeTo(numAttr(aBars[0], "x"), numAttr(aBars[1], "x"), 0.01, "A bars at same x position"); - assert.operator(numAttr(aBars[0], "y"), ">", numAttr(aBars[1], "y"), "first dataset A bar under second"); - assert.closeTo(numAttr(bBars[0], "x"), numAttr(bBars[1], "x"), 0.01, "B bars at same x position"); - assert.closeTo(numAttr(bBars[1], "x"), numAttr(bBars[2], "x"), 0.01, "B bars at same x position"); - assert.operator(numAttr(bBars[0], "y"), ">", numAttr(bBars[1], "y"), "first dataset B bar under second"); - assert.operator(numAttr(bBars[1], "y"), ">", numAttr(bBars[2], "y"), "second dataset B bar under third"); - assert.closeTo(numAttr(cBars[0], "x"), numAttr(cBars[1], "x"), 0.01, "C bars at same x position"); - assert.operator(numAttr(cBars[0], "y"), ">", numAttr(cBars[1], "y"), "first dataset C bar under second"); + assert.closeTo(TestMethods.numAttr(aBars[0], "x"), TestMethods.numAttr(aBars[1], "x"), 0.01, "A bars at same x position"); + assert.operator(TestMethods.numAttr(aBars[0], "y"), ">", TestMethods.numAttr(aBars[1], "y"), "first dataset A bar under second"); + assert.closeTo(TestMethods.numAttr(bBars[0], "x"), TestMethods.numAttr(bBars[1], "x"), 0.01, "B bars at same x position"); + assert.closeTo(TestMethods.numAttr(bBars[1], "x"), TestMethods.numAttr(bBars[2], "x"), 0.01, "B bars at same x position"); + assert.operator(TestMethods.numAttr(bBars[0], "y"), ">", TestMethods.numAttr(bBars[1], "y"), "first dataset B bar under second"); + assert.operator(TestMethods.numAttr(bBars[1], "y"), ">", TestMethods.numAttr(bBars[2], "y"), "second dataset B bar under third"); + assert.closeTo(TestMethods.numAttr(cBars[0], "x"), TestMethods.numAttr(cBars[1], "x"), 0.01, "C bars at same x position"); + assert.operator(TestMethods.numAttr(cBars[0], "y"), ">", TestMethods.numAttr(cBars[1], "y"), "first dataset C bar under second"); svg.remove(); }); }); @@ -5495,11 +5332,10 @@ describe("Plots", function () { var plot; var SVG_WIDTH = 600; var SVG_HEIGHT = 400; - var numAttr = function (s, a) { return parseFloat(s.attr(a)); }; beforeEach(function () { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Category(); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Category(); var data1 = [ { y: "A", x: 1, type: "a" }, { y: "B", x: 2, type: "a" }, @@ -5513,12 +5349,12 @@ describe("Plots", function () { { y: "B", x: 1, type: "c" }, { y: "C", x: 7, type: "c" } ]; - plot = new Plottable.Plot.StackedBar(xScale, yScale, false); - plot.addDataset(data1); - plot.addDataset(data2); - plot.addDataset(data3); - plot.project("x", "x", xScale); - plot.project("y", "y", yScale); + plot = new Plottable.Plots.StackedBar(xScale, yScale, false); + plot.addDataset(new Plottable.Dataset(data1)); + plot.addDataset(new Plottable.Dataset(data2)); + plot.addDataset(new Plottable.Dataset(data3)); + plot.x(function (d) { return d.x; }, xScale); + plot.y(function (d) { return d.y; }, yScale); plot.renderTo(svg); }); it("renders correctly", function () { @@ -5527,14 +5363,14 @@ describe("Plots", function () { var aBars = [d3.select(bars[0][0]), d3.select(bars[0][3])]; var bBars = [d3.select(bars[0][1]), d3.select(bars[0][4]), d3.select(bars[0][5])]; var cBars = [d3.select(bars[0][2]), d3.select(bars[0][6])]; - assert.closeTo(numAttr(aBars[0], "y"), numAttr(aBars[1], "y"), 0.01, "A bars at same y position"); - assert.operator(numAttr(aBars[0], "x"), "<", numAttr(aBars[1], "x"), "first dataset A bar under second"); - assert.closeTo(numAttr(bBars[0], "y"), numAttr(bBars[1], "y"), 0.01, "B bars at same y position"); - assert.closeTo(numAttr(bBars[1], "y"), numAttr(bBars[2], "y"), 0.01, "B bars at same y position"); - assert.operator(numAttr(bBars[0], "x"), "<", numAttr(bBars[1], "x"), "first dataset B bar under second"); - assert.operator(numAttr(bBars[1], "x"), "<", numAttr(bBars[2], "x"), "second dataset B bar under third"); - assert.closeTo(numAttr(cBars[0], "y"), numAttr(cBars[1], "y"), 0.01, "C bars at same y position"); - assert.operator(numAttr(cBars[0], "x"), "<", numAttr(cBars[1], "x"), "first dataset C bar under second"); + assert.closeTo(TestMethods.numAttr(aBars[0], "y"), TestMethods.numAttr(aBars[1], "y"), 0.01, "A bars at same y position"); + assert.operator(TestMethods.numAttr(aBars[0], "x"), "<", TestMethods.numAttr(aBars[1], "x"), "first dataset A bar under second"); + assert.closeTo(TestMethods.numAttr(bBars[0], "y"), TestMethods.numAttr(bBars[1], "y"), 0.01, "B bars at same y position"); + assert.closeTo(TestMethods.numAttr(bBars[1], "y"), TestMethods.numAttr(bBars[2], "y"), 0.01, "B bars at same y position"); + assert.operator(TestMethods.numAttr(bBars[0], "x"), "<", TestMethods.numAttr(bBars[1], "x"), "first dataset B bar under second"); + assert.operator(TestMethods.numAttr(bBars[1], "x"), "<", TestMethods.numAttr(bBars[2], "x"), "second dataset B bar under third"); + assert.closeTo(TestMethods.numAttr(cBars[0], "y"), TestMethods.numAttr(cBars[1], "y"), 0.01, "C bars at same y position"); + assert.operator(TestMethods.numAttr(cBars[0], "x"), "<", TestMethods.numAttr(cBars[1], "x"), "first dataset C bar under second"); svg.remove(); }); }); @@ -5546,19 +5382,19 @@ describe("Plots", function () { var data2 = [ { x: "A", y: 1, fill: "red" }, ]; - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Linear(); - var plot = new Plottable.Plot.StackedBar(xScale, yScale); - plot.addDataset("d1", data1); - plot.addDataset("d2", data2); - plot.project("fill", "fill"); - plot.project("x", "x", xScale).project("y", "y", yScale); - var ds1FirstColumnOffset = plot._key2PlotDatasetKey.get("d1").plotMetadata.offsets.get("A"); - var ds2FirstColumnOffset = plot._key2PlotDatasetKey.get("d2").plotMetadata.offsets.get("A"); - assert.strictEqual(typeof ds1FirstColumnOffset, "number", "ds1 offset should be a number"); - assert.strictEqual(typeof ds2FirstColumnOffset, "number", "ds2 offset should be a number"); - assert.isFalse(Plottable._Util.Methods.isNaN(ds1FirstColumnOffset), "ds1 offset should not be NaN"); - assert.isFalse(Plottable._Util.Methods.isNaN(ds1FirstColumnOffset), "ds2 offset should not be NaN"); + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Linear(); + var plot = new Plottable.Plots.StackedBar(xScale, yScale); + plot.addDataset(new Plottable.Dataset(data1)); + plot.addDataset(new Plottable.Dataset(data2)); + plot.attr("fill", "fill"); + plot.x(function (d) { return d.x; }, xScale).y(function (d) { return d.y; }, yScale); + var ds1FirstColumnOffset = plot._key2PlotDatasetKey.get("_0").plotMetadata.offsets.get("A"); + var ds2FirstColumnOffset = plot._key2PlotDatasetKey.get("_1").plotMetadata.offsets.get("A"); + assert.strictEqual(typeof ds1FirstColumnOffset, "number", "ds0 offset should be a number"); + assert.strictEqual(typeof ds2FirstColumnOffset, "number", "ds1 offset should be a number"); + assert.isFalse(Plottable.Utils.Methods.isNaN(ds1FirstColumnOffset), "ds0 offset should not be NaN"); + assert.isFalse(Plottable.Utils.Methods.isNaN(ds1FirstColumnOffset), "ds1 offset should not be NaN"); }); it("bad values on the primary axis should default to 0 (be ignored)", function () { var data1 = [ @@ -5576,22 +5412,23 @@ describe("Plots", function () { var data5 = [ { x: "A", y: 3, fill: "pink" }, ]; - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Linear(); - var plot = new Plottable.Plot.StackedBar(xScale, yScale); - plot.addDataset("d1", data1); - plot.addDataset("d2", data2); - plot.addDataset("d3", data3); - plot.addDataset("d4", data4); - plot.addDataset("d5", data5); - plot.project("fill", "fill"); - plot.project("x", "x", xScale).project("y", "y", yScale); - var offset1 = plot._key2PlotDatasetKey.get("d1").plotMetadata.offsets.get("A"); - var offset3 = plot._key2PlotDatasetKey.get("d3").plotMetadata.offsets.get("A"); - var offset5 = plot._key2PlotDatasetKey.get("d5").plotMetadata.offsets.get("A"); - assert.strictEqual(offset1, 0, "Plot columns should start from offset 0 (at the very bottom)"); - assert.strictEqual(offset3, 1, "Bar 3 should have offset 1, because bar 2 was not rendered"); - assert.strictEqual(offset5, 3, "Bar 5 should have offset 3, because bar 4 was not rendered"); + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Linear(); + var plot = new Plottable.Plots.StackedBar(xScale, yScale); + plot.addDataset(new Plottable.Dataset(data1)); + plot.addDataset(new Plottable.Dataset(data2)); + plot.addDataset(new Plottable.Dataset(data3)); + plot.addDataset(new Plottable.Dataset(data4)); + plot.addDataset(new Plottable.Dataset(data5)); + plot.attr("fill", "fill"); + plot.x(function (d) { return d.x; }, xScale).y(function (d) { return d.y; }, yScale); + var keys = plot._key2PlotDatasetKey.keys(); + var offset0 = plot._key2PlotDatasetKey.get(keys[0]).plotMetadata.offsets.get("A"); + var offset2 = plot._key2PlotDatasetKey.get(keys[2]).plotMetadata.offsets.get("A"); + var offset4 = plot._key2PlotDatasetKey.get(keys[4]).plotMetadata.offsets.get("A"); + assert.strictEqual(offset0, 0, "Plot columns should start from offset 0 (at the very bottom)"); + assert.strictEqual(offset2, 1, "third bar should have offset 1, because second bar was not rendered"); + assert.strictEqual(offset4, 3, "fifth bar should have offset 3, because fourth bar was not rendered"); }); }); }); @@ -5613,9 +5450,9 @@ describe("Plots", function () { var originalData1; var originalData2; beforeEach(function () { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - xScale = new Plottable.Scale.Category(); - yScale = new Plottable.Scale.Linear().domain([0, 2]); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + xScale = new Plottable.Scales.Category(); + yScale = new Plottable.Scales.Linear().domain([0, 2]); originalData1 = [ { x: "A", y: 1 }, { x: "B", y: 2 } @@ -5634,14 +5471,14 @@ describe("Plots", function () { ]; dataset1 = new Plottable.Dataset(data1); dataset2 = new Plottable.Dataset(data2); - renderer = new Plottable.Plot.ClusteredBar(xScale, yScale); + renderer = new Plottable.Plots.ClusteredBar(xScale, yScale); renderer.addDataset(dataset1); renderer.addDataset(dataset2); renderer.baseline(0); - renderer.project("x", "x", xScale); - renderer.project("y", "y", yScale); - var xAxis = new Plottable.Axis.Category(xScale, "bottom"); - var table = new Plottable.Component.Table([[renderer], [xAxis]]).renderTo(svg); + renderer.x(function (d) { return d.x; }, xScale); + renderer.y(function (d) { return d.y; }, yScale); + var xAxis = new Plottable.Axes.Category(xScale, "bottom"); + new Plottable.Components.Table([[renderer], [xAxis]]).renderTo(svg); axisHeight = xAxis.height(); bandWidth = xScale.rangeBand(); }); @@ -5656,22 +5493,23 @@ describe("Plots", function () { var bar2X = bar2.data()[0].x; var bar3X = bar3.data()[0].x; // check widths - assert.closeTo(numAttr(bar0, "width"), 40, 2); - assert.closeTo(numAttr(bar1, "width"), 40, 2); - assert.closeTo(numAttr(bar2, "width"), 40, 2); - assert.closeTo(numAttr(bar3, "width"), 40, 2); + assert.closeTo(TestMethods.numAttr(bar0, "width"), 40, 2); + assert.closeTo(TestMethods.numAttr(bar1, "width"), 40, 2); + assert.closeTo(TestMethods.numAttr(bar2, "width"), 40, 2); + assert.closeTo(TestMethods.numAttr(bar3, "width"), 40, 2); // check heights - assert.closeTo(numAttr(bar0, "height"), (400 - axisHeight) / 2, 0.01, "height is correct for bar0"); - assert.closeTo(numAttr(bar1, "height"), (400 - axisHeight), 0.01, "height is correct for bar1"); - assert.closeTo(numAttr(bar2, "height"), (400 - axisHeight), 0.01, "height is correct for bar2"); - assert.closeTo(numAttr(bar3, "height"), (400 - axisHeight) / 2, 0.01, "height is correct for bar3"); + assert.closeTo(TestMethods.numAttr(bar0, "height"), (400 - axisHeight) / 2, 0.01, "height is correct for bar0"); + assert.closeTo(TestMethods.numAttr(bar1, "height"), (400 - axisHeight), 0.01, "height is correct for bar1"); + assert.closeTo(TestMethods.numAttr(bar2, "height"), (400 - axisHeight), 0.01, "height is correct for bar2"); + assert.closeTo(TestMethods.numAttr(bar3, "height"), (400 - axisHeight) / 2, 0.01, "height is correct for bar3"); // check that clustering is correct var innerScale = renderer._makeInnerScale(); var off = innerScale.scale("_0"); - assert.closeTo(numAttr(bar0, "x") + numAttr(bar0, "width") / 2, xScale.scale(bar0X) - xScale.rangeBand() / 2 + off, 0.01, "x pos correct for bar0"); - assert.closeTo(numAttr(bar1, "x") + numAttr(bar1, "width") / 2, xScale.scale(bar1X) - xScale.rangeBand() / 2 + off, 0.01, "x pos correct for bar1"); - assert.closeTo(numAttr(bar2, "x") + numAttr(bar2, "width") / 2, xScale.scale(bar2X) + xScale.rangeBand() / 2 - off, 0.01, "x pos correct for bar2"); - assert.closeTo(numAttr(bar3, "x") + numAttr(bar3, "width") / 2, xScale.scale(bar3X) + xScale.rangeBand() / 2 - off, 0.01, "x pos correct for bar3"); + var width = xScale.rangeBand() / 2; + assert.closeTo(TestMethods.numAttr(bar0, "x") + TestMethods.numAttr(bar0, "width") / 2, xScale.scale(bar0X) - width + off, 0.01, "x pos correct for bar0"); + assert.closeTo(TestMethods.numAttr(bar1, "x") + TestMethods.numAttr(bar1, "width") / 2, xScale.scale(bar1X) - width + off, 0.01, "x pos correct for bar1"); + assert.closeTo(TestMethods.numAttr(bar2, "x") + TestMethods.numAttr(bar2, "width") / 2, xScale.scale(bar2X) + width - off, 0.01, "x pos correct for bar2"); + assert.closeTo(TestMethods.numAttr(bar3, "x") + TestMethods.numAttr(bar3, "width") / 2, xScale.scale(bar3X) + width - off, 0.01, "x pos correct for bar3"); assert.deepEqual(dataset1.data(), originalData1, "underlying data is not modified"); assert.deepEqual(dataset2.data(), originalData2, "underlying data is not modified"); svg.remove(); @@ -5689,9 +5527,9 @@ describe("Plots", function () { var rendererWidth; var bandWidth = 0; beforeEach(function () { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - yScale = new Plottable.Scale.Category(); - xScale = new Plottable.Scale.Linear().domain([0, 2]); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + yScale = new Plottable.Scales.Category(); + xScale = new Plottable.Scales.Linear().domain([0, 2]); var data1 = [ { y: "A", x: 1 }, { y: "B", x: 2 } @@ -5702,14 +5540,14 @@ describe("Plots", function () { ]; dataset1 = new Plottable.Dataset(data1); dataset2 = new Plottable.Dataset(data2); - renderer = new Plottable.Plot.ClusteredBar(xScale, yScale, false); - renderer.addDataset(data1); - renderer.addDataset(data2); + renderer = new Plottable.Plots.ClusteredBar(xScale, yScale, false); + renderer.addDataset(new Plottable.Dataset(data1)); + renderer.addDataset(new Plottable.Dataset(data2)); renderer.baseline(0); - renderer.project("x", "x", xScale); - renderer.project("y", "y", yScale); - var yAxis = new Plottable.Axis.Category(yScale, "left"); - var table = new Plottable.Component.Table([[yAxis, renderer]]).renderTo(svg); + renderer.x(function (d) { return d.x; }, xScale); + renderer.y(function (d) { return d.y; }, yScale); + var yAxis = new Plottable.Axes.Category(yScale, "left"); + new Plottable.Components.Table([[yAxis, renderer]]).renderTo(svg); rendererWidth = renderer.width(); bandWidth = yScale.rangeBand(); }); @@ -5720,15 +5558,15 @@ describe("Plots", function () { var bar2 = d3.select(bars[0][2]); var bar3 = d3.select(bars[0][3]); // check widths - assert.closeTo(numAttr(bar0, "height"), 26, 2, "height is correct for bar0"); - assert.closeTo(numAttr(bar1, "height"), 26, 2, "height is correct for bar1"); - assert.closeTo(numAttr(bar2, "height"), 26, 2, "height is correct for bar2"); - assert.closeTo(numAttr(bar3, "height"), 26, 2, "height is correct for bar3"); + assert.closeTo(TestMethods.numAttr(bar0, "height"), 26, 2, "height is correct for bar0"); + assert.closeTo(TestMethods.numAttr(bar1, "height"), 26, 2, "height is correct for bar1"); + assert.closeTo(TestMethods.numAttr(bar2, "height"), 26, 2, "height is correct for bar2"); + assert.closeTo(TestMethods.numAttr(bar3, "height"), 26, 2, "height is correct for bar3"); // check heights - assert.closeTo(numAttr(bar0, "width"), rendererWidth / 2, 0.01, "width is correct for bar0"); - assert.closeTo(numAttr(bar1, "width"), rendererWidth, 0.01, "width is correct for bar1"); - assert.closeTo(numAttr(bar2, "width"), rendererWidth, 0.01, "width is correct for bar2"); - assert.closeTo(numAttr(bar3, "width"), rendererWidth / 2, 0.01, "width is correct for bar3"); + assert.closeTo(TestMethods.numAttr(bar0, "width"), rendererWidth / 2, 0.01, "width is correct for bar0"); + assert.closeTo(TestMethods.numAttr(bar1, "width"), rendererWidth, 0.01, "width is correct for bar1"); + assert.closeTo(TestMethods.numAttr(bar2, "width"), rendererWidth, 0.01, "width is correct for bar2"); + assert.closeTo(TestMethods.numAttr(bar3, "width"), rendererWidth / 2, 0.01, "width is correct for bar3"); var bar0Y = bar0.data()[0].y; var bar1Y = bar1.data()[0].y; var bar2Y = bar2.data()[0].y; @@ -5736,35 +5574,35 @@ describe("Plots", function () { // check that clustering is correct var innerScale = renderer._makeInnerScale(); var off = innerScale.scale("_0"); - assert.closeTo(numAttr(bar0, "y") + numAttr(bar0, "height") / 2, yScale.scale(bar0Y) - yScale.rangeBand() / 2 + off, 0.01, "y pos correct for bar0"); - assert.closeTo(numAttr(bar1, "y") + numAttr(bar1, "height") / 2, yScale.scale(bar1Y) - yScale.rangeBand() / 2 + off, 0.01, "y pos correct for bar1"); - assert.closeTo(numAttr(bar2, "y") + numAttr(bar2, "height") / 2, yScale.scale(bar2Y) + yScale.rangeBand() / 2 - off, 0.01, "y pos correct for bar2"); - assert.closeTo(numAttr(bar3, "y") + numAttr(bar3, "height") / 2, yScale.scale(bar3Y) + yScale.rangeBand() / 2 - off, 0.01, "y pos correct for bar3"); + var width = yScale.rangeBand() / 2; + assert.closeTo(TestMethods.numAttr(bar0, "y") + TestMethods.numAttr(bar0, "height") / 2, yScale.scale(bar0Y) - width + off, 0.01, "y pos correct for bar0"); + assert.closeTo(TestMethods.numAttr(bar1, "y") + TestMethods.numAttr(bar1, "height") / 2, yScale.scale(bar1Y) - width + off, 0.01, "y pos correct for bar1"); + assert.closeTo(TestMethods.numAttr(bar2, "y") + TestMethods.numAttr(bar2, "height") / 2, yScale.scale(bar2Y) + width - off, 0.01, "y pos correct for bar2"); + assert.closeTo(TestMethods.numAttr(bar3, "y") + TestMethods.numAttr(bar3, "height") / 2, yScale.scale(bar3Y) + width - off, 0.01, "y pos correct for bar3"); svg.remove(); }); }); describe("Clustered Bar Plot Missing Values", function () { var svg; var plot; - var numAttr = function (s, a) { return parseFloat(s.attr(a)); }; beforeEach(function () { var SVG_WIDTH = 600; var SVG_HEIGHT = 400; - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Linear(); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Linear(); var data1 = [{ x: "A", y: 1 }, { x: "B", y: 2 }, { x: "C", y: 1 }]; var data2 = [{ x: "A", y: 2 }, { x: "B", y: 4 }]; var data3 = [{ x: "B", y: 15 }, { x: "C", y: 15 }]; - plot = new Plottable.Plot.ClusteredBar(xScale, yScale); - plot.addDataset(data1); - plot.addDataset(data2); - plot.addDataset(data3); + plot = new Plottable.Plots.ClusteredBar(xScale, yScale); + plot.addDataset(new Plottable.Dataset(data1)); + plot.addDataset(new Plottable.Dataset(data2)); + plot.addDataset(new Plottable.Dataset(data3)); plot.baseline(0); - plot.project("x", "x", xScale); - plot.project("y", "y", yScale); - var xAxis = new Plottable.Axis.Category(xScale, "bottom"); - new Plottable.Component.Table([[plot], [xAxis]]).renderTo(svg); + plot.x(function (d) { return d.x; }, xScale); + plot.y(function (d) { return d.y; }, yScale); + var xAxis = new Plottable.Axes.Category(xScale, "bottom"); + new Plottable.Components.Table([[plot], [xAxis]]).renderTo(svg); }); it("renders correctly", function () { var bars = plot._renderArea.selectAll("rect"); @@ -5777,15 +5615,15 @@ describe("Plots", function () { var cBar0 = d3.select(bars[0][2]); var cBar1 = d3.select(bars[0][6]); // check bars are in domain order - assert.operator(numAttr(aBar0, "x"), "<", numAttr(bBar0, "x"), "first dataset bars ordered correctly"); - assert.operator(numAttr(bBar0, "x"), "<", numAttr(cBar0, "x"), "first dataset bars ordered correctly"); - assert.operator(numAttr(aBar1, "x"), "<", numAttr(bBar1, "x"), "second dataset bars ordered correctly"); - assert.operator(numAttr(bBar2, "x"), "<", numAttr(cBar1, "x"), "third dataset bars ordered correctly"); + assert.operator(TestMethods.numAttr(aBar0, "x"), "<", TestMethods.numAttr(bBar0, "x"), "first dataset bars ordered correctly"); + assert.operator(TestMethods.numAttr(bBar0, "x"), "<", TestMethods.numAttr(cBar0, "x"), "first dataset bars ordered correctly"); + assert.operator(TestMethods.numAttr(aBar1, "x"), "<", TestMethods.numAttr(bBar1, "x"), "second dataset bars ordered correctly"); + assert.operator(TestMethods.numAttr(bBar2, "x"), "<", TestMethods.numAttr(cBar1, "x"), "third dataset bars ordered correctly"); // check that clustering is correct - assert.operator(numAttr(aBar0, "x"), "<", numAttr(aBar1, "x"), "A bars clustered in dataset order"); - assert.operator(numAttr(bBar0, "x"), "<", numAttr(bBar1, "x"), "B bars clustered in dataset order"); - assert.operator(numAttr(bBar1, "x"), "<", numAttr(bBar2, "x"), "B bars clustered in dataset order"); - assert.operator(numAttr(cBar0, "x"), "<", numAttr(cBar1, "x"), "C bars clustered in dataset order"); + assert.operator(TestMethods.numAttr(aBar0, "x"), "<", TestMethods.numAttr(aBar1, "x"), "A bars clustered in dataset order"); + assert.operator(TestMethods.numAttr(bBar0, "x"), "<", TestMethods.numAttr(bBar1, "x"), "B bars clustered in dataset order"); + assert.operator(TestMethods.numAttr(bBar1, "x"), "<", TestMethods.numAttr(bBar2, "x"), "B bars clustered in dataset order"); + assert.operator(TestMethods.numAttr(cBar0, "x"), "<", TestMethods.numAttr(cBar1, "x"), "C bars clustered in dataset order"); svg.remove(); }); }); @@ -5795,18 +5633,18 @@ describe("Plots", function () { beforeEach(function () { var SVG_WIDTH = 600; var SVG_HEIGHT = 400; - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Category(); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Category(); var data1 = [{ y: "A", x: 1 }, { y: "B", x: 2 }, { y: "C", x: 1 }]; var data2 = [{ y: "A", x: 2 }, { y: "B", x: 4 }]; var data3 = [{ y: "B", x: 15 }, { y: "C", x: 15 }]; - plot = new Plottable.Plot.ClusteredBar(xScale, yScale, false); - plot.addDataset(data1); - plot.addDataset(data2); - plot.addDataset(data3); - plot.project("x", "x", xScale); - plot.project("y", "y", yScale); + plot = new Plottable.Plots.ClusteredBar(xScale, yScale, false); + plot.addDataset(new Plottable.Dataset(data1)); + plot.addDataset(new Plottable.Dataset(data2)); + plot.addDataset(new Plottable.Dataset(data3)); + plot.x(function (d) { return d.x; }, xScale); + plot.y(function (d) { return d.y; }, yScale); plot.renderTo(svg); }); it("renders correctly", function () { @@ -5820,86 +5658,20 @@ describe("Plots", function () { var cBar0 = d3.select(bars[0][2]); var cBar1 = d3.select(bars[0][6]); // check bars are in domain order - assert.operator(numAttr(aBar0, "y"), "<", numAttr(bBar0, "y"), "first dataset bars ordered correctly"); - assert.operator(numAttr(bBar0, "y"), "<", numAttr(cBar0, "y"), "first dataset bars ordered correctly"); - assert.operator(numAttr(aBar1, "y"), "<", numAttr(bBar1, "y"), "second dataset bars ordered correctly"); - assert.operator(numAttr(bBar2, "y"), "<", numAttr(cBar1, "y"), "third dataset bars ordered correctly"); + assert.operator(TestMethods.numAttr(aBar0, "y"), "<", TestMethods.numAttr(bBar0, "y"), "first dataset bars ordered correctly"); + assert.operator(TestMethods.numAttr(bBar0, "y"), "<", TestMethods.numAttr(cBar0, "y"), "first dataset bars ordered correctly"); + assert.operator(TestMethods.numAttr(aBar1, "y"), "<", TestMethods.numAttr(bBar1, "y"), "second dataset bars ordered correctly"); + assert.operator(TestMethods.numAttr(bBar2, "y"), "<", TestMethods.numAttr(cBar1, "y"), "third dataset bars ordered correctly"); // check that clustering is correct - assert.operator(numAttr(aBar0, "y"), "<", numAttr(aBar1, "y"), "A bars clustered in dataset order"); - assert.operator(numAttr(bBar0, "y"), "<", numAttr(bBar1, "y"), "B bars clustered in dataset order"); - assert.operator(numAttr(bBar1, "y"), "<", numAttr(bBar2, "y"), "B bars clustered in dataset order"); - assert.operator(numAttr(cBar0, "y"), "<", numAttr(cBar1, "y"), "C bars clustered in dataset order"); + assert.operator(TestMethods.numAttr(aBar0, "y"), "<", TestMethods.numAttr(aBar1, "y"), "A bars clustered in dataset order"); + assert.operator(TestMethods.numAttr(bBar0, "y"), "<", TestMethods.numAttr(bBar1, "y"), "B bars clustered in dataset order"); + assert.operator(TestMethods.numAttr(bBar1, "y"), "<", TestMethods.numAttr(bBar2, "y"), "B bars clustered in dataset order"); + assert.operator(TestMethods.numAttr(cBar0, "y"), "<", TestMethods.numAttr(cBar1, "y"), "C bars clustered in dataset order"); svg.remove(); }); }); }); -/// -var assert = chai.assert; -describe("Broadcasters", function () { - var b; - var called; - var cb; - var listenable = {}; - beforeEach(function () { - b = new Plottable.Core.Broadcaster(listenable); - called = false; - cb = function () { - called = true; - }; - }); - it("listeners are called by the broadcast method", function () { - b.registerListener(null, cb); - b.broadcast(); - assert.isTrue(called, "callback was called"); - }); - it("same listener can only be associated with one callback", function () { - var called2 = false; - var cb2 = function () { - called2 = true; - }; - var listener = {}; - b.registerListener(listener, cb); - b.registerListener(listener, cb2); - b.broadcast(); - assert.isFalse(called, "first (overwritten) callback not called"); - assert.isTrue(called2, "second callback was called"); - }); - it("listeners can be deregistered", function () { - var listener = {}; - b.registerListener(listener, cb); - b.deregisterListener(listener); - b.broadcast(); - assert.isFalse(called, "callback was not called after deregistering only listener"); - b.registerListener(5, cb); - b.registerListener(6, cb); - b.deregisterAllListeners(); - b.broadcast(); - assert.isFalse(called, "callback was not called after deregistering all listeners"); - b.registerListener(5, cb); - b.registerListener(6, cb); - b.deregisterListener(5); - b.broadcast(); - assert.isTrue(called, "callback was called even after 1/2 listeners were deregistered"); - }); - it("arguments are passed through to callback", function () { - var g2 = {}; - var g3 = "foo"; - var cb = function (arg1, arg2, arg3) { - assert.strictEqual(listenable, arg1, "broadcaster passed through"); - assert.strictEqual(g2, arg2, "g2 passed through"); - assert.strictEqual(g3, arg3, "g3 passed through"); - called = true; - }; - b.registerListener(null, cb); - b.broadcast(g2, g3); - assert.isTrue(called, "the cb was called"); - }); - it("deregistering an unregistered listener doesn't throw an error", function () { - assert.doesNotThrow(function () { return b.deregisterListener({}); }); - }); -}); - /// var assert = chai.assert; describe("Metadata", function () { @@ -5908,26 +5680,27 @@ describe("Metadata", function () { var data1 = [{ x: 0, y: 0 }, { x: 1, y: 1 }]; var data2 = [{ x: 2, y: 2 }, { x: 3, y: 3 }]; before(function () { - xScale = new Plottable.Scale.Linear(); - yScale = new Plottable.Scale.Linear(); + xScale = new Plottable.Scales.Linear(); + yScale = new Plottable.Scales.Linear(); xScale.domain([0, 400]); yScale.domain([400, 0]); }); it("plot metadata is set properly", function () { var d1 = new Plottable.Dataset(); - var r = new Plottable.Plot.AbstractPlot().addDataset("d1", d1).addDataset(d1).addDataset("d2", []).addDataset([]); + var d2 = new Plottable.Dataset(); + var r = new Plottable.Plot().addDataset(d1).addDataset(d2); r._datasetKeysInOrder.forEach(function (key) { var plotMetadata = r._key2PlotDatasetKey.get(key).plotMetadata; assert.propertyVal(plotMetadata, "datasetKey", key, "metadata has correct dataset key"); }); }); - it("user metadata is applied", function () { - var svg = generateSVG(400, 400); + it("Dataset is passed in", function () { + var svg = TestMethods.generateSVG(400, 400); var metadata = { foo: 10, bar: 20 }; - var xAccessor = function (d, i, u) { return d.x + i * u.foo; }; - var yAccessor = function (d, i, u) { return u.bar; }; + var xAccessor = function (d, i, dataset) { return d.x + i * dataset.metadata().foo; }; + var yAccessor = function (d, i, dataset) { return dataset.metadata().bar; }; var dataset = new Plottable.Dataset(data1, metadata); - var plot = new Plottable.Plot.Scatter(xScale, yScale).project("x", xAccessor).project("y", yAccessor); + var plot = new Plottable.Plots.Scatter(xScale, yScale).x(xAccessor, xScale).y(yAccessor, yScale); plot.addDataset(dataset); plot.renderTo(svg); var circles = plot.getAllSelections(); @@ -5950,14 +5723,14 @@ describe("Metadata", function () { svg.remove(); }); it("user metadata is applied to associated dataset", function () { - var svg = generateSVG(400, 400); + var svg = TestMethods.generateSVG(400, 400); var metadata1 = { foo: 10 }; var metadata2 = { foo: 30 }; - var xAccessor = function (d, i, u) { return d.x + (i + 1) * u.foo; }; + var xAccessor = function (d, i, dataset) { return d.x + (i + 1) * dataset.metadata().foo; }; var yAccessor = function () { return 0; }; var dataset1 = new Plottable.Dataset(data1, metadata1); var dataset2 = new Plottable.Dataset(data2, metadata2); - var plot = new Plottable.Plot.Scatter(xScale, yScale).project("x", xAccessor).project("y", yAccessor); + var plot = new Plottable.Plots.Scatter(xScale, yScale).x(xAccessor, xScale).y(yAccessor, yScale); plot.addDataset(dataset1); plot.addDataset(dataset2); plot.renderTo(svg); @@ -5977,18 +5750,18 @@ describe("Metadata", function () { svg.remove(); }); it("plot metadata is applied", function () { - var svg = generateSVG(400, 400); - var xAccessor = function (d, i, u, m) { return d.x + (i + 1) * m.foo; }; + var svg = TestMethods.generateSVG(400, 400); + var xAccessor = function (d, i, dataset, m) { return d.x + (i + 1) * m.foo; }; var yAccessor = function () { return 0; }; - var plot = new Plottable.Plot.Scatter(xScale, yScale).project("x", xAccessor).project("y", yAccessor); + var plot = new Plottable.Plots.Scatter(xScale, yScale).x(xAccessor, xScale).y(yAccessor, yScale); plot._getPlotMetadataForDataset = function (key) { return { datasetKey: key, foo: 10 }; }; - plot.addDataset(data1); - plot.addDataset(data2); + plot.addDataset(new Plottable.Dataset(data1)); + plot.addDataset(new Plottable.Dataset(data2)); plot.renderTo(svg); var circles = plot.getAllSelections(); var c1 = d3.select(circles[0][0]); @@ -6006,27 +5779,29 @@ describe("Metadata", function () { svg.remove(); }); it("plot metadata is per plot", function () { - var svg = generateSVG(400, 400); - var xAccessor = function (d, i, u, m) { return d.x + (i + 1) * m.foo; }; + var svg = TestMethods.generateSVG(400, 400); + var xAccessor = function (d, i, dataset, m) { return d.x + (i + 1) * m.foo; }; var yAccessor = function () { return 0; }; - var plot1 = new Plottable.Plot.Scatter(xScale, yScale).project("x", xAccessor).project("y", yAccessor); + var plot1 = new Plottable.Plots.Scatter(xScale, yScale).x(xAccessor, xScale).y(yAccessor, yScale); plot1._getPlotMetadataForDataset = function (key) { return { datasetKey: key, foo: 10 }; }; - plot1.addDataset(data1); - plot1.addDataset(data2); - var plot2 = new Plottable.Plot.Scatter(xScale, yScale).project("x", xAccessor).project("y", yAccessor); + var dataset1 = new Plottable.Dataset(data1); + var dataset2 = new Plottable.Dataset(data2); + plot1.addDataset(dataset1); + plot1.addDataset(dataset2); + var plot2 = new Plottable.Plots.Scatter(xScale, yScale).x(xAccessor, xScale).y(yAccessor, yScale); plot2._getPlotMetadataForDataset = function (key) { return { datasetKey: key, foo: 20 }; }; - plot2.addDataset(data1); - plot2.addDataset(data2); + plot2.addDataset(dataset1); + plot2.addDataset(dataset2); plot1.renderTo(svg); plot2.renderTo(svg); var circles = plot1.getAllSelections(); @@ -6057,165 +5832,175 @@ describe("Metadata", function () { assert.closeTo(parseFloat(c4Position[0]), 43, 0.01, "fourth circle is correct for second plot"); svg.remove(); }); - it("_getExtent works as expected with plot metadata", function () { - var svg = generateSVG(400, 400); - var metadata = { foo: 11 }; - var id = function (d) { return d; }; - var dataset = new Plottable.Dataset(data1, metadata); - var a = function (d, i, u, m) { return d.x + u.foo + m.foo; }; - var plot = new Plottable.Plot.AbstractPlot().project("a", a, xScale); - plot._getPlotMetadataForDataset = function (key) { - return { - datasetKey: key, - foo: 5 - }; - }; - plot.addDataset(dataset); - plot.renderTo(svg); - assert.deepEqual(dataset._getExtent(a, id), [16, 17], "plot metadata is reflected in extent results"); - dataset.metadata({ foo: 0 }); - assert.deepEqual(dataset._getExtent(a, id), [5, 6], "plot metadata is reflected in extent results after change user metadata"); - svg.remove(); - }); it("each plot passes metadata to projectors", function () { - var svg = generateSVG(400, 400); + var svg = TestMethods.generateSVG(400, 400); var metadata = { foo: 11 }; var dataset1 = new Plottable.Dataset(data1, metadata); var dataset2 = new Plottable.Dataset(data2, metadata); - var checkPlot = function (plot) { - plot.addDataset("ds1", dataset1).addDataset("ds2", dataset2).project("x", function (d, i, u, m) { return d.x + u.foo + m.datasetKey.length; }).project("y", function (d, i, u, m) { return d.y + u.foo - m.datasetKey.length; }); + var checkXYPlot = function (plot) { + var xAccessor = function (d, i, dataset, m) { + return d.x + dataset.metadata().foo + m.datasetKey.length; + }; + var yAccessor = function (d, i, dataset, m) { + return d.y + dataset.metadata().foo - m.datasetKey.length; + }; + plot.addDataset(dataset1).addDataset(dataset2); + plot.x(xAccessor, xScale).y(yAccessor, yScale); // This should not crash. If some metadata is not passed, undefined property error will be raised during accessor call. plot.renderTo(svg); - plot.remove(); + plot.destroy(); }; - checkPlot(new Plottable.Plot.Area(xScale, yScale)); - checkPlot(new Plottable.Plot.StackedArea(xScale, yScale)); - checkPlot(new Plottable.Plot.Bar(xScale, yScale)); - checkPlot(new Plottable.Plot.StackedBar(xScale, yScale)); - checkPlot(new Plottable.Plot.StackedBar(yScale, xScale, false)); - checkPlot(new Plottable.Plot.ClusteredBar(xScale, yScale)); - checkPlot(new Plottable.Plot.Pie().project("value", "x")); - checkPlot(new Plottable.Plot.Bar(xScale, yScale, false)); - checkPlot(new Plottable.Plot.Scatter(xScale, yScale)); + var checkPiePlot = function (plot) { + plot.sectorValue(function (d) { return d.x; }).addDataset(dataset1); + // This should not crash. If some metadata is not passed, undefined property error will be raised during accessor call. + plot.renderTo(svg); + plot.destroy(); + }; + checkXYPlot(new Plottable.Plots.Area(xScale, yScale)); + checkXYPlot(new Plottable.Plots.StackedArea(xScale, yScale)); + checkXYPlot(new Plottable.Plots.Bar(xScale, yScale)); + checkXYPlot(new Plottable.Plots.StackedBar(xScale, yScale)); + checkXYPlot(new Plottable.Plots.StackedBar(yScale, xScale, false)); + checkXYPlot(new Plottable.Plots.ClusteredBar(xScale, yScale)); + checkXYPlot(new Plottable.Plots.Bar(xScale, yScale, false)); + checkXYPlot(new Plottable.Plots.Scatter(xScale, yScale)); + checkPiePlot(new Plottable.Plots.Pie()); svg.remove(); }); }); /// var assert = chai.assert; -describe("ComponentContainer", function () { - it("_addComponent()", function () { - var container = new Plottable.Component.AbstractComponentContainer(); - var c1 = new Plottable.Component.AbstractComponent(); - var c2 = new Plottable.Component.AbstractComponent(); - var c3 = new Plottable.Component.AbstractComponent(); - assert.isTrue(container._addComponent(c1), "returns true on successful adding"); - assert.deepEqual(container.components(), [c1], "component was added"); - container._addComponent(c2); - assert.deepEqual(container.components(), [c1, c2], "can append components"); - container._addComponent(c3, true); - assert.deepEqual(container.components(), [c3, c1, c2], "can prepend components"); - assert.isFalse(container._addComponent(null), "returns false for null arguments"); - assert.deepEqual(container.components(), [c3, c1, c2], "component list was unchanged"); - assert.isFalse(container._addComponent(c1), "returns false if adding an already-added component"); - assert.deepEqual(container.components(), [c3, c1, c2], "component list was unchanged"); - }); - it("_removeComponent()", function () { - var container = new Plottable.Component.AbstractComponentContainer(); - var c1 = new Plottable.Component.AbstractComponent(); - var c2 = new Plottable.Component.AbstractComponent(); - container._addComponent(c1); - container._addComponent(c2); - container._removeComponent(c2); - assert.deepEqual(container.components(), [c1], "component 2 was removed"); - container._removeComponent(c2); - assert.deepEqual(container.components(), [c1], "there are no side effects from removing already-removed components"); - }); - it("empty()", function () { - var container = new Plottable.Component.AbstractComponentContainer(); - assert.isTrue(container.empty()); - var c1 = new Plottable.Component.AbstractComponent(); - container._addComponent(c1); - assert.isFalse(container.empty()); - }); - it("detachAll()", function () { - var container = new Plottable.Component.AbstractComponentContainer(); - var c1 = new Plottable.Component.AbstractComponent(); - var c2 = new Plottable.Component.AbstractComponent(); - container._addComponent(c1); - container._addComponent(c2); - container.detachAll(); - assert.deepEqual(container.components(), [], "container was cleared of components"); +describe("RenderController", function () { + // HACKHACK: #2083 + it.skip("Components whose render() is triggered by another Component's render() will be drawn", function () { + var link1 = new Plottable.Component(); + var svg1 = TestMethods.generateSVG(); + link1.anchor(svg1).computeLayout(); + var link2 = new Plottable.Component(); + var svg2 = TestMethods.generateSVG(); + link2.anchor(svg2).computeLayout(); + link1.renderImmediately = function () { return link2.render(); }; + var link2Rendered = false; + link2.renderImmediately = function () { return link2Rendered = true; }; + link1.render(); + assert.isTrue(link2Rendered, "dependent Component was render()-ed"); + svg1.remove(); + svg2.remove(); }); }); /// var assert = chai.assert; describe("ComponentGroups", function () { - it("components in componentGroups overlap", function () { - var c1 = makeFixedSizeComponent(10, 10); - var c2 = new Plottable.Component.AbstractComponent(); - var c3 = new Plottable.Component.AbstractComponent(); - var cg = new Plottable.Component.Group([c1, c2, c3]); - var svg = generateSVG(400, 400); - cg._anchor(svg); - c1._addBox("test-box1"); - c2._addBox("test-box2"); - c3._addBox("test-box3"); - cg._computeLayout()._render(); - var t1 = svg.select(".test-box1"); - var t2 = svg.select(".test-box2"); - var t3 = svg.select(".test-box3"); - assertWidthHeight(t1, 10, 10, "rect1 sized correctly"); - assertWidthHeight(t2, 400, 400, "rect2 sized correctly"); - assertWidthHeight(t3, 400, 400, "rect3 sized correctly"); + it("append()", function () { + var componentGroup = new Plottable.Components.Group(); + var c1 = new Plottable.Component(); + componentGroup.append(c1); + assert.deepEqual(componentGroup.components(), [c1], "Component 1 was added to the Group"); + var c2 = new Plottable.Component(); + componentGroup.append(c2); + assert.deepEqual(componentGroup.components(), [c1, c2], "appended Component 2 to the Group"); + componentGroup.append(c1); + assert.deepEqual(componentGroup.components(), [c1, c2], "adding an already-added Component does nothing"); + var svg = TestMethods.generateSVG(); + componentGroup.renderTo(svg); + var c3 = new Plottable.Component(); + componentGroup.append(c3); + assert.deepEqual(componentGroup.components(), [c1, c2, c3], "Components can be append()-ed after rendering"); + svg.remove(); + }); + it("can add null to a Group without failing", function () { + var cg1 = new Plottable.Components.Group(); + var c = new Plottable.Component; + cg1.append(c); + assert.strictEqual(cg1.components().length, 1, "there should first be 1 element in the group"); + assert.doesNotThrow(function () { return cg1.append(null); }); + assert.strictEqual(cg1.components().length, 1, "adding null to a group should have no effect on the group"); + }); + it("append()-ing a Component to the Group should detach() it from its current location", function () { + var c1 = new Plottable.Component; + var svg = TestMethods.generateSVG(); + c1.renderTo(svg); + var group = new Plottable.Components.Group(); + group.append(c1); + assert.isFalse(svg.node().hasChildNodes(), "Component was detach()-ed"); + svg.remove(); + }); + it("remove()", function () { + var c0 = new Plottable.Component(); + var c1 = new Plottable.Component(); + var c2 = new Plottable.Component(); + var componentGroup = new Plottable.Components.Group([c0, c1, c2]); + componentGroup.remove(c1); + assert.deepEqual(componentGroup.components(), [c0, c2], "removing a Component respects the order of the remaining Components"); + var svg = TestMethods.generateSVG(); + c1.renderTo(svg); + componentGroup.remove(c1); + assert.deepEqual(componentGroup.components(), [c0, c2], "removing a Component not in the Group does not remove Components from the Group"); + assert.strictEqual(svg.node().childNodes[0], c1._element.node(), "The Component not in the Group stayed put"); + svg.remove(); + }); + it("detach()-ing a Component that is in the Group removes it from the Group", function () { + var c0 = new Plottable.Component(); + var componentGroup = new Plottable.Components.Group([c0]); + var svg = TestMethods.generateSVG(); + componentGroup.renderTo(svg); + c0.detach(); + assert.lengthOf(componentGroup.components(), 0, "Component is no longer in the Group"); + assert.isNull(c0.parent(), "Component disconnected from Group"); svg.remove(); }); - it("components can be added before and after anchoring", function () { - var c1 = makeFixedSizeComponent(10, 10); - var c2 = makeFixedSizeComponent(20, 20); - var c3 = new Plottable.Component.AbstractComponent(); - var cg = new Plottable.Component.Group([c1]); - var svg = generateSVG(400, 400); - cg.below(c2)._anchor(svg); + it("can move components to other groups after anchoring", function () { + var svg = TestMethods.generateSVG(); + var cg1 = new Plottable.Components.Group(); + var cg2 = new Plottable.Components.Group(); + var c = new Plottable.Component(); + cg1.append(c); + cg1.renderTo(svg); + cg2.renderTo(svg); + assert.strictEqual(cg2.components().length, 0, "second group should have no component before movement"); + assert.strictEqual(cg1.components().length, 1, "first group should have 1 component before movement"); + assert.strictEqual(c.parent(), cg1, "component's parent before moving should be the group 1"); + assert.doesNotThrow(function () { return cg2.append(c); }, Error, "should be able to move components between groups after anchoring"); + assert.strictEqual(cg2.components().length, 1, "second group should have 1 component after movement"); + assert.strictEqual(cg1.components().length, 0, "first group should have no components after movement"); + assert.strictEqual(c.parent(), cg2, "component's parent after movement should be the group 2"); + svg.remove(); + }); + it("has()", function () { + var c0 = new Plottable.Component(); + var componentGroup = new Plottable.Components.Group([c0]); + assert.isTrue(componentGroup.has(c0), "correctly checks that Component is in the Group"); + componentGroup.remove(c0); + assert.isFalse(componentGroup.has(c0), "correctly checks that Component is no longer in the Group"); + componentGroup.append(c0); + assert.isTrue(componentGroup.has(c0), "correctly checks that Component is in the Group again"); + }); + it("components in componentGroups overlap", function () { + var c1 = TestMethods.makeFixedSizeComponent(10, 10); + var c2 = new Plottable.Component(); + var c3 = new Plottable.Component(); + var cg = new Plottable.Components.Group([c1, c2, c3]); + var svg = TestMethods.generateSVG(400, 400); + cg.anchor(svg); c1._addBox("test-box1"); c2._addBox("test-box2"); - cg._computeLayout()._render(); + c3._addBox("test-box3"); + cg.computeLayout().render(); var t1 = svg.select(".test-box1"); var t2 = svg.select(".test-box2"); - assertWidthHeight(t1, 10, 10, "rect1 sized correctly"); - assertWidthHeight(t2, 20, 20, "rect2 sized correctly"); - cg.below(c3); - c3._addBox("test-box3"); - cg._computeLayout()._render(); var t3 = svg.select(".test-box3"); - assertWidthHeight(t3, 400, 400, "rect3 sized correctly"); + TestMethods.assertWidthHeight(t1, 10, 10, "rect1 sized correctly"); + TestMethods.assertWidthHeight(t2, 400, 400, "rect2 sized correctly"); + TestMethods.assertWidthHeight(t3, 400, 400, "rect3 sized correctly"); svg.remove(); }); - it("componentGroup subcomponents have xOffset, yOffset of 0", function () { - var cg = new Plottable.Component.Group(); - var c1 = new Plottable.Component.AbstractComponent(); - var c2 = new Plottable.Component.AbstractComponent(); - cg.below(c1).below(c2); - var svg = generateSVG(); - cg._anchor(svg); - cg._computeLayout(50, 50, 350, 350); - var cgTranslate = d3.transform(cg._element.attr("transform")).translate; - var c1Translate = d3.transform(c1._element.attr("transform")).translate; - var c2Translate = d3.transform(c2._element.attr("transform")).translate; - assert.equal(cgTranslate[0], 50, "componentGroup has 50 xOffset"); - assert.equal(cgTranslate[1], 50, "componentGroup has 50 yOffset"); - assert.equal(c1Translate[0], 0, "componentGroup has 0 xOffset"); - assert.equal(c1Translate[1], 0, "componentGroup has 0 yOffset"); - assert.equal(c2Translate[0], 0, "componentGroup has 0 xOffset"); - assert.equal(c2Translate[1], 0, "componentGroup has 0 yOffset"); - svg.remove(); - }); - it("detach() and _removeComponent work correctly for componentGroup", function () { - var c1 = new Plottable.Component.AbstractComponent().classed("component-1", true); - var c2 = new Plottable.Component.AbstractComponent().classed("component-2", true); - var cg = new Plottable.Component.Group([c1, c2]); - var svg = generateSVG(200, 200); + it("detach()", function () { + var c1 = new Plottable.Component().classed("component-1", true); + var c2 = new Plottable.Component().classed("component-2", true); + var cg = new Plottable.Components.Group([c1, c2]); + var svg = TestMethods.generateSVG(200, 200); cg.renderTo(svg); var c1Node = svg.select(".component-1").node(); var c2Node = svg.select(".component-2").node(); @@ -6238,178 +6023,50 @@ describe("ComponentGroups", function () { assert.isNotNull(c1Node, "componet 1 was also added back to the DOM"); svg.remove(); }); - it("detachAll() works as expected", function () { - var cg = new Plottable.Component.Group(); - var c1 = new Plottable.Component.AbstractComponent(); - var c2 = new Plottable.Component.AbstractComponent(); - var c3 = new Plottable.Component.AbstractComponent(); - assert.isTrue(cg.empty(), "cg initially empty"); - cg.below(c1).below(c2).below(c3); - assert.isFalse(cg.empty(), "cg not empty after merging components"); - cg.detachAll(); - assert.isTrue(cg.empty(), "cg empty after detachAll()"); - assert.isFalse(c1._isAnchored, "c1 was detached"); - assert.isFalse(c2._isAnchored, "c2 was detached"); - assert.isFalse(c3._isAnchored, "c3 was detached"); - assert.lengthOf(cg.components(), 0, "cg has no components"); - }); describe("requests space based on contents, but occupies total offered space", function () { var SVG_WIDTH = 400; var SVG_HEIGHT = 400; it("with no Components", function () { - var svg = generateSVG(); - var cg = new Plottable.Component.Group([]); - var request = cg._requestedSpace(SVG_WIDTH, SVG_HEIGHT); - verifySpaceRequest(request, 0, 0, false, false, "empty Group doesn't request any space"); + var svg = TestMethods.generateSVG(); + var cg = new Plottable.Components.Group([]); + var request = cg.requestedSpace(SVG_WIDTH, SVG_HEIGHT); + TestMethods.verifySpaceRequest(request, 0, 0, "empty Group doesn't request any space"); cg.renderTo(svg); assert.strictEqual(cg.width(), SVG_WIDTH, "occupies all offered width"); assert.strictEqual(cg.height(), SVG_HEIGHT, "occupies all offered height"); svg.remove(); }); it("with a non-fixed-size Component", function () { - var svg = generateSVG(); - var c1 = new Plottable.Component.AbstractComponent(); - var c2 = new Plottable.Component.AbstractComponent(); - var cg = new Plottable.Component.Group([c1, c2]); - var groupRequest = cg._requestedSpace(SVG_WIDTH, SVG_HEIGHT); - var c1Request = c1._requestedSpace(SVG_WIDTH, SVG_HEIGHT); + var svg = TestMethods.generateSVG(); + var c1 = new Plottable.Component(); + var c2 = new Plottable.Component(); + var cg = new Plottable.Components.Group([c1, c2]); + var groupRequest = cg.requestedSpace(SVG_WIDTH, SVG_HEIGHT); + var c1Request = c1.requestedSpace(SVG_WIDTH, SVG_HEIGHT); assert.deepEqual(groupRequest, c1Request, "request reflects request of sub-component"); - assert.isFalse(cg._isFixedWidth(), "width is not fixed if subcomponents are not fixed width"); - assert.isFalse(cg._isFixedHeight(), "height is not fixed if subcomponents are not fixed height"); + assert.isFalse(cg.fixedWidth(), "width is not fixed if subcomponents are not fixed width"); + assert.isFalse(cg.fixedHeight(), "height is not fixed if subcomponents are not fixed height"); cg.renderTo(svg); assert.strictEqual(cg.width(), SVG_WIDTH, "occupies all offered width"); assert.strictEqual(cg.height(), SVG_HEIGHT, "occupies all offered height"); svg.remove(); }); it("with fixed-size Components", function () { - var svg = generateSVG(); + var svg = TestMethods.generateSVG(); var tall = new Mocks.FixedSizeComponent(SVG_WIDTH / 4, SVG_WIDTH / 2); var wide = new Mocks.FixedSizeComponent(SVG_WIDTH / 2, SVG_WIDTH / 4); - var cg = new Plottable.Component.Group([tall, wide]); - var request = cg._requestedSpace(SVG_WIDTH, SVG_HEIGHT); - assert.strictEqual(request.width, SVG_WIDTH / 2, "requested enough space for widest Component"); - assert.isFalse(request.wantsWidth, "does not request more width if enough was supplied for widest Component"); - assert.strictEqual(request.height, SVG_HEIGHT / 2, "requested enough space for tallest Component"); - assert.isFalse(request.wantsHeight, "does not request more height if enough was supplied for tallest Component"); - var constrainedRequest = cg._requestedSpace(SVG_WIDTH / 10, SVG_HEIGHT / 10); - assert.strictEqual(constrainedRequest.width, SVG_WIDTH / 2, "requested enough space for widest Component"); - assert.isTrue(constrainedRequest.wantsWidth, "requests more width if not enough was supplied for widest Component"); - assert.strictEqual(constrainedRequest.height, SVG_HEIGHT / 2, "requested enough space for tallest Component"); - assert.isTrue(constrainedRequest.wantsHeight, "requests more height if not enough was supplied for tallest Component"); + var cg = new Plottable.Components.Group([tall, wide]); + var request = cg.requestedSpace(SVG_WIDTH, SVG_HEIGHT); + assert.strictEqual(request.minWidth, SVG_WIDTH / 2, "requested enough space for widest Component"); + assert.strictEqual(request.minHeight, SVG_HEIGHT / 2, "requested enough space for tallest Component"); + var constrainedRequest = cg.requestedSpace(SVG_WIDTH / 10, SVG_HEIGHT / 10); + assert.strictEqual(constrainedRequest.minWidth, SVG_WIDTH / 2, "requested enough space for widest Component"); + assert.strictEqual(constrainedRequest.minHeight, SVG_HEIGHT / 2, "requested enough space for tallest Component"); cg.renderTo(svg); assert.strictEqual(cg.width(), SVG_WIDTH, "occupies all offered width"); assert.strictEqual(cg.height(), SVG_HEIGHT, "occupies all offered height"); svg.remove(); }); - it("can move components to other groups after anchoring", function () { - var svg = generateSVG(); - var cg1 = new Plottable.Component.AbstractComponentContainer(); - var cg2 = new Plottable.Component.AbstractComponentContainer(); - var c = new Plottable.Component.AbstractComponent(); - cg1._addComponent(c); - cg1.renderTo(svg); - cg2.renderTo(svg); - assert.strictEqual(cg2.components().length, 0, "second group should have no component before movement"); - assert.strictEqual(cg1.components().length, 1, "first group should have 1 component before movement"); - assert.strictEqual(c._parent(), cg1, "component's parent before moving should be the group 1"); - assert.doesNotThrow(function () { return cg2._addComponent(c); }, Error, "should be able to move components between groups after anchoring"); - assert.strictEqual(cg2.components().length, 1, "second group should have 1 component after movement"); - assert.strictEqual(cg1.components().length, 0, "first group should have no components after movement"); - assert.strictEqual(c._parent(), cg2, "component's parent after movement should be the group 2"); - svg.remove(); - }); - it("can add null to a component without failing", function () { - var cg1 = new Plottable.Component.AbstractComponentContainer(); - var c = new Plottable.Component.AbstractComponent; - cg1._addComponent(c); - assert.strictEqual(cg1.components().length, 1, "there should first be 1 element in the group"); - assert.doesNotThrow(function () { return cg1._addComponent(null); }); - assert.strictEqual(cg1.components().length, 1, "adding null to a group should have no effect on the group"); - }); - }); - describe("Merging components works as expected", function () { - var c1 = new Plottable.Component.AbstractComponent(); - var c2 = new Plottable.Component.AbstractComponent(); - var c3 = new Plottable.Component.AbstractComponent(); - var c4 = new Plottable.Component.AbstractComponent(); - describe("above()", function () { - it("Component.above works as expected (Component.above Component)", function () { - var cg = c2.above(c1); - var innerComponents = cg.components(); - assert.lengthOf(innerComponents, 2, "There are two components"); - assert.equal(innerComponents[0], c1, "first component correct"); - assert.equal(innerComponents[1], c2, "second component correct"); - }); - it("Component.above works as expected (Component.above ComponentGroup)", function () { - var cg = new Plottable.Component.Group([c1, c2, c3]); - var cg2 = c4.above(cg); - assert.equal(cg, cg2, "c4.above(cg) returns cg"); - var components = cg.components(); - assert.lengthOf(components, 4, "four components"); - assert.equal(components[2], c3, "third component in third"); - assert.equal(components[3], c4, "fourth component is last"); - }); - it("Component.above works as expected (ComponentGroup.above Component)", function () { - var cg = new Plottable.Component.Group([c2, c3, c4]); - var cg2 = cg.above(c1); - assert.equal(cg, cg2, "cg.merge(c1) returns cg"); - var components = cg.components(); - assert.lengthOf(components, 4, "there are four components"); - assert.equal(components[0], c1, "first is first"); - assert.equal(components[3], c4, "fourth is fourth"); - }); - it("Component.above works as expected (ComponentGroup.above ComponentGroup)", function () { - var cg1 = new Plottable.Component.Group([c1, c2]); - var cg2 = new Plottable.Component.Group([c3, c4]); - var cg = cg1.above(cg2); - assert.equal(cg, cg1, "merged == cg1"); - assert.notEqual(cg, cg2, "merged != cg2"); - var components = cg.components(); - assert.lengthOf(components, 3, "there are three inner components"); - assert.equal(components[0], cg2, "componentGroup2 inside componentGroup1"); - assert.equal(components[1], c1, "components are inside"); - assert.equal(components[2], c2, "components are inside"); - }); - }); - describe("below()", function () { - it("Component.below works as expected (Component.below Component)", function () { - var cg = c1.below(c2); - var innerComponents = cg.components(); - assert.lengthOf(innerComponents, 2, "There are two components"); - assert.equal(innerComponents[0], c1, "first component correct"); - assert.equal(innerComponents[1], c2, "second component correct"); - }); - it("Component.below works as expected (Component.below ComponentGroup)", function () { - var cg = new Plottable.Component.Group([c2, c3, c4]); - var cg2 = c1.below(cg); - assert.equal(cg, cg2, "c1.below(cg) returns cg"); - var components = cg.components(); - assert.lengthOf(components, 4, "four components"); - assert.equal(components[0], c1, "first component in front"); - assert.equal(components[1], c2, "second component is second"); - }); - it("Component.below works as expected (ComponentGroup.below Component)", function () { - var cg = new Plottable.Component.Group([c1, c2, c3]); - var cg2 = cg.below(c4); - assert.equal(cg, cg2, "cg.merge(c4) returns cg"); - var components = cg.components(); - assert.lengthOf(components, 4, "there are four components"); - assert.equal(components[0], c1, "first is first"); - assert.equal(components[3], c4, "fourth is fourth"); - }); - it("Component.below works as expected (ComponentGroup.below ComponentGroup)", function () { - var cg1 = new Plottable.Component.Group([c1, c2]); - var cg2 = new Plottable.Component.Group([c3, c4]); - var cg = cg1.below(cg2); - assert.equal(cg, cg1, "merged group == cg1"); - assert.notEqual(cg, cg2, "merged group != cg2"); - var components = cg.components(); - assert.lengthOf(components, 3, "there are three inner components"); - assert.equal(components[0], c1, "components are inside"); - assert.equal(components[1], c2, "components are inside"); - assert.equal(components[2], cg2, "componentGroup2 inside componentGroup1"); - }); - }); }); }); @@ -6420,8 +6077,8 @@ function assertComponentXY(component, x, y, message) { var translate = d3.transform(component._element.attr("transform")).translate; var xActual = translate[0]; var yActual = translate[1]; - assert.equal(xActual, x, "X: " + message); - assert.equal(yActual, y, "Y: " + message); + assert.strictEqual(xActual, x, "X: " + message); + assert.strictEqual(yActual, y, "Y: " + message); } describe("Component behavior", function () { var svg; @@ -6429,40 +6086,130 @@ describe("Component behavior", function () { var SVG_WIDTH = 400; var SVG_HEIGHT = 300; beforeEach(function () { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - c = new Plottable.Component.AbstractComponent(); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + c = new Plottable.Component(); }); - describe("anchor", function () { - it("anchoring works as expected", function () { - c._anchor(svg); - assert.equal(c._element.node(), svg.select("g").node(), "the component anchored to a beneath the "); + describe("anchor()", function () { + it("anchor()-ing works as expected", function () { + c.anchor(svg); + assert.strictEqual(c._element.node(), svg.select("g").node(), "the component anchored to a beneath the "); assert.isTrue(svg.classed("plottable"), " was given \"plottable\" CSS class"); svg.remove(); }); - it("can re-anchor to a different element", function () { - c._anchor(svg); - var svg2 = generateSVG(SVG_WIDTH, SVG_HEIGHT); - c._anchor(svg2); - assert.equal(c._element.node(), svg2.select("g").node(), "the component re-achored under the second "); + it("can re-anchor() to a different element", function () { + c.anchor(svg); + var svg2 = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + c.anchor(svg2); + assert.strictEqual(c._element.node(), svg2.select("g").node(), "the component re-achored under the second "); assert.isTrue(svg2.classed("plottable"), "second was given \"plottable\" CSS class"); svg.remove(); svg2.remove(); }); + describe("anchor() callbacks", function () { + it("callbacks called on anchor()-ing", function () { + var callbackCalled = false; + var passedComponent; + var callback = function (component) { + callbackCalled = true; + passedComponent = component; + }; + c.onAnchor(callback); + c.anchor(svg); + assert.isTrue(callbackCalled, "callback was called on anchor()-ing"); + assert.strictEqual(passedComponent, c, "callback was passed the Component that anchor()-ed"); + var svg2 = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + callbackCalled = false; + c.anchor(svg2); + assert.isTrue(callbackCalled, "callback was called on anchor()-ing to a new "); + assert.strictEqual(passedComponent, c, "callback was passed the Component that anchor()-ed"); + svg.remove(); + svg2.remove(); + }); + it("callbacks called immediately if already anchor()-ed", function () { + var callbackCalled = false; + var passedComponent; + var callback = function (component) { + callbackCalled = true; + passedComponent = component; + }; + c.anchor(svg); + c.onAnchor(callback); + assert.isTrue(callbackCalled, "callback was immediately if Component was already anchor()-ed"); + assert.strictEqual(passedComponent, c, "callback was passed the Component that anchor()-ed"); + svg.remove(); + }); + it("removing callbacks", function () { + var callbackCalled = false; + var callback = function (component) { + callbackCalled = true; + }; + c.onAnchor(callback); + c.offAnchor(callback); + c.anchor(svg); + assert.isFalse(callbackCalled, "removed callback is not called"); + svg.remove(); + }); + }); + }); + describe("detach()", function () { + it("detach() works as expected", function () { + var c1 = new Plottable.Component(); + c1.renderTo(svg); + assert.isTrue(svg.node().hasChildNodes(), "the svg has children"); + c1.detach(); + assert.isFalse(svg.node().hasChildNodes(), "the svg has no children"); + svg.remove(); + }); + it("components can be detach()-ed even if not anchor()-ed", function () { + var c = new Plottable.Component(); + c.detach(); // no error thrown + svg.remove(); + }); + it("callbacks called on detach()-ing", function () { + c = new Plottable.Component(); + c.renderTo(svg); + var callbackCalled = false; + var passedComponent; + var callback = function (component) { + callbackCalled = true; + passedComponent = component; + }; + c.onDetach(callback); + c.detach(); + assert.isTrue(callbackCalled, "callback was called when the Component was detach()-ed"); + assert.strictEqual(passedComponent, c, "callback was passed the Component that detach()-ed"); + svg.remove(); + }); + }); + it("parent()", function () { + var c = new Plottable.Component(); + var acceptingContainer = { + has: function (component) { return true; } + }; + c.parent(acceptingContainer); + assert.strictEqual(c.parent(), acceptingContainer, "Component's parent was set if the Component is contained in the parent"); + var rejectingContainer = { + has: function (component) { return false; } + }; + assert.throws(function () { return c.parent(rejectingContainer); }, Error, "invalid parent"); + svg.remove(); }); describe("computeLayout", function () { it("computeLayout defaults and updates intelligently", function () { - c._anchor(svg); - c._computeLayout(); - assert.equal(c.width(), SVG_WIDTH, "computeLayout defaulted width to svg width"); - assert.equal(c.height(), SVG_HEIGHT, "computeLayout defaulted height to svg height"); - assert.equal(c._xOrigin, 0, "xOrigin defaulted to 0"); - assert.equal(c._yOrigin, 0, "yOrigin defaulted to 0"); + c.anchor(svg); + c.computeLayout(); + assert.strictEqual(c.width(), SVG_WIDTH, "computeLayout defaulted width to svg width"); + assert.strictEqual(c.height(), SVG_HEIGHT, "computeLayout defaulted height to svg height"); + var origin = c.origin(); + assert.strictEqual(origin.x, 0, "xOrigin defaulted to 0"); + assert.strictEqual(origin.y, 0, "yOrigin defaulted to 0"); svg.attr("width", 2 * SVG_WIDTH).attr("height", 2 * SVG_HEIGHT); - c._computeLayout(); - assert.equal(c.width(), 2 * SVG_WIDTH, "computeLayout updated width to new svg width"); - assert.equal(c.height(), 2 * SVG_HEIGHT, "computeLayout updated height to new svg height"); - assert.equal(c._xOrigin, 0, "xOrigin is still 0"); - assert.equal(c._yOrigin, 0, "yOrigin is still 0"); + c.computeLayout(); + assert.strictEqual(c.width(), 2 * SVG_WIDTH, "computeLayout updated width to new svg width"); + assert.strictEqual(c.height(), 2 * SVG_HEIGHT, "computeLayout updated height to new svg height"); + origin = c.origin(); + assert.strictEqual(origin.x, 0, "xOrigin is still 0"); + assert.strictEqual(origin.y, 0, "yOrigin is still 0"); svg.remove(); }); it("computeLayout works with CSS layouts", function () { @@ -6472,24 +6219,27 @@ describe("Component behavior", function () { parent.style("height", "200px"); // Remove width/height attributes and style with CSS svg.attr("width", null).attr("height", null); - c._anchor(svg); - c._computeLayout(); - assert.equal(c.width(), 400, "defaults to width of parent if width is not specified on "); - assert.equal(c.height(), 200, "defaults to height of parent if width is not specified on "); - assert.equal(c._xOrigin, 0, "xOrigin defaulted to 0"); - assert.equal(c._yOrigin, 0, "yOrigin defaulted to 0"); + c.anchor(svg); + c.computeLayout(); + assert.strictEqual(c.width(), 400, "defaults to width of parent if width is not specified on "); + assert.strictEqual(c.height(), 200, "defaults to height of parent if width is not specified on "); + var origin = c.origin(); + assert.strictEqual(origin.x, 0, "xOrigin defaulted to 0"); + assert.strictEqual(origin.y, 0, "yOrigin defaulted to 0"); svg.style("width", "50%").style("height", "50%"); - c._computeLayout(); - assert.equal(c.width(), 200, "computeLayout defaulted width to svg width"); - assert.equal(c.height(), 100, "computeLayout defaulted height to svg height"); - assert.equal(c._xOrigin, 0, "xOrigin defaulted to 0"); - assert.equal(c._yOrigin, 0, "yOrigin defaulted to 0"); + c.computeLayout(); + assert.strictEqual(c.width(), 200, "computeLayout defaulted width to svg width"); + assert.strictEqual(c.height(), 100, "computeLayout defaulted height to svg height"); + origin = c.origin(); + assert.strictEqual(origin.x, 0, "xOrigin defaulted to 0"); + assert.strictEqual(origin.y, 0, "yOrigin defaulted to 0"); svg.style("width", "25%").style("height", "25%"); - c._computeLayout(); - assert.equal(c.width(), 100, "computeLayout updated width to new svg width"); - assert.equal(c.height(), 50, "computeLayout updated height to new svg height"); - assert.equal(c._xOrigin, 0, "xOrigin is still 0"); - assert.equal(c._yOrigin, 0, "yOrigin is still 0"); + c.computeLayout(); + assert.strictEqual(c.width(), 100, "computeLayout updated width to new svg width"); + assert.strictEqual(c.height(), 50, "computeLayout updated height to new svg height"); + origin = c.origin(); + assert.strictEqual(origin.x, 0, "xOrigin is still 0"); + assert.strictEqual(origin.y, 0, "yOrigin is still 0"); // reset test page DOM parent.style("width", "auto"); parent.style("height", "auto"); @@ -6497,26 +6247,27 @@ describe("Component behavior", function () { }); it("computeLayout will not default when attached to non-root node", function () { var g = svg.append("g"); - c._anchor(g); - assert.throws(function () { return c._computeLayout(); }, "null arguments"); + c.anchor(g); + assert.throws(function () { return c.computeLayout(); }, "null arguments"); svg.remove(); }); it("computeLayout throws an error when called on un-anchored component", function () { - assert.throws(function () { return c._computeLayout(); }, Error, "anchor must be called before computeLayout"); + assert.throws(function () { return c.computeLayout(); }, Error); svg.remove(); }); it("computeLayout uses its arguments apropriately", function () { - var g = svg.append("g"); - var xOff = 10; - var yOff = 20; + var origin = { + x: 10, + y: 20 + }; var width = 100; var height = 200; - c._anchor(svg); - c._computeLayout(xOff, yOff, width, height); - var translate = getTranslate(c._element); - assert.deepEqual(translate, [xOff, yOff], "the element translated appropriately"); - assert.equal(c.width(), width, "the width set properly"); - assert.equal(c.height(), height, "the height set propery"); + c.anchor(svg); + c.computeLayout(origin, width, height); + var translate = TestMethods.getTranslate(c._element); + assert.deepEqual(translate, [origin.x, origin.y], "the element translated appropriately"); + assert.strictEqual(c.width(), width, "the width set properly"); + assert.strictEqual(c.height(), height, "the height set propery"); svg.remove(); }); }); @@ -6533,77 +6284,28 @@ describe("Component behavior", function () { assert.isTrue(g3.classed("box-container"), "the fourth g is a box container"); svg.remove(); }); + it("component defaults are as expected", function () { + assert.strictEqual(c.xAlignment(), "left", "x alignment defaults to \"left\""); + assert.strictEqual(c.yAlignment(), "top", "y alignment defaults to \"top\""); + var layout = c.requestedSpace(1, 1); + assert.strictEqual(layout.minWidth, 0, "requested minWidth defaults to 0"); + assert.strictEqual(layout.minHeight, 0, "requested minHeight defaults to 0"); + svg.remove(); + }); it("fixed-width component will align to the right spot", function () { - fixComponentSize(c, 100, 100); - c._anchor(svg); - c._computeLayout(); + TestMethods.fixComponentSize(c, 100, 100); + c.anchor(svg); + c.xAlignment("left").yAlignment("top"); + c.computeLayout(); assertComponentXY(c, 0, 0, "top-left component aligns correctly"); - c.xAlign("CENTER").yAlign("CENTER"); - c._computeLayout(); + c.xAlignment("center").yAlignment("center"); + c.computeLayout(); assertComponentXY(c, 150, 100, "center component aligns correctly"); - c.xAlign("RIGHT").yAlign("BOTTOM"); - c._computeLayout(); + c.xAlignment("right").yAlignment("bottom"); + c.computeLayout(); assertComponentXY(c, 300, 200, "bottom-right component aligns correctly"); svg.remove(); }); - it("components can be offset relative to their alignment, and throw errors if there is insufficient space", function () { - fixComponentSize(c, 100, 100); - c._anchor(svg); - c.xOffset(20).yOffset(20); - c._computeLayout(); - assertComponentXY(c, 20, 20, "top-left component offsets correctly"); - c.xAlign("CENTER").yAlign("CENTER"); - c._computeLayout(); - assertComponentXY(c, 170, 120, "center component offsets correctly"); - c.xAlign("RIGHT").yAlign("BOTTOM"); - c._computeLayout(); - assertComponentXY(c, 320, 220, "bottom-right component offsets correctly"); - c.xOffset(0).yOffset(0); - c._computeLayout(); - assertComponentXY(c, 300, 200, "bottom-right component offset resets"); - c.xOffset(-20).yOffset(-30); - c._computeLayout(); - assertComponentXY(c, 280, 170, "negative offsets work properly"); - svg.remove(); - }); - it("component defaults are as expected", function () { - var layout = c._requestedSpace(1, 1); - assert.equal(layout.width, 0, "requested width defaults to 0"); - assert.equal(layout.height, 0, "requested height defaults to 0"); - assert.equal(layout.wantsWidth, false, "_requestedSpace().wantsWidth defaults to false"); - assert.equal(layout.wantsHeight, false, "_requestedSpace().wantsHeight defaults to false"); - assert.equal(c._xAlignProportion, 0, "_xAlignProportion defaults to 0"); - assert.equal(c._yAlignProportion, 0, "_yAlignProportion defaults to 0"); - assert.equal(c._xOffset, 0, "xOffset defaults to 0"); - assert.equal(c._yOffset, 0, "yOffset defaults to 0"); - svg.remove(); - }); - it("clipPath works as expected", function () { - assert.isFalse(c.clipPathEnabled, "clipPathEnabled defaults to false"); - c.clipPathEnabled = true; - var expectedClipPathID = c.getID(); - c._anchor(svg); - c._computeLayout(0, 0, 100, 100); - c._render(); - var expectedPrefix = /MSIE [5-9]/.test(navigator.userAgent) ? "" : document.location.href; - expectedPrefix = expectedPrefix.replace(/#.*/g, ""); - var expectedClipPathURL = "url(" + expectedPrefix + "#clipPath" + expectedClipPathID + ")"; - // IE 9 has clipPath like 'url("#clipPath")', must accomodate - var normalizeClipPath = function (s) { return s.replace(/"/g, ""); }; - assert.isTrue(normalizeClipPath(c._element.attr("clip-path")) === expectedClipPathURL, "the element has clip-path url attached"); - var clipRect = c._boxContainer.select(".clip-rect"); - assert.equal(clipRect.attr("width"), 100, "the clipRect has an appropriate width"); - assert.equal(clipRect.attr("height"), 100, "the clipRect has an appropriate height"); - svg.remove(); - }); - it("componentID works as expected", function () { - var expectedID = Plottable.Core.PlottableObject._nextID; - var c1 = new Plottable.Component.AbstractComponent(); - assert.equal(c1.getID(), expectedID, "component id on next component was as expected"); - var c2 = new Plottable.Component.AbstractComponent(); - assert.equal(c2.getID(), expectedID + 1, "future components increment appropriately"); - svg.remove(); - }); it("boxes work as expected", function () { assert.throws(function () { return c._addBox("pre-anchor"); }, Error, "Adding boxes before anchoring is currently disallowed"); c.renderTo(svg); @@ -6614,46 +6316,15 @@ describe("Component behavior", function () { boxStrings.forEach(function (s) { var box = boxContainer.select(s); assert.isNotNull(box.node(), s + " box was created and placed inside boxContainer"); - var bb = Plottable._Util.DOM.getBBox(box); - assert.equal(bb.width, SVG_WIDTH, s + " width as expected"); - assert.equal(bb.height, SVG_HEIGHT, s + " height as expected"); + var bb = Plottable.Utils.DOM.getBBox(box); + assert.strictEqual(bb.width, SVG_WIDTH, s + " width as expected"); + assert.strictEqual(bb.height, SVG_HEIGHT, s + " height as expected"); }); svg.remove(); }); - it("hitboxes are created iff there are registered interactions that require hitboxes", function () { - function verifyHitbox(component) { - var hitBox = component._hitBox; - assert.isNotNull(hitBox, "the hitbox was created"); - var hitBoxFill = hitBox.style("fill"); - var hitBoxFilled = hitBoxFill === "#ffffff" || hitBoxFill === "rgb(255, 255, 255)"; - assert.isTrue(hitBoxFilled, hitBoxFill + " <- this should be filled, so the hitbox will detect events"); - assert.equal(hitBox.style("opacity"), "0", "the hitBox is transparent, otherwise it would look weird"); - } - c._anchor(svg); - assert.isUndefined(c._hitBox, "no hitBox was created when there were no registered interactions"); - svg.remove(); - svg = generateSVG(); - // registration before anchoring - c = new Plottable.Component.AbstractComponent(); - var i = new Plottable.Interaction.AbstractInteraction(); - i._requiresHitbox = function () { return true; }; - c.registerInteraction(i); - c._anchor(svg); - verifyHitbox(c); - svg.remove(); - svg = generateSVG(); - // registration after anchoring - c = new Plottable.Component.AbstractComponent(); - c._anchor(svg); - i = new Plottable.Interaction.AbstractInteraction(); - i._requiresHitbox = function () { return true; }; - c.registerInteraction(i); - verifyHitbox(c); - svg.remove(); - }); it("errors are thrown on bad alignments", function () { - assert.throws(function () { return c.xAlign("foo"); }, Error, "Unsupported alignment"); - assert.throws(function () { return c.yAlign("foo"); }, Error, "Unsupported alignment"); + assert.throws(function () { return c.xAlignment("foo"); }, Error, "Unsupported alignment"); + assert.throws(function () { return c.yAlignment("foo"); }, Error, "Unsupported alignment"); svg.remove(); }); it("css classing works as expected", function () { @@ -6664,7 +6335,7 @@ describe("Component behavior", function () { assert.isTrue(c.classed("CSS-PREANCHOR-REMOVE")); c.classed("CSS-PREANCHOR-REMOVE", false); assert.isFalse(c.classed("CSS-PREANCHOR-REMOVE")); - c._anchor(svg); + c.anchor(svg); assert.isTrue(c.classed("CSS-PREANCHOR-KEEP")); assert.isFalse(c.classed("CSS-PREANCHOR-REMOVE")); assert.isFalse(c.classed("CSS-POSTANCHOR")); @@ -6673,64 +6344,51 @@ describe("Component behavior", function () { c.classed("CSS-POSTANCHOR", false); assert.isFalse(c.classed("CSS-POSTANCHOR")); assert.isFalse(c.classed(undefined), "returns false when classed called w/ undefined"); - assert.equal(c.classed(undefined, true), c, "returns this when classed called w/ undefined and true"); - svg.remove(); - }); - it("detach() works as expected", function () { - var c1 = new Plottable.Component.AbstractComponent(); - c1.renderTo(svg); - assert.isTrue(svg.node().hasChildNodes(), "the svg has children"); - c1.detach(); - assert.isFalse(svg.node().hasChildNodes(), "the svg has no children"); + assert.strictEqual(c.classed(undefined, true), c, "returns this when classed called w/ undefined and true"); svg.remove(); }); - it("can't reuse component if it's been remove()-ed", function () { - var c1 = new Plottable.Component.AbstractComponent(); + it("can't reuse component if it's been destroy()-ed", function () { + var c1 = new Plottable.Component(); c1.renderTo(svg); - c1.remove(); + c1.destroy(); assert.throws(function () { return c1.renderTo(svg); }, "reuse"); svg.remove(); }); - it("_invalidateLayout works as expected", function () { - var cg = new Plottable.Component.Group(); - var c = makeFixedSizeComponent(10, 10); - cg._addComponent(c); + it("redraw() works as expected", function () { + var cg = new Plottable.Components.Group(); + var c = TestMethods.makeFixedSizeComponent(10, 10); + cg.append(c); cg.renderTo(svg); - assert.equal(cg.height(), 300, "height() is the entire available height"); - assert.equal(cg.width(), 400, "width() is the entire available width"); - fixComponentSize(c, 50, 50); - c._invalidateLayout(); - assert.equal(cg.height(), 300, "height() after resizing is the entire available height"); - assert.equal(cg.width(), 400, "width() after resizing is the entire available width"); - svg.remove(); - }); - it("components can be detached even if not anchored", function () { - var c = new Plottable.Component.AbstractComponent(); - c.detach(); // no error thrown + assert.strictEqual(cg.height(), 300, "height() is the entire available height"); + assert.strictEqual(cg.width(), 400, "width() is the entire available width"); + TestMethods.fixComponentSize(c, 50, 50); + c.redraw(); + assert.strictEqual(cg.height(), 300, "height() after resizing is the entire available height"); + assert.strictEqual(cg.width(), 400, "width() after resizing is the entire available width"); svg.remove(); }); it("component remains in own cell", function () { - var horizontalComponent = new Plottable.Component.AbstractComponent(); - var verticalComponent = new Plottable.Component.AbstractComponent(); - var placeHolder = new Plottable.Component.AbstractComponent(); - var t = new Plottable.Component.Table().addComponent(0, 0, verticalComponent).addComponent(0, 1, new Plottable.Component.AbstractComponent()).addComponent(1, 0, placeHolder).addComponent(1, 1, horizontalComponent); + var horizontalComponent = new Plottable.Component(); + var verticalComponent = new Plottable.Component(); + var placeHolder = new Plottable.Component(); + var t = new Plottable.Components.Table().add(verticalComponent, 0, 0).add(new Plottable.Component(), 0, 1).add(placeHolder, 1, 0).add(horizontalComponent, 1, 1); t.renderTo(svg); - horizontalComponent.xAlign("center"); - verticalComponent.yAlign("bottom"); - assertBBoxNonIntersection(verticalComponent._element.select(".bounding-box"), placeHolder._element.select(".bounding-box")); - assertBBoxInclusion(t._boxContainer.select(".bounding-box"), horizontalComponent._element.select(".bounding-box")); + horizontalComponent.xAlignment("center"); + verticalComponent.yAlignment("bottom"); + TestMethods.assertBBoxNonIntersection(verticalComponent._element.select(".bounding-box"), placeHolder._element.select(".bounding-box")); + TestMethods.assertBBoxInclusion(t._boxContainer.select(".bounding-box"), horizontalComponent._element.select(".bounding-box")); svg.remove(); }); it("Components will not translate if they are fixed width/height and request more space than offered", function () { // catches #1188 - var c = new Plottable.Component.AbstractComponent(); - c._requestedSpace = function () { - return { width: 500, height: 500, wantsWidth: true, wantsHeight: true }; + var c = new Plottable.Component(); + c.requestedSpace = function () { + return { minWidth: 500, minHeight: 500 }; }; c._fixedWidthFlag = true; c._fixedHeightFlag = true; - c.xAlign("left"); - var t = new Plottable.Component.Table([[c]]); + c.xAlignment("left"); + var t = new Plottable.Components.Table([[c]]); t.renderTo(svg); var transform = d3.transform(c._element.attr("transform")); assert.deepEqual(transform.translate, [0, 0], "the element was not translated"); @@ -6738,36 +6396,36 @@ describe("Component behavior", function () { }); it("components do not render unless allocated space", function () { var renderFlag = false; - var c = new Plottable.Component.AbstractComponent(); - c._doRender = function () { return renderFlag = true; }; - c._anchor(svg); + var c = new Plottable.Component(); + c.renderImmediately = function () { return renderFlag = true; }; + c.anchor(svg); c._setup(); - c._render(); + c.render(); assert.isFalse(renderFlag, "no render until width/height set to nonzero"); c._width = 10; c._height = 0; - c._render(); + c.render(); assert.isTrue(renderFlag, "render still occurs if one of width/height is zero"); c._height = 10; - c._render(); + c.render(); assert.isTrue(renderFlag, "render occurs if width and height are positive"); svg.remove(); }); it("rendering to a new svg detaches the component", function () { var SVG_HEIGHT_1 = 300; var SVG_HEIGHT_2 = 50; - var svg1 = generateSVG(300, SVG_HEIGHT_1); - var svg2 = generateSVG(300, SVG_HEIGHT_2); - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - var plot = new Plottable.Plot.Line(xScale, yScale); - var group = new Plottable.Component.Group; + var svg1 = TestMethods.generateSVG(300, SVG_HEIGHT_1); + var svg2 = TestMethods.generateSVG(300, SVG_HEIGHT_2); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); + var plot = new Plottable.Plots.Line(xScale, yScale); + var group = new Plottable.Components.Group; group.renderTo(svg1); - group._addComponent(plot); - assert.deepEqual(plot._parent(), group, "the plot should be inside the group"); + group.append(plot); + assert.deepEqual(plot.parent(), group, "the plot should be inside the group"); assert.strictEqual(plot.height(), SVG_HEIGHT_1, "the plot should occupy the entire space of the first svg"); plot.renderTo(svg2); - assert.equal(plot._parent(), null, "the plot should be outside the group"); + assert.strictEqual(plot.parent(), null, "the plot should be outside the group"); assert.strictEqual(plot.height(), SVG_HEIGHT_2, "the plot should occupy the entire space of the second svg"); svg1.remove(); svg2.remove(); @@ -6776,102 +6434,88 @@ describe("Component behavior", function () { describe("origin methods", function () { var cWidth = 100; var cHeight = 100; + it("modifying returned value does not affect origin", function () { + c.renderTo(svg); + var receivedOrigin = c.origin(); + var delta = 10; + receivedOrigin.x += delta; + receivedOrigin.y += delta; + assert.notStrictEqual(receivedOrigin.x, c.origin().x, "receieved point can be modified without affecting origin (x)"); + assert.notStrictEqual(receivedOrigin.y, c.origin().y, "receieved point can be modified without affecting origin (y)"); + svg.remove(); + }); it("origin() (top-level component)", function () { - fixComponentSize(c, cWidth, cHeight); + TestMethods.fixComponentSize(c, cWidth, cHeight); c.renderTo(svg); - c.xAlign("left").yAlign("top"); + c.xAlignment("left").yAlignment("top"); var origin = c.origin(); assert.strictEqual(origin.x, 0, "returns correct value (xAlign left)"); assert.strictEqual(origin.y, 0, "returns correct value (yAlign top)"); - c.xAlign("center").yAlign("center"); + c.xAlignment("center").yAlignment("center"); origin = c.origin(); assert.strictEqual(origin.x, (SVG_WIDTH - cWidth) / 2, "returns correct value (xAlign center)"); assert.strictEqual(origin.y, (SVG_HEIGHT - cHeight) / 2, "returns correct value (yAlign center)"); - c.xAlign("right").yAlign("bottom"); + c.xAlignment("right").yAlignment("bottom"); origin = c.origin(); assert.strictEqual(origin.x, SVG_WIDTH - cWidth, "returns correct value (xAlign right)"); assert.strictEqual(origin.y, SVG_HEIGHT - cHeight, "returns correct value (yAlign bottom)"); - c.xAlign("left").yAlign("top"); - var xOffsetValue = 40; - var yOffsetValue = 30; - c.xOffset(xOffsetValue); - c.yOffset(yOffsetValue); - origin = c.origin(); - assert.strictEqual(origin.x, xOffsetValue, "accounts for xOffset"); - assert.strictEqual(origin.y, yOffsetValue, "accounts for yOffset"); svg.remove(); }); it("origin() (nested)", function () { - fixComponentSize(c, cWidth, cHeight); - var group = new Plottable.Component.Group([c]); - var groupXOffset = 40; - var groupYOffset = 30; - group.xOffset(groupXOffset); - group.yOffset(groupYOffset); + TestMethods.fixComponentSize(c, cWidth, cHeight); + var group = new Plottable.Components.Group([c]); group.renderTo(svg); var groupWidth = group.width(); var groupHeight = group.height(); - c.xAlign("left").yAlign("top"); + c.xAlignment("left").yAlignment("top"); var origin = c.origin(); assert.strictEqual(origin.x, 0, "returns correct value (xAlign left)"); assert.strictEqual(origin.y, 0, "returns correct value (yAlign top)"); - c.xAlign("center").yAlign("center"); + c.xAlignment("center").yAlignment("center"); origin = c.origin(); assert.strictEqual(origin.x, (groupWidth - cWidth) / 2, "returns correct value (xAlign center)"); assert.strictEqual(origin.y, (groupHeight - cHeight) / 2, "returns correct value (yAlign center)"); - c.xAlign("right").yAlign("bottom"); + c.xAlignment("right").yAlignment("bottom"); origin = c.origin(); assert.strictEqual(origin.x, groupWidth - cWidth, "returns correct value (xAlign right)"); assert.strictEqual(origin.y, groupHeight - cHeight, "returns correct value (yAlign bottom)"); svg.remove(); }); it("originToSVG() (top-level component)", function () { - fixComponentSize(c, cWidth, cHeight); + TestMethods.fixComponentSize(c, cWidth, cHeight); c.renderTo(svg); - c.xAlign("left").yAlign("top"); + c.xAlignment("left").yAlignment("top"); var origin = c.originToSVG(); assert.strictEqual(origin.x, 0, "returns correct value (xAlign left)"); assert.strictEqual(origin.y, 0, "returns correct value (yAlign top)"); - c.xAlign("center").yAlign("center"); + c.xAlignment("center").yAlignment("center"); origin = c.originToSVG(); assert.strictEqual(origin.x, (SVG_WIDTH - cWidth) / 2, "returns correct value (xAlign center)"); assert.strictEqual(origin.y, (SVG_HEIGHT - cHeight) / 2, "returns correct value (yAlign center)"); - c.xAlign("right").yAlign("bottom"); + c.xAlignment("right").yAlignment("bottom"); origin = c.originToSVG(); assert.strictEqual(origin.x, SVG_WIDTH - cWidth, "returns correct value (xAlign right)"); assert.strictEqual(origin.y, SVG_HEIGHT - cHeight, "returns correct value (yAlign bottom)"); - c.xAlign("left").yAlign("top"); - var xOffsetValue = 40; - var yOffsetValue = 30; - c.xOffset(xOffsetValue); - c.yOffset(yOffsetValue); - origin = c.originToSVG(); - assert.strictEqual(origin.x, xOffsetValue, "accounts for xOffset"); - assert.strictEqual(origin.y, yOffsetValue, "accounts for yOffset"); svg.remove(); }); it("originToSVG() (nested)", function () { - fixComponentSize(c, cWidth, cHeight); - var group = new Plottable.Component.Group([c]); - var groupXOffset = 40; - var groupYOffset = 30; - group.xOffset(groupXOffset); - group.yOffset(groupYOffset); + TestMethods.fixComponentSize(c, cWidth, cHeight); + var group = new Plottable.Components.Group([c]); group.renderTo(svg); var groupWidth = group.width(); var groupHeight = group.height(); - c.xAlign("left").yAlign("top"); + c.xAlignment("left").yAlignment("top"); var origin = c.originToSVG(); - assert.strictEqual(origin.x, groupXOffset, "returns correct value (xAlign left)"); - assert.strictEqual(origin.y, groupYOffset, "returns correct value (yAlign top)"); - c.xAlign("center").yAlign("center"); + assert.strictEqual(origin.x, 0, "returns correct value (xAlign left)"); + assert.strictEqual(origin.y, 0, "returns correct value (yAlign top)"); + c.xAlignment("center").yAlignment("center"); origin = c.originToSVG(); - assert.strictEqual(origin.x, (groupWidth - cWidth) / 2 + groupXOffset, "returns correct value (xAlign center)"); - assert.strictEqual(origin.y, (groupHeight - cHeight) / 2 + groupYOffset, "returns correct value (yAlign center)"); - c.xAlign("right").yAlign("bottom"); + assert.strictEqual(origin.x, (groupWidth - cWidth) / 2, "returns correct value (xAlign center)"); + assert.strictEqual(origin.y, (groupHeight - cHeight) / 2, "returns correct value (yAlign center)"); + c.xAlignment("right").yAlignment("bottom"); origin = c.originToSVG(); - assert.strictEqual(origin.x, groupWidth - cWidth + groupXOffset, "returns correct value (xAlign right)"); - assert.strictEqual(origin.y, groupHeight - cHeight + groupYOffset, "returns correct value (yAlign bottom)"); + assert.strictEqual(origin.x, groupWidth - cWidth, "returns correct value (xAlign right)"); + assert.strictEqual(origin.y, groupHeight - cHeight, "returns correct value (yAlign bottom)"); svg.remove(); }); }); @@ -6885,11 +6529,11 @@ describe("Dataset", function () { var newData = [1, 2, 3]; var callbackCalled = false; var callback = function (listenable) { - assert.equal(listenable, ds, "Callback received the Dataset as the first argument"); + assert.strictEqual(listenable, ds, "Callback received the Dataset as the first argument"); assert.deepEqual(ds.data(), newData, "Dataset arrives with correct data"); callbackCalled = true; }; - ds.broadcaster.registerListener(null, callback); + ds.onUpdate(callback); ds.data(newData); assert.isTrue(callbackCalled, "callback was called when the data was changed"); }); @@ -6898,31 +6542,30 @@ describe("Dataset", function () { var newMetadata = "blargh"; var callbackCalled = false; var callback = function (listenable) { - assert.equal(listenable, ds, "Callback received the Dataset as the first argument"); + assert.strictEqual(listenable, ds, "Callback received the Dataset as the first argument"); assert.deepEqual(ds.metadata(), newMetadata, "Dataset arrives with correct metadata"); callbackCalled = true; }; - ds.broadcaster.registerListener(null, callback); + ds.onUpdate(callback); ds.metadata(newMetadata); assert.isTrue(callbackCalled, "callback was called when the metadata was changed"); }); - it("_getExtent works as expected with user metadata", function () { - var data = [1, 2, 3, 4, 1]; - var metadata = { foo: 11 }; - var id = function (d) { return d; }; - var dataset = new Plottable.Dataset(data, metadata); - var plot = new Plottable.Plot.AbstractPlot().addDataset(dataset); - var a1 = function (d, i, m) { return d + i - 2; }; - assert.deepEqual(dataset._getExtent(a1, id), [-1, 5], "extent for numerical data works properly"); - var a2 = function (d, i, m) { return d + m.foo; }; - assert.deepEqual(dataset._getExtent(a2, id), [12, 15], "extent uses metadata appropriately"); - dataset.metadata({ foo: -1 }); - assert.deepEqual(dataset._getExtent(a2, id), [0, 3], "metadata change is reflected in extent results"); - var a3 = function (d, i, m) { return "_" + d; }; - assert.deepEqual(dataset._getExtent(a3, id), ["_1", "_2", "_3", "_4"], "extent works properly on string domains (no repeats)"); - var a_toString = function (d) { return (d + 2).toString(); }; - var coerce = function (d) { return +d; }; - assert.deepEqual(dataset._getExtent(a_toString, coerce), [3, 6], "type coercion works as expected"); + it("Removing listener from dataset should be possible", function () { + var ds = new Plottable.Dataset(); + var newData1 = [1, 2, 3]; + var newData2 = [4, 5, 6]; + var callbackCalled = false; + var callback = function (listenable) { + assert.strictEqual(listenable, ds, "Callback received the Dataset as the first argument"); + callbackCalled = true; + }; + ds.onUpdate(callback); + ds.data(newData1); + assert.isTrue(callbackCalled, "callback was called when the data was changed"); + callbackCalled = false; + ds.offUpdate(callback); + ds.data(newData2); + assert.isFalse(callbackCalled, "callback was called when the data was changed"); }); }); @@ -6931,13 +6574,12 @@ var assert = chai.assert; function generateBasicTable(nRows, nCols) { // makes a table with exactly nRows * nCols children in a regular grid, with each // child being a basic component - var table = new Plottable.Component.Table(); - var rows = []; + var table = new Plottable.Components.Table(); var components = []; for (var i = 0; i < nRows; i++) { for (var j = 0; j < nCols; j++) { - var r = new Plottable.Component.AbstractComponent(); - table.addComponent(i, j, r); + var r = new Plottable.Component(); + table.add(r, i, j); components.push(r); } } @@ -6945,11 +6587,11 @@ function generateBasicTable(nRows, nCols) { } describe("Tables", function () { it("tables are classed properly", function () { - var table = new Plottable.Component.Table(); + var table = new Plottable.Components.Table(); assert.isTrue(table.classed("table")); }); it("padTableToSize works properly", function () { - var t = new Plottable.Component.Table(); + var t = new Plottable.Components.Table(); assert.deepEqual(t._rows, [], "the table rows is an empty list"); t._padTableToSize(1, 1); var rows = t._rows; @@ -6960,93 +6602,81 @@ describe("Tables", function () { assert.isNull(firstComponent, "the row only has a null component"); t._padTableToSize(5, 2); assert.lengthOf(rows, 5, "there are five rows"); - rows.forEach(function (r) { return assert.lengthOf(r, 2, "there are two columsn per row"); }); - assert.equal(rows[0][0], firstComponent, "the first component is unchanged"); + rows.forEach(function (r) { return assert.lengthOf(r, 2, "there are two columns per row"); }); + assert.strictEqual(rows[0][0], firstComponent, "the first component is unchanged"); }); it("table constructor can take a list of lists of components", function () { - var c0 = new Plottable.Component.AbstractComponent(); + var c0 = new Plottable.Component(); var row1 = [null, c0]; - var row2 = [new Plottable.Component.AbstractComponent(), null]; - var table = new Plottable.Component.Table([row1, row2]); - assert.equal(table._rows[0][1], c0, "the component is in the right spot"); - var c1 = new Plottable.Component.AbstractComponent(); - table.addComponent(2, 2, c1); - assert.equal(table._rows[2][2], c1, "the inserted component went to the right spot"); - }); - it("tables can be constructed by adding components in matrix style", function () { - var table = new Plottable.Component.Table(); - var c1 = new Plottable.Component.AbstractComponent(); - var c2 = new Plottable.Component.AbstractComponent(); - table.addComponent(0, 0, c1); - table.addComponent(1, 1, c2); - var rows = table._rows; - assert.lengthOf(rows, 2, "there are two rows"); - assert.lengthOf(rows[0], 2, "two cols in first row"); - assert.lengthOf(rows[1], 2, "two cols in second row"); - assert.equal(rows[0][0], c1, "first component added correctly"); - assert.equal(rows[1][1], c2, "second component added correctly"); - assert.isNull(rows[0][1], "component at (0, 1) is null"); - assert.isNull(rows[1][0], "component at (1, 0) is null"); - }); - it("add a component where one already exists creates a new group", function () { - var c1 = new Plottable.Component.AbstractComponent(); - var c2 = new Plottable.Component.AbstractComponent(); - var c3 = new Plottable.Component.AbstractComponent(); - var t = new Plottable.Component.Table(); - t.addComponent(0, 2, c1); - t.addComponent(0, 0, c2); - t.addComponent(0, 2, c3); - assert.isTrue(Plottable.Component.Group.prototype.isPrototypeOf(t._rows[0][2]), "A group was created"); - var components = t._rows[0][2].components(); - assert.lengthOf(components, 2, "The group created should have 2 components"); - assert.equal(components[0], c1, "First element in the group at (0, 2) should be c1"); - assert.equal(components[1], c3, "Second element in the group at (0, 2) should be c3"); - }); - it("add a component where a group already exists adds the component to the group", function () { - var c1 = new Plottable.Component.AbstractComponent(); - var c2 = new Plottable.Component.AbstractComponent(); - var grp = new Plottable.Component.Group([c1, c2]); - var c3 = new Plottable.Component.AbstractComponent(); - var t = new Plottable.Component.Table(); - t.addComponent(0, 2, grp); - t.addComponent(0, 2, c3); - assert.isTrue(Plottable.Component.Group.prototype.isPrototypeOf(t._rows[0][2]), "The cell still contains a group"); - var components = t._rows[0][2].components(); - assert.lengthOf(components, 3, "The group created should have 3 components"); - assert.equal(components[0], c1, "First element in the group at (0, 2) should still be c1"); - assert.equal(components[1], c2, "Second element in the group at (0, 2) should still be c2"); - assert.equal(components[2], c3, "The Component was added to the existing Group"); - }); - it("adding null to a table cell should throw an error", function () { - var c1 = new Plottable.Component.AbstractComponent(); - var t = new Plottable.Component.Table([[c1]]); - assert.throw(function () { return t.addComponent(0, 0, null); }, "Cannot add null to a table cell"); - }); - it("addComponent works even if a component is added with a high column and low row index", function () { - // Solves #180, a weird bug - var t = new Plottable.Component.Table(); - var svg = generateSVG(); - t.addComponent(1, 0, new Plottable.Component.AbstractComponent()); - t.addComponent(0, 2, new Plottable.Component.AbstractComponent()); - t.renderTo(svg); //would throw an error without the fix (tested); - svg.remove(); + var row2 = [new Plottable.Component(), null]; + var table = new Plottable.Components.Table([row1, row2]); + assert.strictEqual(table._rows[0][1], c0, "the component is in the right spot"); + var c1 = new Plottable.Component(); + table.add(c1, 2, 2); + assert.strictEqual(table._rows[2][2], c1, "the inserted component went to the right spot"); + }); + describe("add()", function () { + it("adds Component and pads out other empty cells with null", function () { + var table = new Plottable.Components.Table(); + var c1 = new Plottable.Component(); + var c2 = new Plottable.Component(); + table.add(c1, 0, 0); + table.add(c2, 1, 1); + var rows = table._rows; + assert.lengthOf(rows, 2, "there are two rows"); + assert.lengthOf(rows[0], 2, "two cols in first row"); + assert.lengthOf(rows[1], 2, "two cols in second row"); + assert.strictEqual(rows[0][0], c1, "first component added correctly"); + assert.strictEqual(rows[1][1], c2, "second component added correctly"); + assert.isNull(rows[0][1], "component at (0, 1) is null"); + assert.isNull(rows[1][0], "component at (1, 0) is null"); + }); + it("adding a Component where one already exists throws an Error", function () { + var c1 = new Plottable.Component(); + var t = new Plottable.Components.Table([[c1]]); + var c2 = new Plottable.Component(); + assert.throws(function () { return t.add(c2, 0, 0); }, Error, "occupied"); + }); + it("adding null to a table cell should throw an error", function () { + var c1 = new Plottable.Component(); + var t = new Plottable.Components.Table([[c1]]); + assert.throw(function () { return t.add(null, 0, 0); }, "Cannot add null to a table cell"); + }); + it("add()-ing a Component to the Group should detach() it from its current location", function () { + var c1 = new Plottable.Component; + var svg = TestMethods.generateSVG(); + c1.renderTo(svg); + var table = new Plottable.Components.Table(); + table.add(c1, 0, 0); + assert.isFalse(svg.node().hasChildNodes(), "Component was detach()-ed"); + svg.remove(); + }); + it("add() works even if a component is added with a high column and low row index", function () { + // Solves #180, a weird bug + var t = new Plottable.Components.Table(); + var svg = TestMethods.generateSVG(); + t.add(new Plottable.Component(), 1, 0); + t.add(new Plottable.Component(), 0, 2); + t.renderTo(svg); // would throw an error without the fix (tested); + svg.remove(); + }); }); it("basic table with 2 rows 2 cols lays out properly", function () { var tableAndcomponents = generateBasicTable(2, 2); var table = tableAndcomponents.table; var components = tableAndcomponents.components; - var svg = generateSVG(); + var svg = TestMethods.generateSVG(); table.renderTo(svg); var elements = components.map(function (r) { return r._element; }); - var translates = elements.map(function (e) { return getTranslate(e); }); + var translates = elements.map(function (e) { return TestMethods.getTranslate(e); }); assert.deepEqual(translates[0], [0, 0], "first element is centered at origin"); assert.deepEqual(translates[1], [200, 0], "second element is located properly"); assert.deepEqual(translates[2], [0, 200], "third element is located properly"); assert.deepEqual(translates[3], [200, 200], "fourth element is located properly"); - var bboxes = elements.map(function (e) { return Plottable._Util.DOM.getBBox(e); }); + var bboxes = elements.map(function (e) { return Plottable.Utils.DOM.getBBox(e); }); bboxes.forEach(function (b) { - assert.equal(b.width, 200, "bbox is 200 pixels wide"); - assert.equal(b.height, 200, "bbox is 200 pixels tall"); + assert.strictEqual(b.width, 200, "bbox is 200 pixels wide"); + assert.strictEqual(b.height, 200, "bbox is 200 pixels tall"); }); svg.remove(); }); @@ -7054,39 +6684,39 @@ describe("Tables", function () { var tableAndcomponents = generateBasicTable(2, 2); var table = tableAndcomponents.table; var components = tableAndcomponents.components; - table.padding(5, 5); - var svg = generateSVG(415, 415); + table.rowPadding(5).columnPadding(5); + var svg = TestMethods.generateSVG(415, 415); table.renderTo(svg); var elements = components.map(function (r) { return r._element; }); - var translates = elements.map(function (e) { return getTranslate(e); }); - var bboxes = elements.map(function (e) { return Plottable._Util.DOM.getBBox(e); }); + var translates = elements.map(function (e) { return TestMethods.getTranslate(e); }); + var bboxes = elements.map(function (e) { return Plottable.Utils.DOM.getBBox(e); }); assert.deepEqual(translates[0], [0, 0], "first element is centered properly"); assert.deepEqual(translates[1], [210, 0], "second element is located properly"); assert.deepEqual(translates[2], [0, 210], "third element is located properly"); assert.deepEqual(translates[3], [210, 210], "fourth element is located properly"); bboxes.forEach(function (b) { - assert.equal(b.width, 205, "bbox is 205 pixels wide"); - assert.equal(b.height, 205, "bbox is 205 pixels tall"); + assert.strictEqual(b.width, 205, "bbox is 205 pixels wide"); + assert.strictEqual(b.height, 205, "bbox is 205 pixels tall"); }); svg.remove(); }); it("table with fixed-size objects on every side lays out properly", function () { - var svg = generateSVG(); - var c4 = new Plottable.Component.AbstractComponent(); + var svg = TestMethods.generateSVG(); + var c4 = new Plottable.Component(); // [0 1 2] \\ // [3 4 5] \\ // [6 7 8] \\ // give the axis-like objects a minimum - var c1 = makeFixedSizeComponent(null, 30); - var c7 = makeFixedSizeComponent(null, 30); - var c3 = makeFixedSizeComponent(50, null); - var c5 = makeFixedSizeComponent(50, null); - var table = new Plottable.Component.Table([[null, c1, null], [c3, c4, c5], [null, c7, null]]); + var c1 = TestMethods.makeFixedSizeComponent(null, 30); + var c7 = TestMethods.makeFixedSizeComponent(null, 30); + var c3 = TestMethods.makeFixedSizeComponent(50, null); + var c5 = TestMethods.makeFixedSizeComponent(50, null); + var table = new Plottable.Components.Table([[null, c1, null], [c3, c4, c5], [null, c7, null]]); var components = [c1, c3, c4, c5, c7]; table.renderTo(svg); var elements = components.map(function (r) { return r._element; }); - var translates = elements.map(function (e) { return getTranslate(e); }); - var bboxes = elements.map(function (e) { return Plottable._Util.DOM.getBBox(e); }); + var translates = elements.map(function (e) { return TestMethods.getTranslate(e); }); + var bboxes = elements.map(function (e) { return Plottable.Utils.DOM.getBBox(e); }); // test the translates assert.deepEqual(translates[0], [50, 0], "top axis translate"); assert.deepEqual(translates[4], [50, 370], "bottom axis translate"); @@ -7094,44 +6724,44 @@ describe("Tables", function () { assert.deepEqual(translates[3], [350, 30], "right axis translate"); assert.deepEqual(translates[2], [50, 30], "plot translate"); // test the bboxes - assertBBoxEquivalence(bboxes[0], [300, 30], "top axis bbox"); - assertBBoxEquivalence(bboxes[4], [300, 30], "bottom axis bbox"); - assertBBoxEquivalence(bboxes[1], [50, 340], "left axis bbox"); - assertBBoxEquivalence(bboxes[3], [50, 340], "right axis bbox"); - assertBBoxEquivalence(bboxes[2], [300, 340], "plot bbox"); + TestMethods.assertBBoxEquivalence(bboxes[0], [300, 30], "top axis bbox"); + TestMethods.assertBBoxEquivalence(bboxes[4], [300, 30], "bottom axis bbox"); + TestMethods.assertBBoxEquivalence(bboxes[1], [50, 340], "left axis bbox"); + TestMethods.assertBBoxEquivalence(bboxes[3], [50, 340], "right axis bbox"); + TestMethods.assertBBoxEquivalence(bboxes[2], [300, 340], "plot bbox"); svg.remove(); }); it("table space fixity calculates properly", function () { var tableAndcomponents = generateBasicTable(3, 3); var table = tableAndcomponents.table; var components = tableAndcomponents.components; - components.forEach(function (c) { return fixComponentSize(c, 10, 10); }); - assert.isTrue(table._isFixedWidth(), "fixed width when all subcomponents fixed width"); - assert.isTrue(table._isFixedHeight(), "fixedHeight when all subcomponents fixed height"); - fixComponentSize(components[0], null, 10); - assert.isFalse(table._isFixedWidth(), "width not fixed when some subcomponent width not fixed"); - assert.isTrue(table._isFixedHeight(), "the height is still fixed when some subcomponent width not fixed"); - fixComponentSize(components[8], 10, null); - fixComponentSize(components[0], 10, 10); - assert.isTrue(table._isFixedWidth(), "width fixed again once no subcomponent width not fixed"); - assert.isFalse(table._isFixedHeight(), "height unfixed now that a subcomponent has unfixed height"); - }); - it.skip("table._requestedSpace works properly", function () { + components.forEach(function (c) { return TestMethods.fixComponentSize(c, 10, 10); }); + assert.isTrue(table.fixedWidth(), "fixed width when all subcomponents fixed width"); + assert.isTrue(table.fixedHeight(), "fixedHeight when all subcomponents fixed height"); + TestMethods.fixComponentSize(components[0], null, 10); + assert.isFalse(table.fixedWidth(), "width not fixed when some subcomponent width not fixed"); + assert.isTrue(table.fixedHeight(), "the height is still fixed when some subcomponent width not fixed"); + TestMethods.fixComponentSize(components[8], 10, null); + TestMethods.fixComponentSize(components[0], 10, 10); + assert.isTrue(table.fixedWidth(), "width fixed again once no subcomponent width not fixed"); + assert.isFalse(table.fixedHeight(), "height unfixed now that a subcomponent has unfixed height"); + }); + it.skip("table.requestedSpace works properly", function () { // [0 1] // [2 3] - var c0 = new Plottable.Component.AbstractComponent(); - var c1 = makeFixedSizeComponent(50, 50); - var c2 = makeFixedSizeComponent(20, 50); - var c3 = makeFixedSizeComponent(20, 20); - var table = new Plottable.Component.Table([[c0, c1], [c2, c3]]); - var spaceRequest = table._requestedSpace(30, 30); - verifySpaceRequest(spaceRequest, 30, 30, true, true, "1"); - spaceRequest = table._requestedSpace(50, 50); - verifySpaceRequest(spaceRequest, 50, 50, true, true, "2"); - spaceRequest = table._requestedSpace(90, 90); - verifySpaceRequest(spaceRequest, 70, 90, false, true, "3"); - spaceRequest = table._requestedSpace(200, 200); - verifySpaceRequest(spaceRequest, 70, 100, false, false, "4"); + var c0 = new Plottable.Component(); + var c1 = TestMethods.makeFixedSizeComponent(50, 50); + var c2 = TestMethods.makeFixedSizeComponent(20, 50); + var c3 = TestMethods.makeFixedSizeComponent(20, 20); + var table = new Plottable.Components.Table([[c0, c1], [c2, c3]]); + var spaceRequest = table.requestedSpace(30, 30); + TestMethods.verifySpaceRequest(spaceRequest, 30, 30, "1"); + spaceRequest = table.requestedSpace(50, 50); + TestMethods.verifySpaceRequest(spaceRequest, 50, 50, "2"); + spaceRequest = table.requestedSpace(90, 90); + TestMethods.verifySpaceRequest(spaceRequest, 70, 90, "3"); + spaceRequest = table.requestedSpace(200, 200); + TestMethods.verifySpaceRequest(spaceRequest, 70, 100, "4"); }); describe("table._iterateLayout works properly", function () { // This test battery would have caught #405 @@ -7143,86 +6773,93 @@ describe("Tables", function () { assert.deepEqual(result.wantsWidth, wW, "wantsWidth:" + id); assert.deepEqual(result.wantsHeight, wH, "wantsHeight:" + id); } - var c1 = new Plottable.Component.AbstractComponent(); - var c2 = new Plottable.Component.AbstractComponent(); - var c3 = new Plottable.Component.AbstractComponent(); - var c4 = new Plottable.Component.AbstractComponent(); - var table = new Plottable.Component.Table([ - [c1, c2], - [c3, c4] - ]); it("iterateLayout works in the easy case where there is plenty of space and everything is satisfied on first go", function () { - fixComponentSize(c1, 50, 50); - fixComponentSize(c4, 20, 10); + var c1 = new Mocks.FixedSizeComponent(50, 50); + var c2 = new Plottable.Component(); + var c3 = new Plottable.Component(); + var c4 = new Mocks.FixedSizeComponent(20, 10); + var table = new Plottable.Components.Table([ + [c1, c2], + [c3, c4] + ]); var result = table._iterateLayout(500, 500); verifyLayoutResult(result, [215, 215], [220, 220], [50, 20], [50, 10], false, false, ""); }); it.skip("iterateLayout works in the difficult case where there is a shortage of space and layout requires iterations", function () { - fixComponentSize(c1, 490, 50); + var c1 = new Mocks.FixedSizeComponent(490, 50); + var c2 = new Plottable.Component(); + var c3 = new Plottable.Component(); + var c4 = new Plottable.Component(); + var table = new Plottable.Components.Table([ + [c1, c2], + [c3, c4] + ]); var result = table._iterateLayout(500, 500); verifyLayoutResult(result, [0, 0], [220, 220], [480, 20], [50, 10], true, false, ""); }); it("iterateLayout works in the case where all components are fixed-size", function () { - fixComponentSize(c1, 50, 50); - fixComponentSize(c2, 50, 50); - fixComponentSize(c3, 50, 50); - fixComponentSize(c4, 50, 50); + var c1 = new Mocks.FixedSizeComponent(50, 50); + var c2 = new Mocks.FixedSizeComponent(50, 50); + var c3 = new Mocks.FixedSizeComponent(50, 50); + var c4 = new Mocks.FixedSizeComponent(50, 50); + var table = new Plottable.Components.Table([ + [c1, c2], + [c3, c4] + ]); var result = table._iterateLayout(100, 100); - verifyLayoutResult(result, [0, 0], [0, 0], [50, 50], [50, 50], false, false, "..when there's exactly enough space"); + verifyLayoutResult(result, [0, 0], [0, 0], [50, 50], [50, 50], false, false, "when there's exactly enough space"); result = table._iterateLayout(80, 80); - verifyLayoutResult(result, [0, 0], [0, 0], [40, 40], [40, 40], true, true, "..when there's not enough space"); + verifyLayoutResult(result, [0, 0], [0, 0], [50, 50], [50, 50], true, true, "still requests more space if constrained"); + result = table._iterateLayout(80, 80, true); + verifyLayoutResult(result, [0, 0], [0, 0], [40, 40], [40, 40], true, true, "accepts suboptimal layout if it's the final offer"); result = table._iterateLayout(120, 120); // If there is extra space in a fixed-size table, the extra space should not be allocated to proportional space - verifyLayoutResult(result, [0, 0], [0, 0], [50, 50], [50, 50], false, false, "..when there's extra space"); + verifyLayoutResult(result, [0, 0], [0, 0], [50, 50], [50, 50], false, false, "when there's extra space"); }); - it.skip("iterateLayout works in the tricky case when components can be unsatisfied but request little space", function () { - table = new Plottable.Component.Table([[c1, c2]]); - fixComponentSize(c1, null, null); - c2._requestedSpace = function (w, h) { - return { - width: w >= 200 ? 200 : 0, - height: h >= 200 ? 200 : 0, - wantsWidth: w < 200, - wantsHeight: h < 200 - }; - }; - var result = table._iterateLayout(200, 200); - verifyLayoutResult(result, [0, 0], [0], [0, 200], [200], false, false, "when there's sufficient space"); - result = table._iterateLayout(150, 200); - verifyLayoutResult(result, [150, 0], [0], [0, 0], [200], true, false, "when there's insufficient space"); - }); - }); - describe("table._removeComponent works properly", function () { - var c1 = new Plottable.Component.AbstractComponent(); - var c2 = new Plottable.Component.AbstractComponent(); - var c3 = new Plottable.Component.AbstractComponent(); - var c4 = new Plottable.Component.AbstractComponent(); - var c5 = new Plottable.Component.AbstractComponent(); - var c6 = new Plottable.Component.AbstractComponent(); + }); + describe("remove()", function () { + var c1 = new Plottable.Component(); + var c2 = new Plottable.Component(); + var c3 = new Plottable.Component(); + var c4 = new Plottable.Component(); + var c5 = new Plottable.Component(); + var c6 = new Plottable.Component(); var table; - it("table._removeComponent works in basic case", function () { - table = new Plottable.Component.Table([[c1, c2], [c3, c4], [c5, c6]]); - table._removeComponent(c4); + it("works in basic case", function () { + table = new Plottable.Components.Table([[c1, c2], [c3, c4], [c5, c6]]); + table.remove(c4); assert.deepEqual(table._rows, [[c1, c2], [c3, null], [c5, c6]], "remove one element"); }); - it("table._removeComponent does nothing when component is not found", function () { - table = new Plottable.Component.Table([[c1, c2], [c3, c4]]); - table._removeComponent(c5); + it("does nothing when component is not found", function () { + table = new Plottable.Components.Table([[c1, c2], [c3, c4]]); + table.remove(c5); assert.deepEqual(table._rows, [[c1, c2], [c3, c4]], "remove nonexistent component"); }); - it("table._removeComponent removing component twice should have same effect as removing it once", function () { - table = new Plottable.Component.Table([[c1, c2, c3], [c4, c5, c6]]); - table._removeComponent(c1); + it("removing component twice should have same effect as removing it once", function () { + table = new Plottable.Components.Table([[c1, c2, c3], [c4, c5, c6]]); + table.remove(c1); assert.deepEqual(table._rows, [[null, c2, c3], [c4, c5, c6]], "item twice"); - table._removeComponent(c1); + table.remove(c1); assert.deepEqual(table._rows, [[null, c2, c3], [c4, c5, c6]], "item twice"); }); - it("table._removeComponent doesn't do anything weird when called with null", function () { - table = new Plottable.Component.Table([[c1, null], [c2, c3]]); - table._removeComponent(null); - assert.deepEqual(table._rows, [[c1, null], [c2, c3]]); + it("detach()-ing a Component removes it from the Table", function () { + table = new Plottable.Components.Table([[c1]]); + var svg = TestMethods.generateSVG(); + table.renderTo(svg); + c1.detach(); + assert.deepEqual(table._rows, [[null]], "calling detach() on the Component removed it from the Table"); + svg.remove(); }); }); + it("has()", function () { + var c0 = new Plottable.Component(); + var componentGroup = new Plottable.Components.Table([[c0]]); + assert.isTrue(componentGroup.has(c0), "correctly checks that Component is in the Table"); + componentGroup.remove(c0); + assert.isFalse(componentGroup.has(c0), "correctly checks that Component is no longer in the Table"); + componentGroup.add(c0, 1, 1); + assert.isTrue(componentGroup.has(c0), "correctly checks that Component is in the Table again"); + }); }); /// @@ -7231,21 +6868,23 @@ describe("Domainer", function () { var scale; var domainer; beforeEach(function () { - scale = new Plottable.Scale.Linear(); + scale = new Plottable.Scales.Linear(); domainer = new Plottable.Domainer(); }); it("pad() works in general case", function () { - scale._updateExtent("1", "x", [100, 200]); + scale.addExtentsProvider(function (scale) { return [[100, 200]]; }); + scale.autoDomain(); scale.domainer(new Plottable.Domainer().pad(0.2)); assert.closeTo(scale.domain()[0], 90, 0.1, "lower bound of domain correct"); assert.closeTo(scale.domain()[1], 210, 0.1, "upper bound of domain correct"); }); it("pad() works for date scales", function () { - var timeScale = new Plottable.Scale.Time(); + var timeScale = new Plottable.Scales.Time(); var f = d3.time.format("%x"); var d1 = f.parse("06/02/2014"); var d2 = f.parse("06/03/2014"); - timeScale._updateExtent("1", "x", [d1, d2]); + timeScale.addExtentsProvider(function (scale) { return [[d1, d2]]; }); + timeScale.autoDomain(); timeScale.domainer(new Plottable.Domainer().pad()); var dd1 = timeScale.domain()[0]; var dd2 = timeScale.domain()[1]; @@ -7253,24 +6892,8 @@ describe("Domainer", function () { assert.isNotNull(dd1.toDateString, "padDomain produced dates"); assert.notEqual(d1.valueOf(), dd1.valueOf(), "date1 changed"); assert.notEqual(d2.valueOf(), dd2.valueOf(), "date2 changed"); - assert.equal(dd1.valueOf(), dd1.valueOf(), "date1 is not NaN"); - assert.equal(dd2.valueOf(), dd2.valueOf(), "date2 is not NaN"); - }); - it("pad() works on log scales", function () { - var logScale = new Plottable.Scale.Log(); - logScale._updateExtent("1", "x", [10, 100]); - logScale.range([0, 1]); - logScale.domainer(domainer.pad(2.0)); - assert.closeTo(logScale.domain()[0], 1, 0.001); - assert.closeTo(logScale.domain()[1], 1000, 0.001); - logScale.range([50, 60]); - logScale.autoDomain(); - assert.closeTo(logScale.domain()[0], 1, 0.001); - assert.closeTo(logScale.domain()[1], 1000, 0.001); - logScale.range([-1, -2]); - logScale.autoDomain(); - assert.closeTo(logScale.domain()[0], 1, 0.001); - assert.closeTo(logScale.domain()[1], 1000, 0.001); + assert.strictEqual(dd1.valueOf(), dd1.valueOf(), "date1 is not NaN"); + assert.strictEqual(dd2.valueOf(), dd2.valueOf(), "date2 is not NaN"); }); it("pad() defaults to [v-1, v+1] if there's only one numeric value", function () { domainer.pad(); @@ -7282,13 +6905,10 @@ describe("Domainer", function () { var d2 = new Date(2000, 5, 5); var dayBefore = new Date(2000, 5, 4); var dayAfter = new Date(2000, 5, 6); - var timeScale = new Plottable.Scale.Time(); - // the result of computeDomain() will be number[], but when it - // gets fed back into timeScale, it will be adjusted back to a Date. - // That's why I'm using _updateExtent() instead of domainer.computeDomain() - timeScale._updateExtent("1", "x", [d, d2]); - timeScale.domainer(new Plottable.Domainer().pad()); - assert.deepEqual(timeScale.domain(), [dayBefore, dayAfter]); + domainer.pad(); + var domain = domainer.computeDomain([[d, d2]], scale); + assert.strictEqual(domain[0], dayBefore.valueOf(), "domain start was set to the day before"); + assert.strictEqual(domain[1], dayAfter.valueOf(), "domain end was set to the day after"); }); it("pad() only takes the last value", function () { domainer.pad(1000).pad(4).pad(0.1); @@ -7309,78 +6929,78 @@ describe("Domainer", function () { assert.deepEqual(domain, [0, 100]); }); it("paddingException(n) will not pad beyond n", function () { - domainer.pad(0.1).addPaddingException(0, "key").addPaddingException(200); + domainer.pad(0.1).addPaddingException("keyLeft", 0).addPaddingException("keyRight", 200); var domain = domainer.computeDomain([[0, 100]], scale); assert.deepEqual(domain, [0, 105], "padding exceptions can be added by key"); domain = domainer.computeDomain([[-100, 0]], scale); assert.deepEqual(domain, [-105, 0]); domain = domainer.computeDomain([[0, 200]], scale); assert.deepEqual(domain, [0, 200]); - domainer.removePaddingException("key"); + domainer.removePaddingException("keyLeft"); domain = domainer.computeDomain([[0, 200]], scale); assert.deepEqual(domain, [-10, 200], "paddingExceptions can be removed by key"); - domainer.removePaddingException(200); + domainer.removePaddingException("keyRight"); domain = domainer.computeDomain([[0, 200]], scale); assert.notEqual(domain[1], 200, "unregistered paddingExceptions can be removed using boolean argument"); }); it("paddingException(n) works on dates", function () { - var a = new Date(2000, 5, 5); - var b = new Date(2003, 0, 1); - domainer.pad().addPaddingException(a); - var timeScale = new Plottable.Scale.Time(); - timeScale._updateExtent("1", "x", [a, b]); + var startDate = new Date(2000, 5, 5); + var endDate = new Date(2003, 0, 1); + var timeScale = new Plottable.Scales.Time(); + timeScale.addExtentsProvider(function (scale) { return [[startDate, endDate]]; }); + timeScale.autoDomain(); + domainer.pad().addPaddingException("key", startDate); timeScale.domainer(domainer); var domain = timeScale.domain(); - assert.deepEqual(domain[0], a); - assert.isTrue(b < domain[1]); + assert.deepEqual(domain[0], startDate); + assert.isTrue(endDate < domain[1]); }); it("include(n) works an expected", function () { - domainer.addIncludedValue(5); + domainer.addIncludedValue("key1", 5); var domain = domainer.computeDomain([[0, 10]], scale); assert.deepEqual(domain, [0, 10]); domain = domainer.computeDomain([[0, 3]], scale); assert.deepEqual(domain, [0, 5]); domain = domainer.computeDomain([[100, 200]], scale); assert.deepEqual(domain, [5, 200]); - domainer.addIncludedValue(-3).addIncludedValue(0).addIncludedValue(10, "key"); + domainer.addIncludedValue("key2", -3).addIncludedValue("key3", 0).addIncludedValue("key4", 10); domain = domainer.computeDomain([[100, 200]], scale); assert.deepEqual(domain, [-3, 200]); domain = domainer.computeDomain([[0, 0]], scale); assert.deepEqual(domain, [-3, 10]); - domainer.removeIncludedValue("key"); + domainer.removeIncludedValue("key4"); domain = domainer.computeDomain([[100, 200]], scale); assert.deepEqual(domain, [-3, 200]); domain = domainer.computeDomain([[-100, -50]], scale); assert.deepEqual(domain, [-100, 5]); - domainer.addIncludedValue(10); + domainer.addIncludedValue("key5", 10); domain = domainer.computeDomain([[-100, -50]], scale); assert.deepEqual(domain, [-100, 10], "unregistered includedValues can be added"); - domainer.removeIncludedValue(10); + domainer.removeIncludedValue("key5"); domain = domainer.computeDomain([[-100, -50]], scale); assert.deepEqual(domain, [-100, 5], "unregistered includedValues can be removed with addOrRemove argument"); }); it("include(n) works on dates", function () { - var a = new Date(2000, 5, 4); - var b = new Date(2000, 5, 5); - var c = new Date(2000, 5, 6); - var d = new Date(2003, 0, 1); - domainer.addIncludedValue(b); - var timeScale = new Plottable.Scale.Time(); - timeScale._updateExtent("1", "x", [c, d]); + var includedDate = new Date(2000, 5, 5); + var startDate = new Date(2000, 5, 6); + var endDate = new Date(2003, 0, 1); + var timeScale = new Plottable.Scales.Time(); + timeScale.addExtentsProvider(function (scale) { return [[startDate, endDate]]; }); + timeScale.autoDomain(); + domainer.addIncludedValue("key", includedDate); timeScale.domainer(domainer); - assert.deepEqual(timeScale.domain(), [b, d]); + assert.deepEqual(timeScale.domain(), [includedDate, endDate], "domain was expanded to contain included date"); }); it("exceptions are setup properly on an area plot", function () { - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - var domainer = yScale.domainer(); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); var data = [{ x: 0, y: 0, y0: 0 }, { x: 5, y: 5, y0: 5 }]; var dataset = new Plottable.Dataset(data); - var r = new Plottable.Plot.Area(xScale, yScale); + var r = new Plottable.Plots.Area(xScale, yScale); r.addDataset(dataset); - var svg = generateSVG(); - r.project("x", "x", xScale); - r.project("y", "y", yScale); + var svg = TestMethods.generateSVG(); + r.x(function (d) { return d.x; }, xScale); + r.y(function (d) { return d.y; }, yScale); r.renderTo(svg); function getExceptions() { yScale.autoDomain(); @@ -7396,73 +7016,48 @@ describe("Domainer", function () { } assert.deepEqual(getExceptions(), [0], "initializing the plot adds a padding exception at 0"); // assert.deepEqual(getExceptions(), [], "Initially there are no padding exceptions"); - r.project("y0", "y0", yScale); - assert.deepEqual(getExceptions(), [], "projecting a non-constant y0 removes the padding exception"); - r.project("y0", 0, yScale); + r.y0(function (d) { return d.y0; }, yScale); + assert.deepEqual(getExceptions(), [], "projecting a non-constant y0 removes the padding exception 1"); + r.y0(0, yScale); assert.deepEqual(getExceptions(), [0], "projecting constant y0 adds the exception back"); - r.project("y0", function () { return 5; }, yScale); + r.y0(5, yScale); assert.deepEqual(getExceptions(), [5], "projecting a different constant y0 removed the old exception and added a new one"); - r.project("y0", "y0", yScale); - assert.deepEqual(getExceptions(), [], "projecting a non-constant y0 removes the padding exception"); + r.y0(function (d) { return d.y0; }, yScale); + assert.deepEqual(getExceptions(), [], "projecting a non-constant y0 removes the padding exception 2"); dataset.data([{ x: 0, y: 0, y0: 0 }, { x: 5, y: 5, y0: 0 }]); assert.deepEqual(getExceptions(), [0], "changing to constant values via change in datasource adds exception"); svg.remove(); }); }); -/// -var assert = chai.assert; -describe("Coordinators", function () { - describe("ScaleDomainCoordinator", function () { - it("domains are coordinated", function () { - var s1 = new Plottable.Scale.Linear(); - var s2 = new Plottable.Scale.Linear(); - var s3 = new Plottable.Scale.Linear(); - var dc = new Plottable._Util.ScaleDomainCoordinator([s1, s2, s3]); - s1.domain([0, 100]); - assert.deepEqual(s1.domain(), [0, 100]); - assert.deepEqual(s1.domain(), s2.domain()); - assert.deepEqual(s1.domain(), s3.domain()); - s1.domain([-100, 5000]); - assert.deepEqual(s1.domain(), [-100, 5000]); - assert.deepEqual(s1.domain(), s2.domain()); - assert.deepEqual(s1.domain(), s3.domain()); - }); - }); -}); - /// var assert = chai.assert; describe("Scales", function () { - it("Scale's copy() works correctly", function () { + it("Scale alerts listeners when its domain is updated", function () { + var scale = new Plottable.Scale(d3.scale.identity()); + var callbackWasCalled = false; var testCallback = function (listenable) { - return true; // doesn't do anything + assert.strictEqual(listenable, scale, "Callback received the calling scale as the first argument"); + callbackWasCalled = true; }; - var scale = new Plottable.Scale.Linear(); - scale.broadcaster.registerListener(null, testCallback); - var scaleCopy = scale.copy(); - assert.deepEqual(scale.domain(), scaleCopy.domain(), "Copied scale has the same domain as the original."); - assert.deepEqual(scale.range(), scaleCopy.range(), "Copied scale has the same range as the original."); - assert.notDeepEqual(scale.broadcaster, scaleCopy.broadcaster, "Broadcasters are not copied over"); + scale.onUpdate(testCallback); + scale.domain([0, 10]); + assert.isTrue(callbackWasCalled, "The registered callback was called"); }); - it("Scale alerts listeners when its domain is updated", function () { - var scale = new Plottable.Scale.Linear(); + it("Scale update listeners can be turned off", function () { + var scale = new Plottable.Scale(d3.scale.identity()); var callbackWasCalled = false; var testCallback = function (listenable) { - assert.equal(listenable, scale, "Callback received the calling scale as the first argument"); + assert.strictEqual(listenable, scale, "Callback received the calling scale as the first argument"); callbackWasCalled = true; }; - scale.broadcaster.registerListener(null, testCallback); + scale.onUpdate(testCallback); scale.domain([0, 10]); assert.isTrue(callbackWasCalled, "The registered callback was called"); - scale._autoDomainAutomatically = true; - scale._updateExtent("1", "x", [0.08, 9.92]); callbackWasCalled = false; - scale.domainer(new Plottable.Domainer().nice()); - assert.isTrue(callbackWasCalled, "The registered callback was called when nice() is used to set the domain"); - callbackWasCalled = false; - scale.domainer(new Plottable.Domainer().pad()); - assert.isTrue(callbackWasCalled, "The registered callback was called when padDomain() is used to set the domain"); + scale.offUpdate(testCallback); + scale.domain([11, 19]); + assert.isFalse(callbackWasCalled, "The registered callback was not called because the callback was removed"); }); describe("autoranging behavior", function () { var data; @@ -7471,18 +7066,18 @@ describe("Scales", function () { beforeEach(function () { data = [{ foo: 2, bar: 1 }, { foo: 5, bar: -20 }, { foo: 0, bar: 0 }]; dataset = new Plottable.Dataset(data); - scale = new Plottable.Scale.Linear(); + scale = new Plottable.Scales.Linear(); }); it("scale autoDomain flag is not overwritten without explicitly setting the domain", function () { - scale._updateExtent("1", "x", d3.extent(data, function (e) { return e.foo; })); + scale.addExtentsProvider(function (scale) { return [d3.extent(data, function (e) { return e.foo; })]; }); scale.domainer(new Plottable.Domainer().pad().nice()); assert.isTrue(scale._autoDomainAutomatically, "the autoDomain flag is still set after autoranginging and padding and nice-ing"); scale.domain([0, 5]); assert.isFalse(scale._autoDomainAutomatically, "the autoDomain flag is false after domain explicitly set"); }); it("scale autorange works as expected with single dataset", function () { - var svg = generateSVG(100, 100); - var renderer = new Plottable.Plot.AbstractPlot().addDataset(dataset).project("x", "foo", scale).renderTo(svg); + var svg = TestMethods.generateSVG(100, 100); + new Plottable.Plot().addDataset(dataset).attr("x", function (d) { return d.foo; }, scale).renderTo(svg); assert.deepEqual(scale.domain(), [0, 5], "scale domain was autoranged properly"); data.push({ foo: 100, bar: 200 }); dataset.data(data); @@ -7490,89 +7085,77 @@ describe("Scales", function () { svg.remove(); }); it("scale reference counting works as expected", function () { - var svg1 = generateSVG(100, 100); - var svg2 = generateSVG(100, 100); - var renderer1 = new Plottable.Plot.AbstractPlot().addDataset(dataset).project("x", "foo", scale); + var svg1 = TestMethods.generateSVG(100, 100); + var svg2 = TestMethods.generateSVG(100, 100); + var renderer1 = new Plottable.Plot().addDataset(dataset).attr("x", function (d) { return d.foo; }, scale); renderer1.renderTo(svg1); - var renderer2 = new Plottable.Plot.AbstractPlot().addDataset(dataset).project("x", "foo", scale); + var renderer2 = new Plottable.Plot().addDataset(dataset).attr("x", function (d) { return d.foo; }, scale); renderer2.renderTo(svg2); - var otherScale = new Plottable.Scale.Linear(); - renderer1.project("x", "foo", otherScale); + var otherScale = new Plottable.Scales.Linear(); + renderer1.attr("x", function (d) { return d.foo; }, otherScale); dataset.data([{ foo: 10 }, { foo: 11 }]); assert.deepEqual(scale.domain(), [10, 11], "scale was still listening to dataset after one perspective deregistered"); - renderer2.project("x", "foo", otherScale); + renderer2.attr("x", function (d) { return d.foo; }, otherScale); // "scale not listening to the dataset after all perspectives removed" dataset.data([{ foo: 99 }, { foo: 100 }]); assert.deepEqual(scale.domain(), [0, 1], "scale shows default values when all perspectives removed"); svg1.remove(); svg2.remove(); }); - it("scale perspectives can be removed appropriately", function () { - assert.isTrue(scale._autoDomainAutomatically, "autoDomain enabled1"); - scale._updateExtent("1", "x", d3.extent(data, function (e) { return e.foo; })); - scale._updateExtent("2", "x", d3.extent(data, function (e) { return e.bar; })); - assert.isTrue(scale._autoDomainAutomatically, "autoDomain enabled2"); - assert.deepEqual(scale.domain(), [-20, 5], "scale domain includes both perspectives"); - assert.isTrue(scale._autoDomainAutomatically, "autoDomain enabled3"); - scale._removeExtent("1", "x"); - assert.isTrue(scale._autoDomainAutomatically, "autoDomain enabled4"); - assert.closeTo(scale.domain()[0], -20, 0.1, "only the bar accessor is active"); - assert.closeTo(scale.domain()[1], 1, 0.1, "only the bar accessor is active"); - scale._updateExtent("2", "x", d3.extent(data, function (e) { return e.foo; })); - assert.isTrue(scale._autoDomainAutomatically, "autoDomain enabled5"); - assert.closeTo(scale.domain()[0], 0, 0.1, "the bar accessor was overwritten"); - assert.closeTo(scale.domain()[1], 5, 0.1, "the bar accessor was overwritten"); + it("addExtentsProvider()", function () { + scale.addExtentsProvider(function (scale) { return [[0, 10]]; }); + scale.autoDomain(); + assert.deepEqual(scale.domain(), [0, 10], "scale domain accounts for first provider"); + scale.addExtentsProvider(function (scale) { return [[-10, 0]]; }); + scale.autoDomain(); + assert.deepEqual(scale.domain(), [-10, 10], "scale domain accounts for second provider"); + }); + it("removeExtentsProvider()", function () { + var posProvider = function (scale) { return [[0, 10]]; }; + scale.addExtentsProvider(posProvider); + var negProvider = function (scale) { return [[-10, 0]]; }; + scale.addExtentsProvider(negProvider); + scale.autoDomain(); + assert.deepEqual(scale.domain(), [-10, 10], "scale domain accounts for both providers"); + scale.removeExtentsProvider(negProvider); + scale.autoDomain(); + assert.deepEqual(scale.domain(), [0, 10], "scale domain only accounts for remaining provider"); }); it("should resize when a plot is removed", function () { - var svg = generateSVG(400, 400); - var ds1 = [{ x: 0, y: 0 }, { x: 1, y: 1 }]; - var ds2 = [{ x: 1, y: 1 }, { x: 2, y: 2 }]; - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); + var svg = TestMethods.generateSVG(400, 400); + var ds1 = new Plottable.Dataset([{ x: 0, y: 0 }, { x: 1, y: 1 }]); + var ds2 = new Plottable.Dataset([{ x: 1, y: 1 }, { x: 2, y: 2 }]); + var xScale = new Plottable.Scales.Linear(); + var yScale = new Plottable.Scales.Linear(); xScale.domainer(new Plottable.Domainer()); - var xAxis = new Plottable.Axis.Numeric(xScale, "bottom"); - var yAxis = new Plottable.Axis.Numeric(yScale, "left"); - var renderAreaD1 = new Plottable.Plot.Line(xScale, yScale); + var renderAreaD1 = new Plottable.Plots.Line(xScale, yScale); renderAreaD1.addDataset(ds1); - renderAreaD1.project("x", "x", xScale); - renderAreaD1.project("y", "y", yScale); - var renderAreaD2 = new Plottable.Plot.Line(xScale, yScale); + renderAreaD1.x(function (d) { return d.x; }, xScale); + renderAreaD1.y(function (d) { return d.y; }, yScale); + var renderAreaD2 = new Plottable.Plots.Line(xScale, yScale); renderAreaD2.addDataset(ds2); - renderAreaD2.project("x", "x", xScale); - renderAreaD2.project("y", "y", yScale); - var renderAreas = renderAreaD1.below(renderAreaD2); + renderAreaD2.x(function (d) { return d.x; }, xScale); + renderAreaD2.y(function (d) { return d.y; }, yScale); + var renderAreas = new Plottable.Components.Group([renderAreaD1, renderAreaD2]); renderAreas.renderTo(svg); assert.deepEqual(xScale.domain(), [0, 2]); renderAreaD1.detach(); assert.deepEqual(xScale.domain(), [1, 2], "resize on plot.detach()"); - renderAreas.below(renderAreaD1); + renderAreas.append(renderAreaD1); assert.deepEqual(xScale.domain(), [0, 2], "resize on plot.merge()"); svg.remove(); }); }); describe("Quantitative Scales", function () { it("autorange defaults to [0, 1] if no perspectives set", function () { - var scale = new Plottable.Scale.Linear(); + var scale = new Plottable.Scales.Linear(); scale.autoDomain(); var d = scale.domain(); - assert.equal(d[0], 0); - assert.equal(d[1], 1); - }); - it("can change the number of ticks generated", function () { - var scale = new Plottable.Scale.Linear(); - var ticks10 = scale.ticks(); - assert.closeTo(ticks10.length, 10, 1, "defaults to (about) 10 ticks"); - scale.numTicks(20); - var ticks20 = scale.ticks(); - assert.closeTo(ticks20.length, 20, 1, "can request a different number of ticks"); - }); - it("autorange defaults to [1, 10] on log scale", function () { - var scale = new Plottable.Scale.Log(); - scale.autoDomain(); - assert.deepEqual(scale.domain(), [1, 10]); + assert.strictEqual(d[0], 0); + assert.strictEqual(d[1], 1); }); it("domain can't include NaN or Infinity", function () { - var scale = new Plottable.Scale.Linear(); + var scale = new Plottable.Scales.Linear(); scale.domain([0, 1]); scale.domain([5, Infinity]); assert.deepEqual(scale.domain(), [0, 1], "Infinity containing domain was ignored"); @@ -7583,23 +7166,8 @@ describe("Scales", function () { scale.domain([-1, 5]); assert.deepEqual(scale.domain(), [-1, 5], "Regular domains still accepted"); }); - it("autoranges appropriately even if stringy numbers are projected", function () { - var sadTimesData = ["999", "10", "100", "1000", "2", "999"]; - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - var plot = new Plottable.Plot.Scatter(xScale, yScale); - plot.addDataset(sadTimesData); - var id = function (d) { return d; }; - xScale.domainer(new Plottable.Domainer()); // to disable padding, etc - plot.project("x", id, xScale); - plot.project("y", id, yScale); - var svg = generateSVG(); - plot.renderTo(svg); - assert.deepEqual(xScale.domain(), [2, 1000], "the domain was calculated appropriately"); - svg.remove(); - }); it("custom tick generator", function () { - var scale = new Plottable.Scale.Linear(); + var scale = new Plottable.Scales.Linear(); scale.domain([0, 10]); var ticks = scale.ticks(); assert.closeTo(ticks.length, 10, 1, "ticks were generated correctly with default generator"); @@ -7610,7 +7178,7 @@ describe("Scales", function () { }); describe("Category Scales", function () { it("rangeBand is updated when domain changes", function () { - var scale = new Plottable.Scale.Category(); + var scale = new Plottable.Scales.Category(); scale.range([0, 2679]); scale.domain(["1", "2", "3", "4"]); assert.closeTo(scale.rangeBand(), 399, 1); @@ -7618,7 +7186,7 @@ describe("Scales", function () { assert.closeTo(scale.rangeBand(), 329, 1); }); it("stepWidth operates normally", function () { - var scale = new Plottable.Scale.Category(); + var scale = new Plottable.Scales.Category(); scale.range([0, 3000]); scale.domain(["1", "2", "3", "4"]); var widthSum = scale.rangeBand() * (1 + scale.innerPadding()); @@ -7627,16 +7195,17 @@ describe("Scales", function () { }); it("CategoryScale + BarPlot combo works as expected when the data is swapped", function () { // This unit test taken from SLATE, see SLATE-163 a fix for SLATE-102 - var xScale = new Plottable.Scale.Category(); - var yScale = new Plottable.Scale.Linear(); + var xScale = new Plottable.Scales.Category(); + var yScale = new Plottable.Scales.Linear(); var dA = { x: "A", y: 2 }; var dB = { x: "B", y: 2 }; var dC = { x: "C", y: 2 }; var dataset = new Plottable.Dataset([dA, dB]); - var barPlot = new Plottable.Plot.Bar(xScale, yScale).addDataset(dataset); - barPlot.project("x", "x", xScale); - barPlot.project("y", "y", yScale); - var svg = generateSVG(); + var barPlot = new Plottable.Plots.Bar(xScale, yScale); + barPlot.addDataset(dataset); + barPlot.x(function (d) { return d.x; }, xScale); + barPlot.y(function (d) { return d.y; }, yScale); + var svg = TestMethods.generateSVG(); assert.deepEqual(xScale.domain(), [], "before anchoring, the bar plot doesn't proxy data to the scale"); barPlot.renderTo(svg); assert.deepEqual(xScale.domain(), ["A", "B"], "after anchoring, the bar plot's data is on the scale"); @@ -7659,48 +7228,48 @@ describe("Scales", function () { }); describe("Color Scales", function () { it("accepts categorical string types and Category domain", function () { - var scale = new Plottable.Scale.Color("10"); + var scale = new Plottable.Scales.Color("10"); scale.domain(["yes", "no", "maybe"]); - assert.equal("#1f77b4", scale.scale("yes")); - assert.equal("#ff7f0e", scale.scale("no")); - assert.equal("#2ca02c", scale.scale("maybe")); + assert.strictEqual("#1f77b4", scale.scale("yes")); + assert.strictEqual("#ff7f0e", scale.scale("no")); + assert.strictEqual("#2ca02c", scale.scale("maybe")); }); it("default colors are generated", function () { - var scale = new Plottable.Scale.Color(); + var scale = new Plottable.Scales.Color(); var colorArray = ["#5279c7", "#fd373e", "#63c261", "#fad419", "#2c2b6f", "#ff7939", "#db2e65", "#99ce50", "#962565", "#06cccc"]; assert.deepEqual(scale.range(), colorArray); }); it("uses altered colors if size of domain exceeds size of range", function () { - var scale = new Plottable.Scale.Color(); + var scale = new Plottable.Scales.Color(); scale.range(["#5279c7", "#fd373e"]); scale.domain(["a", "b", "c"]); assert.notEqual(scale.scale("c"), "#5279c7"); }); it("interprets named color values correctly", function () { - var scale = new Plottable.Scale.Color(); + var scale = new Plottable.Scales.Color(); scale.range(["red", "blue"]); scale.domain(["a", "b"]); - assert.equal(scale.scale("a"), "#ff0000"); - assert.equal(scale.scale("b"), "#0000ff"); + assert.strictEqual(scale.scale("a"), "#ff0000"); + assert.strictEqual(scale.scale("b"), "#0000ff"); }); it("accepts CSS specified colors", function () { var style = d3.select("body").append("style"); style.html(".plottable-colors-0 {background-color: #ff0000 !important; }"); - var scale = new Plottable.Scale.Color(); + var scale = new Plottable.Scales.Color(); style.remove(); assert.strictEqual(scale.range()[0], "#ff0000", "User has specified red color for first color scale color"); assert.strictEqual(scale.range()[1], "#fd373e", "The second color of the color scale should be the same"); - var defaultScale = new Plottable.Scale.Color(); + var defaultScale = new Plottable.Scales.Color(); assert.strictEqual(scale.range()[0], "#ff0000", "Unloading the CSS should not modify the first scale color (this will not be the case if we support dynamic CSS"); assert.strictEqual(defaultScale.range()[0], "#5279c7", "Unloading the CSS should cause color scales fallback to default colors"); }); it("should try to recover from malicious CSS styleseets", function () { var defaultNumberOfColors = 10; - var initialScale = new Plottable.Scale.Color(); + var initialScale = new Plottable.Scales.Color(); assert.strictEqual(initialScale.range().length, defaultNumberOfColors, "there should initially be " + defaultNumberOfColors + " default colors"); var maliciousStyle = d3.select("body").append("style"); maliciousStyle.html("* {background-color: #fff000;}"); - var affectedScale = new Plottable.Scale.Color(); + var affectedScale = new Plottable.Scales.Color(); maliciousStyle.remove(); var colorRange = affectedScale.range(); assert.strictEqual(colorRange.length, defaultNumberOfColors + 1, "it should detect the end of the given colors and the fallback to the * selector, " + "but should still include the last occurance of the * selector color"); @@ -7708,78 +7277,72 @@ describe("Scales", function () { assert.notStrictEqual(colorRange[colorRange.length - 2], "#fff000", "the * selector background color should be added at most once at the end"); }); it("does not crash by malicious CSS stylesheets", function () { - var initialScale = new Plottable.Scale.Color(); + var initialScale = new Plottable.Scales.Color(); assert.strictEqual(initialScale.range().length, 10, "there should initially be 10 default colors"); var maliciousStyle = d3.select("body").append("style"); maliciousStyle.html("[class^='plottable-'] {background-color: pink;}"); - var affectedScale = new Plottable.Scale.Color(); + var affectedScale = new Plottable.Scales.Color(); maliciousStyle.remove(); - var maximumColorsFromCss = Plottable.Scale.Color.MAXIMUM_COLORS_FROM_CSS; + var maximumColorsFromCss = Plottable.Scales.Color.MAXIMUM_COLORS_FROM_CSS; assert.strictEqual(affectedScale.range().length, maximumColorsFromCss, "current malicious CSS countermeasure is to cap maximum number of colors to 256"); }); }); describe("Interpolated Color Scales", function () { it("default scale uses reds and a linear scale type", function () { - var scale = new Plottable.Scale.InterpolatedColor(); + var scale = new Plottable.Scales.InterpolatedColor(); scale.domain([0, 16]); - assert.equal("#ffffff", scale.scale(0)); - assert.equal("#feb24c", scale.scale(8)); - assert.equal("#b10026", scale.scale(16)); + assert.strictEqual("#ffffff", scale.scale(0)); + assert.strictEqual("#feb24c", scale.scale(8)); + assert.strictEqual("#b10026", scale.scale(16)); }); it("linearly interpolates colors in L*a*b color space", function () { - var scale = new Plottable.Scale.InterpolatedColor("reds"); + var scale = new Plottable.Scales.InterpolatedColor(); scale.domain([0, 1]); - assert.equal("#b10026", scale.scale(1)); - assert.equal("#d9151f", scale.scale(0.9)); + assert.strictEqual("#b10026", scale.scale(1)); + assert.strictEqual("#d9151f", scale.scale(0.9)); }); it("accepts array types with color hex values", function () { - var scale = new Plottable.Scale.InterpolatedColor(["#000", "#FFF"]); + var scale = new Plottable.Scales.InterpolatedColor(["#000", "#FFF"]); scale.domain([0, 16]); - assert.equal("#000000", scale.scale(0)); - assert.equal("#ffffff", scale.scale(16)); - assert.equal("#777777", scale.scale(8)); + assert.strictEqual("#000000", scale.scale(0)); + assert.strictEqual("#ffffff", scale.scale(16)); + assert.strictEqual("#777777", scale.scale(8)); }); it("accepts array types with color names", function () { - var scale = new Plottable.Scale.InterpolatedColor(["black", "white"]); + var scale = new Plottable.Scales.InterpolatedColor(["black", "white"]); scale.domain([0, 16]); - assert.equal("#000000", scale.scale(0)); - assert.equal("#ffffff", scale.scale(16)); - assert.equal("#777777", scale.scale(8)); + assert.strictEqual("#000000", scale.scale(0)); + assert.strictEqual("#ffffff", scale.scale(16)); + assert.strictEqual("#777777", scale.scale(8)); }); it("overflow scale values clamp to range", function () { - var scale = new Plottable.Scale.InterpolatedColor(["black", "white"]); + var scale = new Plottable.Scales.InterpolatedColor(["black", "white"]); scale.domain([0, 16]); - assert.equal("#000000", scale.scale(0)); - assert.equal("#ffffff", scale.scale(16)); - assert.equal("#000000", scale.scale(-100)); - assert.equal("#ffffff", scale.scale(100)); + assert.strictEqual("#000000", scale.scale(0)); + assert.strictEqual("#ffffff", scale.scale(16)); + assert.strictEqual("#000000", scale.scale(-100)); + assert.strictEqual("#ffffff", scale.scale(100)); }); it("can be converted to a different range", function () { - var scale = new Plottable.Scale.InterpolatedColor(["black", "white"]); + var scale = new Plottable.Scales.InterpolatedColor(["black", "white"]); scale.domain([0, 16]); - assert.equal("#000000", scale.scale(0)); - assert.equal("#ffffff", scale.scale(16)); - scale.colorRange("reds"); - assert.equal("#b10026", scale.scale(16)); - }); - it("can be converted to a different scale type", function () { - var scale = new Plottable.Scale.InterpolatedColor(["black", "white"]); - scale.domain([0, 16]); - assert.equal("#000000", scale.scale(0)); - assert.equal("#ffffff", scale.scale(16)); - assert.equal("#777777", scale.scale(8)); - scale.scaleType("log"); - assert.equal("#000000", scale.scale(0)); - assert.equal("#ffffff", scale.scale(16)); - assert.equal("#e3e3e3", scale.scale(8)); + assert.strictEqual("#000000", scale.scale(0)); + assert.strictEqual("#ffffff", scale.scale(16)); + scale.colorRange(Plottable.Scales.InterpolatedColor.REDS); + assert.strictEqual("#b10026", scale.scale(16)); }); }); +}); + +/// +var assert = chai.assert; +describe("Scales", function () { describe("Modified Log Scale", function () { var scale; var base = 10; var epsilon = 0.00001; beforeEach(function () { - scale = new Plottable.Scale.ModifiedLog(base); + scale = new Plottable.Scales.ModifiedLog(base); }); it("is an increasing, continuous function that can go negative", function () { d3.range(-base * 2, base * 2, base / 20).forEach(function (x) { @@ -7792,7 +7355,7 @@ describe("Scales", function () { }); assert.closeTo(scale.scale(0), 0, epsilon); }); - it("is close to log() for large values", function () { + it("Has log() behavior at values > base", function () { [10, 100, 23103.4, 5].forEach(function (x) { assert.closeTo(scale.scale(x), Math.log(x) / Math.log(10), 0.1); }); @@ -7803,12 +7366,12 @@ describe("Scales", function () { assert.closeTo(x, scale.scale(scale.invert(x)), epsilon); }); }); - it("domain defaults to [0, 1]", function () { - scale = new Plottable.Scale.ModifiedLog(base); - assert.deepEqual(scale.domain(), [0, 1]); + it("domain defaults to [0, base]", function () { + scale = new Plottable.Scales.ModifiedLog(base); + assert.deepEqual(scale.domain(), [0, base]); }); - it("works with a domainer", function () { - scale._updateExtent("1", "x", [0, base * 2]); + it("works with a Domainer", function () { + scale.addExtentsProvider(function (scale) { return [[0, base * 2]]; }); var domain = scale.domain(); scale.domainer(new Plottable.Domainer().pad(0.1)); assert.operator(scale.domain()[0], "<", domain[0]); @@ -7816,15 +7379,18 @@ describe("Scales", function () { scale.domainer(new Plottable.Domainer().nice()); assert.operator(scale.domain()[0], "<=", domain[0]); assert.operator(domain[1], "<=", scale.domain()[1]); - scale = new Plottable.Scale.ModifiedLog(base); + scale = new Plottable.Scales.ModifiedLog(base); scale.domainer(new Plottable.Domainer()); - assert.deepEqual(scale.domain(), [0, 1]); + assert.deepEqual(scale.domain(), [0, base]); }); it("gives reasonable values for ticks()", function () { - scale._updateExtent("1", "x", [0, base / 2]); + var providedExtents = [[0, base / 2]]; + scale.addExtentsProvider(function (scale) { return providedExtents; }); + scale.autoDomain(); var ticks = scale.ticks(); assert.operator(ticks.length, ">", 0); - scale._updateExtent("1", "x", [-base * 2, base * 2]); + providedExtents = [[-base * 2, base * 2]]; + scale.autoDomain(); ticks = scale.ticks(); var beforePivot = ticks.filter(function (x) { return x <= -base; }); var afterPivot = ticks.filter(function (x) { return base <= x; }); @@ -7834,7 +7400,8 @@ describe("Scales", function () { assert.operator(betweenPivots.length, ">", 0, "should be ticks between -base and base"); }); it("works on inverted domain", function () { - scale._updateExtent("1", "x", [200, -100]); + scale.addExtentsProvider(function (scale) { return [[200, -100]]; }); + scale.autoDomain(); var range = scale.range(); assert.closeTo(scale.scale(-100), range[1], epsilon); assert.closeTo(scale.scale(200), range[0], epsilon); @@ -7844,7 +7411,7 @@ describe("Scales", function () { assert.deepEqual(b.slice().reverse(), b.slice().sort(function (x, y) { return x - y; })); var ticks = scale.ticks(); assert.deepEqual(ticks, ticks.slice().sort(function (x, y) { return x - y; }), "ticks should be sorted"); - assert.deepEqual(ticks, Plottable._Util.Methods.uniq(ticks), "ticks should not be repeated"); + assert.deepEqual(ticks, Plottable.Utils.Methods.uniq(ticks), "ticks should not be repeated"); var beforePivot = ticks.filter(function (x) { return x <= -base; }); var afterPivot = ticks.filter(function (x) { return base <= x; }); var betweenPivots = ticks.filter(function (x) { return -base < x && x < base; }); @@ -7853,8 +7420,11 @@ describe("Scales", function () { assert.operator(betweenPivots.length, ">", 0, "should be ticks between -base and base"); }); it("ticks() is always non-empty", function () { - [[2, 9], [0, 1], [1, 2], [0.001, 0.01], [-0.1, 0.1], [-3, -2]].forEach(function (domain) { - scale._updateExtent("1", "x", domain); + var desiredExtents = []; + scale.addExtentsProvider(function (scale) { return desiredExtents; }); + [[2, 9], [0, 1], [1, 2], [0.001, 0.01], [-0.1, 0.1], [-3, -2]].forEach(function (extent) { + desiredExtents = [extent]; + scale.autoDomain(); var ticks = scale.ticks(); assert.operator(ticks.length, ">", 0); }); @@ -7865,64 +7435,40 @@ describe("Scales", function () { /// var assert = chai.assert; describe("TimeScale tests", function () { - it("parses reasonable formats for dates", function () { - var scale = new Plottable.Scale.Time(); - var firstDate = new Date(2014, 9, 1, 0, 0, 0, 0).valueOf(); - var secondDate = new Date(2014, 10, 1, 0, 0, 0).valueOf(); - function checkDomain(domain) { - scale.domain(domain); - var time1 = scale.domain()[0].valueOf(); - assert.equal(time1, firstDate, "first value of domain set correctly"); - var time2 = scale.domain()[1].valueOf(); - assert.equal(time2, secondDate, "first value of domain set correctly"); - } - checkDomain(["10/1/2014", "11/1/2014"]); - checkDomain(["October 1, 2014", "November 1, 2014"]); - checkDomain(["Oct 1, 2014", "Nov 1, 2014"]); - }); it("can't set reversed domain", function () { - var scale = new Plottable.Scale.Time(); - assert.throws(function () { return scale.domain(["1985-10-26", "1955-11-05"]); }, "chronological"); - }); - it("time coercer works as intended", function () { - var tc = new Plottable.Scale.Time()._typeCoercer; - assert.equal(tc(null).getMilliseconds(), 0, "null converted to Date(0)"); - // converting null to Date(0) is the correct behavior as it mirror's d3's semantics - assert.equal(tc("Wed Dec 31 1969 16:00:00 GMT-0800 (PST)").getMilliseconds(), 0, "string parsed to date"); - assert.equal(tc(0).getMilliseconds(), 0, "number parsed to date"); - var d = new Date(0); - assert.equal(tc(d), d, "date passed thru unchanged"); + var scale = new Plottable.Scales.Time(); + assert.throws(function () { return scale.domain([new Date("1985-10-26"), new Date("1955-11-05")]); }, "chronological"); }); it("tickInterval produces correct number of ticks", function () { - var scale = new Plottable.Scale.Time(); + var scale = new Plottable.Scales.Time(); // 100 year span scale.domain([new Date(2000, 0, 1, 0, 0, 0, 0), new Date(2100, 0, 1, 0, 0, 0, 0)]); - var ticks = scale.tickInterval(d3.time.year); - assert.equal(ticks.length, 101, "generated correct number of ticks"); + var ticks = scale.tickInterval(Plottable.TimeInterval.year); + assert.strictEqual(ticks.length, 101, "generated correct number of ticks"); // 1 year span scale.domain([new Date(2000, 0, 1, 0, 0, 0, 0), new Date(2000, 11, 31, 0, 0, 0, 0)]); - ticks = scale.tickInterval(d3.time.month); - assert.equal(ticks.length, 12, "generated correct number of ticks"); - ticks = scale.tickInterval(d3.time.month, 3); - assert.equal(ticks.length, 4, "generated correct number of ticks"); + ticks = scale.tickInterval(Plottable.TimeInterval.month); + assert.strictEqual(ticks.length, 12, "generated correct number of ticks"); + ticks = scale.tickInterval(Plottable.TimeInterval.month, 3); + assert.strictEqual(ticks.length, 4, "generated correct number of ticks"); // 1 month span scale.domain([new Date(2000, 0, 1, 0, 0, 0, 0), new Date(2000, 1, 1, 0, 0, 0, 0)]); - ticks = scale.tickInterval(d3.time.day); - assert.equal(ticks.length, 32, "generated correct number of ticks"); + ticks = scale.tickInterval(Plottable.TimeInterval.day); + assert.strictEqual(ticks.length, 32, "generated correct number of ticks"); // 1 day span scale.domain([new Date(2000, 0, 1, 0, 0, 0, 0), new Date(2000, 0, 1, 23, 0, 0, 0)]); - ticks = scale.tickInterval(d3.time.hour); - assert.equal(ticks.length, 24, "generated correct number of ticks"); + ticks = scale.tickInterval(Plottable.TimeInterval.hour); + assert.strictEqual(ticks.length, 24, "generated correct number of ticks"); // 1 hour span scale.domain([new Date(2000, 0, 1, 0, 0, 0, 0), new Date(2000, 0, 1, 1, 0, 0, 0)]); - ticks = scale.tickInterval(d3.time.minute); - assert.equal(ticks.length, 61, "generated correct number of ticks"); - ticks = scale.tickInterval(d3.time.minute, 10); - assert.equal(ticks.length, 7, "generated correct number of ticks"); + ticks = scale.tickInterval(Plottable.TimeInterval.minute); + assert.strictEqual(ticks.length, 61, "generated correct number of ticks"); + ticks = scale.tickInterval(Plottable.TimeInterval.minute, 10); + assert.strictEqual(ticks.length, 7, "generated correct number of ticks"); // 1 minute span scale.domain([new Date(2000, 0, 1, 0, 0, 0, 0), new Date(2000, 0, 1, 0, 1, 0, 0)]); - ticks = scale.tickInterval(d3.time.second); - assert.equal(ticks.length, 61, "generated correct number of ticks"); + ticks = scale.tickInterval(Plottable.TimeInterval.second); + assert.strictEqual(ticks.length, 61, "generated correct number of ticks"); }); }); @@ -7932,53 +7478,52 @@ describe("Tick generators", function () { describe("interval", function () { it("generate ticks within domain", function () { var start = 0.5, end = 4.01, interval = 1; - var scale = new Plottable.Scale.Linear().domain([start, end]); - var ticks = Plottable.Scale.TickGenerators.intervalTickGenerator(interval)(scale); + var scale = new Plottable.Scales.Linear().domain([start, end]); + var ticks = Plottable.Scales.TickGenerators.intervalTickGenerator(interval)(scale); assert.deepEqual(ticks, [0.5, 1, 2, 3, 4, 4.01], "generated ticks contains all possible ticks within range"); }); it("domain crossing 0", function () { var start = -1.5, end = 1, interval = 0.5; - var scale = new Plottable.Scale.Linear().domain([start, end]); - var ticks = Plottable.Scale.TickGenerators.intervalTickGenerator(interval)(scale); + var scale = new Plottable.Scales.Linear().domain([start, end]); + var ticks = Plottable.Scales.TickGenerators.intervalTickGenerator(interval)(scale); assert.deepEqual(ticks, [-1.5, -1, -0.5, 0, 0.5, 1], "generated all number divisible by 0.5 in domain"); }); it("generate ticks with reversed domain", function () { var start = -2.2, end = -7.6, interval = 2.5; - var scale = new Plottable.Scale.Linear().domain([start, end]); - var ticks = Plottable.Scale.TickGenerators.intervalTickGenerator(interval)(scale); + var scale = new Plottable.Scales.Linear().domain([start, end]); + var ticks = Plottable.Scales.TickGenerators.intervalTickGenerator(interval)(scale); assert.deepEqual(ticks, [-7.6, -7.5, -5, -2.5, -2.2], "generated all ticks between lower and higher value"); }); it("passing big interval", function () { var start = 0.5, end = 10.01, interval = 11; - var scale = new Plottable.Scale.Linear().domain([start, end]); - var ticks = Plottable.Scale.TickGenerators.intervalTickGenerator(interval)(scale); + var scale = new Plottable.Scales.Linear().domain([start, end]); + var ticks = Plottable.Scales.TickGenerators.intervalTickGenerator(interval)(scale); assert.deepEqual(ticks, [0.5, 10.01], "no middle ticks were added"); }); it("passing non positive interval", function () { - var scale = new Plottable.Scale.Linear().domain([0, 1]); - assert.throws(function () { return Plottable.Scale.TickGenerators.intervalTickGenerator(0); }, "interval must be positive number"); - assert.throws(function () { return Plottable.Scale.TickGenerators.intervalTickGenerator(-2); }, "interval must be positive number"); + assert.throws(function () { return Plottable.Scales.TickGenerators.intervalTickGenerator(0); }, "interval must be positive number"); + assert.throws(function () { return Plottable.Scales.TickGenerators.intervalTickGenerator(-2); }, "interval must be positive number"); }); }); describe("integer", function () { it("normal case", function () { - var scale = new Plottable.Scale.Linear().domain([0, 4]); - var ticks = Plottable.Scale.TickGenerators.integerTickGenerator()(scale); + var scale = new Plottable.Scales.Linear().domain([0, 4]); + var ticks = Plottable.Scales.TickGenerators.integerTickGenerator()(scale); assert.deepEqual(ticks, [0, 1, 2, 3, 4], "only the integers are returned"); }); it("works across negative numbers", function () { - var scale = new Plottable.Scale.Linear().domain([-2, 1]); - var ticks = Plottable.Scale.TickGenerators.integerTickGenerator()(scale); + var scale = new Plottable.Scales.Linear().domain([-2, 1]); + var ticks = Plottable.Scales.TickGenerators.integerTickGenerator()(scale); assert.deepEqual(ticks, [-2, -1, 0, 1], "only the integers are returned"); }); it("includes endticks", function () { - var scale = new Plottable.Scale.Linear().domain([-2.7, 1.5]); - var ticks = Plottable.Scale.TickGenerators.integerTickGenerator()(scale); + var scale = new Plottable.Scales.Linear().domain([-2.7, 1.5]); + var ticks = Plottable.Scales.TickGenerators.integerTickGenerator()(scale); assert.deepEqual(ticks, [-2.5, -2, -1, 0, 1, 1.5], "end ticks are included"); }); it("all float ticks", function () { - var scale = new Plottable.Scale.Linear().domain([1.1, 1.5]); - var ticks = Plottable.Scale.TickGenerators.integerTickGenerator()(scale); + var scale = new Plottable.Scales.Linear().domain([1.1, 1.5]); + var ticks = Plottable.Scales.TickGenerators.integerTickGenerator()(scale); assert.deepEqual(ticks, [1.1, 1.5], "only the end ticks are returned"); }); }); @@ -7986,9 +7531,9 @@ describe("Tick generators", function () { /// var assert = chai.assert; -describe("_Util.DOM", function () { +describe("Utils.DOM", function () { it("getBBox works properly", function () { - var svg = generateSVG(); + var svg = TestMethods.generateSVG(); var expectedBox = { x: 0, y: 0, @@ -7996,7 +7541,7 @@ describe("_Util.DOM", function () { height: 20 }; var rect = svg.append("rect").attr(expectedBox); - var measuredBox = Plottable._Util.DOM.getBBox(rect); + var measuredBox = Plottable.Utils.DOM.getBBox(rect); assert.deepEqual(measuredBox, expectedBox, "getBBox measures correctly"); svg.remove(); }); @@ -8007,60 +7552,74 @@ describe("_Util.DOM", function () { width: 40, height: 20 }; - var removedSVG = generateSVG().remove(); + var removedSVG = TestMethods.generateSVG().remove(); var rect = removedSVG.append("rect").attr(expectedBox); - Plottable._Util.DOM.getBBox(rect); // could throw NS_ERROR on FF - var noneSVG = generateSVG().style("display", "none"); + Plottable.Utils.DOM.getBBox(rect); // could throw NS_ERROR on FF + var noneSVG = TestMethods.generateSVG().style("display", "none"); rect = noneSVG.append("rect").attr(expectedBox); - Plottable._Util.DOM.getBBox(rect); // could throw NS_ERROR on FF + Plottable.Utils.DOM.getBBox(rect); // could throw NS_ERROR on FF noneSVG.remove(); }); describe("getElementWidth, getElementHeight", function () { it("can get a plain element's size", function () { - var parent = getSVGParent(); + var parent = TestMethods.getSVGParent(); parent.style("width", "300px"); parent.style("height", "200px"); var parentElem = parent[0][0]; - var width = Plottable._Util.DOM.getElementWidth(parentElem); - assert.equal(width, 300, "measured width matches set width"); - var height = Plottable._Util.DOM.getElementHeight(parentElem); - assert.equal(height, 200, "measured height matches set height"); + var width = Plottable.Utils.DOM.getElementWidth(parentElem); + assert.strictEqual(width, 300, "measured width matches set width"); + var height = Plottable.Utils.DOM.getElementHeight(parentElem); + assert.strictEqual(height, 200, "measured height matches set height"); }); it("can get the svg's size", function () { - var svg = generateSVG(450, 120); + var svg = TestMethods.generateSVG(450, 120); var svgElem = svg[0][0]; - var width = Plottable._Util.DOM.getElementWidth(svgElem); - assert.equal(width, 450, "measured width matches set width"); - var height = Plottable._Util.DOM.getElementHeight(svgElem); - assert.equal(height, 120, "measured height matches set height"); + var width = Plottable.Utils.DOM.getElementWidth(svgElem); + assert.strictEqual(width, 450, "measured width matches set width"); + var height = Plottable.Utils.DOM.getElementHeight(svgElem); + assert.strictEqual(height, 120, "measured height matches set height"); svg.remove(); }); it("can accept multiple units and convert to pixels", function () { - var parent = getSVGParent(); + var parent = TestMethods.getSVGParent(); var parentElem = parent[0][0]; var child = parent.append("div"); var childElem = child[0][0]; parent.style("width", "200px"); parent.style("height", "50px"); - assert.equal(Plottable._Util.DOM.getElementWidth(parentElem), 200, "width is correct"); - assert.equal(Plottable._Util.DOM.getElementHeight(parentElem), 50, "height is correct"); + assert.strictEqual(Plottable.Utils.DOM.getElementWidth(parentElem), 200, "width is correct"); + assert.strictEqual(Plottable.Utils.DOM.getElementHeight(parentElem), 50, "height is correct"); child.style("width", "20px"); child.style("height", "10px"); - assert.equal(Plottable._Util.DOM.getElementWidth(childElem), 20, "width is correct"); - assert.equal(Plottable._Util.DOM.getElementHeight(childElem), 10, "height is correct"); + assert.strictEqual(Plottable.Utils.DOM.getElementWidth(childElem), 20, "width is correct"); + assert.strictEqual(Plottable.Utils.DOM.getElementHeight(childElem), 10, "height is correct"); child.style("width", "100%"); child.style("height", "100%"); - assert.equal(Plottable._Util.DOM.getElementWidth(childElem), 200, "width is correct"); - assert.equal(Plottable._Util.DOM.getElementHeight(childElem), 50, "height is correct"); + assert.strictEqual(Plottable.Utils.DOM.getElementWidth(childElem), 200, "width is correct"); + assert.strictEqual(Plottable.Utils.DOM.getElementHeight(childElem), 50, "height is correct"); child.style("width", "50%"); child.style("height", "50%"); - assert.equal(Plottable._Util.DOM.getElementWidth(childElem), 100, "width is correct"); - assert.equal(Plottable._Util.DOM.getElementHeight(childElem), 25, "height is correct"); + assert.strictEqual(Plottable.Utils.DOM.getElementWidth(childElem), 100, "width is correct"); + assert.strictEqual(Plottable.Utils.DOM.getElementHeight(childElem), 25, "height is correct"); // reset test page DOM parent.style("width", "auto"); parent.style("height", "auto"); child.remove(); }); + it("getUniqueClipPathId works as expected", function () { + var firstClipPathId = Plottable.Utils.DOM.getUniqueClipPathId(); + var secondClipPathId = Plottable.Utils.DOM.getUniqueClipPathId(); + var firstClipPathIDPrefix = firstClipPathId.split(/\d/)[0]; + var secondClipPathIDPrefix = secondClipPathId.split(/\d/)[0]; + assert.strictEqual(firstClipPathIDPrefix, secondClipPathIDPrefix, "clip path ids should have the same prefix"); + var prefix = firstClipPathIDPrefix; + assert.isTrue(/plottable/.test(prefix), "the prefix should contain the word plottable to avoid collisions"); + var firstClipPathIdNumber = +firstClipPathId.replace(prefix, ""); + var secondClipPathIdNumber = +secondClipPathId.replace(prefix, ""); + assert.isFalse(Plottable.Utils.Methods.isNaN(firstClipPathIdNumber), "first clip path id should only have a number after the prefix"); + assert.isFalse(Plottable.Utils.Methods.isNaN(secondClipPathIdNumber), "second clip path id should only have a number after the prefix"); + assert.strictEqual(firstClipPathIdNumber + 1, secondClipPathIdNumber, "Consecutive calls to getUniqueClipPathId should give consecutive numbers after the prefix"); + }); }); }); @@ -8107,12 +7666,10 @@ describe("Formatters", function () { }); it("throws an error on strange precision", function () { assert.throws(function () { - var general = Plottable.Formatters.general(-1); - var result = general(5); + Plottable.Formatters.general(-1); }); assert.throws(function () { - var general = Plottable.Formatters.general(100); - var result = general(5); + Plottable.Formatters.general(100); }); }); }); @@ -8248,38 +7805,85 @@ describe("Formatters", function () { /// var assert = chai.assert; -describe("StrictEqualityAssociativeArray", function () { - it("StrictEqualityAssociativeArray works as expected", function () { - var s = new Plottable._Util.StrictEqualityAssociativeArray(); +describe("Map", function () { + it("Map works as expected", function () { + var map = new Plottable.Utils.Map(); var o1 = {}; var o2 = {}; - assert.isFalse(s.has(o1)); - assert.isFalse(s.delete(o1)); - assert.isUndefined(s.get(o1)); - assert.isFalse(s.set(o1, "foo")); - assert.equal(s.get(o1), "foo"); - assert.isTrue(s.set(o1, "bar")); - assert.equal(s.get(o1), "bar"); - s.set(o2, "baz"); - s.set(3, "bam"); - s.set("3", "ball"); - assert.equal(s.get(o1), "bar"); - assert.equal(s.get(o2), "baz"); - assert.equal(s.get(3), "bam"); - assert.equal(s.get("3"), "ball"); - assert.isTrue(s.delete(3)); - assert.isUndefined(s.get(3)); - assert.equal(s.get(o2), "baz"); - assert.equal(s.get("3"), "ball"); + assert.isFalse(map.has(o1)); + assert.isFalse(map.delete(o1)); + assert.isUndefined(map.get(o1)); + assert.isFalse(map.set(o1, "foo")); + assert.strictEqual(map.get(o1), "foo"); + assert.isTrue(map.set(o1, "bar")); + assert.strictEqual(map.get(o1), "bar"); + map.set(o2, "baz"); + map.set(3, "bam"); + map.set("3", "ball"); + assert.strictEqual(map.get(o1), "bar"); + assert.strictEqual(map.get(o2), "baz"); + assert.strictEqual(map.get(3), "bam"); + assert.strictEqual(map.get("3"), "ball"); + assert.isTrue(map.delete(3)); + assert.isUndefined(map.get(3)); + assert.strictEqual(map.get(o2), "baz"); + assert.strictEqual(map.get("3"), "ball"); }); it("Array-level operations (retrieve keys, vals, and map)", function () { - var s = new Plottable._Util.StrictEqualityAssociativeArray(); - s.set(2, "foo"); - s.set(3, "bar"); - s.set(4, "baz"); - assert.deepEqual(s.values(), ["foo", "bar", "baz"]); - assert.deepEqual(s.keys(), [2, 3, 4]); - assert.deepEqual(s.map(function (k, v, i) { return [k, v, i]; }), [[2, "foo", 0], [3, "bar", 1], [4, "baz", 2]]); + var map = new Plottable.Utils.Map(); + map.set(2, "foo"); + map.set(3, "bar"); + map.set(4, "baz"); + assert.deepEqual(map.values(), ["foo", "bar", "baz"]); + assert.deepEqual(map.keys(), [2, 3, 4]); + assert.deepEqual(map.map(function (k, v, i) { return [k, v, i]; }), [[2, "foo", 0], [3, "bar", 1], [4, "baz", 2]]); + }); +}); + +/// +var assert = chai.assert; +describe("Utils", function () { + describe("Set", function () { + it("add()", function () { + var set = new Plottable.Utils.Set(); + var value1 = { value: "one" }; + set.add(value1); + var setValues = set.values(); + assert.lengthOf(setValues, 1, "set contains one value"); + assert.strictEqual(setValues[0], value1, "the value was added to the set"); + set.add(value1); + setValues = set.values(); + assert.lengthOf(setValues, 1, "same value is not added twice"); + assert.strictEqual(setValues[0], value1, "list still contains the value"); + var value2 = { value: "two" }; + set.add(value2); + setValues = set.values(); + assert.lengthOf(setValues, 2, "set now contains two values"); + assert.strictEqual(setValues[0], value1, "set contains value 1"); + assert.strictEqual(setValues[1], value2, "set contains value 2"); + }); + it("delete()", function () { + var set = new Plottable.Utils.Set(); + var value1 = { value: "one" }; + set.add(value1); + assert.lengthOf(set.values(), 1, "set contains one value after adding"); + set.delete(value1); + assert.lengthOf(set.values(), 0, "value was delete"); + set.add(value1); + var value2 = { value: "two" }; + set.delete(value2); + assert.lengthOf(set.values(), 1, "removing a non-existent value does nothing"); + }); + it("has()", function () { + var set = new Plottable.Utils.Set(); + var value1 = { value: "one" }; + set.add(value1); + assert.isTrue(set.has(value1), "correctly checks that value is in the set"); + var similarValue1 = { value: "one" }; + assert.isFalse(set.has(similarValue1), "correctly determines that similar object is not in the set"); + set.delete(value1); + assert.isFalse(set.has(value1), "correctly checks that value is no longer in the set"); + }); }); }); @@ -8287,15 +7891,15 @@ describe("StrictEqualityAssociativeArray", function () { var assert = chai.assert; describe("ClientToSVGTranslator", function () { it("getTranslator() creates only one ClientToSVGTranslator per ", function () { - var svg = generateSVG(); - var t1 = Plottable._Util.ClientToSVGTranslator.getTranslator(svg.node()); + var svg = TestMethods.generateSVG(); + var t1 = Plottable.Utils.ClientToSVGTranslator.getTranslator(svg.node()); assert.isNotNull(t1, "created a new ClientToSVGTranslator on a "); - var t2 = Plottable._Util.ClientToSVGTranslator.getTranslator(svg.node()); + var t2 = Plottable.Utils.ClientToSVGTranslator.getTranslator(svg.node()); assert.strictEqual(t1, t2, "returned the existing ClientToSVGTranslator if called again with same "); svg.remove(); }); it("converts points to -space correctly", function () { - var svg = generateSVG(); + var svg = TestMethods.generateSVG(); var rectOrigin = { x: 19, y: 85 @@ -8306,52 +7910,50 @@ describe("ClientToSVGTranslator", function () { width: 30, height: 30 }); - var translator = Plottable._Util.ClientToSVGTranslator.getTranslator(svg.node()); + var translator = Plottable.Utils.ClientToSVGTranslator.getTranslator(svg.node()); var rectBCR = rect.node().getBoundingClientRect(); var computedOrigin = translator.computePosition(rectBCR.left, rectBCR.top); - assertPointsClose(computedOrigin, rectOrigin, 0.5, "translates client coordinates to coordinates correctly"); + TestMethods.assertPointsClose(computedOrigin, rectOrigin, 0.5, "translates client coordinates to coordinates correctly"); svg.remove(); }); }); /// var assert = chai.assert; -describe("_Util.Methods", function () { +describe("Utils.Methods", function () { it("inRange works correct", function () { - assert.isTrue(Plottable._Util.Methods.inRange(0, -1, 1), "basic functionality works"); - assert.isTrue(Plottable._Util.Methods.inRange(0, 0, 1), "it is a closed interval"); - assert.isTrue(!Plottable._Util.Methods.inRange(0, 1, 2), "returns false when false"); - }); - it("sortedIndex works properly", function () { - var a = [1, 2, 3, 4, 5]; - var si = Plottable._Util.OpenSource.sortedIndex; - assert.equal(si(0, a), 0, "return 0 when val is <= arr[0]"); - assert.equal(si(6, a), a.length, "returns a.length when val >= arr[arr.length-1]"); - assert.equal(si(1.5, a), 1, "returns 1 when val is between the first and second elements"); - }); - it("accessorize works properly", function () { - var datum = { "foo": 2, "bar": 3, "key": 4 }; - var f = function (d, i, m) { return d + i; }; - var a1 = Plottable._Util.Methods.accessorize(f); - assert.equal(f, a1, "function passes through accessorize unchanged"); - var a2 = Plottable._Util.Methods.accessorize("key"); - assert.equal(a2(datum, 0, null), 4, "key accessor works appropriately"); - var a3 = Plottable._Util.Methods.accessorize("#aaaa"); - assert.equal(a3(datum, 0, null), "#aaaa", "strings beginning with # are returned as final value"); - var a4 = Plottable._Util.Methods.accessorize(33); - assert.equal(a4(datum, 0, null), 33, "numbers are return as final value"); - var a5 = Plottable._Util.Methods.accessorize(datum); - assert.equal(a5(datum, 0, null), datum, "objects are return as final value"); + assert.isTrue(Plottable.Utils.Methods.inRange(0, -1, 1), "basic functionality works"); + assert.isTrue(Plottable.Utils.Methods.inRange(0, 0, 1), "it is a closed interval"); + assert.isTrue(!Plottable.Utils.Methods.inRange(0, 1, 2), "returns false when false"); }); it("uniq works as expected", function () { var strings = ["foo", "bar", "foo", "foo", "baz", "bam"]; - assert.deepEqual(Plottable._Util.Methods.uniq(strings), ["foo", "bar", "baz", "bam"]); + assert.deepEqual(Plottable.Utils.Methods.uniq(strings), ["foo", "bar", "baz", "bam"]); }); - describe("min/max", function () { - var max = Plottable._Util.Methods.max; - var min = Plottable._Util.Methods.min; + describe("max() and min()", function () { + var max = Plottable.Utils.Methods.max; + var min = Plottable.Utils.Methods.min; var today = new Date(); - it("max/min work as expected", function () { + it("return the default value if max or min can't be computed", function () { + var minValue = 1; + var maxValue = 5; + var defaultValue = 3; + var goodArray = [ + [minValue], + [maxValue] + ]; + // bad array is technically of type number[][], but subarrays are empty! + var badArray = [ + [], + [] + ]; + var accessor = function (arr) { return arr[0]; }; + assert.strictEqual(min(goodArray, accessor, defaultValue), minValue, "min(): minimum value is returned in good case"); + assert.strictEqual(min(badArray, accessor, defaultValue), defaultValue, "min(): default value is returned in bad case"); + assert.strictEqual(max(goodArray, accessor, defaultValue), maxValue, "max(): maximum value is returned in good case"); + assert.strictEqual(max(badArray, accessor, defaultValue), defaultValue, "max(): default value is returned in bad case"); + }); + it("max() and min() work on numbers", function () { var alist = [1, 2, 3, 4, 5]; var dbl = function (x) { return x * 2; }; var dblIndexOffset = function (x, i) { return x * 2 - i; }; @@ -8374,12 +7976,12 @@ describe("_Util.Methods", function () { assert.deepEqual(min([], dbl, 5), 5, "min accepts custom default and function"); assert.deepEqual(min([], numToDate, today), today, "min accepts non-numeric default and function"); }); - it("max/min works as expected on non-numeric values (strings)", function () { + it("max() and min() work on strings", function () { var strings = ["a", "bb", "ccc", "ddd"]; assert.deepEqual(max(strings, function (s) { return s.length; }, 0), 3, "works on arrays of non-numbers with a function"); assert.deepEqual(max([], function (s) { return s.length; }, 5), 5, "defaults work even with non-number function type"); }); - it("max/min works as expected on non-numeric values (dates)", function () { + it("max() and min() work on dates", function () { var tomorrow = new Date(today.getTime()); tomorrow.setDate(today.getDate() + 1); var dayAfterTomorrow = new Date(today.getTime()); @@ -8387,12 +7989,12 @@ describe("_Util.Methods", function () { var dates = [today, tomorrow, dayAfterTomorrow, null]; assert.deepEqual(min(dates, dayAfterTomorrow), today, "works on arrays of non-numeric values but comparable"); assert.deepEqual(max(dates, today), dayAfterTomorrow, "works on arrays of non-number values but comparable"); - assert.deepEqual(max([null], today), undefined, "returns undefined from array of null values"); - assert.deepEqual(max([], today), today, "correct default non-numeric value returned"); + assert.deepEqual(max([null], today), today, "returns default value if passed array of null values"); + assert.deepEqual(max([], today), today, "returns default value if passed empty"); }); }); it("isNaN works as expected", function () { - var isNaN = Plottable._Util.Methods.isNaN; + var isNaN = Plottable.Utils.Methods.isNaN; assert.isTrue(isNaN(NaN), "Only NaN should pass the isNaN check"); assert.isFalse(isNaN(undefined), "undefined should fail the isNaN check"); assert.isFalse(isNaN(null), "null should fail the isNaN check"); @@ -8404,7 +8006,7 @@ describe("_Util.Methods", function () { assert.isFalse(isNaN({}), "empty Objects should fail the isNaN check"); }); it("isValidNumber works as expected", function () { - var isValidNumber = Plottable._Util.Methods.isValidNumber; + var isValidNumber = Plottable.Utils.Methods.isValidNumber; assert.isTrue(isValidNumber(0), "(0 is a valid number"); assert.isTrue(isValidNumber(1), "(1 is a valid number"); assert.isTrue(isValidNumber(-1), "(-1 is a valid number"); @@ -8424,25 +8026,25 @@ describe("_Util.Methods", function () { assert.isFalse(isValidNumber({ 1: 1 }), "({1: 1} is not a valid number"); }); it("objEq works as expected", function () { - assert.isTrue(Plottable._Util.Methods.objEq({}, {})); - assert.isTrue(Plottable._Util.Methods.objEq({ a: 5 }, { a: 5 })); - assert.isFalse(Plottable._Util.Methods.objEq({ a: 5, b: 6 }, { a: 5 })); - assert.isFalse(Plottable._Util.Methods.objEq({ a: 5 }, { a: 5, b: 6 })); - assert.isTrue(Plottable._Util.Methods.objEq({ a: "hello" }, { a: "hello" })); - assert.isFalse(Plottable._Util.Methods.objEq({ constructor: {}.constructor }, {}), "using \"constructor\" isn't hidden"); + assert.isTrue(Plottable.Utils.Methods.objEq({}, {})); + assert.isTrue(Plottable.Utils.Methods.objEq({ a: 5 }, { a: 5 })); + assert.isFalse(Plottable.Utils.Methods.objEq({ a: 5, b: 6 }, { a: 5 })); + assert.isFalse(Plottable.Utils.Methods.objEq({ a: 5 }, { a: 5, b: 6 })); + assert.isTrue(Plottable.Utils.Methods.objEq({ a: "hello" }, { a: "hello" })); + assert.isFalse(Plottable.Utils.Methods.objEq({ constructor: {}.constructor }, {}), "using \"constructor\" isn't hidden"); }); it("populateMap works as expected", function () { var keys = ["a", "b", "c"]; - var map = Plottable._Util.Methods.populateMap(keys, function (key) { return key + "Value"; }); + var map = Plottable.Utils.Methods.populateMap(keys, function (key) { return key + "Value"; }); assert.strictEqual(map.get("a"), "aValue", "key properly goes through map function"); assert.strictEqual(map.get("b"), "bValue", "key properly goes through map function"); assert.strictEqual(map.get("c"), "cValue", "key properly goes through map function"); - var indexMap = Plottable._Util.Methods.populateMap(keys, function (key, i) { return key + i + "Value"; }); + var indexMap = Plottable.Utils.Methods.populateMap(keys, function (key, i) { return key + i + "Value"; }); assert.strictEqual(indexMap.get("a"), "a0Value", "key and index properly goes through map function"); assert.strictEqual(indexMap.get("b"), "b1Value", "key and index properly goes through map function"); assert.strictEqual(indexMap.get("c"), "c2Value", "key and index properly goes through map function"); var emptyKeys = []; - var emptyMap = Plottable._Util.Methods.populateMap(emptyKeys, function (key) { return key + "Value"; }); + var emptyMap = Plottable.Utils.Methods.populateMap(emptyKeys, function (key) { return key + "Value"; }); assert.isTrue(emptyMap.empty(), "no entries in map if no keys in input array"); }); it("copyMap works as expected", function () { @@ -8455,30 +8057,30 @@ describe("_Util.Methods", function () { oldMap["fun"] = function (d) { return d; }; oldMap["NaN"] = 0 / 0; oldMap["inf"] = 1 / 0; - var map = Plottable._Util.Methods.copyMap(oldMap); + var map = Plottable.Utils.Methods.copyMap(oldMap); assert.deepEqual(map, oldMap, "All values were copied."); - map = Plottable._Util.Methods.copyMap({}); + map = Plottable.Utils.Methods.copyMap({}); assert.deepEqual(map, {}, "No values were added."); }); it("range works as expected", function () { var start = 0; var end = 6; - var range = Plottable._Util.Methods.range(start, end); + var range = Plottable.Utils.Methods.range(start, end); assert.deepEqual(range, [0, 1, 2, 3, 4, 5], "all entries has been generated"); - range = Plottable._Util.Methods.range(start, end, 2); + range = Plottable.Utils.Methods.range(start, end, 2); assert.deepEqual(range, [0, 2, 4], "all entries has been generated"); - range = Plottable._Util.Methods.range(start, end, 11); + range = Plottable.Utils.Methods.range(start, end, 11); assert.deepEqual(range, [0], "all entries has been generated"); - assert.throws(function () { return Plottable._Util.Methods.range(start, end, 0); }, "step cannot be 0"); - range = Plottable._Util.Methods.range(start, end, -1); + assert.throws(function () { return Plottable.Utils.Methods.range(start, end, 0); }, "step cannot be 0"); + range = Plottable.Utils.Methods.range(start, end, -1); assert.lengthOf(range, 0, "no entries because of invalid step"); - range = Plottable._Util.Methods.range(end, start, -1); + range = Plottable.Utils.Methods.range(end, start, -1); assert.deepEqual(range, [6, 5, 4, 3, 2, 1], "all entries has been generated"); - range = Plottable._Util.Methods.range(-2, 2); + range = Plottable.Utils.Methods.range(-2, 2); assert.deepEqual(range, [-2, -1, 0, 1], "all entries has been generated range crossing 0"); - range = Plottable._Util.Methods.range(0.2, 4); + range = Plottable.Utils.Methods.range(0.2, 4); assert.deepEqual(range, [0.2, 1.2, 2.2, 3.2], "all entries has been generated with float start"); - range = Plottable._Util.Methods.range(0.6, 2.2, 0.5); + range = Plottable.Utils.Methods.range(0.6, 2.2, 0.5); assert.deepEqual(range, [0.6, 1.1, 1.6, 2.1], "all entries has been generated with float step"); }); it("colorTest works as expected", function () { @@ -8486,101 +8088,187 @@ describe("_Util.Methods", function () { var style = colorTester.append("style"); style.attr("type", "text/css"); style.text(".plottable-colors-0 { background-color: blue; }"); - var blueHexcode = Plottable._Util.Methods.colorTest(colorTester, "plottable-colors-0"); + var blueHexcode = Plottable.Utils.Methods.colorTest(colorTester, "plottable-colors-0"); assert.strictEqual(blueHexcode, "#0000ff", "hexcode for blue returned"); style.text(".plottable-colors-2 { background-color: #13EADF; }"); - var hexcode = Plottable._Util.Methods.colorTest(colorTester, "plottable-colors-2"); + var hexcode = Plottable.Utils.Methods.colorTest(colorTester, "plottable-colors-2"); assert.strictEqual(hexcode, "#13eadf", "hexcode for blue returned"); - var nullHexcode = Plottable._Util.Methods.colorTest(colorTester, "plottable-colors-11"); + var nullHexcode = Plottable.Utils.Methods.colorTest(colorTester, "plottable-colors-11"); assert.strictEqual(nullHexcode, null, "null hexcode returned"); colorTester.remove(); }); it("lightenColor()", function () { var colorHex = "#12fced"; var oldColor = d3.hsl(colorHex); - var lightenedColor = Plottable._Util.Methods.lightenColor(colorHex, 1); + var lightenedColor = Plottable.Utils.Methods.lightenColor(colorHex, 1); assert.operator(d3.hsl(lightenedColor).l, ">", oldColor.l, "color got lighter"); }); }); +/// +var assert = chai.assert; +describe("Utils", function () { + describe("CallbackSet", function () { + it("callCallbacks()", function () { + var expectedS = "Plottable"; + var expectedI = 1; + var cb1called = false; + var cb1 = function (s, i) { + assert.strictEqual(s, expectedS, "was passed the correct first argument"); + assert.strictEqual(i, expectedI, "was passed the correct second argument"); + cb1called = true; + }; + var cb2called = false; + var cb2 = function (s, i) { + assert.strictEqual(s, expectedS, "was passed the correct first argument"); + assert.strictEqual(i, expectedI, "was passed the correct second argument"); + cb2called = true; + }; + var callbackSet = new Plottable.Utils.CallbackSet(); + callbackSet.add(cb1); + callbackSet.add(cb2); + callbackSet.callCallbacks(expectedS, expectedI); + assert.isTrue(cb1called, "callback 1 was called"); + assert.isTrue(cb2called, "callback 2 was called"); + }); + }); +}); + /// var assert = chai.assert; describe("Interactions", function () { - describe("PanZoomInteraction", function () { - it("Pans properly", function () { - // The only difference between pan and zoom is internal to d3 - // Simulating zoom events is painful, so panning will suffice here - var xScale = new Plottable.Scale.Linear().domain([0, 11]); - var yScale = new Plottable.Scale.Linear().domain([11, 0]); - var svg = generateSVG(); - var dataset = makeLinearSeries(11); - var plot = new Plottable.Plot.Scatter(xScale, yScale).addDataset(dataset); - plot.project("x", "x", xScale); - plot.project("y", "y", yScale); - plot.renderTo(svg); - var xDomainBefore = xScale.domain(); - var yDomainBefore = yScale.domain(); - var interaction = new Plottable.Interaction.PanZoom(xScale, yScale); - plot.registerInteraction(interaction); - var hb = plot.hitBox().node(); - var dragDistancePixelX = 10; - var dragDistancePixelY = 20; - $(hb).simulate("drag", { - dx: dragDistancePixelX, - dy: dragDistancePixelY - }); - var xDomainAfter = xScale.domain(); - var yDomainAfter = yScale.domain(); - assert.notDeepEqual(xDomainAfter, xDomainBefore, "x domain was changed by panning"); - assert.notDeepEqual(yDomainAfter, yDomainBefore, "y domain was changed by panning"); - function getSlope(scale) { - var range = scale.range(); - var domain = scale.domain(); - return (domain[1] - domain[0]) / (range[1] - range[0]); - } - ; - var expectedXDragChange = -dragDistancePixelX * getSlope(xScale); - var expectedYDragChange = -dragDistancePixelY * getSlope(yScale); - assert.closeTo(xDomainAfter[0] - xDomainBefore[0], expectedXDragChange, 1, "x domain changed by the correct amount"); - assert.closeTo(yDomainAfter[0] - yDomainBefore[0], expectedYDragChange, 1, "y domain changed by the correct amount"); + describe("Interaction", function () { + var SVG_WIDTH = 400; + var SVG_HEIGHT = 400; + it("attaching/detaching a component modifies the state of the interaction", function () { + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var component = new Plottable.Component(); + var interaction = new Plottable.Interaction(); + component.renderTo(svg); + interaction.attachTo(component); + assert.strictEqual(interaction._componentAttachedTo, component, "the _componentAttachedTo field should contain the component the interaction is attached to"); + interaction.detachFrom(component); + assert.isNull(interaction._componentAttachedTo, "the _componentAttachedTo field should be blanked upon detaching"); svg.remove(); }); - it("Resets zoom when the scale domain changes", function () { - var xScale = new Plottable.Scale.Linear(); - var yScale = new Plottable.Scale.Linear(); - var svg = generateSVG(); - var c = new Plottable.Component.AbstractComponent(); - c.renderTo(svg); - var pzi = new Plottable.Interaction.PanZoom(xScale, yScale); - c.registerInteraction(pzi); - var zoomBeforeX = pzi._zoom; - xScale.domain([10, 1000]); - var zoomAfterX = pzi._zoom; - assert.notStrictEqual(zoomBeforeX, zoomAfterX, "D3 Zoom was regenerated after x scale domain changed"); - var zoomBeforeY = pzi._zoom; - yScale.domain([10, 1000]); - var zoomAfterY = pzi._zoom; - assert.notStrictEqual(zoomBeforeY, zoomAfterY, "D3 Zoom was regenerated after y scale domain changed"); + it("can attach interaction to component", function () { + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var component = new Plottable.Component(); + component.renderTo(svg); + var clickInteraction = new Plottable.Interactions.Click(); + var callbackCalled = false; + var callback = function () { return callbackCalled = true; }; + clickInteraction.onClick(callback); + clickInteraction.attachTo(component); + TestMethods.triggerFakeMouseEvent("mousedown", component.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mouseup", component.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + assert.isTrue(callbackCalled, "callback called on clicking Component (mouse)"); svg.remove(); }); + it("can detach interaction from component", function () { + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var component = new Plottable.Component(); + component.renderTo(svg); + var clickInteraction = new Plottable.Interactions.Click(); + var callbackCalled = false; + var callback = function () { return callbackCalled = true; }; + clickInteraction.onClick(callback); + clickInteraction.attachTo(component); + TestMethods.triggerFakeMouseEvent("mousedown", component.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mouseup", component.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + assert.isTrue(callbackCalled, "callback called on clicking Component (mouse)"); + callbackCalled = false; + clickInteraction.detachFrom(component); + TestMethods.triggerFakeMouseEvent("mousedown", component.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mouseup", component.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + assert.isFalse(callbackCalled, "callback was removed from component and should not be called"); + svg.remove(); + }); + it("calling detachFrom() on a detached Interaction has no effect", function () { + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var component = new Plottable.Component(); + var clickInteraction = new Plottable.Interactions.Click(); + assert.doesNotThrow(function () { + clickInteraction.detachFrom(component); + }, "detaching an Interaction which was not attached should not throw an error"); + clickInteraction.attachTo(component); + clickInteraction.detachFrom(component); + assert.doesNotThrow(function () { + clickInteraction.detachFrom(component); + }, "calling detachFrom() twice should not throw an error"); + component.renderTo(svg); + clickInteraction.attachTo(component); + clickInteraction.detachFrom(component); + assert.doesNotThrow(function () { + clickInteraction.detachFrom(component); + }, "calling detachFrom() twice should not throw an error even if the Component is anchored"); + svg.remove(); + }); + it("can move interaction from one component to another", function () { + var svg1 = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var svg2 = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var component1 = new Plottable.Component(); + var component2 = new Plottable.Component(); + component1.renderTo(svg1); + component2.renderTo(svg2); + var clickInteraction = new Plottable.Interactions.Click(); + var callbackCalled = false; + var callback = function () { return callbackCalled = true; }; + clickInteraction.onClick(callback); + clickInteraction.attachTo(component1); + callbackCalled = false; + TestMethods.triggerFakeMouseEvent("mousedown", component1.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mouseup", component1.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + assert.isTrue(callbackCalled, "Round 1 callback called for component 1"); + callbackCalled = false; + TestMethods.triggerFakeMouseEvent("mousedown", component2.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mouseup", component2.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + assert.isFalse(callbackCalled, "Round 1 callback not called for component 2"); + clickInteraction.detachFrom(component1); + clickInteraction.attachTo(component2); + callbackCalled = false; + TestMethods.triggerFakeMouseEvent("mousedown", component1.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mouseup", component1.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + assert.isFalse(callbackCalled, "Round 2 (after longhand attaching) callback not called for component 1"); + callbackCalled = false; + TestMethods.triggerFakeMouseEvent("mousedown", component2.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mouseup", component2.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + assert.isTrue(callbackCalled, "Round 2 (after longhand attaching) callback called for component 2"); + clickInteraction.attachTo(component1); + callbackCalled = false; + TestMethods.triggerFakeMouseEvent("mousedown", component1.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mouseup", component1.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + assert.isTrue(callbackCalled, "Round 3 (after shorthand attaching) callback called for component 1"); + callbackCalled = false; + TestMethods.triggerFakeMouseEvent("mousedown", component2.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mouseup", component2.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + assert.isFalse(callbackCalled, "Round 3 (after shorthand attaching) callback not called for component 2"); + svg1.remove(); + svg2.remove(); + }); }); +}); + +/// +var assert = chai.assert; +describe("Interactions", function () { describe("KeyInteraction", function () { it("Triggers appropriate callback for the key pressed", function () { - var svg = generateSVG(400, 400); - var component = new Plottable.Component.AbstractComponent(); + var svg = TestMethods.generateSVG(400, 400); + var component = new Plottable.Component(); component.renderTo(svg); - var ki = new Plottable.Interaction.Key(); + var keyInteraction = new Plottable.Interactions.Key(); var aCode = 65; // "a" key var bCode = 66; // "b" key var aCallbackCalled = false; var aCallback = function () { return aCallbackCalled = true; }; var bCallbackCalled = false; var bCallback = function () { return bCallbackCalled = true; }; - ki.on(aCode, aCallback); - ki.on(bCode, bCallback); - component.registerInteraction(ki); + keyInteraction.onKey(aCode, aCallback); + keyInteraction.onKey(bCode, bCallback); + keyInteraction.attachTo(component); var $target = $(component.background().node()); - triggerFakeMouseEvent("mouseover", component.background(), 100, 100); + TestMethods.triggerFakeMouseEvent("mouseover", component.background(), 100, 100); $target.simulate("keydown", { keyCode: aCode }); assert.isTrue(aCallbackCalled, "callback for \"a\" was called when \"a\" key was pressed"); assert.isFalse(bCallbackCalled, "callback for \"b\" was not called when \"a\" key was pressed"); @@ -8588,12 +8276,80 @@ describe("Interactions", function () { $target.simulate("keydown", { keyCode: bCode }); assert.isFalse(aCallbackCalled, "callback for \"a\" was not called when \"b\" key was pressed"); assert.isTrue(bCallbackCalled, "callback for \"b\" was called when \"b\" key was pressed"); - triggerFakeMouseEvent("mouseout", component.background(), -100, -100); + TestMethods.triggerFakeMouseEvent("mouseout", component.background(), -100, -100); aCallbackCalled = false; $target.simulate("keydown", { keyCode: aCode }); assert.isFalse(aCallbackCalled, "callback for \"a\" was not called when not moused over the Component"); svg.remove(); }); + it("appropriate keyCode is sent to the callback", function () { + var svg = TestMethods.generateSVG(400, 400); + var component = new Plottable.Component(); + component.renderTo(svg); + var keyInteraction = new Plottable.Interactions.Key(); + var bCode = 66; // "b" key + var bCallbackCalled = false; + var bCallback = function (keyCode) { + bCallbackCalled = true; + assert.strictEqual(keyCode, bCode, "keyCode 65(a) was sent to the callback"); + }; + keyInteraction.onKey(bCode, bCallback); + keyInteraction.attachTo(component); + var $target = $(component.background().node()); + TestMethods.triggerFakeMouseEvent("mouseover", component.background(), 100, 100); + $target.simulate("keydown", { keyCode: bCode }); + assert.isTrue(bCallbackCalled, "callback for \"b\" was called when \"b\" key was pressed"); + svg.remove(); + }); + it("canceling callbacks is possible", function () { + var svg = TestMethods.generateSVG(400, 400); + var component = new Plottable.Component(); + component.renderTo(svg); + var keyInteraction = new Plottable.Interactions.Key(); + var aCode = 65; // "a" key + var aCallbackCalled = false; + var aCallback = function () { return aCallbackCalled = true; }; + keyInteraction.onKey(aCode, aCallback); + keyInteraction.attachTo(component); + var $target = $(component.background().node()); + TestMethods.triggerFakeMouseEvent("mouseover", component.background(), 100, 100); + $target.simulate("keydown", { keyCode: aCode }); + assert.isTrue(aCallbackCalled, "callback for \"a\" was called when \"a\" key was pressed"); + keyInteraction.offKey(aCode, aCallback); + aCallbackCalled = false; + $target.simulate("keydown", { keyCode: aCode }); + assert.isFalse(aCallbackCalled, "callback for \"a\" was disconnected from the interaction"); + keyInteraction.onKey(aCode, aCallback); + $target.simulate("keydown", { keyCode: aCode }); + assert.isTrue(aCallbackCalled, "callback for \"a\" was properly connected back to the interaction"); + svg.remove(); + }); + it("multiple callbacks are possible", function () { + var svg = TestMethods.generateSVG(400, 400); + var component = new Plottable.Component(); + component.renderTo(svg); + var keyInteraction = new Plottable.Interactions.Key(); + var aCode = 65; // "a" key + var aCallback1Called = false; + var aCallback1 = function () { return aCallback1Called = true; }; + var aCallback2Called = false; + var aCallback2 = function () { return aCallback2Called = true; }; + keyInteraction.onKey(aCode, aCallback1); + keyInteraction.onKey(aCode, aCallback2); + keyInteraction.attachTo(component); + var $target = $(component.background().node()); + TestMethods.triggerFakeMouseEvent("mouseover", component.background(), 100, 100); + $target.simulate("keydown", { keyCode: aCode }); + assert.isTrue(aCallback1Called, "callback 1 for \"a\" was called when \"a\" key was pressed"); + assert.isTrue(aCallback1Called, "callback 2 for \"b\" was called when \"a\" key was pressed"); + keyInteraction.offKey(aCode, aCallback1); + aCallback1Called = false; + aCallback2Called = false; + $target.simulate("keydown", { keyCode: aCode }); + assert.isFalse(aCallback1Called, "callback 1 for \"a\" was disconnected from the interaction"); + assert.isTrue(aCallback2Called, "callback 2 for \"a\" is still connected to the interaction"); + svg.remove(); + }); }); }); @@ -8604,11 +8360,11 @@ describe("Interactions", function () { var SVG_WIDTH = 400; var SVG_HEIGHT = 400; it("onPointerEnter", function () { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var c = new Plottable.Component.AbstractComponent(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var c = new Plottable.Component(); c.renderTo(svg); - var pointerInteraction = new Plottable.Interaction.Pointer(); - c.registerInteraction(pointerInteraction); + var pointerInteraction = new Plottable.Interactions.Pointer(); + pointerInteraction.attachTo(c); var callbackCalled = false; var lastPoint; var callback = function (p) { @@ -8616,37 +8372,36 @@ describe("Interactions", function () { lastPoint = p; }; pointerInteraction.onPointerEnter(callback); - assert.strictEqual(pointerInteraction.onPointerEnter(), callback, "callback can be retrieved"); var target = c.background(); - triggerFakeMouseEvent("mousemove", target, SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mousemove", target, SVG_WIDTH / 2, SVG_HEIGHT / 2); assert.isTrue(callbackCalled, "callback called on entering Component (mouse)"); assert.deepEqual(lastPoint, { x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }, "was passed correct point (mouse)"); callbackCalled = false; - triggerFakeMouseEvent("mousemove", target, SVG_WIDTH / 4, SVG_HEIGHT / 4); + TestMethods.triggerFakeMouseEvent("mousemove", target, SVG_WIDTH / 4, SVG_HEIGHT / 4); assert.isFalse(callbackCalled, "callback not called again if already in Component (mouse)"); - triggerFakeMouseEvent("mousemove", target, 2 * SVG_WIDTH, 2 * SVG_HEIGHT); + TestMethods.triggerFakeMouseEvent("mousemove", target, 2 * SVG_WIDTH, 2 * SVG_HEIGHT); assert.isFalse(callbackCalled, "not called when moving outside of the Component (mouse)"); callbackCalled = false; lastPoint = null; - triggerFakeTouchEvent("touchstart", target, [{ x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{ x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }]); assert.isTrue(callbackCalled, "callback called on entering Component (touch)"); assert.deepEqual(lastPoint, { x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }, "was passed correct point (touch)"); callbackCalled = false; - triggerFakeTouchEvent("touchstart", target, [{ x: SVG_WIDTH / 4, y: SVG_HEIGHT / 4 }]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{ x: SVG_WIDTH / 4, y: SVG_HEIGHT / 4 }]); assert.isFalse(callbackCalled, "callback not called again if already in Component (touch)"); - triggerFakeTouchEvent("touchstart", target, [{ x: 2 * SVG_WIDTH, y: 2 * SVG_HEIGHT }]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{ x: 2 * SVG_WIDTH, y: 2 * SVG_HEIGHT }]); assert.isFalse(callbackCalled, "not called when moving outside of the Component (touch)"); - pointerInteraction.onPointerEnter(null); - triggerFakeMouseEvent("mousemove", target, SVG_WIDTH / 2, SVG_HEIGHT / 2); + pointerInteraction.offPointerEnter(callback); + TestMethods.triggerFakeMouseEvent("mousemove", target, SVG_WIDTH / 2, SVG_HEIGHT / 2); assert.isFalse(callbackCalled, "callback removed by passing null"); svg.remove(); }); it("onPointerMove", function () { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var c = new Plottable.Component.AbstractComponent(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var c = new Plottable.Component(); c.renderTo(svg); - var pointerInteraction = new Plottable.Interaction.Pointer(); - c.registerInteraction(pointerInteraction); + var pointerInteraction = new Plottable.Interactions.Pointer(); + pointerInteraction.attachTo(c); var callbackCalled = false; var lastPoint; var callback = function (p) { @@ -8654,40 +8409,39 @@ describe("Interactions", function () { lastPoint = p; }; pointerInteraction.onPointerMove(callback); - assert.strictEqual(pointerInteraction.onPointerMove(), callback, "callback can be retrieved"); var target = c.background(); - triggerFakeMouseEvent("mousemove", target, SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mousemove", target, SVG_WIDTH / 2, SVG_HEIGHT / 2); assert.isTrue(callbackCalled, "callback called on entering Component (mouse)"); assert.deepEqual(lastPoint, { x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }, "was passed correct point (mouse)"); callbackCalled = false; - triggerFakeMouseEvent("mousemove", target, SVG_WIDTH / 4, SVG_HEIGHT / 4); + TestMethods.triggerFakeMouseEvent("mousemove", target, SVG_WIDTH / 4, SVG_HEIGHT / 4); assert.isTrue(callbackCalled, "callback on moving inside Component (mouse)"); assert.deepEqual(lastPoint, { x: SVG_WIDTH / 4, y: SVG_HEIGHT / 4 }, "was passed correct point (mouse)"); callbackCalled = false; - triggerFakeMouseEvent("mousemove", target, 2 * SVG_WIDTH, 2 * SVG_HEIGHT); + TestMethods.triggerFakeMouseEvent("mousemove", target, 2 * SVG_WIDTH, 2 * SVG_HEIGHT); assert.isFalse(callbackCalled, "not called when moving outside of the Component (mouse)"); callbackCalled = false; - triggerFakeTouchEvent("touchstart", target, [{ x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{ x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }]); assert.isTrue(callbackCalled, "callback called on entering Component (touch)"); assert.deepEqual(lastPoint, { x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }, "was passed correct point (touch)"); callbackCalled = false; - triggerFakeTouchEvent("touchstart", target, [{ x: SVG_WIDTH / 4, y: SVG_HEIGHT / 4 }]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{ x: SVG_WIDTH / 4, y: SVG_HEIGHT / 4 }]); assert.isTrue(callbackCalled, "callback on moving inside Component (touch)"); assert.deepEqual(lastPoint, { x: SVG_WIDTH / 4, y: SVG_HEIGHT / 4 }, "was passed correct point (touch)"); callbackCalled = false; - triggerFakeTouchEvent("touchstart", target, [{ x: 2 * SVG_WIDTH, y: 2 * SVG_HEIGHT }]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{ x: 2 * SVG_WIDTH, y: 2 * SVG_HEIGHT }]); assert.isFalse(callbackCalled, "not called when moving outside of the Component (touch)"); - pointerInteraction.onPointerMove(null); - triggerFakeMouseEvent("mousemove", target, SVG_WIDTH / 2, SVG_HEIGHT / 2); + pointerInteraction.offPointerMove(callback); + TestMethods.triggerFakeMouseEvent("mousemove", target, SVG_WIDTH / 2, SVG_HEIGHT / 2); assert.isFalse(callbackCalled, "callback removed by passing null"); svg.remove(); }); it("onPointerExit", function () { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var c = new Plottable.Component.AbstractComponent(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var c = new Plottable.Component(); c.renderTo(svg); - var pointerInteraction = new Plottable.Interaction.Pointer(); - c.registerInteraction(pointerInteraction); + var pointerInteraction = new Plottable.Interactions.Pointer(); + pointerInteraction.attachTo(c); var callbackCalled = false; var lastPoint; var callback = function (p) { @@ -8695,204 +8449,87 @@ describe("Interactions", function () { lastPoint = p; }; pointerInteraction.onPointerExit(callback); - assert.strictEqual(pointerInteraction.onPointerExit(), callback, "callback can be retrieved"); var target = c.background(); - triggerFakeMouseEvent("mousemove", target, 2 * SVG_WIDTH, 2 * SVG_HEIGHT); + TestMethods.triggerFakeMouseEvent("mousemove", target, 2 * SVG_WIDTH, 2 * SVG_HEIGHT); assert.isFalse(callbackCalled, "not called when moving outside of the Component (mouse)"); - triggerFakeMouseEvent("mousemove", target, SVG_WIDTH / 2, SVG_HEIGHT / 2); - triggerFakeMouseEvent("mousemove", target, 2 * SVG_WIDTH, 2 * SVG_HEIGHT); + TestMethods.triggerFakeMouseEvent("mousemove", target, SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mousemove", target, 2 * SVG_WIDTH, 2 * SVG_HEIGHT); assert.isTrue(callbackCalled, "callback called on exiting Component (mouse)"); assert.deepEqual(lastPoint, { x: 2 * SVG_WIDTH, y: 2 * SVG_HEIGHT }, "was passed correct point (mouse)"); callbackCalled = false; - triggerFakeMouseEvent("mousemove", target, 3 * SVG_WIDTH, 3 * SVG_HEIGHT); + TestMethods.triggerFakeMouseEvent("mousemove", target, 3 * SVG_WIDTH, 3 * SVG_HEIGHT); assert.isFalse(callbackCalled, "callback not called again if already outside of Component (mouse)"); callbackCalled = false; lastPoint = null; - triggerFakeTouchEvent("touchstart", target, [{ x: 2 * SVG_WIDTH, y: 2 * SVG_HEIGHT }]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{ x: 2 * SVG_WIDTH, y: 2 * SVG_HEIGHT }]); assert.isFalse(callbackCalled, "not called when moving outside of the Component (touch)"); - triggerFakeTouchEvent("touchstart", target, [{ x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }]); - triggerFakeTouchEvent("touchstart", target, [{ x: 2 * SVG_WIDTH, y: 2 * SVG_HEIGHT }]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{ x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{ x: 2 * SVG_WIDTH, y: 2 * SVG_HEIGHT }]); assert.isTrue(callbackCalled, "callback called on exiting Component (touch)"); assert.deepEqual(lastPoint, { x: 2 * SVG_WIDTH, y: 2 * SVG_HEIGHT }, "was passed correct point (touch)"); callbackCalled = false; - triggerFakeTouchEvent("touchstart", target, [{ x: 3 * SVG_WIDTH, y: 3 * SVG_HEIGHT }]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{ x: 3 * SVG_WIDTH, y: 3 * SVG_HEIGHT }]); assert.isFalse(callbackCalled, "callback not called again if already outside of Component (touch)"); - pointerInteraction.onPointerExit(null); - triggerFakeMouseEvent("mousemove", target, SVG_WIDTH / 2, SVG_HEIGHT / 2); - triggerFakeMouseEvent("mousemove", target, 2 * SVG_WIDTH, 2 * SVG_HEIGHT); + pointerInteraction.offPointerExit(callback); + TestMethods.triggerFakeMouseEvent("mousemove", target, SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mousemove", target, 2 * SVG_WIDTH, 2 * SVG_HEIGHT); assert.isFalse(callbackCalled, "callback removed by passing null"); svg.remove(); }); - }); -}); - -/// -var __extends = this.__extends || function (d, b) { - for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; - function __() { this.constructor = d; } - __.prototype = b.prototype; - d.prototype = new __(); -}; -var assert = chai.assert; -var TestHoverable = (function (_super) { - __extends(TestHoverable, _super); - function TestHoverable() { - _super.apply(this, arguments); - this.leftPoint = { x: 100, y: 200 }; - this.rightPoint = { x: 300, y: 200 }; - } - TestHoverable.prototype._hoverOverComponent = function (p) { - // cast-override - }; - TestHoverable.prototype._hoverOutComponent = function (p) { - // cast-override - }; - TestHoverable.prototype._doHover = function (p) { - var data = []; - var points = []; - if (p.x < 250) { - data.push("left"); - points.push(this.leftPoint); - } - if (p.x > 150) { - data.push("right"); - points.push(this.rightPoint); - } - return { - data: data, - pixelPositions: points, - selection: this._element - }; - }; - return TestHoverable; -})(Plottable.Component.AbstractComponent); -describe("Interactions", function () { - describe("Hover", function () { - var svg; - var testTarget; - var target; - var hoverInteraction; - var overData; - var overCallbackCalled = false; - var outData; - var outCallbackCalled = false; - beforeEach(function () { - svg = generateSVG(); - testTarget = new TestHoverable(); - testTarget.classed("test-hoverable", true); - testTarget.renderTo(svg); - hoverInteraction = new Plottable.Interaction.Hover(); - overCallbackCalled = false; - hoverInteraction.onHoverOver(function (hd) { - overCallbackCalled = true; - overData = hd; - }); - outCallbackCalled = false; - hoverInteraction.onHoverOut(function (hd) { - outCallbackCalled = true; - outData = hd; - }); - testTarget.registerInteraction(hoverInteraction); - target = testTarget.background(); - }); - it("correctly triggers onHoverOver() callbacks (mouse events)", function () { - overCallbackCalled = false; - triggerFakeMouseEvent("mouseover", target, 100, 200); - assert.isTrue(overCallbackCalled, "onHoverOver was called on mousing over a target area"); - assert.deepEqual(overData.pixelPositions, [testTarget.leftPoint], "onHoverOver was called with the correct pixel position (mouse onto left)"); - assert.deepEqual(overData.data, ["left"], "onHoverOver was called with the correct data (mouse onto left)"); - overCallbackCalled = false; - triggerFakeMouseEvent("mousemove", target, 100, 200); - assert.isFalse(overCallbackCalled, "onHoverOver isn't called if the hover data didn't change"); - overCallbackCalled = false; - triggerFakeMouseEvent("mousemove", target, 200, 200); - assert.isTrue(overCallbackCalled, "onHoverOver was called when mousing into a new region"); - assert.deepEqual(overData.pixelPositions, [testTarget.rightPoint], "onHoverOver was called with the correct pixel position (left --> center)"); - assert.deepEqual(overData.data, ["right"], "onHoverOver was called with the new data only (left --> center)"); - triggerFakeMouseEvent("mouseout", target, 401, 200); - overCallbackCalled = false; - triggerFakeMouseEvent("mouseover", target, 200, 200); - assert.deepEqual(overData.pixelPositions, [testTarget.leftPoint, testTarget.rightPoint], "onHoverOver was called with the correct pixel positions"); - assert.deepEqual(overData.data, ["left", "right"], "onHoverOver is called with the correct data"); - svg.remove(); - }); - it("correctly triggers onHoverOver() callbacks (touch events)", function () { - overCallbackCalled = false; - triggerFakeTouchEvent("touchstart", target, [{ x: 100, y: 200 }]); - assert.isTrue(overCallbackCalled, "onHoverOver was called on touching a target area"); - assert.deepEqual(overData.pixelPositions, [testTarget.leftPoint], "onHoverOver was called with the correct pixel position (mouse onto left)"); - assert.deepEqual(overData.data, ["left"], "onHoverOver was called with the correct data (mouse onto left)"); - overCallbackCalled = false; - triggerFakeTouchEvent("touchstart", target, [{ x: 100, y: 200 }]); - assert.isFalse(overCallbackCalled, "onHoverOver isn't called if the hover data didn't change"); - overCallbackCalled = false; - triggerFakeTouchEvent("touchstart", target, [{ x: 200, y: 200 }]); - assert.isTrue(overCallbackCalled, "onHoverOver was called when touch moves into a new region"); - assert.deepEqual(overData.pixelPositions, [testTarget.rightPoint], "onHoverOver was called with the correct pixel position (left --> center)"); - assert.deepEqual(overData.data, ["right"], "onHoverOver was called with the new data only (left --> center)"); - triggerFakeTouchEvent("touchstart", target, [{ x: 401, y: 200 }]); - overCallbackCalled = false; - triggerFakeTouchEvent("touchstart", target, [{ x: 200, y: 200 }]); - assert.deepEqual(overData.pixelPositions, [testTarget.leftPoint, testTarget.rightPoint], "onHoverOver was called with the correct pixel positions"); - assert.deepEqual(overData.data, ["left", "right"], "onHoverOver is called with the correct data"); - svg.remove(); - }); - it("correctly triggers onHoverOut() callbacks (mouse events)", function () { - triggerFakeMouseEvent("mouseover", target, 100, 200); - outCallbackCalled = false; - triggerFakeMouseEvent("mousemove", target, 200, 200); - assert.isFalse(outCallbackCalled, "onHoverOut isn't called when mousing into a new region without leaving the old one"); - outCallbackCalled = false; - triggerFakeMouseEvent("mousemove", target, 300, 200); - assert.isTrue(outCallbackCalled, "onHoverOut was called when the hover data changes"); - assert.deepEqual(outData.pixelPositions, [testTarget.leftPoint], "onHoverOut was called with the correct pixel position (center --> right)"); - assert.deepEqual(outData.data, ["left"], "onHoverOut was called with the correct data (center --> right)"); - outCallbackCalled = false; - triggerFakeMouseEvent("mouseout", target, 401, 200); - assert.isTrue(outCallbackCalled, "onHoverOut is called on mousing out of the Component"); - assert.deepEqual(outData.pixelPositions, [testTarget.rightPoint], "onHoverOut was called with the correct pixel position"); - assert.deepEqual(outData.data, ["right"], "onHoverOut was called with the correct data"); - outCallbackCalled = false; - triggerFakeMouseEvent("mouseover", target, 200, 200); - triggerFakeMouseEvent("mouseout", target, 200, 401); - assert.deepEqual(outData.pixelPositions, [testTarget.leftPoint, testTarget.rightPoint], "onHoverOut was called with the correct pixel positions"); - assert.deepEqual(outData.data, ["left", "right"], "onHoverOut is called with the correct data"); - svg.remove(); - }); - it("correctly triggers onHoverOut() callbacks (touch events)", function () { - triggerFakeTouchEvent("touchstart", target, [{ x: 100, y: 200 }]); - outCallbackCalled = false; - triggerFakeTouchEvent("touchstart", target, [{ x: 200, y: 200 }]); - assert.isFalse(outCallbackCalled, "onHoverOut isn't called when mousing into a new region without leaving the old one"); - outCallbackCalled = false; - triggerFakeTouchEvent("touchstart", target, [{ x: 300, y: 200 }]); - assert.isTrue(outCallbackCalled, "onHoverOut was called when the hover data changes"); - assert.deepEqual(outData.pixelPositions, [testTarget.leftPoint], "onHoverOut was called with the correct pixel position (center --> right)"); - assert.deepEqual(outData.data, ["left"], "onHoverOut was called with the correct data (center --> right)"); - outCallbackCalled = false; - triggerFakeTouchEvent("touchstart", target, [{ x: 401, y: 200 }]); - assert.isTrue(outCallbackCalled, "onHoverOut is called on mousing out of the Component"); - assert.deepEqual(outData.pixelPositions, [testTarget.rightPoint], "onHoverOut was called with the correct pixel position"); - assert.deepEqual(outData.data, ["right"], "onHoverOut was called with the correct data"); - outCallbackCalled = false; - triggerFakeTouchEvent("touchstart", target, [{ x: 200, y: 200 }]); - triggerFakeTouchEvent("touchstart", target, [{ x: 200, y: 401 }]); - assert.deepEqual(outData.pixelPositions, [testTarget.leftPoint, testTarget.rightPoint], "onHoverOut was called with the correct pixel positions"); - assert.deepEqual(outData.data, ["left", "right"], "onHoverOut is called with the correct data"); - svg.remove(); - }); - it("getCurrentHoverData()", function () { - triggerFakeMouseEvent("mouseover", target, 100, 200); - var currentlyHovered = hoverInteraction.getCurrentHoverData(); - assert.deepEqual(currentlyHovered.pixelPositions, [testTarget.leftPoint], "retrieves pixel positions corresponding to the current position"); - assert.deepEqual(currentlyHovered.data, ["left"], "retrieves data corresponding to the current position"); - triggerFakeMouseEvent("mousemove", target, 200, 200); - currentlyHovered = hoverInteraction.getCurrentHoverData(); - assert.deepEqual(currentlyHovered.pixelPositions, [testTarget.leftPoint, testTarget.rightPoint], "retrieves pixel positions corresponding to the current position"); - assert.deepEqual(currentlyHovered.data, ["left", "right"], "retrieves data corresponding to the current position"); - triggerFakeMouseEvent("mouseout", target, 401, 200); - currentlyHovered = hoverInteraction.getCurrentHoverData(); - assert.isNull(currentlyHovered.data, "returns null if not currently hovering"); + it("multiple callbacks can be added to pointer interaction", function () { + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var component = new Plottable.Component(); + component.renderTo(svg); + var pointer = new Plottable.Interactions.Pointer(); + var enterCallback1Called = false; + var enterCallback2Called = false; + var moveCallback1Called = false; + var moveCallback2Called = false; + var exitCallback1Called = false; + var exitCallback2Called = false; + var enterCallback1 = function () { return enterCallback1Called = true; }; + var enterCallback2 = function () { return enterCallback2Called = true; }; + var moveCallback1 = function () { return moveCallback1Called = true; }; + var moveCallback2 = function () { return moveCallback2Called = true; }; + var exitCallback1 = function () { return exitCallback1Called = true; }; + var exitCallback2 = function () { return exitCallback2Called = true; }; + pointer.onPointerEnter(enterCallback1); + pointer.onPointerEnter(enterCallback2); + pointer.onPointerMove(moveCallback1); + pointer.onPointerMove(moveCallback2); + pointer.onPointerExit(exitCallback1); + pointer.onPointerExit(exitCallback2); + pointer.attachTo(component); + var target = component.background(); + var insidePoint = { x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }; + var outsidePoint = { x: SVG_WIDTH * 2, y: SVG_HEIGHT * 2 }; + TestMethods.triggerFakeMouseEvent("mousemove", target, outsidePoint.x, outsidePoint.y); + TestMethods.triggerFakeMouseEvent("mousemove", target, insidePoint.x, insidePoint.y); + TestMethods.triggerFakeMouseEvent("mousemove", target, outsidePoint.x, outsidePoint.y); + assert.isTrue(enterCallback1Called, "callback 1 was called on entering Component (mouse)"); + assert.isTrue(enterCallback2Called, "callback 2 was called on entering Component (mouse)"); + assert.isTrue(moveCallback1Called, "callback 1 was called on moving inside Component (mouse)"); + assert.isTrue(moveCallback2Called, "callback 2 was called on moving inside Component (mouse)"); + assert.isTrue(exitCallback1Called, "callback 1 was called on exiting Component (mouse)"); + assert.isTrue(exitCallback2Called, "callback 2 was called on exiting Component (mouse)"); + enterCallback1Called = false; + enterCallback2Called = false; + moveCallback1Called = false; + moveCallback2Called = false; + exitCallback1Called = false; + exitCallback2Called = false; + pointer.offPointerEnter(enterCallback1); + pointer.offPointerMove(moveCallback1); + pointer.offPointerExit(exitCallback1); + TestMethods.triggerFakeMouseEvent("mousemove", target, outsidePoint.x, outsidePoint.y); + TestMethods.triggerFakeMouseEvent("mousemove", target, insidePoint.x, insidePoint.y); + TestMethods.triggerFakeMouseEvent("mousemove", target, outsidePoint.x, outsidePoint.y); + assert.isFalse(enterCallback1Called, "callback 1 was disconnected from pointer enter interaction"); + assert.isTrue(enterCallback2Called, "callback 2 is still connected to the pointer enter interaction"); + assert.isFalse(moveCallback1Called, "callback 1 was disconnected from pointer interaction"); + assert.isTrue(moveCallback2Called, "callback 2 is still connected to the pointer interaction"); + assert.isFalse(exitCallback1Called, "callback 1 was disconnected from the pointer exit interaction"); + assert.isTrue(exitCallback2Called, "callback 2 is still connected to the pointer exit interaction"); svg.remove(); }); }); @@ -8905,11 +8542,11 @@ describe("Interactions", function () { var SVG_WIDTH = 400; var SVG_HEIGHT = 400; it("onClick", function () { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var c = new Plottable.Component.AbstractComponent(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var c = new Plottable.Component(); c.renderTo(svg); - var clickInteraction = new Plottable.Interaction.Click(); - c.registerInteraction(clickInteraction); + var clickInteraction = new Plottable.Interactions.Click(); + clickInteraction.attachTo(c); var callbackCalled = false; var lastPoint; var callback = function (p) { @@ -8917,54 +8554,112 @@ describe("Interactions", function () { lastPoint = p; }; clickInteraction.onClick(callback); - assert.strictEqual(clickInteraction.onClick(), callback, "callback can be retrieved"); - triggerFakeMouseEvent("mousedown", c.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); - triggerFakeMouseEvent("mouseup", c.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mousedown", c.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mouseup", c.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); assert.isTrue(callbackCalled, "callback called on clicking Component (mouse)"); assert.deepEqual(lastPoint, { x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }, "was passed correct point (mouse)"); callbackCalled = false; lastPoint = null; - triggerFakeMouseEvent("mousedown", c.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); - triggerFakeMouseEvent("mouseup", c.content(), SVG_WIDTH / 4, SVG_HEIGHT / 4); + TestMethods.triggerFakeMouseEvent("mousedown", c.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mouseup", c.content(), SVG_WIDTH / 4, SVG_HEIGHT / 4); assert.isTrue(callbackCalled, "callback called on clicking Component (mouse)"); assert.deepEqual(lastPoint, { x: SVG_WIDTH / 4, y: SVG_HEIGHT / 4 }, "was passed mouseup point (mouse)"); callbackCalled = false; - triggerFakeMouseEvent("mousedown", c.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); - triggerFakeMouseEvent("mouseup", c.content(), SVG_WIDTH * 2, SVG_HEIGHT * 2); + TestMethods.triggerFakeMouseEvent("mousedown", c.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mouseup", c.content(), SVG_WIDTH * 2, SVG_HEIGHT * 2); assert.isFalse(callbackCalled, "callback not called if released outside component (mouse)"); - triggerFakeMouseEvent("mousedown", c.content(), SVG_WIDTH * 2, SVG_HEIGHT * 2); - triggerFakeMouseEvent("mouseup", c.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mousedown", c.content(), SVG_WIDTH * 2, SVG_HEIGHT * 2); + TestMethods.triggerFakeMouseEvent("mouseup", c.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); assert.isFalse(callbackCalled, "callback not called if started outside component (mouse)"); - triggerFakeMouseEvent("mousedown", c.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); - triggerFakeMouseEvent("mousemove", c.content(), SVG_WIDTH * 2, SVG_HEIGHT * 2); - triggerFakeMouseEvent("mouseup", c.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mousedown", c.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); + TestMethods.triggerFakeMouseEvent("mousemove", c.content(), SVG_WIDTH * 2, SVG_HEIGHT * 2); + TestMethods.triggerFakeMouseEvent("mouseup", c.content(), SVG_WIDTH / 2, SVG_HEIGHT / 2); assert.isTrue(callbackCalled, "callback called even if moved outside component (mouse)"); callbackCalled = false; lastPoint = null; - triggerFakeTouchEvent("touchstart", c.content(), [{ x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }]); - triggerFakeTouchEvent("touchend", c.content(), [{ x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }]); + TestMethods.triggerFakeTouchEvent("touchstart", c.content(), [{ x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }]); + TestMethods.triggerFakeTouchEvent("touchend", c.content(), [{ x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }]); assert.isTrue(callbackCalled, "callback called on entering Component (touch)"); assert.deepEqual(lastPoint, { x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }, "was passed correct point (touch)"); callbackCalled = false; lastPoint = null; - triggerFakeTouchEvent("touchstart", c.content(), [{ x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }]); - triggerFakeTouchEvent("touchend", c.content(), [{ x: SVG_WIDTH / 4, y: SVG_HEIGHT / 4 }]); + TestMethods.triggerFakeTouchEvent("touchstart", c.content(), [{ x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }]); + TestMethods.triggerFakeTouchEvent("touchend", c.content(), [{ x: SVG_WIDTH / 4, y: SVG_HEIGHT / 4 }]); assert.isTrue(callbackCalled, "callback called on clicking Component (mouse)"); assert.deepEqual(lastPoint, { x: SVG_WIDTH / 4, y: SVG_HEIGHT / 4 }, "was passed mouseup point (touch)"); callbackCalled = false; - triggerFakeTouchEvent("touchstart", c.content(), [{ x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }]); - triggerFakeTouchEvent("touchend", c.content(), [{ x: SVG_WIDTH * 2, y: SVG_HEIGHT * 2 }]); + TestMethods.triggerFakeTouchEvent("touchstart", c.content(), [{ x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }]); + TestMethods.triggerFakeTouchEvent("touchend", c.content(), [{ x: SVG_WIDTH * 2, y: SVG_HEIGHT * 2 }]); assert.isFalse(callbackCalled, "callback not called if released outside component (touch)"); callbackCalled = false; - triggerFakeTouchEvent("touchstart", c.content(), [{ x: SVG_WIDTH * 2, y: SVG_HEIGHT * 2 }]); - triggerFakeTouchEvent("touchend", c.content(), [{ x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }]); + TestMethods.triggerFakeTouchEvent("touchstart", c.content(), [{ x: SVG_WIDTH * 2, y: SVG_HEIGHT * 2 }]); + TestMethods.triggerFakeTouchEvent("touchend", c.content(), [{ x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }]); assert.isFalse(callbackCalled, "callback not called if started outside component (touch)"); - triggerFakeTouchEvent("touchstart", c.content(), [{ x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }]); - triggerFakeTouchEvent("touchmove", c.content(), [{ x: SVG_WIDTH * 2, y: SVG_HEIGHT * 2 }]); - triggerFakeTouchEvent("touchend", c.content(), [{ x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }]); + TestMethods.triggerFakeTouchEvent("touchstart", c.content(), [{ x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }]); + TestMethods.triggerFakeTouchEvent("touchmove", c.content(), [{ x: SVG_WIDTH * 2, y: SVG_HEIGHT * 2 }]); + TestMethods.triggerFakeTouchEvent("touchend", c.content(), [{ x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }]); assert.isTrue(callbackCalled, "callback called even if moved outside component (touch)"); svg.remove(); }); + it("offClick()", function () { + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var component = new Plottable.Component(); + component.renderTo(svg); + var clickInteraction = new Plottable.Interactions.Click(); + clickInteraction.attachTo(component); + var callbackWasCalled = false; + var callback = function () { return callbackWasCalled = true; }; + clickInteraction.onClick(callback); + TestMethods.triggerFakeMouseEvent("mousedown", component.content(), 0, 0); + TestMethods.triggerFakeMouseEvent("mouseup", component.content(), 0, 0); + assert.isTrue(callbackWasCalled, "Click interaction should trigger the callback"); + clickInteraction.offClick(callback); + callbackWasCalled = false; + TestMethods.triggerFakeMouseEvent("mousedown", component.content(), 0, 0); + TestMethods.triggerFakeMouseEvent("mouseup", component.content(), 0, 0); + assert.isFalse(callbackWasCalled, "Callback should be disconnected from the click interaction"); + svg.remove(); + }); + it("multiple click listeners", function () { + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var component = new Plottable.Component(); + component.renderTo(svg); + var clickInteraction = new Plottable.Interactions.Click(); + clickInteraction.attachTo(component); + var callback1WasCalled = false; + var callback1 = function () { return callback1WasCalled = true; }; + var callback2WasCalled = false; + var callback2 = function () { return callback2WasCalled = true; }; + clickInteraction.onClick(callback1); + clickInteraction.onClick(callback2); + TestMethods.triggerFakeMouseEvent("mousedown", component.content(), 0, 0); + TestMethods.triggerFakeMouseEvent("mouseup", component.content(), 0, 0); + assert.isTrue(callback1WasCalled, "Click interaction should trigger the first callback"); + assert.isTrue(callback2WasCalled, "Click interaction should trigger the second callback"); + clickInteraction.offClick(callback1); + callback1WasCalled = false; + callback2WasCalled = false; + TestMethods.triggerFakeMouseEvent("mousedown", component.content(), 0, 0); + TestMethods.triggerFakeMouseEvent("mouseup", component.content(), 0, 0); + assert.isFalse(callback1WasCalled, "Callback1 should be disconnected from the click interaction"); + assert.isTrue(callback2WasCalled, "Callback2 should still exist on the click interaction"); + svg.remove(); + }); + it("cancelling touches cancels any ongoing clicks", function () { + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var c = new Plottable.Component(); + c.renderTo(svg); + var clickInteraction = new Plottable.Interactions.Click(); + clickInteraction.attachTo(c); + var callbackCalled = false; + var callback = function () { return callbackCalled = true; }; + clickInteraction.onClick(callback); + TestMethods.triggerFakeTouchEvent("touchstart", c.content(), [{ x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }]); + TestMethods.triggerFakeTouchEvent("touchcancel", c.content(), [{ x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }]); + TestMethods.triggerFakeTouchEvent("touchend", c.content(), [{ x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }]); + assert.isFalse(callbackCalled, "callback not called since click was interrupted"); + svg.remove(); + }); }); }); @@ -8981,46 +8676,80 @@ describe("Interactions", function () { var doubleClickedPoint = null; var dblClickCallback = function (p) { return doubleClickedPoint = p; }; beforeEach(function () { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - component = new Plottable.Component.AbstractComponent(); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + component = new Plottable.Component(); component.renderTo(svg); - dblClickInteraction = new Plottable.Interaction.DoubleClick(); - component.registerInteraction(dblClickInteraction); + dblClickInteraction = new Plottable.Interactions.DoubleClick(); + dblClickInteraction.attachTo(component); dblClickInteraction.onDoubleClick(dblClickCallback); }); afterEach(function () { doubleClickedPoint = null; }); - it("onDblClick callback can be retrieved", function () { - assert.strictEqual(dblClickInteraction.onDoubleClick(), dblClickCallback, "callback can be retrieved"); + it("double click interaction accepts multiple callbacks", function () { + var userClickPoint = { x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }; + var newCallback1WasCalled = false; + var newCallback1 = function () { return newCallback1WasCalled = true; }; + var newCallback2WasCalled = false; + var newCallback2 = function () { return newCallback2WasCalled = true; }; + dblClickInteraction.onDoubleClick(newCallback1); + dblClickInteraction.onDoubleClick(newCallback2); + TestMethods.triggerFakeMouseEvent("mousedown", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeMouseEvent("mouseup", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeMouseEvent("mousedown", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeMouseEvent("mouseup", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeMouseEvent("dblclick", component.content(), userClickPoint.x, userClickPoint.y); + assert.isTrue(newCallback1WasCalled, "Callback 1 should be called on double click"); + assert.isTrue(newCallback2WasCalled, "Callback 2 should be called on double click"); + newCallback1WasCalled = false; + newCallback2WasCalled = false; + dblClickInteraction.offDoubleClick(newCallback1); + TestMethods.triggerFakeMouseEvent("mousedown", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeMouseEvent("mouseup", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeMouseEvent("mousedown", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeMouseEvent("mouseup", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeMouseEvent("dblclick", component.content(), userClickPoint.x, userClickPoint.y); + assert.isFalse(newCallback1WasCalled, "Callback 1 should be disconnected from the interaction"); + assert.isTrue(newCallback2WasCalled, "Callback 2 should still be connected to the interaction"); svg.remove(); }); it("callback sets correct point on normal case", function () { var userClickPoint = { x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }; - triggerFakeMouseEvent("mousedown", component.content(), userClickPoint.x, userClickPoint.y); - triggerFakeMouseEvent("mouseup", component.content(), userClickPoint.x, userClickPoint.y); - triggerFakeMouseEvent("mousedown", component.content(), userClickPoint.x, userClickPoint.y); - triggerFakeMouseEvent("mouseup", component.content(), userClickPoint.x, userClickPoint.y); - triggerFakeMouseEvent("dblclick", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeMouseEvent("mousedown", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeMouseEvent("mouseup", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeMouseEvent("mousedown", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeMouseEvent("mouseup", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeMouseEvent("dblclick", component.content(), userClickPoint.x, userClickPoint.y); assert.deepEqual(doubleClickedPoint, userClickPoint, "was passed correct point (mouse)"); svg.remove(); }); it("callback not called if clicked in different locations", function () { var userClickPoint = { x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }; - triggerFakeMouseEvent("mousedown", component.content(), userClickPoint.x, userClickPoint.y); - triggerFakeMouseEvent("mouseup", component.content(), userClickPoint.x, userClickPoint.y); - triggerFakeMouseEvent("mousedown", component.content(), userClickPoint.x + 10, userClickPoint.y + 10); - triggerFakeMouseEvent("mouseup", component.content(), userClickPoint.x + 10, userClickPoint.y + 10); - triggerFakeMouseEvent("dblclick", component.content(), userClickPoint.x + 10, userClickPoint.y + 10); + TestMethods.triggerFakeMouseEvent("mousedown", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeMouseEvent("mouseup", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeMouseEvent("mousedown", component.content(), userClickPoint.x + 10, userClickPoint.y + 10); + TestMethods.triggerFakeMouseEvent("mouseup", component.content(), userClickPoint.x + 10, userClickPoint.y + 10); + TestMethods.triggerFakeMouseEvent("dblclick", component.content(), userClickPoint.x + 10, userClickPoint.y + 10); + assert.deepEqual(doubleClickedPoint, null, "point never set"); + svg.remove(); + }); + it("callback not called does not receive dblclick confirmation", function () { + var userClickPoint = { x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }; + TestMethods.triggerFakeMouseEvent("mousedown", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeMouseEvent("mouseup", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeMouseEvent("mousedown", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeMouseEvent("mouseup", component.content(), userClickPoint.x, userClickPoint.y); assert.deepEqual(doubleClickedPoint, null, "point never set"); svg.remove(); }); it("callback not called does not receive dblclick confirmation", function () { var userClickPoint = { x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }; - triggerFakeMouseEvent("mousedown", component.content(), userClickPoint.x, userClickPoint.y); - triggerFakeMouseEvent("mouseup", component.content(), userClickPoint.x, userClickPoint.y); - triggerFakeMouseEvent("mousedown", component.content(), userClickPoint.x, userClickPoint.y); - triggerFakeMouseEvent("mouseup", component.content(), userClickPoint.x, userClickPoint.y); + TestMethods.triggerFakeTouchEvent("touchstart", component.content(), [{ x: userClickPoint.x, y: userClickPoint.y }]); + TestMethods.triggerFakeTouchEvent("touchend", component.content(), [{ x: userClickPoint.x, y: userClickPoint.y }]); + TestMethods.triggerFakeTouchEvent("touchstart", component.content(), [{ x: userClickPoint.x, y: userClickPoint.y }]); + TestMethods.triggerFakeTouchEvent("touchend", component.content(), [{ x: userClickPoint.x, y: userClickPoint.y }]); + TestMethods.triggerFakeTouchEvent("touchcancel", component.content(), [{ x: userClickPoint.x, y: userClickPoint.y }]); + TestMethods.triggerFakeMouseEvent("dblclick", component.content(), userClickPoint.x, userClickPoint.y); assert.deepEqual(doubleClickedPoint, null, "point never set"); svg.remove(); }); @@ -9059,10 +8788,10 @@ describe("Interactions", function () { y: 0 }; it("onDragStart()", function () { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var c = new Plottable.Component.AbstractComponent(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var c = new Plottable.Component(); c.renderTo(svg); - var drag = new Plottable.Interaction.Drag(); + var drag = new Plottable.Interactions.Drag(); var startCallbackCalled = false; var receivedStart; var startCallback = function (p) { @@ -9070,39 +8799,42 @@ describe("Interactions", function () { receivedStart = p; }; drag.onDragStart(startCallback); - c.registerInteraction(drag); + drag.attachTo(c); var target = c.background(); - triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); + TestMethods.triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); assert.isTrue(startCallbackCalled, "callback was called on beginning drag (mousedown)"); assert.deepEqual(receivedStart, startPoint, "was passed the correct point"); startCallbackCalled = false; receivedStart = null; - triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y, 2); + TestMethods.triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y, 2); assert.isFalse(startCallbackCalled, "callback is not called on right-click"); startCallbackCalled = false; receivedStart = null; - triggerFakeTouchEvent("touchstart", target, [{ x: startPoint.x, y: startPoint.y }]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{ x: startPoint.x, y: startPoint.y }]); assert.isTrue(startCallbackCalled, "callback was called on beginning drag (touchstart)"); assert.deepEqual(receivedStart, startPoint, "was passed the correct point"); startCallbackCalled = false; - triggerFakeMouseEvent("mousedown", target, outsidePointPos.x, outsidePointPos.y); + TestMethods.triggerFakeMouseEvent("mousedown", target, outsidePointPos.x, outsidePointPos.y); assert.isFalse(startCallbackCalled, "does not trigger callback if drag starts outside the Component (positive) (mousedown)"); - triggerFakeMouseEvent("mousedown", target, outsidePointNeg.x, outsidePointNeg.y); + TestMethods.triggerFakeMouseEvent("mousedown", target, outsidePointNeg.x, outsidePointNeg.y); assert.isFalse(startCallbackCalled, "does not trigger callback if drag starts outside the Component (negative) (mousedown)"); - triggerFakeTouchEvent("touchstart", target, [{ x: outsidePointPos.x, y: outsidePointPos.y }]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{ x: outsidePointPos.x, y: outsidePointPos.y }]); assert.isFalse(startCallbackCalled, "does not trigger callback if drag starts outside the Component (positive) (touchstart)"); - triggerFakeTouchEvent("touchstart", target, [{ x: outsidePointNeg.x, y: outsidePointNeg.y }]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{ x: outsidePointNeg.x, y: outsidePointNeg.y }]); assert.isFalse(startCallbackCalled, "does not trigger callback if drag starts outside the Component (negative) (touchstart)"); - assert.strictEqual(drag.onDragStart(), startCallback, "retrieves the callback if called with no arguments"); - drag.onDragStart(null); - assert.isNull(drag.onDragStart(), "removes the callback if called with null"); + drag.offDragStart(startCallback); + startCallbackCalled = false; + TestMethods.triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); + assert.isFalse(startCallbackCalled, "callback was decoupled from the interaction"); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{ x: startPoint.x, y: startPoint.y }]); + assert.isFalse(startCallbackCalled, "callback was decoupled from the interaction"); svg.remove(); }); it("onDrag()", function () { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var c = new Plottable.Component.AbstractComponent(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var c = new Plottable.Component(); c.renderTo(svg); - var drag = new Plottable.Interaction.Drag(); + var drag = new Plottable.Interactions.Drag(); var moveCallbackCalled = false; var receivedStart; var receivedEnd; @@ -9112,30 +8844,35 @@ describe("Interactions", function () { receivedEnd = end; }; drag.onDrag(moveCallback); - c.registerInteraction(drag); + drag.attachTo(c); var target = c.background(); - triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); - triggerFakeMouseEvent("mousemove", target, endPoint.x, endPoint.y); + TestMethods.triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); + TestMethods.triggerFakeMouseEvent("mousemove", target, endPoint.x, endPoint.y); assert.isTrue(moveCallbackCalled, "callback was called on dragging (mousemove)"); assert.deepEqual(receivedStart, startPoint, "was passed the correct starting point"); assert.deepEqual(receivedEnd, endPoint, "was passed the correct current point"); receivedStart = null; receivedEnd = null; - triggerFakeTouchEvent("touchstart", target, [{ x: startPoint.x, y: startPoint.y }]); - triggerFakeTouchEvent("touchmove", target, [{ x: endPoint.x, y: endPoint.y }]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{ x: startPoint.x, y: startPoint.y }]); + TestMethods.triggerFakeTouchEvent("touchmove", target, [{ x: endPoint.x, y: endPoint.y }]); assert.isTrue(moveCallbackCalled, "callback was called on dragging (touchmove)"); assert.deepEqual(receivedStart, startPoint, "was passed the correct starting point"); assert.deepEqual(receivedEnd, endPoint, "was passed the correct current point"); - assert.strictEqual(drag.onDrag(), moveCallback, "retrieves the callback if called with no arguments"); - drag.onDrag(null); - assert.isNull(drag.onDrag(), "removes the callback if called with null"); + drag.offDrag(moveCallback); + moveCallbackCalled = false; + TestMethods.triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); + TestMethods.triggerFakeMouseEvent("mousemove", target, endPoint.x, endPoint.y); + assert.isFalse(moveCallbackCalled, "callback was decoupled from interaction"); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{ x: startPoint.x, y: startPoint.y }]); + TestMethods.triggerFakeTouchEvent("touchmove", target, [{ x: endPoint.x, y: endPoint.y }]); + assert.isFalse(moveCallbackCalled, "callback was decoupled from interaction"); svg.remove(); }); it("onDragEnd()", function () { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var c = new Plottable.Component.AbstractComponent(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var c = new Plottable.Component(); c.renderTo(svg); - var drag = new Plottable.Interaction.Drag(); + var drag = new Plottable.Interactions.Drag(); var endCallbackCalled = false; var receivedStart; var receivedEnd; @@ -9145,36 +8882,95 @@ describe("Interactions", function () { receivedEnd = end; }; drag.onDragEnd(endCallback); - c.registerInteraction(drag); + drag.attachTo(c); var target = c.background(); - triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); - triggerFakeMouseEvent("mouseup", target, endPoint.x, endPoint.y); + TestMethods.triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); + TestMethods.triggerFakeMouseEvent("mouseup", target, endPoint.x, endPoint.y); assert.isTrue(endCallbackCalled, "callback was called on drag ending (mouseup)"); assert.deepEqual(receivedStart, startPoint, "was passed the correct starting point"); assert.deepEqual(receivedEnd, endPoint, "was passed the correct current point"); receivedStart = null; receivedEnd = null; - triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); - triggerFakeMouseEvent("mouseup", target, endPoint.x, endPoint.y, 2); + TestMethods.triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); + TestMethods.triggerFakeMouseEvent("mouseup", target, endPoint.x, endPoint.y, 2); assert.isTrue(endCallbackCalled, "callback was not called on mouseup from the right-click button"); - triggerFakeMouseEvent("mouseup", target, endPoint.x, endPoint.y); // end the drag + TestMethods.triggerFakeMouseEvent("mouseup", target, endPoint.x, endPoint.y); // end the drag receivedStart = null; receivedEnd = null; - triggerFakeTouchEvent("touchstart", target, [{ x: startPoint.x, y: startPoint.y }]); - triggerFakeTouchEvent("touchend", target, [{ x: endPoint.x, y: endPoint.y }]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{ x: startPoint.x, y: startPoint.y }]); + TestMethods.triggerFakeTouchEvent("touchend", target, [{ x: endPoint.x, y: endPoint.y }]); assert.isTrue(endCallbackCalled, "callback was called on drag ending (touchend)"); assert.deepEqual(receivedStart, startPoint, "was passed the correct starting point"); assert.deepEqual(receivedEnd, endPoint, "was passed the correct current point"); - assert.strictEqual(drag.onDragEnd(), endCallback, "retrieves the callback if called with no arguments"); - drag.onDragEnd(null); - assert.isNull(drag.onDragEnd(), "removes the callback if called with null"); + drag.offDragEnd(endCallback); + endCallbackCalled = false; + TestMethods.triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); + TestMethods.triggerFakeMouseEvent("mouseup", target, endPoint.x, endPoint.y); + assert.isFalse(endCallbackCalled, "callback was called on drag ending (mouseup)"); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{ x: startPoint.x, y: startPoint.y }]); + TestMethods.triggerFakeTouchEvent("touchend", target, [{ x: endPoint.x, y: endPoint.y }]); + assert.isFalse(endCallbackCalled, "callback decoupled from interaction"); + svg.remove(); + }); + it("multiple interactions on drag", function () { + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var component = new Plottable.Component(); + component.renderTo(svg); + var drag = new Plottable.Interactions.Drag(); + var startCallback1Called = false; + var startCallback2Called = false; + var moveCallback1Called = false; + var moveCallback2Called = false; + var endCallback1Called = false; + var endCallback2Called = false; + var startCallback1 = function () { return startCallback1Called = true; }; + var startCallback2 = function () { return startCallback2Called = true; }; + var moveCallback1 = function () { return moveCallback1Called = true; }; + var moveCallback2 = function () { return moveCallback2Called = true; }; + var endCallback1 = function () { return endCallback1Called = true; }; + var endCallback2 = function () { return endCallback2Called = true; }; + drag.onDragStart(startCallback1); + drag.onDragStart(startCallback2); + drag.onDrag(moveCallback1); + drag.onDrag(moveCallback2); + drag.onDragEnd(endCallback1); + drag.onDragEnd(endCallback2); + drag.attachTo(component); + var target = component.background(); + TestMethods.triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); + assert.isTrue(startCallback1Called, "callback 1 was called on beginning drag (mousedown)"); + assert.isTrue(startCallback2Called, "callback 2 was called on beginning drag (mousedown)"); + TestMethods.triggerFakeMouseEvent("mousemove", target, endPoint.x, endPoint.y); + assert.isTrue(moveCallback1Called, "callback 1 was called on dragging (mousemove)"); + assert.isTrue(moveCallback2Called, "callback 2 was called on dragging (mousemove)"); + TestMethods.triggerFakeMouseEvent("mouseup", target, endPoint.x, endPoint.y); + assert.isTrue(endCallback1Called, "callback 1 was called on drag ending (mouseup)"); + assert.isTrue(endCallback2Called, "callback 2 was called on drag ending (mouseup)"); + startCallback1Called = false; + startCallback2Called = false; + moveCallback1Called = false; + moveCallback2Called = false; + endCallback1Called = false; + endCallback2Called = false; + drag.offDragStart(startCallback1); + drag.offDrag(moveCallback1); + drag.offDragEnd(endCallback1); + TestMethods.triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); + assert.isFalse(startCallback1Called, "callback 1 was disconnected from drag start interaction"); + assert.isTrue(startCallback2Called, "callback 2 is still connected to the drag start interaction"); + TestMethods.triggerFakeMouseEvent("mousemove", target, endPoint.x, endPoint.y); + assert.isFalse(moveCallback1Called, "callback 1 was disconnected from drag interaction"); + assert.isTrue(moveCallback2Called, "callback 2 is still connected to the drag interaction"); + TestMethods.triggerFakeMouseEvent("mouseup", target, endPoint.x, endPoint.y); + assert.isFalse(endCallback1Called, "callback 1 was disconnected from the drag end interaction"); + assert.isTrue(endCallback2Called, "callback 2 is still connected to the drag end interaction"); svg.remove(); }); it("constrainToComponent()", function () { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var c = new Plottable.Component.AbstractComponent(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var c = new Plottable.Component(); c.renderTo(svg); - var drag = new Plottable.Interaction.Drag(); + var drag = new Plottable.Interactions.Drag(); assert.isTrue(drag.constrainToComponent(), "constrains by default"); var receivedStart; var receivedEnd; @@ -9188,118 +8984,245 @@ describe("Interactions", function () { receivedEnd = end; }; drag.onDragEnd(endCallback); - c.registerInteraction(drag); + drag.attachTo(c); var target = c.content(); - triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); - triggerFakeMouseEvent("mousemove", target, outsidePointPos.x, outsidePointPos.y); + TestMethods.triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); + TestMethods.triggerFakeMouseEvent("mousemove", target, outsidePointPos.x, outsidePointPos.y); assert.deepEqual(receivedEnd, constrainedPos, "dragging outside the Component is constrained (positive) (mousemove)"); - triggerFakeMouseEvent("mousemove", target, outsidePointNeg.x, outsidePointNeg.y); + TestMethods.triggerFakeMouseEvent("mousemove", target, outsidePointNeg.x, outsidePointNeg.y); assert.deepEqual(receivedEnd, constrainedNeg, "dragging outside the Component is constrained (negative) (mousemove)"); receivedEnd = null; - triggerFakeTouchEvent("touchmove", target, [{ x: outsidePointPos.x, y: outsidePointPos.y }]); + TestMethods.triggerFakeTouchEvent("touchmove", target, [{ x: outsidePointPos.x, y: outsidePointPos.y }]); assert.deepEqual(receivedEnd, constrainedPos, "dragging outside the Component is constrained (positive) (touchmove)"); - triggerFakeTouchEvent("touchmove", target, [{ x: outsidePointNeg.x, y: outsidePointNeg.y }]); + TestMethods.triggerFakeTouchEvent("touchmove", target, [{ x: outsidePointNeg.x, y: outsidePointNeg.y }]); assert.deepEqual(receivedEnd, constrainedNeg, "dragging outside the Component is constrained (negative) (touchmove)"); receivedEnd = null; - triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); - triggerFakeMouseEvent("mouseup", target, outsidePointPos.x, outsidePointPos.y); + TestMethods.triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); + TestMethods.triggerFakeMouseEvent("mouseup", target, outsidePointPos.x, outsidePointPos.y); assert.deepEqual(receivedEnd, constrainedPos, "dragging outside the Component is constrained (positive) (mouseup)"); - triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); - triggerFakeMouseEvent("mouseup", target, outsidePointNeg.x, outsidePointNeg.y); + TestMethods.triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); + TestMethods.triggerFakeMouseEvent("mouseup", target, outsidePointNeg.x, outsidePointNeg.y); assert.deepEqual(receivedEnd, constrainedNeg, "dragging outside the Component is constrained (negative) (mouseup)"); receivedEnd = null; - triggerFakeTouchEvent("touchstart", target, [{ x: startPoint.x, y: startPoint.y }]); - triggerFakeTouchEvent("touchend", target, [{ x: outsidePointPos.x, y: outsidePointPos.y }]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{ x: startPoint.x, y: startPoint.y }]); + TestMethods.triggerFakeTouchEvent("touchend", target, [{ x: outsidePointPos.x, y: outsidePointPos.y }]); assert.deepEqual(receivedEnd, constrainedPos, "dragging outside the Component is constrained (positive) (touchend)"); - triggerFakeTouchEvent("touchstart", target, [{ x: startPoint.x, y: startPoint.y }]); - triggerFakeTouchEvent("touchend", target, [{ x: outsidePointNeg.x, y: outsidePointNeg.y }]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{ x: startPoint.x, y: startPoint.y }]); + TestMethods.triggerFakeTouchEvent("touchend", target, [{ x: outsidePointNeg.x, y: outsidePointNeg.y }]); assert.deepEqual(receivedEnd, constrainedNeg, "dragging outside the Component is constrained (negative) (touchend)"); drag.constrainToComponent(false); - triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); - triggerFakeMouseEvent("mousemove", target, outsidePointPos.x, outsidePointPos.y); + TestMethods.triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); + TestMethods.triggerFakeMouseEvent("mousemove", target, outsidePointPos.x, outsidePointPos.y); assert.deepEqual(receivedEnd, outsidePointPos, "dragging outside the Component is no longer constrained (positive) (mousemove)"); - triggerFakeMouseEvent("mousemove", target, outsidePointNeg.x, outsidePointNeg.y); + TestMethods.triggerFakeMouseEvent("mousemove", target, outsidePointNeg.x, outsidePointNeg.y); assert.deepEqual(receivedEnd, outsidePointNeg, "dragging outside the Component is no longer constrained (negative) (mousemove)"); receivedEnd = null; - triggerFakeTouchEvent("touchmove", target, [{ x: outsidePointPos.x, y: outsidePointPos.y }]); + TestMethods.triggerFakeTouchEvent("touchmove", target, [{ x: outsidePointPos.x, y: outsidePointPos.y }]); assert.deepEqual(receivedEnd, outsidePointPos, "dragging outside the Component is no longer constrained (positive) (touchmove)"); - triggerFakeTouchEvent("touchmove", target, [{ x: outsidePointNeg.x, y: outsidePointNeg.y }]); + TestMethods.triggerFakeTouchEvent("touchmove", target, [{ x: outsidePointNeg.x, y: outsidePointNeg.y }]); assert.deepEqual(receivedEnd, outsidePointNeg, "dragging outside the Component is no longer constrained (negative) (touchmove)"); receivedEnd = null; - triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); - triggerFakeMouseEvent("mouseup", target, outsidePointPos.x, outsidePointPos.y); + TestMethods.triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); + TestMethods.triggerFakeMouseEvent("mouseup", target, outsidePointPos.x, outsidePointPos.y); assert.deepEqual(receivedEnd, outsidePointPos, "dragging outside the Component is no longer constrained (positive) (mouseup)"); - triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); - triggerFakeMouseEvent("mouseup", target, outsidePointNeg.x, outsidePointNeg.y); + TestMethods.triggerFakeMouseEvent("mousedown", target, startPoint.x, startPoint.y); + TestMethods.triggerFakeMouseEvent("mouseup", target, outsidePointNeg.x, outsidePointNeg.y); assert.deepEqual(receivedEnd, outsidePointNeg, "dragging outside the Component is no longer constrained (negative) (mouseup)"); receivedEnd = null; - triggerFakeTouchEvent("touchstart", target, [{ x: startPoint.x, y: startPoint.y }]); - triggerFakeTouchEvent("touchend", target, [{ x: outsidePointPos.x, y: outsidePointPos.y }]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{ x: startPoint.x, y: startPoint.y }]); + TestMethods.triggerFakeTouchEvent("touchend", target, [{ x: outsidePointPos.x, y: outsidePointPos.y }]); assert.deepEqual(receivedEnd, outsidePointPos, "dragging outside the Component is no longer constrained (positive) (touchend)"); - triggerFakeTouchEvent("touchstart", target, [{ x: startPoint.x, y: startPoint.y }]); - triggerFakeTouchEvent("touchend", target, [{ x: outsidePointNeg.x, y: outsidePointNeg.y }]); + TestMethods.triggerFakeTouchEvent("touchstart", target, [{ x: startPoint.x, y: startPoint.y }]); + TestMethods.triggerFakeTouchEvent("touchend", target, [{ x: outsidePointNeg.x, y: outsidePointNeg.y }]); assert.deepEqual(receivedEnd, outsidePointNeg, "dragging outside the Component is no longer constrained (negative) (touchend)"); svg.remove(); }); + it("touchcancel cancels the current drag", function () { + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var c = new Plottable.Component(); + c.renderTo(svg); + var drag = new Plottable.Interactions.Drag(); + var moveCallbackCalled = false; + var receivedStart; + var receivedEnd; + var moveCallback = function (start, end) { + moveCallbackCalled = true; + receivedStart = start; + receivedEnd = end; + }; + drag.onDrag(moveCallback); + drag.attachTo(c); + var target = c.background(); + receivedStart = null; + receivedEnd = null; + TestMethods.triggerFakeTouchEvent("touchstart", target, [{ x: startPoint.x, y: startPoint.y }]); + TestMethods.triggerFakeTouchEvent("touchmove", target, [{ x: endPoint.x - 10, y: endPoint.y - 10 }]); + TestMethods.triggerFakeTouchEvent("touchcancel", target, [{ x: endPoint.x - 10, y: endPoint.y - 10 }]); + TestMethods.triggerFakeTouchEvent("touchmove", target, [{ x: endPoint.x, y: endPoint.y }]); + assert.notEqual(receivedEnd, endPoint, "was not passed touch point after cancelled"); + svg.remove(); + }); + }); +}); + +/// +var assert = chai.assert; +describe("Interactions", function () { + describe("PanZoomInteraction", function () { + var svg; + var SVG_WIDTH = 400; + var SVG_HEIGHT = 500; + var eventTarget; + var xScale; + var yScale; + beforeEach(function () { + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var component = new Plottable.Component(); + component.renderTo(svg); + xScale = new Plottable.Scales.Linear(); + xScale.domain([0, SVG_WIDTH / 2]).range([0, SVG_WIDTH]); + yScale = new Plottable.Scales.Linear(); + yScale.domain([0, SVG_HEIGHT / 2]).range([0, SVG_HEIGHT]); + (new Plottable.Interactions.PanZoom(xScale, yScale)).attachTo(component); + eventTarget = component.background(); + }); + describe("Panning", function () { + it("dragging a certain amount will translate the scale correctly (mouse)", function () { + var startPoint = { x: SVG_WIDTH / 4, y: SVG_HEIGHT / 4 }; + var endPoint = { x: SVG_WIDTH / 2, y: SVG_HEIGHT * 3 / 4 }; + TestMethods.triggerFakeMouseEvent("mousedown", eventTarget, startPoint.x, startPoint.y); + TestMethods.triggerFakeMouseEvent("mousemove", eventTarget, endPoint.x, endPoint.y); + TestMethods.triggerFakeMouseEvent("mouseend", eventTarget, endPoint.x, endPoint.y); + assert.deepEqual(xScale.domain(), [-SVG_WIDTH / 8, SVG_WIDTH * 3 / 8], "xScale pans to the correct domain via drag (mouse)"); + assert.deepEqual(yScale.domain(), [-SVG_HEIGHT / 4, SVG_HEIGHT / 4], "yScale pans to the correct domain via drag (mouse)"); + svg.remove(); + }); + it("dragging to outside the component will translate the scale correctly (mouse)", function () { + var startPoint = { x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }; + var endPoint = { x: -SVG_WIDTH / 2, y: -SVG_HEIGHT / 2 }; + TestMethods.triggerFakeMouseEvent("mousedown", eventTarget, startPoint.x, startPoint.y); + TestMethods.triggerFakeMouseEvent("mousemove", eventTarget, endPoint.x, endPoint.y); + TestMethods.triggerFakeMouseEvent("mouseend", eventTarget, endPoint.x, endPoint.y); + assert.deepEqual(xScale.domain(), [SVG_WIDTH / 2, SVG_WIDTH], "xScale pans to the correct domain via drag (mouse)"); + assert.deepEqual(yScale.domain(), [SVG_HEIGHT / 2, SVG_HEIGHT], "yScale pans to the correct domain via drag (mouse)"); + svg.remove(); + }); + it("dragging a certain amount will translate the scale correctly (touch)", function () { + // HACKHACK PhantomJS doesn't implement fake creation of WheelEvents + // https://github.com/ariya/phantomjs/issues/11289 + if (window.PHANTOMJS) { + svg.remove(); + return; + } + var startPoint = { x: SVG_WIDTH / 4, y: SVG_HEIGHT / 4 }; + var endPoint = { x: SVG_WIDTH / 2, y: SVG_HEIGHT * 3 / 4 }; + TestMethods.triggerFakeTouchEvent("touchstart", eventTarget, [startPoint]); + TestMethods.triggerFakeTouchEvent("touchmove", eventTarget, [endPoint]); + TestMethods.triggerFakeTouchEvent("touchend", eventTarget, [endPoint]); + assert.deepEqual(xScale.domain(), [-SVG_WIDTH / 8, SVG_WIDTH * 3 / 8], "xScale pans to the correct domain via drag (touch)"); + assert.deepEqual(yScale.domain(), [-SVG_HEIGHT / 4, SVG_HEIGHT / 4], "yScale pans to the correct domain via drag (touch)"); + svg.remove(); + }); + it("dragging to outside the component will translate the scale correctly (touch)", function () { + var startPoint = { x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }; + var endPoint = { x: -SVG_WIDTH / 2, y: -SVG_HEIGHT / 2 }; + TestMethods.triggerFakeTouchEvent("touchstart", eventTarget, [startPoint]); + TestMethods.triggerFakeTouchEvent("touchmove", eventTarget, [endPoint]); + TestMethods.triggerFakeTouchEvent("touchend", eventTarget, [endPoint]); + assert.deepEqual(xScale.domain(), [SVG_WIDTH / 2, SVG_WIDTH], "xScale pans to the correct domain via drag (touch)"); + assert.deepEqual(yScale.domain(), [SVG_HEIGHT / 2, SVG_HEIGHT], "yScale pans to the correct domain via drag (touch)"); + svg.remove(); + }); + }); + it("mousewheeling a certain amount will magnify the scale correctly", function () { + // HACKHACK PhantomJS doesn't implement fake creation of WheelEvents + // https://github.com/ariya/phantomjs/issues/11289 + if (window.PHANTOMJS) { + svg.remove(); + return; + } + var scrollPoint = { x: SVG_WIDTH / 4, y: SVG_HEIGHT / 4 }; + var deltaY = 500; + TestMethods.triggerFakeWheelEvent("wheel", svg, scrollPoint.x, scrollPoint.y, deltaY); + assert.deepEqual(xScale.domain(), [-SVG_WIDTH / 8, SVG_WIDTH * 7 / 8], "xScale zooms to the correct domain via scroll"); + assert.deepEqual(yScale.domain(), [-SVG_HEIGHT / 8, SVG_HEIGHT * 7 / 8], "yScale zooms to the correct domain via scroll"); + svg.remove(); + }); + it("pinching a certain amount will magnify the scale correctly", function () { + // HACKHACK PhantomJS doesn't implement fake creation of WheelEvents + // https://github.com/ariya/phantomjs/issues/11289 + if (window.PHANTOMJS) { + svg.remove(); + return; + } + var startPoint = { x: SVG_WIDTH / 4, y: SVG_HEIGHT / 4 }; + var startPoint2 = { x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }; + TestMethods.triggerFakeTouchEvent("touchstart", eventTarget, [startPoint, startPoint2], [0, 1]); + var endPoint = { x: SVG_WIDTH * 3 / 4, y: SVG_HEIGHT * 3 / 4 }; + TestMethods.triggerFakeTouchEvent("touchmove", eventTarget, [endPoint], [1]); + TestMethods.triggerFakeTouchEvent("touchend", eventTarget, [endPoint], [1]); + assert.deepEqual(xScale.domain(), [SVG_WIDTH / 16, SVG_WIDTH * 5 / 16], "xScale transforms to the correct domain via pinch"); + assert.deepEqual(yScale.domain(), [SVG_HEIGHT / 16, SVG_HEIGHT * 5 / 16], "yScale transforms to the correct domain via pinch"); + svg.remove(); + }); }); }); /// var assert = chai.assert; describe("Dispatchers", function () { - describe("AbstractDispatcher", function () { + describe("Dispatcher", function () { it("_connect() and _disconnect()", function () { - var dispatcher = new Plottable.Dispatcher.AbstractDispatcher(); + var dispatcher = new Plottable.Dispatcher(); var callbackCalls = 0; dispatcher._event2Callback["click"] = function () { return callbackCalls++; }; var d3document = d3.select(document); dispatcher._connect(); - triggerFakeUIEvent("click", d3document); + TestMethods.triggerFakeUIEvent("click", d3document); assert.strictEqual(callbackCalls, 1, "connected correctly (callback was called)"); dispatcher._connect(); callbackCalls = 0; - triggerFakeUIEvent("click", d3document); + TestMethods.triggerFakeUIEvent("click", d3document); assert.strictEqual(callbackCalls, 1, "can't double-connect (callback only called once)"); dispatcher._disconnect(); callbackCalls = 0; - triggerFakeUIEvent("click", d3document); + TestMethods.triggerFakeUIEvent("click", d3document); assert.strictEqual(callbackCalls, 0, "disconnected correctly (callback not called)"); }); - it("won't _disconnect() if broadcasters still have listeners", function () { - var dispatcher = new Plottable.Dispatcher.AbstractDispatcher(); + it("won't _disconnect() if dispatcher still have listeners", function () { + var dispatcher = new Plottable.Dispatcher(); var callbackWasCalled = false; dispatcher._event2Callback["click"] = function () { return callbackWasCalled = true; }; - var b = new Plottable.Core.Broadcaster(dispatcher); - var key = "unit test"; - b.registerListener(key, function () { return null; }); - dispatcher._broadcasters = [b]; + var callback = function () { return null; }; + var callbackSet = new Plottable.Utils.CallbackSet(); + callbackSet.add(callback); + dispatcher._callbacks = [callbackSet]; var d3document = d3.select(document); dispatcher._connect(); - triggerFakeUIEvent("click", d3document); + TestMethods.triggerFakeUIEvent("click", d3document); assert.isTrue(callbackWasCalled, "connected correctly (callback was called)"); dispatcher._disconnect(); callbackWasCalled = false; - triggerFakeUIEvent("click", d3document); - assert.isTrue(callbackWasCalled, "didn't disconnect while broadcaster had listener"); - b.deregisterListener(key); + TestMethods.triggerFakeUIEvent("click", d3document); + assert.isTrue(callbackWasCalled, "didn't disconnect while dispatcher had listener"); + callbackSet.delete(callback); dispatcher._disconnect(); callbackWasCalled = false; - triggerFakeUIEvent("click", d3document); - assert.isFalse(callbackWasCalled, "disconnected when broadcaster had no listeners"); + TestMethods.triggerFakeUIEvent("click", d3document); + assert.isFalse(callbackWasCalled, "disconnected when dispatcher had no listeners"); }); - it("_setCallback()", function () { - var dispatcher = new Plottable.Dispatcher.AbstractDispatcher(); - var b = new Plottable.Core.Broadcaster(dispatcher); - var key = "unit test"; + it("setCallback()", function () { + var dispatcher = new Plottable.Dispatcher(); + var callbackSet = new Plottable.Utils.CallbackSet(); var callbackWasCalled = false; var callback = function () { return callbackWasCalled = true; }; - dispatcher._setCallback(b, key, callback); - b.broadcast(); - assert.isTrue(callbackWasCalled, "callback was called after setting with _setCallback()"); - dispatcher._setCallback(b, key, null); + dispatcher.setCallback(callbackSet, callback); + callbackSet.callCallbacks(); + assert.isTrue(callbackWasCalled, "callback was called after setting with setCallback()"); + dispatcher.unsetCallback(callbackSet, callback); callbackWasCalled = false; - b.broadcast(); - assert.isFalse(callbackWasCalled, "callback was removed by calling _setCallback() with null"); + callbackSet.callCallbacks(); + assert.isFalse(callbackWasCalled, "callback was removed by calling setCallback() with null"); }); }); }); @@ -9308,22 +9231,17 @@ describe("Dispatchers", function () { var assert = chai.assert; describe("Dispatchers", function () { describe("Mouse Dispatcher", function () { - function assertPointsClose(actual, expected, epsilon, message) { - assert.closeTo(actual.x, expected.x, epsilon, message + " (x)"); - assert.closeTo(actual.y, expected.y, epsilon, message + " (y)"); - } - ; it("getDispatcher() creates only one Dispatcher.Mouse per ", function () { - var svg = generateSVG(); - var md1 = Plottable.Dispatcher.Mouse.getDispatcher(svg.node()); + var svg = TestMethods.generateSVG(); + var md1 = Plottable.Dispatchers.Mouse.getDispatcher(svg.node()); assert.isNotNull(md1, "created a new Dispatcher on an SVG"); - var md2 = Plottable.Dispatcher.Mouse.getDispatcher(svg.node()); + var md2 = Plottable.Dispatchers.Mouse.getDispatcher(svg.node()); assert.strictEqual(md1, md2, "returned the existing Dispatcher if called again with same "); svg.remove(); }); it("getLastMousePosition() defaults to a non-null value", function () { - var svg = generateSVG(); - var md = Plottable.Dispatcher.Mouse.getDispatcher(svg.node()); + var svg = TestMethods.generateSVG(); + var md = Plottable.Dispatchers.Mouse.getDispatcher(svg.node()); var p = md.getLastMousePosition(); assert.isNotNull(p, "returns a value after initialization"); assert.isNotNull(p.x, "x value is set"); @@ -9332,52 +9250,51 @@ describe("Dispatchers", function () { }); it("can remove callbacks by passing null", function () { var targetWidth = 400, targetHeight = 400; - var target = generateSVG(targetWidth, targetHeight); + var target = TestMethods.generateSVG(targetWidth, targetHeight); // HACKHACK: PhantomJS can't measure SVGs unless they have something in them occupying space target.append("rect").attr("width", targetWidth).attr("height", targetHeight); var targetX = 17; var targetY = 76; - var md = Plottable.Dispatcher.Mouse.getDispatcher(target.node()); + var md = Plottable.Dispatchers.Mouse.getDispatcher(target.node()); var cb1Called = false; var cb1 = function (p, e) { return cb1Called = true; }; var cb2Called = false; var cb2 = function (p, e) { return cb2Called = true; }; - md.onMouseMove("callback1", cb1); - md.onMouseMove("callback2", cb2); - triggerFakeMouseEvent("mousemove", target, targetX, targetY); + md.onMouseMove(cb1); + md.onMouseMove(cb2); + TestMethods.triggerFakeMouseEvent("mousemove", target, targetX, targetY); assert.isTrue(cb1Called, "callback 1 was called on mousemove"); assert.isTrue(cb2Called, "callback 2 was called on mousemove"); cb1Called = false; cb2Called = false; - md.onMouseMove("callback1", null); - triggerFakeMouseEvent("mousemove", target, targetX, targetY); + md.offMouseMove(cb1); + TestMethods.triggerFakeMouseEvent("mousemove", target, targetX, targetY); assert.isFalse(cb1Called, "callback was not called after blanking"); assert.isTrue(cb2Called, "callback 2 was still called"); target.remove(); }); it("doesn't call callbacks if not in the DOM", function () { var targetWidth = 400, targetHeight = 400; - var target = generateSVG(targetWidth, targetHeight); + var target = TestMethods.generateSVG(targetWidth, targetHeight); // HACKHACK: PhantomJS can't measure SVGs unless they have something in them occupying space target.append("rect").attr("width", targetWidth).attr("height", targetHeight); var targetX = 17; var targetY = 76; - var md = Plottable.Dispatcher.Mouse.getDispatcher(target.node()); + var md = Plottable.Dispatchers.Mouse.getDispatcher(target.node()); var callbackWasCalled = false; var callback = function (p, e) { return callbackWasCalled = true; }; - var keyString = "notInDomTest"; - md.onMouseMove(keyString, callback); - triggerFakeMouseEvent("mousemove", target, targetX, targetY); + md.onMouseMove(callback); + TestMethods.triggerFakeMouseEvent("mousemove", target, targetX, targetY); assert.isTrue(callbackWasCalled, "callback was called on mousemove"); target.remove(); callbackWasCalled = false; - triggerFakeMouseEvent("mousemove", target, targetX, targetY); + TestMethods.triggerFakeMouseEvent("mousemove", target, targetX, targetY); assert.isFalse(callbackWasCalled, "callback was not called after was removed from DOM"); - md.onMouseMove(keyString, null); + md.offMouseMove(callback); }); it("calls callbacks on mouseover, mousemove, and mouseout", function () { var targetWidth = 400, targetHeight = 400; - var target = generateSVG(targetWidth, targetHeight); + var target = TestMethods.generateSVG(targetWidth, targetHeight); // HACKHACK: PhantomJS can't measure SVGs unless they have something in them occupying space target.append("rect").attr("width", targetWidth).attr("height", targetHeight); var targetX = 17; @@ -9386,29 +9303,28 @@ describe("Dispatchers", function () { x: targetX, y: targetY }; - var md = Plottable.Dispatcher.Mouse.getDispatcher(target.node()); + var md = Plottable.Dispatchers.Mouse.getDispatcher(target.node()); var callbackWasCalled = false; var callback = function (p, e) { callbackWasCalled = true; - assertPointsClose(p, expectedPoint, 0.5, "mouse position is correct"); + TestMethods.assertPointsClose(p, expectedPoint, 0.5, "mouse position is correct"); assert.isNotNull(e, "mouse event was passed to the callback"); }; - var keyString = "unit test"; - md.onMouseMove(keyString, callback); - triggerFakeMouseEvent("mouseover", target, targetX, targetY); + md.onMouseMove(callback); + TestMethods.triggerFakeMouseEvent("mouseover", target, targetX, targetY); assert.isTrue(callbackWasCalled, "callback was called on mouseover"); callbackWasCalled = false; - triggerFakeMouseEvent("mousemove", target, targetX, targetY); + TestMethods.triggerFakeMouseEvent("mousemove", target, targetX, targetY); assert.isTrue(callbackWasCalled, "callback was called on mousemove"); callbackWasCalled = false; - triggerFakeMouseEvent("mouseout", target, targetX, targetY); + TestMethods.triggerFakeMouseEvent("mouseout", target, targetX, targetY); assert.isTrue(callbackWasCalled, "callback was called on mouseout"); - md.onMouseMove(keyString, null); + md.offMouseMove(callback); target.remove(); }); it("onMouseDown()", function () { var targetWidth = 400, targetHeight = 400; - var target = generateSVG(targetWidth, targetHeight); + var target = TestMethods.generateSVG(targetWidth, targetHeight); // HACKHACK: PhantomJS can't measure SVGs unless they have something in them occupying space target.append("rect").attr("width", targetWidth).attr("height", targetHeight); var targetX = 17; @@ -9417,23 +9333,22 @@ describe("Dispatchers", function () { x: targetX, y: targetY }; - var md = Plottable.Dispatcher.Mouse.getDispatcher(target.node()); + var md = Plottable.Dispatchers.Mouse.getDispatcher(target.node()); var callbackWasCalled = false; var callback = function (p, e) { callbackWasCalled = true; - assertPointsClose(p, expectedPoint, 0.5, "mouse position is correct"); + TestMethods.assertPointsClose(p, expectedPoint, 0.5, "mouse position is correct"); assert.isNotNull(e, "mouse event was passed to the callback"); }; - var keyString = "unit test"; - md.onMouseDown(keyString, callback); - triggerFakeMouseEvent("mousedown", target, targetX, targetY); + md.onMouseDown(callback); + TestMethods.triggerFakeMouseEvent("mousedown", target, targetX, targetY); assert.isTrue(callbackWasCalled, "callback was called on mousedown"); - md.onMouseDown(keyString, null); + md.offMouseDown(callback); target.remove(); }); it("onMouseUp()", function () { var targetWidth = 400, targetHeight = 400; - var target = generateSVG(targetWidth, targetHeight); + var target = TestMethods.generateSVG(targetWidth, targetHeight); // HACKHACK: PhantomJS can't measure SVGs unless they have something in them occupying space target.append("rect").attr("width", targetWidth).attr("height", targetHeight); var targetX = 17; @@ -9442,18 +9357,17 @@ describe("Dispatchers", function () { x: targetX, y: targetY }; - var md = Plottable.Dispatcher.Mouse.getDispatcher(target.node()); + var md = Plottable.Dispatchers.Mouse.getDispatcher(target.node()); var callbackWasCalled = false; var callback = function (p, e) { callbackWasCalled = true; - assertPointsClose(p, expectedPoint, 0.5, "mouse position is correct"); + TestMethods.assertPointsClose(p, expectedPoint, 0.5, "mouse position is correct"); assert.isNotNull(e, "mouse event was passed to the callback"); }; - var keyString = "unit test"; - md.onMouseUp(keyString, callback); - triggerFakeMouseEvent("mouseup", target, targetX, targetY); + md.onMouseUp(callback); + TestMethods.triggerFakeMouseEvent("mouseup", target, targetX, targetY); assert.isTrue(callbackWasCalled, "callback was called on mouseup"); - md.onMouseUp(keyString, null); + md.offMouseUp(callback); target.remove(); }); it("onWheel()", function () { @@ -9463,7 +9377,7 @@ describe("Dispatchers", function () { return; } var targetWidth = 400, targetHeight = 400; - var svg = generateSVG(targetWidth, targetHeight); + var svg = TestMethods.generateSVG(targetWidth, targetHeight); // HACKHACK: PhantomJS can't measure SVGs unless they have something in them occupying space svg.append("rect").attr("width", targetWidth).attr("height", targetHeight); var targetX = 17; @@ -9473,43 +9387,37 @@ describe("Dispatchers", function () { y: targetY }; var targetDeltaY = 10; - var md = Plottable.Dispatcher.Mouse.getDispatcher(svg.node()); + var md = Plottable.Dispatchers.Mouse.getDispatcher(svg.node()); var callbackWasCalled = false; var callback = function (p, e) { callbackWasCalled = true; assert.strictEqual(e.deltaY, targetDeltaY, "deltaY value was passed to callback"); - assertPointsClose(p, expectedPoint, 0.5, "mouse position is correct"); + TestMethods.assertPointsClose(p, expectedPoint, 0.5, "mouse position is correct"); assert.isNotNull(e, "mouse event was passed to the callback"); }; - var keyString = "unit test"; - md.onWheel(keyString, callback); - triggerFakeWheelEvent("wheel", svg, targetX, targetY, targetDeltaY); + md.onWheel(callback); + TestMethods.triggerFakeWheelEvent("wheel", svg, targetX, targetY, targetDeltaY); assert.isTrue(callbackWasCalled, "callback was called on wheel"); - md.onWheel(keyString, null); + md.offWheel(callback); svg.remove(); }); it("onDblClick()", function () { var targetWidth = 400, targetHeight = 400; - var target = generateSVG(targetWidth, targetHeight); + var target = TestMethods.generateSVG(targetWidth, targetHeight); // HACKHACK: PhantomJS can't measure SVGs unless they have something in them occupying space target.append("rect").attr("width", targetWidth).attr("height", targetHeight); var targetX = 17; var targetY = 76; - var expectedPoint = { - x: targetX, - y: targetY - }; - var md = Plottable.Dispatcher.Mouse.getDispatcher(target.node()); + var md = Plottable.Dispatchers.Mouse.getDispatcher(target.node()); var callbackWasCalled = false; var callback = function (p, e) { callbackWasCalled = true; assert.isNotNull(e, "mouse event was passed to the callback"); }; - var keyString = "unit test"; - md.onDblClick(keyString, callback); - triggerFakeMouseEvent("dblclick", target, targetX, targetY); + md.onDblClick(callback); + TestMethods.triggerFakeMouseEvent("dblclick", target, targetX, targetY); assert.isTrue(callbackWasCalled, "callback was called on dblClick"); - md.onDblClick(keyString, null); + md.offDblClick(callback); target.remove(); }); }); @@ -9520,16 +9428,16 @@ var assert = chai.assert; describe("Dispatchers", function () { describe("Touch Dispatcher", function () { it("getDispatcher() creates only one Dispatcher.Touch per ", function () { - var svg = generateSVG(); - var td1 = Plottable.Dispatcher.Touch.getDispatcher(svg.node()); + var svg = TestMethods.generateSVG(); + var td1 = Plottable.Dispatchers.Touch.getDispatcher(svg.node()); assert.isNotNull(td1, "created a new Dispatcher on an SVG"); - var td2 = Plottable.Dispatcher.Touch.getDispatcher(svg.node()); + var td2 = Plottable.Dispatchers.Touch.getDispatcher(svg.node()); assert.strictEqual(td1, td2, "returned the existing Dispatcher if called again with same "); svg.remove(); }); it("onTouchStart()", function () { var targetWidth = 400, targetHeight = 400; - var target = generateSVG(targetWidth, targetHeight); + var target = TestMethods.generateSVG(targetWidth, targetHeight); // HACKHACK: PhantomJS can't measure SVGs unless they have something in them occupying space target.append("rect").attr("width", targetWidth).attr("height", targetHeight); var targetXs = [17, 18, 12, 23, 44]; @@ -9541,25 +9449,24 @@ describe("Dispatchers", function () { }; }); var ids = targetXs.map(function (targetX, i) { return i; }); - var td = Plottable.Dispatcher.Touch.getDispatcher(target.node()); + var td = Plottable.Dispatchers.Touch.getDispatcher(target.node()); var callbackWasCalled = false; var callback = function (ids, points, e) { callbackWasCalled = true; ids.forEach(function (id) { - assertPointsClose(points[id], expectedPoints[id], 0.5, "touch position is correct"); + TestMethods.assertPointsClose(points[id], expectedPoints[id], 0.5, "touch position is correct"); }); assert.isNotNull(e, "TouchEvent was passed to the Dispatcher"); }; - var keyString = "unit test"; - td.onTouchStart(keyString, callback); - triggerFakeTouchEvent("touchstart", target, expectedPoints, ids); + td.onTouchStart(callback); + TestMethods.triggerFakeTouchEvent("touchstart", target, expectedPoints, ids); assert.isTrue(callbackWasCalled, "callback was called on touchstart"); - td.onTouchStart(keyString, null); + td.offTouchStart(callback); target.remove(); }); it("onTouchMove()", function () { var targetWidth = 400, targetHeight = 400; - var target = generateSVG(targetWidth, targetHeight); + var target = TestMethods.generateSVG(targetWidth, targetHeight); // HACKHACK: PhantomJS can't measure SVGs unless they have something in them occupying space target.append("rect").attr("width", targetWidth).attr("height", targetHeight); var targetXs = [17, 18, 12, 23, 44]; @@ -9571,25 +9478,24 @@ describe("Dispatchers", function () { }; }); var ids = targetXs.map(function (targetX, i) { return i; }); - var td = Plottable.Dispatcher.Touch.getDispatcher(target.node()); + var td = Plottable.Dispatchers.Touch.getDispatcher(target.node()); var callbackWasCalled = false; var callback = function (ids, points, e) { callbackWasCalled = true; ids.forEach(function (id) { - assertPointsClose(points[id], expectedPoints[id], 0.5, "touch position is correct"); + TestMethods.assertPointsClose(points[id], expectedPoints[id], 0.5, "touch position is correct"); }); assert.isNotNull(e, "TouchEvent was passed to the Dispatcher"); }; - var keyString = "unit test"; - td.onTouchMove(keyString, callback); - triggerFakeTouchEvent("touchmove", target, expectedPoints, ids); + td.onTouchMove(callback); + TestMethods.triggerFakeTouchEvent("touchmove", target, expectedPoints, ids); assert.isTrue(callbackWasCalled, "callback was called on touchmove"); - td.onTouchMove(keyString, null); + td.offTouchMove(callback); target.remove(); }); it("onTouchEnd()", function () { var targetWidth = 400, targetHeight = 400; - var target = generateSVG(targetWidth, targetHeight); + var target = TestMethods.generateSVG(targetWidth, targetHeight); // HACKHACK: PhantomJS can't measure SVGs unless they have something in them occupying space target.append("rect").attr("width", targetWidth).attr("height", targetHeight); var targetXs = [17, 18, 12, 23, 44]; @@ -9601,25 +9507,53 @@ describe("Dispatchers", function () { }; }); var ids = targetXs.map(function (targetX, i) { return i; }); - var td = Plottable.Dispatcher.Touch.getDispatcher(target.node()); + var td = Plottable.Dispatchers.Touch.getDispatcher(target.node()); var callbackWasCalled = false; var callback = function (ids, points, e) { callbackWasCalled = true; ids.forEach(function (id) { - assertPointsClose(points[id], expectedPoints[id], 0.5, "touch position is correct"); + TestMethods.assertPointsClose(points[id], expectedPoints[id], 0.5, "touch position is correct"); }); assert.isNotNull(e, "TouchEvent was passed to the Dispatcher"); }; - var keyString = "unit test"; - td.onTouchEnd(keyString, callback); - triggerFakeTouchEvent("touchend", target, expectedPoints, ids); + td.onTouchEnd(callback); + TestMethods.triggerFakeTouchEvent("touchend", target, expectedPoints, ids); assert.isTrue(callbackWasCalled, "callback was called on touchend"); - td.onTouchEnd(keyString, null); + td.offTouchEnd(callback); + target.remove(); + }); + it("onTouchCancel()", function () { + var targetWidth = 400, targetHeight = 400; + var target = TestMethods.generateSVG(targetWidth, targetHeight); + // HACKHACK: PhantomJS can't measure SVGs unless they have something in them occupying space + target.append("rect").attr("width", targetWidth).attr("height", targetHeight); + var targetXs = [17, 18, 12, 23, 44]; + var targetYs = [77, 78, 52, 43, 14]; + var expectedPoints = targetXs.map(function (targetX, i) { + return { + x: targetX, + y: targetYs[i] + }; + }); + var ids = targetXs.map(function (targetX, i) { return i; }); + var td = Plottable.Dispatchers.Touch.getDispatcher(target.node()); + var callbackWasCalled = false; + var callback = function (ids, points, e) { + callbackWasCalled = true; + ids.forEach(function (id) { + TestMethods.assertPointsClose(points[id], expectedPoints[id], 0.5, "touch position is correct"); + }); + assert.isNotNull(e, "TouchEvent was passed to the Dispatcher"); + }; + td.onTouchCancel(callback); + TestMethods.triggerFakeTouchEvent("touchcancel", target, expectedPoints, ids); + assert.isTrue(callbackWasCalled, "callback was called on touchend"); + td.offTouchCancel(callback); target.remove(); }); it("doesn't call callbacks if not in the DOM", function () { var targetWidth = 400, targetHeight = 400; - var target = generateSVG(targetWidth, targetHeight); + var target = TestMethods.generateSVG(targetWidth, targetHeight); // HACKHACK: PhantomJS can't measure SVGs unless they have something in them occupying space target.append("rect").attr("width", targetWidth).attr("height", targetHeight); var targetXs = [17, 18, 12, 23, 44]; @@ -9631,21 +9565,20 @@ describe("Dispatchers", function () { }; }); var ids = targetXs.map(function (targetX, i) { return i; }); - var td = Plottable.Dispatcher.Touch.getDispatcher(target.node()); + var td = Plottable.Dispatchers.Touch.getDispatcher(target.node()); var callbackWasCalled = false; var callback = function (ids, points, e) { callbackWasCalled = true; assert.isNotNull(e, "TouchEvent was passed to the Dispatcher"); }; - var keyString = "notInDomTest"; - td.onTouchMove(keyString, callback); - triggerFakeTouchEvent("touchmove", target, expectedPoints, ids); + td.onTouchMove(callback); + TestMethods.triggerFakeTouchEvent("touchmove", target, expectedPoints, ids); assert.isTrue(callbackWasCalled, "callback was called on touchmove"); target.remove(); callbackWasCalled = false; - triggerFakeTouchEvent("touchmove", target, expectedPoints, ids); + TestMethods.triggerFakeTouchEvent("touchmove", target, expectedPoints, ids); assert.isFalse(callbackWasCalled, "callback was not called after was removed from DOM"); - td.onTouchMove(keyString, null); + td.offTouchMove(callback); }); }); }); @@ -9655,7 +9588,7 @@ var assert = chai.assert; describe("Dispatchers", function () { describe("Key Dispatcher", function () { it("triggers callback on mousedown", function () { - var ked = Plottable.Dispatcher.Key.getDispatcher(); + var ked = Plottable.Dispatchers.Key.getDispatcher(); var keyCodeToSend = 65; var keyDowned = false; var callback = function (code, e) { @@ -9663,24 +9596,23 @@ describe("Dispatchers", function () { assert.strictEqual(code, keyCodeToSend, "correct keycode was passed"); assert.isNotNull(e, "key event was passed to the callback"); }; - var keyString = "unit test"; - ked.onKeyDown(keyString, callback); + ked.onKeyDown(callback); $("body").simulate("keydown", { keyCode: keyCodeToSend }); assert.isTrue(keyDowned, "callback when a key was pressed"); - ked.onKeyDown(keyString, null); // clean up + ked.offKeyDown(callback); // clean up }); }); }); -/// +/// var assert = chai.assert; describe("Interactive Components", function () { describe("DragBoxLayer", function () { var SVG_WIDTH = 400; var SVG_HEIGHT = 400; it("correctly draws box on drag", function () { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var dbl = new Plottable.Component.DragBoxLayer(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var dbl = new Plottable.Components.DragBoxLayer(); dbl.renderTo(svg); assert.isFalse(dbl.boxVisible(), "box is hidden initially"); var startPoint = { @@ -9692,7 +9624,7 @@ describe("Interactive Components", function () { y: SVG_HEIGHT / 2 }; var target = dbl.background(); - triggerFakeDragSequence(target, startPoint, endPoint); + TestMethods.triggerFakeDragSequence(target, startPoint, endPoint); assert.isTrue(dbl.boxVisible(), "box is drawn on drag"); var bounds = dbl.bounds(); assert.deepEqual(bounds.topLeft, startPoint, "top-left point was set correctly"); @@ -9700,26 +9632,32 @@ describe("Interactive Components", function () { svg.remove(); }); it("dismisses on click", function () { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var dbl = new Plottable.Component.DragBoxLayer(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var dbl = new Plottable.Components.DragBoxLayer(); dbl.renderTo(svg); var targetPoint = { x: SVG_WIDTH / 2, y: SVG_HEIGHT / 2 }; var target = dbl.background(); - triggerFakeDragSequence(target, targetPoint, targetPoint); + TestMethods.triggerFakeDragSequence(target, targetPoint, targetPoint); assert.isFalse(dbl.boxVisible(), "box is hidden on click"); svg.remove(); }); it("clipPath enabled", function () { - var dbl = new Plottable.Component.DragBoxLayer(); - assert.isTrue(dbl.clipPathEnabled, "uses clipPath (to hide detection edges)"); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var dbl = new Plottable.Components.DragBoxLayer(); + dbl.renderTo(svg); + TestMethods.verifyClipPath(dbl); + var clipRect = dbl._boxContainer.select(".clip-rect"); + assert.strictEqual(TestMethods.numAttr(clipRect, "width"), SVG_WIDTH, "the clipRect has an appropriate width"); + assert.strictEqual(TestMethods.numAttr(clipRect, "height"), SVG_HEIGHT, "the clipRect has an appropriate height"); + svg.remove(); }); it("detectionRadius()", function () { - var dbl = new Plottable.Component.DragBoxLayer(); + var dbl = new Plottable.Components.DragBoxLayer(); assert.doesNotThrow(function () { return dbl.detectionRadius(3); }, Error, "can set detection radius before anchoring"); - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); dbl.renderTo("svg"); var radius = 5; dbl.detectionRadius(radius); @@ -9739,8 +9677,8 @@ describe("Interactive Components", function () { svg.remove(); }); it("onDragStart()", function () { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var dbl = new Plottable.Component.DragBoxLayer(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var dbl = new Plottable.Components.DragBoxLayer(); dbl.renderTo(svg); var startPoint = { x: SVG_WIDTH / 4, @@ -9751,22 +9689,26 @@ describe("Interactive Components", function () { y: SVG_HEIGHT / 2 }; var receivedBounds; + var callbackCalled = false; var callback = function (b) { receivedBounds = b; + callbackCalled = true; }; dbl.onDragStart(callback); var target = dbl.background(); - triggerFakeDragSequence(target, startPoint, endPoint); + TestMethods.triggerFakeDragSequence(target, startPoint, endPoint); + assert.isTrue(callbackCalled, "the callback was called"); assert.deepEqual(receivedBounds.topLeft, startPoint, "top-left point was set correctly"); assert.deepEqual(receivedBounds.bottomRight, startPoint, "bottom-right point was set correctly"); - assert.strictEqual(dbl.onDragStart(), callback, "can retrieve callback by calling with no args"); - dbl.onDragStart(null); - assert.isNull(dbl.onDragStart(), "can blank callback by passing null"); + dbl.offDragStart(callback); + callbackCalled = false; + TestMethods.triggerFakeDragSequence(target, startPoint, endPoint); + assert.isFalse(callbackCalled, "the callback was detached from the dragBoxLayer and not called"); svg.remove(); }); it("onDrag()", function () { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var dbl = new Plottable.Component.DragBoxLayer(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var dbl = new Plottable.Components.DragBoxLayer(); dbl.renderTo(svg); var startPoint = { x: SVG_WIDTH / 4, @@ -9777,22 +9719,26 @@ describe("Interactive Components", function () { y: SVG_HEIGHT / 2 }; var receivedBounds; + var callbackCalled = false; var callback = function (b) { receivedBounds = b; + callbackCalled = true; }; dbl.onDrag(callback); var target = dbl.background(); - triggerFakeDragSequence(target, startPoint, endPoint); + TestMethods.triggerFakeDragSequence(target, startPoint, endPoint); + assert.isTrue(callbackCalled, "the callback was called"); assert.deepEqual(receivedBounds.topLeft, startPoint, "top-left point was set correctly"); assert.deepEqual(receivedBounds.bottomRight, endPoint, "bottom-right point was set correctly"); - assert.strictEqual(dbl.onDrag(), callback, "can retrieve callback by calling with no args"); - dbl.onDrag(null); - assert.isNull(dbl.onDrag(), "can blank callback by passing null"); + callbackCalled = false; + dbl.offDrag(callback); + TestMethods.triggerFakeDragSequence(target, startPoint, endPoint); + assert.isFalse(callbackCalled, "the callback was detached from the dragoBoxLayer and not called"); svg.remove(); }); it("onDragEnd()", function () { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var dbl = new Plottable.Component.DragBoxLayer(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var dbl = new Plottable.Components.DragBoxLayer(); dbl.renderTo(svg); var startPoint = { x: SVG_WIDTH / 4, @@ -9803,17 +9749,77 @@ describe("Interactive Components", function () { y: SVG_HEIGHT / 2 }; var receivedBounds; + var callbackCalled = false; var callback = function (b) { receivedBounds = b; + callbackCalled = true; }; dbl.onDragEnd(callback); var target = dbl.background(); - triggerFakeDragSequence(target, startPoint, endPoint); + TestMethods.triggerFakeDragSequence(target, startPoint, endPoint); + assert.isTrue(callbackCalled, "the callback was called"); assert.deepEqual(receivedBounds.topLeft, startPoint, "top-left point was set correctly"); assert.deepEqual(receivedBounds.bottomRight, endPoint, "bottom-right point was set correctly"); - assert.strictEqual(dbl.onDragEnd(), callback, "can retrieve callback by calling with no args"); - dbl.onDragEnd(null); - assert.isNull(dbl.onDragEnd(), "can blank callback by passing null"); + dbl.offDragEnd(callback); + callbackCalled = false; + TestMethods.triggerFakeDragSequence(target, startPoint, endPoint); + assert.isFalse(callbackCalled, "the callback was detached from the dragoBoxLayer and not called"); + svg.remove(); + }); + it("multiple drag interaction callbacks", function () { + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var dbl = new Plottable.Components.DragBoxLayer(); + dbl.renderTo(svg); + var startPoint = { + x: SVG_WIDTH / 4, + y: SVG_HEIGHT / 4 + }; + var endPoint = { + x: SVG_WIDTH / 2, + y: SVG_HEIGHT / 2 + }; + var callbackDragStart1Called = false; + var callbackDragStart2Called = false; + var callbackDrag1Called = false; + var callbackDrag2Called = false; + var callbackDragEnd1Called = false; + var callbackDragEnd2Called = false; + var callbackDragStart1 = function () { return callbackDragStart1Called = true; }; + var callbackDragStart2 = function () { return callbackDragStart2Called = true; }; + var callbackDrag1 = function () { return callbackDrag1Called = true; }; + var callbackDrag2 = function () { return callbackDrag2Called = true; }; + var callbackDragEnd1 = function () { return callbackDragEnd1Called = true; }; + var callbackDragEnd2 = function () { return callbackDragEnd2Called = true; }; + dbl.onDragStart(callbackDragStart1); + dbl.onDragStart(callbackDragStart2); + dbl.onDrag(callbackDrag1); + dbl.onDrag(callbackDrag2); + dbl.onDragEnd(callbackDragEnd1); + dbl.onDragEnd(callbackDragEnd2); + var target = dbl.background(); + TestMethods.triggerFakeDragSequence(target, startPoint, endPoint); + assert.isTrue(callbackDragStart1Called, "the callback 1 for drag start was called"); + assert.isTrue(callbackDragStart2Called, "the callback 2 for drag start was called"); + assert.isTrue(callbackDrag1Called, "the callback 1 for drag was called"); + assert.isTrue(callbackDrag2Called, "the callback 2 for drag was called"); + assert.isTrue(callbackDragEnd1Called, "the callback 1 for drag end was called"); + assert.isTrue(callbackDragEnd2Called, "the callback 2 for drag end was called"); + dbl.offDragStart(callbackDragStart1); + dbl.offDrag(callbackDrag1); + dbl.offDragEnd(callbackDragEnd1); + callbackDragStart1Called = false; + callbackDragStart2Called = false; + callbackDrag1Called = false; + callbackDrag2Called = false; + callbackDragEnd1Called = false; + callbackDragEnd2Called = false; + TestMethods.triggerFakeDragSequence(target, startPoint, endPoint); + assert.isFalse(callbackDragStart1Called, "the callback 1 for drag start was disconnected"); + assert.isTrue(callbackDragStart2Called, "the callback 2 for drag start is still connected"); + assert.isFalse(callbackDrag1Called, "the callback 1 for drag was called disconnected"); + assert.isTrue(callbackDrag2Called, "the callback 2 for drag is still connected"); + assert.isFalse(callbackDragEnd1Called, "the callback 1 for drag end was disconnected"); + assert.isTrue(callbackDragEnd2Called, "the callback 2 for drag end is still connected"); svg.remove(); }); describe("resizing", function () { @@ -9830,12 +9836,12 @@ describe("Interactive Components", function () { topLeft: { x: 0, y: 0 }, bottomRight: { x: 0, y: 0 } }); - triggerFakeDragSequence(target, { x: SVG_WIDTH / 4, y: SVG_HEIGHT / 4 }, { x: SVG_WIDTH * 3 / 4, y: SVG_HEIGHT * 3 / 4 }); + TestMethods.triggerFakeDragSequence(target, { x: SVG_WIDTH / 4, y: SVG_HEIGHT / 4 }, { x: SVG_WIDTH * 3 / 4, y: SVG_HEIGHT * 3 / 4 }); initialBounds = dbl.bounds(); } beforeEach(function () { - svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - dbl = new Plottable.Component.DragBoxLayer(); + svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + dbl = new Plottable.Components.DragBoxLayer(); dbl.renderTo(svg); target = dbl.background(); resetBox(); @@ -9846,56 +9852,56 @@ describe("Interactive Components", function () { }); it("resize from top edge", function () { dbl.resizable(true); - triggerFakeDragSequence(target, { x: midPoint.x, y: initialBounds.topLeft.y }, { x: midPoint.x, y: 0 }); + TestMethods.triggerFakeDragSequence(target, { x: midPoint.x, y: initialBounds.topLeft.y }, { x: midPoint.x, y: 0 }); var bounds = dbl.bounds(); assert.strictEqual(bounds.topLeft.y, 0, "top edge was repositioned"); assert.strictEqual(bounds.bottomRight.y, initialBounds.bottomRight.y, "bottom edge was not moved"); assert.strictEqual(bounds.topLeft.x, initialBounds.topLeft.x, "left edge was not moved"); assert.strictEqual(bounds.bottomRight.x, initialBounds.bottomRight.x, "right edge was not moved"); resetBox(); - triggerFakeDragSequence(target, { x: midPoint.x, y: initialBounds.topLeft.y }, { x: midPoint.x, y: SVG_HEIGHT }); + TestMethods.triggerFakeDragSequence(target, { x: midPoint.x, y: initialBounds.topLeft.y }, { x: midPoint.x, y: SVG_HEIGHT }); bounds = dbl.bounds(); assert.strictEqual(bounds.bottomRight.y, SVG_HEIGHT, "can drag through to other side"); svg.remove(); }); it("resize from bottom edge", function () { dbl.resizable(true); - triggerFakeDragSequence(target, { x: midPoint.x, y: initialBounds.bottomRight.y }, { x: midPoint.x, y: SVG_HEIGHT }); + TestMethods.triggerFakeDragSequence(target, { x: midPoint.x, y: initialBounds.bottomRight.y }, { x: midPoint.x, y: SVG_HEIGHT }); var bounds = dbl.bounds(); assert.strictEqual(bounds.topLeft.y, initialBounds.topLeft.y, "top edge was not moved"); assert.strictEqual(bounds.bottomRight.y, SVG_HEIGHT, "bottom edge was repositioned"); assert.strictEqual(bounds.topLeft.x, initialBounds.topLeft.x, "left edge was not moved"); assert.strictEqual(bounds.bottomRight.x, initialBounds.bottomRight.x, "right edge was not moved"); resetBox(); - triggerFakeDragSequence(target, { x: midPoint.x, y: initialBounds.bottomRight.y }, { x: midPoint.x, y: 0 }); + TestMethods.triggerFakeDragSequence(target, { x: midPoint.x, y: initialBounds.bottomRight.y }, { x: midPoint.x, y: 0 }); bounds = dbl.bounds(); assert.strictEqual(bounds.topLeft.y, 0, "can drag through to other side"); svg.remove(); }); it("resize from left edge", function () { dbl.resizable(true); - triggerFakeDragSequence(target, { x: initialBounds.topLeft.x, y: midPoint.y }, { x: 0, y: midPoint.y }); + TestMethods.triggerFakeDragSequence(target, { x: initialBounds.topLeft.x, y: midPoint.y }, { x: 0, y: midPoint.y }); var bounds = dbl.bounds(); assert.strictEqual(bounds.topLeft.y, initialBounds.topLeft.y, "top edge was not moved"); assert.strictEqual(bounds.bottomRight.y, initialBounds.bottomRight.y, "bottom edge was not moved"); assert.strictEqual(bounds.topLeft.x, 0, "left edge was repositioned"); assert.strictEqual(bounds.bottomRight.x, initialBounds.bottomRight.x, "right edge was not moved"); resetBox(); - triggerFakeDragSequence(target, { x: initialBounds.topLeft.x, y: midPoint.y }, { x: SVG_WIDTH, y: midPoint.y }); + TestMethods.triggerFakeDragSequence(target, { x: initialBounds.topLeft.x, y: midPoint.y }, { x: SVG_WIDTH, y: midPoint.y }); bounds = dbl.bounds(); assert.strictEqual(bounds.bottomRight.x, SVG_WIDTH, "can drag through to other side"); svg.remove(); }); it("resize from right edge", function () { dbl.resizable(true); - triggerFakeDragSequence(target, { x: initialBounds.bottomRight.x, y: midPoint.y }, { x: SVG_WIDTH, y: midPoint.y }); + TestMethods.triggerFakeDragSequence(target, { x: initialBounds.bottomRight.x, y: midPoint.y }, { x: SVG_WIDTH, y: midPoint.y }); var bounds = dbl.bounds(); assert.strictEqual(bounds.topLeft.y, initialBounds.topLeft.y, "top edge was not moved"); assert.strictEqual(bounds.bottomRight.y, initialBounds.bottomRight.y, "bottom edge was not moved"); assert.strictEqual(bounds.topLeft.x, initialBounds.topLeft.x, "left edge was not moved"); assert.strictEqual(bounds.bottomRight.x, SVG_WIDTH, "right edge was repositioned"); resetBox(); - triggerFakeDragSequence(target, { x: initialBounds.bottomRight.x, y: midPoint.y }, { x: 0, y: midPoint.y }); + TestMethods.triggerFakeDragSequence(target, { x: initialBounds.bottomRight.x, y: midPoint.y }, { x: 0, y: midPoint.y }); bounds = dbl.bounds(); assert.strictEqual(bounds.topLeft.x, 0, "can drag through to other side"); svg.remove(); @@ -9903,7 +9909,7 @@ describe("Interactive Components", function () { it("resizes if grabbed within detectionRadius()", function () { dbl.resizable(true); var detectionRadius = dbl.detectionRadius(); - triggerFakeDragSequence(target, { x: midPoint.x, y: initialBounds.bottomRight.y + detectionRadius - 1 }, { x: midPoint.x, y: SVG_HEIGHT }); + TestMethods.triggerFakeDragSequence(target, { x: midPoint.x, y: initialBounds.bottomRight.y + detectionRadius - 1 }, { x: midPoint.x, y: SVG_HEIGHT }); var bounds = dbl.bounds(); assert.strictEqual(bounds.topLeft.y, initialBounds.topLeft.y, "top edge was not moved"); assert.strictEqual(bounds.bottomRight.y, SVG_HEIGHT, "bottom edge was repositioned"); @@ -9911,22 +9917,21 @@ describe("Interactive Components", function () { assert.strictEqual(bounds.bottomRight.x, initialBounds.bottomRight.x, "right edge was not moved"); resetBox(); var startYOutside = initialBounds.bottomRight.y + detectionRadius + 1; - triggerFakeDragSequence(target, { x: midPoint.x, y: startYOutside }, { x: midPoint.x, y: SVG_HEIGHT }); + TestMethods.triggerFakeDragSequence(target, { x: midPoint.x, y: startYOutside }, { x: midPoint.x, y: SVG_HEIGHT }); bounds = dbl.bounds(); assert.strictEqual(bounds.topLeft.y, startYOutside, "new box was started at the drag start position"); svg.remove(); }); it("doesn't dismiss on no-op resize", function () { dbl.resizable(true); - triggerFakeDragSequence(target, { x: midPoint.x, y: initialBounds.topLeft.y }, { x: midPoint.x, y: initialBounds.topLeft.y }); - var bounds = dbl.bounds(); + TestMethods.triggerFakeDragSequence(target, { x: midPoint.x, y: initialBounds.topLeft.y }, { x: midPoint.x, y: initialBounds.topLeft.y }); assert.isTrue(dbl.boxVisible(), "box was not dismissed"); svg.remove(); }); it("can't resize if hidden", function () { dbl.resizable(true); dbl.boxVisible(false); - triggerFakeDragSequence(target, { x: midPoint.x, y: initialBounds.bottomRight.y }, { x: midPoint.x, y: SVG_HEIGHT }); + TestMethods.triggerFakeDragSequence(target, { x: midPoint.x, y: initialBounds.bottomRight.y }, { x: midPoint.x, y: SVG_HEIGHT }); var bounds = dbl.bounds(); assert.strictEqual(bounds.topLeft.y, initialBounds.bottomRight.y, "new box was started at the drag start position"); svg.remove(); @@ -9935,15 +9940,15 @@ describe("Interactive Components", function () { }); }); -/// +/// var assert = chai.assert; describe("Interactive Components", function () { describe("XDragBoxLayer", function () { var SVG_WIDTH = 400; var SVG_HEIGHT = 400; it("bounds()", function () { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var dbl = new Plottable.Component.XDragBoxLayer(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var dbl = new Plottable.Components.XDragBoxLayer(); dbl.boxVisible(true); dbl.renderTo(svg); var topLeft = { @@ -9966,8 +9971,8 @@ describe("Interactive Components", function () { svg.remove(); }); it("resizes only in x", function () { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var dbl = new Plottable.Component.XDragBoxLayer(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var dbl = new Plottable.Components.XDragBoxLayer(); dbl.boxVisible(true); dbl.resizable(true); dbl.renderTo(svg); @@ -9989,7 +9994,7 @@ describe("Interactive Components", function () { y: SVG_HEIGHT / 2 }; var target = dbl.background(); - triggerFakeDragSequence(target, actualBounds.bottomRight, dragTo); + TestMethods.triggerFakeDragSequence(target, actualBounds.bottomRight, dragTo); actualBounds = dbl.bounds(); assert.strictEqual(actualBounds.bottomRight.x, dragTo.x, "resized in x"); assert.strictEqual(actualBounds.topLeft.y, 0, "box still starts at top"); @@ -9997,8 +10002,8 @@ describe("Interactive Components", function () { svg.remove(); }); it("stays full height after resizing", function () { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var dbl = new Plottable.Component.XDragBoxLayer(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var dbl = new Plottable.Components.XDragBoxLayer(); dbl.boxVisible(true); dbl.resizable(true); dbl.renderTo(svg); @@ -10029,15 +10034,15 @@ describe("Interactive Components", function () { }); }); -/// +/// var assert = chai.assert; describe("Interactive Components", function () { describe("YDragBoxLayer", function () { var SVG_WIDTH = 400; var SVG_HEIGHT = 400; it("bounds()", function () { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var dbl = new Plottable.Component.YDragBoxLayer(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var dbl = new Plottable.Components.YDragBoxLayer(); dbl.boxVisible(true); dbl.renderTo(svg); var topLeft = { @@ -10060,8 +10065,8 @@ describe("Interactive Components", function () { svg.remove(); }); it("resizes only in y", function () { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var dbl = new Plottable.Component.YDragBoxLayer(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var dbl = new Plottable.Components.YDragBoxLayer(); dbl.boxVisible(true); dbl.resizable(true); dbl.renderTo(svg); @@ -10083,7 +10088,7 @@ describe("Interactive Components", function () { y: SVG_HEIGHT * 3 / 4 }; var target = dbl.background(); - triggerFakeDragSequence(target, actualBounds.bottomRight, dragTo); + TestMethods.triggerFakeDragSequence(target, actualBounds.bottomRight, dragTo); actualBounds = dbl.bounds(); assert.strictEqual(actualBounds.topLeft.x, 0, "box still starts at left"); assert.strictEqual(actualBounds.bottomRight.x, dbl.width(), "box still ends at right"); @@ -10091,8 +10096,8 @@ describe("Interactive Components", function () { svg.remove(); }); it("stays full width after resizing", function () { - var svg = generateSVG(SVG_WIDTH, SVG_HEIGHT); - var dbl = new Plottable.Component.YDragBoxLayer(); + var svg = TestMethods.generateSVG(SVG_WIDTH, SVG_HEIGHT); + var dbl = new Plottable.Components.YDragBoxLayer(); dbl.boxVisible(true); dbl.resizable(true); dbl.renderTo(svg); diff --git a/test/utils/callbackSetTests.ts b/test/utils/callbackSetTests.ts new file mode 100644 index 0000000000..e67ba4e04b --- /dev/null +++ b/test/utils/callbackSetTests.ts @@ -0,0 +1,33 @@ +/// + +var assert = chai.assert; + +describe("Utils", () => { + describe("CallbackSet", () => { + it("callCallbacks()", () => { + var expectedS = "Plottable"; + var expectedI = 1; + + var cb1called = false; + var cb1 = (s: string, i: number) => { + assert.strictEqual(s, expectedS, "was passed the correct first argument"); + assert.strictEqual(i, expectedI, "was passed the correct second argument"); + cb1called = true; + }; + var cb2called = false; + var cb2 = (s: string, i: number) => { + assert.strictEqual(s, expectedS, "was passed the correct first argument"); + assert.strictEqual(i, expectedI, "was passed the correct second argument"); + cb2called = true; + }; + + var callbackSet = new Plottable.Utils.CallbackSet<(s: string, i: number) => any>(); + callbackSet.add(cb1); + callbackSet.add(cb2); + + callbackSet.callCallbacks(expectedS, expectedI); + assert.isTrue(cb1called, "callback 1 was called"); + assert.isTrue(cb2called, "callback 2 was called"); + }); + }); +}); diff --git a/test/utils/clientToSVGTranslatorTests.ts b/test/utils/clientToSVGTranslatorTests.ts index af647c986f..f8834eb471 100644 --- a/test/utils/clientToSVGTranslatorTests.ts +++ b/test/utils/clientToSVGTranslatorTests.ts @@ -4,18 +4,18 @@ var assert = chai.assert; describe("ClientToSVGTranslator", () => { it("getTranslator() creates only one ClientToSVGTranslator per ", () => { - var svg = generateSVG(); + var svg = TestMethods.generateSVG(); - var t1 = Plottable._Util.ClientToSVGTranslator.getTranslator( svg.node()); + var t1 = Plottable.Utils.ClientToSVGTranslator.getTranslator( svg.node()); assert.isNotNull(t1, "created a new ClientToSVGTranslator on a "); - var t2 = Plottable._Util.ClientToSVGTranslator.getTranslator( svg.node()); + var t2 = Plottable.Utils.ClientToSVGTranslator.getTranslator( svg.node()); assert.strictEqual(t1, t2, "returned the existing ClientToSVGTranslator if called again with same "); svg.remove(); }); it("converts points to -space correctly", () => { - var svg = generateSVG(); + var svg = TestMethods.generateSVG(); var rectOrigin: Plottable.Point = { x: 19, @@ -28,11 +28,11 @@ describe("ClientToSVGTranslator", () => { height: 30 }); - var translator = Plottable._Util.ClientToSVGTranslator.getTranslator( svg.node()); + var translator = Plottable.Utils.ClientToSVGTranslator.getTranslator( svg.node()); var rectBCR = rect.node().getBoundingClientRect(); var computedOrigin = translator.computePosition(rectBCR.left, rectBCR.top); - assertPointsClose(computedOrigin, rectOrigin, 0.5, "translates client coordinates to coordinates correctly"); + TestMethods.assertPointsClose(computedOrigin, rectOrigin, 0.5, "translates client coordinates to coordinates correctly"); svg.remove(); }); diff --git a/test/utils/domUtilsTests.ts b/test/utils/domUtilsTests.ts index cd65bdecd3..5814cbaaeb 100644 --- a/test/utils/domUtilsTests.ts +++ b/test/utils/domUtilsTests.ts @@ -2,10 +2,10 @@ var assert = chai.assert; -describe("_Util.DOM", () => { +describe("Utils.DOM", () => { it("getBBox works properly", () => { - var svg = generateSVG(); + var svg = TestMethods.generateSVG(); var expectedBox = { x: 0, y: 0, @@ -13,7 +13,7 @@ describe("_Util.DOM", () => { height: 20 }; var rect = svg.append("rect").attr(expectedBox); - var measuredBox = Plottable._Util.DOM.getBBox(rect); + var measuredBox = Plottable.Utils.DOM.getBBox(rect); assert.deepEqual(measuredBox, expectedBox, "getBBox measures correctly"); svg.remove(); }); @@ -26,71 +26,98 @@ describe("_Util.DOM", () => { height: 20 }; - var removedSVG = generateSVG().remove(); + var removedSVG = TestMethods.generateSVG().remove(); var rect = removedSVG.append("rect").attr(expectedBox); - Plottable._Util.DOM.getBBox(rect); // could throw NS_ERROR on FF + Plottable.Utils.DOM.getBBox(rect); // could throw NS_ERROR on FF - var noneSVG = generateSVG().style("display", "none"); + var noneSVG = TestMethods.generateSVG().style("display", "none"); rect = noneSVG.append("rect").attr(expectedBox); - Plottable._Util.DOM.getBBox(rect); // could throw NS_ERROR on FF + Plottable.Utils.DOM.getBBox(rect); // could throw NS_ERROR on FF noneSVG.remove(); }); describe("getElementWidth, getElementHeight", () => { it("can get a plain element's size", () => { - var parent = getSVGParent(); + var parent = TestMethods.getSVGParent(); parent.style("width", "300px"); parent.style("height", "200px"); var parentElem = parent[0][0]; - var width = Plottable._Util.DOM.getElementWidth(parentElem); - assert.equal(width, 300, "measured width matches set width"); - var height = Plottable._Util.DOM.getElementHeight(parentElem); - assert.equal(height, 200, "measured height matches set height"); + var width = Plottable.Utils.DOM.getElementWidth(parentElem); + assert.strictEqual(width, 300, "measured width matches set width"); + var height = Plottable.Utils.DOM.getElementHeight(parentElem); + assert.strictEqual(height, 200, "measured height matches set height"); }); it("can get the svg's size", () => { - var svg = generateSVG(450, 120); + var svg = TestMethods.generateSVG(450, 120); var svgElem = svg[0][0]; - var width = Plottable._Util.DOM.getElementWidth(svgElem); - assert.equal(width, 450, "measured width matches set width"); - var height = Plottable._Util.DOM.getElementHeight(svgElem); - assert.equal(height, 120, "measured height matches set height"); + var width = Plottable.Utils.DOM.getElementWidth(svgElem); + assert.strictEqual(width, 450, "measured width matches set width"); + var height = Plottable.Utils.DOM.getElementHeight(svgElem); + assert.strictEqual(height, 120, "measured height matches set height"); svg.remove(); }); it("can accept multiple units and convert to pixels", () => { - var parent = getSVGParent(); + var parent = TestMethods.getSVGParent(); var parentElem = parent[0][0]; var child = parent.append("div"); var childElem = child[0][0]; parent.style("width", "200px"); parent.style("height", "50px"); - assert.equal(Plottable._Util.DOM.getElementWidth(parentElem), 200, "width is correct"); - assert.equal(Plottable._Util.DOM.getElementHeight(parentElem), 50, "height is correct"); + assert.strictEqual(Plottable.Utils.DOM.getElementWidth(parentElem), 200, "width is correct"); + assert.strictEqual(Plottable.Utils.DOM.getElementHeight(parentElem), 50, "height is correct"); child.style("width", "20px"); child.style("height", "10px"); - assert.equal(Plottable._Util.DOM.getElementWidth(childElem), 20, "width is correct"); - assert.equal(Plottable._Util.DOM.getElementHeight(childElem), 10, "height is correct"); + assert.strictEqual(Plottable.Utils.DOM.getElementWidth(childElem), 20, "width is correct"); + assert.strictEqual(Plottable.Utils.DOM.getElementHeight(childElem), 10, "height is correct"); child.style("width", "100%"); child.style("height", "100%"); - assert.equal(Plottable._Util.DOM.getElementWidth(childElem), 200, "width is correct"); - assert.equal(Plottable._Util.DOM.getElementHeight(childElem), 50, "height is correct"); + assert.strictEqual(Plottable.Utils.DOM.getElementWidth(childElem), 200, "width is correct"); + assert.strictEqual(Plottable.Utils.DOM.getElementHeight(childElem), 50, "height is correct"); child.style("width", "50%"); child.style("height", "50%"); - assert.equal(Plottable._Util.DOM.getElementWidth(childElem), 100, "width is correct"); - assert.equal(Plottable._Util.DOM.getElementHeight(childElem), 25, "height is correct"); + assert.strictEqual(Plottable.Utils.DOM.getElementWidth(childElem), 100, "width is correct"); + assert.strictEqual(Plottable.Utils.DOM.getElementHeight(childElem), 25, "height is correct"); // reset test page DOM parent.style("width", "auto"); parent.style("height", "auto"); child.remove(); }); + + it("getUniqueClipPathId works as expected", () => { + var firstClipPathId = Plottable.Utils.DOM.getUniqueClipPathId(); + var secondClipPathId = Plottable.Utils.DOM.getUniqueClipPathId(); + + var firstClipPathIDPrefix = firstClipPathId.split(/\d/)[0]; + var secondClipPathIDPrefix = secondClipPathId.split(/\d/)[0]; + + assert.strictEqual(firstClipPathIDPrefix, secondClipPathIDPrefix, + "clip path ids should have the same prefix"); + + var prefix = firstClipPathIDPrefix; + + assert.isTrue(/plottable/.test(prefix), + "the prefix should contain the word plottable to avoid collisions"); + + var firstClipPathIdNumber = +firstClipPathId.replace(prefix, ""); + var secondClipPathIdNumber = +secondClipPathId.replace(prefix, ""); + + assert.isFalse(Plottable.Utils.Methods.isNaN(firstClipPathIdNumber), + "first clip path id should only have a number after the prefix"); + assert.isFalse(Plottable.Utils.Methods.isNaN(secondClipPathIdNumber), + "second clip path id should only have a number after the prefix"); + + assert.strictEqual(firstClipPathIdNumber + 1, secondClipPathIdNumber, + "Consecutive calls to getUniqueClipPathId should give consecutive numbers after the prefix"); + }); }); }); diff --git a/test/utils/formattersTests.ts b/test/utils/formattersTests.ts index 8c1a4c41af..613971b42d 100644 --- a/test/utils/formattersTests.ts +++ b/test/utils/formattersTests.ts @@ -48,12 +48,10 @@ describe("Formatters", () => { it("throws an error on strange precision", () => { assert.throws(() => { - var general = Plottable.Formatters.general(-1); - var result = general(5); + Plottable.Formatters.general(-1); }); assert.throws(() => { - var general = Plottable.Formatters.general(100); - var result = general(5); + Plottable.Formatters.general(100); }); }); }); diff --git a/test/utils/mapTests.ts b/test/utils/mapTests.ts new file mode 100644 index 0000000000..5cbd3b9ac0 --- /dev/null +++ b/test/utils/mapTests.ts @@ -0,0 +1,40 @@ +/// + +var assert = chai.assert; + +describe("Map", () => { + + it("Map works as expected", () => { + var map = new Plottable.Utils.Map(); + var o1 = {}; + var o2 = {}; + assert.isFalse(map.has(o1)); + assert.isFalse(map.delete(o1)); + assert.isUndefined(map.get(o1)); + assert.isFalse(map.set(o1, "foo")); + assert.strictEqual(map.get(o1), "foo"); + assert.isTrue(map.set(o1, "bar")); + assert.strictEqual(map.get(o1), "bar"); + map.set(o2, "baz"); + map.set(3, "bam"); + map.set("3", "ball"); + assert.strictEqual(map.get(o1), "bar"); + assert.strictEqual(map.get(o2), "baz"); + assert.strictEqual(map.get(3), "bam"); + assert.strictEqual(map.get("3"), "ball"); + assert.isTrue(map.delete(3)); + assert.isUndefined(map.get(3)); + assert.strictEqual(map.get(o2), "baz"); + assert.strictEqual(map.get("3"), "ball"); + }); + + it("Array-level operations (retrieve keys, vals, and map)", () => { + var map = new Plottable.Utils.Map(); + map.set(2, "foo"); + map.set(3, "bar"); + map.set(4, "baz"); + assert.deepEqual(map.values(), ["foo", "bar", "baz"]); + assert.deepEqual(map.keys(), [2, 3, 4]); + assert.deepEqual(map.map((k, v, i) => [k, v, i]), [[2, "foo", 0], [3, "bar", 1], [4, "baz", 2]]); + }); +}); diff --git a/test/utils/setTests.ts b/test/utils/setTests.ts new file mode 100644 index 0000000000..801045c0ee --- /dev/null +++ b/test/utils/setTests.ts @@ -0,0 +1,58 @@ +/// + +var assert = chai.assert; + +describe("Utils", () => { + describe("Set", () => { + it("add()", () => { + var set = new Plottable.Utils.Set(); + + var value1 = { value: "one" }; + set.add(value1); + var setValues = set.values(); + assert.lengthOf(setValues, 1, "set contains one value"); + assert.strictEqual(setValues[0], value1, "the value was added to the set"); + + set.add(value1); + setValues = set.values(); + assert.lengthOf(setValues, 1, "same value is not added twice"); + assert.strictEqual(setValues[0], value1, "list still contains the value"); + + var value2 = { value: "two" }; + set.add(value2); + setValues = set.values(); + assert.lengthOf(setValues, 2, "set now contains two values"); + assert.strictEqual(setValues[0], value1, "set contains value 1"); + assert.strictEqual(setValues[1], value2, "set contains value 2"); + }); + + it("delete()", () => { + var set = new Plottable.Utils.Set(); + + var value1 = { value: "one" }; + set.add(value1); + assert.lengthOf(set.values(), 1, "set contains one value after adding"); + set.delete(value1); + assert.lengthOf(set.values(), 0, "value was delete"); + + set.add(value1); + var value2 = { value: "two" }; + set.delete(value2); + assert.lengthOf(set.values(), 1, "removing a non-existent value does nothing"); + }); + + it("has()", () => { + var set = new Plottable.Utils.Set(); + + var value1 = { value: "one" }; + set.add(value1); + assert.isTrue(set.has(value1), "correctly checks that value is in the set"); + + var similarValue1 = { value: "one" }; + assert.isFalse(set.has(similarValue1), "correctly determines that similar object is not in the set"); + + set.delete(value1); + assert.isFalse(set.has(value1), "correctly checks that value is no longer in the set"); + }); + }); +}); diff --git a/test/utils/strictEqualityAssociativeArrayTests.ts b/test/utils/strictEqualityAssociativeArrayTests.ts deleted file mode 100644 index 5993a36f65..0000000000 --- a/test/utils/strictEqualityAssociativeArrayTests.ts +++ /dev/null @@ -1,40 +0,0 @@ -/// - -var assert = chai.assert; - -describe("StrictEqualityAssociativeArray", () => { - - it("StrictEqualityAssociativeArray works as expected", () => { - var s = new Plottable._Util.StrictEqualityAssociativeArray(); - var o1 = {}; - var o2 = {}; - assert.isFalse(s.has(o1)); - assert.isFalse(s.delete(o1)); - assert.isUndefined(s.get(o1)); - assert.isFalse(s.set(o1, "foo")); - assert.equal(s.get(o1), "foo"); - assert.isTrue(s.set(o1, "bar")); - assert.equal(s.get(o1), "bar"); - s.set(o2, "baz"); - s.set(3, "bam"); - s.set("3", "ball"); - assert.equal(s.get(o1), "bar"); - assert.equal(s.get(o2), "baz"); - assert.equal(s.get(3), "bam"); - assert.equal(s.get("3"), "ball"); - assert.isTrue(s.delete(3)); - assert.isUndefined(s.get(3)); - assert.equal(s.get(o2), "baz"); - assert.equal(s.get("3"), "ball"); - }); - - it("Array-level operations (retrieve keys, vals, and map)", () => { - var s = new Plottable._Util.StrictEqualityAssociativeArray(); - s.set(2, "foo"); - s.set(3, "bar"); - s.set(4, "baz"); - assert.deepEqual(s.values(), ["foo", "bar", "baz"]); - assert.deepEqual(s.keys(), [2, 3, 4]); - assert.deepEqual(s.map((k, v, i) => [k, v, i]), [[2, "foo", 0], [3, "bar", 1], [4, "baz", 2]]); - }); -}); diff --git a/test/utils/utilsTests.ts b/test/utils/utilsTests.ts index fe5d779467..9be8ec6616 100644 --- a/test/utils/utilsTests.ts +++ b/test/utils/utilsTests.ts @@ -2,53 +2,48 @@ var assert = chai.assert; -describe("_Util.Methods", () => { +describe("Utils.Methods", () => { it("inRange works correct", () => { - assert.isTrue(Plottable._Util.Methods.inRange(0, -1, 1), "basic functionality works"); - assert.isTrue(Plottable._Util.Methods.inRange(0, 0, 1), "it is a closed interval"); - assert.isTrue(!Plottable._Util.Methods.inRange(0, 1, 2), "returns false when false"); - }); - - it("sortedIndex works properly", () => { - var a = [1, 2, 3, 4, 5]; - var si = Plottable._Util.OpenSource.sortedIndex; - assert.equal(si(0, a), 0, "return 0 when val is <= arr[0]"); - assert.equal(si(6, a), a.length, "returns a.length when val >= arr[arr.length-1]"); - assert.equal(si(1.5, a), 1, "returns 1 when val is between the first and second elements"); - }); - - it("accessorize works properly", () => { - var datum = {"foo": 2, "bar": 3, "key": 4}; - - var f = (d: any, i: number, m: any) => d + i; - var a1 = Plottable._Util.Methods.accessorize(f); - assert.equal(f, a1, "function passes through accessorize unchanged"); - - var a2 = Plottable._Util.Methods.accessorize("key"); - assert.equal(a2(datum, 0, null), 4, "key accessor works appropriately"); - - var a3 = Plottable._Util.Methods.accessorize("#aaaa"); - assert.equal(a3(datum, 0, null), "#aaaa", "strings beginning with # are returned as final value"); - - var a4 = Plottable._Util.Methods.accessorize(33); - assert.equal(a4(datum, 0, null), 33, "numbers are return as final value"); - - var a5 = Plottable._Util.Methods.accessorize(datum); - assert.equal(a5(datum, 0, null), datum, "objects are return as final value"); + assert.isTrue(Plottable.Utils.Methods.inRange(0, -1, 1), "basic functionality works"); + assert.isTrue(Plottable.Utils.Methods.inRange(0, 0, 1), "it is a closed interval"); + assert.isTrue(!Plottable.Utils.Methods.inRange(0, 1, 2), "returns false when false"); }); it("uniq works as expected", () => { var strings = ["foo", "bar", "foo", "foo", "baz", "bam"]; - assert.deepEqual(Plottable._Util.Methods.uniq(strings), ["foo", "bar", "baz", "bam"]); + assert.deepEqual(Plottable.Utils.Methods.uniq(strings), ["foo", "bar", "baz", "bam"]); }); - - describe("min/max", () => { - var max = Plottable._Util.Methods.max; - var min = Plottable._Util.Methods.min; + describe("max() and min()", () => { + var max = Plottable.Utils.Methods.max; + var min = Plottable.Utils.Methods.min; var today = new Date(); - it("max/min work as expected", () => { + it("return the default value if max or min can't be computed", () => { + var minValue = 1; + var maxValue = 5; + var defaultValue = 3; + var goodArray: number[][] = [ + [minValue], + [maxValue] + ]; + // bad array is technically of type number[][], but subarrays are empty! + var badArray: number[][] = [ + [], + [] + ]; + var accessor = (arr: number[]) => arr[0]; + assert.strictEqual(min(goodArray, accessor, defaultValue), + minValue, "min(): minimum value is returned in good case"); + assert.strictEqual(min(badArray, accessor, defaultValue), + defaultValue, "min(): default value is returned in bad case"); + assert.strictEqual(max(goodArray, accessor, defaultValue), + maxValue, "max(): maximum value is returned in good case"); + assert.strictEqual(max(badArray, accessor, defaultValue), + defaultValue, "max(): default value is returned in bad case"); + }); + + it("max() and min() work on numbers", () => { var alist = [1, 2, 3, 4, 5]; var dbl = (x: number) => x * 2; var dblIndexOffset = (x: number, i: number) => x * 2 - i; @@ -74,13 +69,13 @@ describe("_Util.Methods", () => { assert.deepEqual(min([], numToDate, today), today, "min accepts non-numeric default and function"); }); - it("max/min works as expected on non-numeric values (strings)", () => { + it("max() and min() work on strings", () => { var strings = ["a", "bb", "ccc", "ddd"]; assert.deepEqual(max(strings, (s: string) => s.length, 0), 3, "works on arrays of non-numbers with a function"); assert.deepEqual(max([], (s: string) => s.length, 5), 5, "defaults work even with non-number function type"); }); - it("max/min works as expected on non-numeric values (dates)", () => { + it("max() and min() work on dates", () => { var tomorrow = new Date(today.getTime()); tomorrow.setDate(today.getDate() + 1); var dayAfterTomorrow = new Date(today.getTime()); @@ -88,13 +83,13 @@ describe("_Util.Methods", () => { var dates: Date[] = [today, tomorrow, dayAfterTomorrow, null]; assert.deepEqual(min(dates, dayAfterTomorrow), today, "works on arrays of non-numeric values but comparable"); assert.deepEqual(max(dates, today), dayAfterTomorrow, "works on arrays of non-number values but comparable"); - assert.deepEqual(max([null], today), undefined, "returns undefined from array of null values"); - assert.deepEqual(max([], today), today, "correct default non-numeric value returned"); + assert.deepEqual(max([null], today), today, "returns default value if passed array of null values"); + assert.deepEqual(max([], today), today, "returns default value if passed empty"); }); }); it("isNaN works as expected", () => { - var isNaN = Plottable._Util.Methods.isNaN; + var isNaN = Plottable.Utils.Methods.isNaN; assert.isTrue(isNaN(NaN), "Only NaN should pass the isNaN check"); @@ -109,7 +104,7 @@ describe("_Util.Methods", () => { }); it("isValidNumber works as expected", () => { - var isValidNumber = Plottable._Util.Methods.isValidNumber; + var isValidNumber = Plottable.Utils.Methods.isValidNumber; assert.isTrue(isValidNumber(0), "(0 is a valid number"); @@ -152,31 +147,31 @@ describe("_Util.Methods", () => { }); it("objEq works as expected", () => { - assert.isTrue(Plottable._Util.Methods.objEq({}, {})); - assert.isTrue(Plottable._Util.Methods.objEq({a: 5}, {a: 5})); - assert.isFalse(Plottable._Util.Methods.objEq({a: 5, b: 6}, {a: 5})); - assert.isFalse(Plottable._Util.Methods.objEq({a: 5}, {a: 5, b: 6})); - assert.isTrue(Plottable._Util.Methods.objEq({a: "hello"}, {a: "hello"})); - assert.isFalse(Plottable._Util.Methods.objEq({constructor: {}.constructor}, {}), + assert.isTrue(Plottable.Utils.Methods.objEq({}, {})); + assert.isTrue(Plottable.Utils.Methods.objEq({a: 5}, {a: 5})); + assert.isFalse(Plottable.Utils.Methods.objEq({a: 5, b: 6}, {a: 5})); + assert.isFalse(Plottable.Utils.Methods.objEq({a: 5}, {a: 5, b: 6})); + assert.isTrue(Plottable.Utils.Methods.objEq({a: "hello"}, {a: "hello"})); + assert.isFalse(Plottable.Utils.Methods.objEq({constructor: {}.constructor}, {}), "using \"constructor\" isn't hidden"); }); it("populateMap works as expected", () => { var keys = ["a", "b", "c"]; - var map = Plottable._Util.Methods.populateMap(keys, (key) => key + "Value"); + var map = Plottable.Utils.Methods.populateMap(keys, (key) => key + "Value"); assert.strictEqual(map.get("a"), "aValue", "key properly goes through map function"); assert.strictEqual(map.get("b"), "bValue", "key properly goes through map function"); assert.strictEqual(map.get("c"), "cValue", "key properly goes through map function"); - var indexMap = Plottable._Util.Methods.populateMap(keys, (key, i) => key + i + "Value"); + var indexMap = Plottable.Utils.Methods.populateMap(keys, (key, i) => key + i + "Value"); assert.strictEqual(indexMap.get("a"), "a0Value", "key and index properly goes through map function"); assert.strictEqual(indexMap.get("b"), "b1Value", "key and index properly goes through map function"); assert.strictEqual(indexMap.get("c"), "c2Value", "key and index properly goes through map function"); var emptyKeys: string[] = []; - var emptyMap = Plottable._Util.Methods.populateMap(emptyKeys, (key) => key + "Value"); + var emptyMap = Plottable.Utils.Methods.populateMap(emptyKeys, (key) => key + "Value"); assert.isTrue(emptyMap.empty(), "no entries in map if no keys in input array"); @@ -193,11 +188,11 @@ describe("_Util.Methods", () => { oldMap["NaN"] = 0 / 0; oldMap["inf"] = 1 / 0; - var map = Plottable._Util.Methods.copyMap(oldMap); + var map = Plottable.Utils.Methods.copyMap(oldMap); assert.deepEqual(map, oldMap, "All values were copied."); - map = Plottable._Util.Methods.copyMap({}); + map = Plottable.Utils.Methods.copyMap({}); assert.deepEqual(map, {}, "No values were added."); }); @@ -205,30 +200,30 @@ describe("_Util.Methods", () => { it("range works as expected", () => { var start = 0; var end = 6; - var range = Plottable._Util.Methods.range(start, end); + var range = Plottable.Utils.Methods.range(start, end); assert.deepEqual(range, [0, 1, 2, 3, 4, 5], "all entries has been generated"); - range = Plottable._Util.Methods.range(start, end, 2); + range = Plottable.Utils.Methods.range(start, end, 2); assert.deepEqual(range, [0, 2, 4], "all entries has been generated"); - range = Plottable._Util.Methods.range(start, end, 11); + range = Plottable.Utils.Methods.range(start, end, 11); assert.deepEqual(range, [0], "all entries has been generated"); - assert.throws(() => Plottable._Util.Methods.range(start, end, 0), "step cannot be 0"); + assert.throws(() => Plottable.Utils.Methods.range(start, end, 0), "step cannot be 0"); - range = Plottable._Util.Methods.range(start, end, -1); + range = Plottable.Utils.Methods.range(start, end, -1); assert.lengthOf(range, 0, "no entries because of invalid step"); - range = Plottable._Util.Methods.range(end, start, -1); + range = Plottable.Utils.Methods.range(end, start, -1); assert.deepEqual(range, [6, 5, 4, 3, 2, 1], "all entries has been generated"); - range = Plottable._Util.Methods.range(-2, 2); + range = Plottable.Utils.Methods.range(-2, 2); assert.deepEqual(range, [-2, -1, 0, 1], "all entries has been generated range crossing 0"); - range = Plottable._Util.Methods.range(0.2, 4); + range = Plottable.Utils.Methods.range(0.2, 4); assert.deepEqual(range, [0.2, 1.2, 2.2, 3.2], "all entries has been generated with float start"); - range = Plottable._Util.Methods.range(0.6, 2.2, 0.5); + range = Plottable.Utils.Methods.range(0.6, 2.2, 0.5); assert.deepEqual(range, [0.6, 1.1, 1.6, 2.1], "all entries has been generated with float step"); }); @@ -238,14 +233,14 @@ describe("_Util.Methods", () => { style.attr("type", "text/css"); style.text(".plottable-colors-0 { background-color: blue; }"); - var blueHexcode = Plottable._Util.Methods.colorTest(colorTester, "plottable-colors-0"); + var blueHexcode = Plottable.Utils.Methods.colorTest(colorTester, "plottable-colors-0"); assert.strictEqual(blueHexcode, "#0000ff", "hexcode for blue returned"); style.text(".plottable-colors-2 { background-color: #13EADF; }"); - var hexcode = Plottable._Util.Methods.colorTest(colorTester, "plottable-colors-2"); + var hexcode = Plottable.Utils.Methods.colorTest(colorTester, "plottable-colors-2"); assert.strictEqual(hexcode, "#13eadf", "hexcode for blue returned"); - var nullHexcode = Plottable._Util.Methods.colorTest(colorTester, "plottable-colors-11"); + var nullHexcode = Plottable.Utils.Methods.colorTest(colorTester, "plottable-colors-11"); assert.strictEqual(nullHexcode, null, "null hexcode returned"); colorTester.remove(); }); @@ -253,7 +248,7 @@ describe("_Util.Methods", () => { it("lightenColor()", () => { var colorHex = "#12fced"; var oldColor = d3.hsl(colorHex); - var lightenedColor = Plottable._Util.Methods.lightenColor(colorHex, 1); + var lightenedColor = Plottable.Utils.Methods.lightenColor(colorHex, 1); assert.operator(d3.hsl(lightenedColor).l, ">", oldColor.l, "color got lighter"); }); }); diff --git a/tslint.json b/tslint.json index 182affe385..251b5b91d3 100644 --- a/tslint.json +++ b/tslint.json @@ -1,6 +1,12 @@ { "rules": { + "ban": [true, + ["d3", "max"], + ["d3", "min"], + ["assert", "equal"] + ], "class-name": true, + "comment-format": [true, "check-space"], "curly": true, "eofline": true, "forin": true, @@ -10,7 +16,7 @@ "max-line-length": [true, 140], "no-arg": true, "no-bitwise": true, - "no-construct": true, + "no-consecutive-blank-lines": true, "no-console": [true, "log", "debug", @@ -20,26 +26,48 @@ "trace", "warn" ], + "no-construct": true, + "no-constructor-vars": true, + "no-debugger": true, "no-duplicate-key": true, "no-duplicate-variable": true, "no-empty": true, "no-eval": true, + "no-switch-case-fall-through": true, "no-trailing-comma": true, "no-trailing-whitespace": true, - "no-unused-variable": false, "no-unreachable": true, + "no-unused-expression": true, + "no-unused-variable": true, "no-use-before-declare": true, "one-line": [true, - "check-open-brace", "check-catch", - "check-else" + "check-else", + "check-open-brace", + "check-whitespace" + ], + "quotemark": [true, + "double" ], - "quotemark": [true, "double"], "radix": true, "semicolon": true, - "triple-equals": [true, "allow-null-check"], + "triple-equals": [true, + "allow-null-check" + ], + "typedef-whitespace": [true, { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + }], "variable-name": false, - "whitespace": ["check-branch", "check-decl", "check-operator", "check-separator", "check-type"], - "ban": [true, ["d3", "max"], ["d3", "min"]] + "whitespace": [true, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type" + ] } }