diff --git a/examples/exampleUtil.js b/examples/exampleUtil.js
index 2c44365025..94a8de476e 100644
--- a/examples/exampleUtil.js
+++ b/examples/exampleUtil.js
@@ -9,7 +9,7 @@ function makeRandomData(numPoints, scaleFactor) {
data.sort(function (a, b) {
return a.x - b.x;
});
- return { data: data, metadata: { cssClass: "random-data" } };
+ return data;
}
function makeNormallyDistributedData(n, xMean, xStdDev, yMean, yStdDev) {
@@ -85,10 +85,7 @@ function makeRandomBucketData(numBuckets, bucketWidth, maxValue) {
y: Math.round(Math.random() * maxValue)
});
}
- return {
- "data": data,
- metadata: { cssClass: "random-buckets" }
- };
+ return data;
}
function generateHeightWeightData(n) {
@@ -104,8 +101,5 @@ function generateHeightWeightData(n) {
});
}
- return {
- data: data,
- metadata: { cssClass: "height-weight-data" }
- };
+ return data;
}
diff --git a/plottable.d.ts b/plottable.d.ts
index 494347ca52..479111627d 100644
--- a/plottable.d.ts
+++ b/plottable.d.ts
@@ -10,6 +10,8 @@ declare module Plottable {
* @returns {SVGRed} The bounding box.
*/
function getBBox(element: D3.Selection): SVGRect;
+ function getElementWidth(elem: HTMLScriptElement): number;
+ function getElementHeight(elem: HTMLScriptElement): number;
/**
* Truncates a text string to a max length, given the element in which to draw the text
*
@@ -27,6 +29,33 @@ declare module Plottable {
*/
function getTextHeight(textElement: D3.Selection): number;
function getSVGPixelWidth(svg: D3.Selection): number;
+ function accessorize(accessor: any): IAccessor;
+ function applyAccessor(accessor: IAccessor, dataSource: DataSource): (d: any, i: number) => any;
+ function uniq(strings: string[]): string[];
+ /**
+ * 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 {
+ /**
+ * Set a new key/value pair in the store.
+ *
+ * @param {any} Key to set in the store
+ * @param {any} Value to set in the store
+ * @return {boolean} True if key already in store, false otherwise
+ */
+ public set(key: any, value: any): boolean;
+ public get(key: any): any;
+ public has(key: any): boolean;
+ public values(): any[];
+ public delete(key: any): boolean;
+ }
+ class IDCounter {
+ public increment(id: any): number;
+ public decrement(id: any): number;
+ public get(id: any): number;
+ }
}
}
declare module Plottable {
@@ -72,7 +101,38 @@ declare module Plottable {
}
}
declare module Plottable {
- class Component {
+ class PlottableObject {
+ }
+}
+declare module Plottable {
+ class Broadcaster extends PlottableObject {
+ /**
+ * Registers a callback to be called when the broadcast method is called. Also takes a listener which
+ * is used to support deregistering the same callback later, by passing in the same listener.
+ * If there is already a callback associated with that listener, then the callback will be replaced.
+ *
+ * @param listener The listener associated with the callback.
+ * @param {IBroadcasterCallback} callback A callback to be called when the Scale's domain changes.
+ * @returns {Broadcaster} this object
+ */
+ public registerListener(listener: any, callback: IBroadcasterCallback): Broadcaster;
+ /**
+ * Call all listening callbacks, optionally with arguments passed through.
+ *
+ * @param ...args A variable number of optional arguments
+ * @returns {Broadcaster} this object
+ */
+ /**
+ * Registers deregister the callback associated with a listener.
+ *
+ * @param listener The listener to deregister.
+ * @returns {Broadcaster} this object
+ */
+ public deregisterListener(listener: any): Broadcaster;
+ }
+}
+declare module Plottable {
+ class Component extends PlottableObject {
public element: D3.Selection;
public content: D3.Selection;
public backgroundContainer: D3.Selection;
@@ -194,7 +254,7 @@ declare module Plottable {
}
}
declare module Plottable {
- class Scale implements IBroadcaster {
+ class Scale extends Broadcaster {
/**
* Creates a new Scale.
*
@@ -203,6 +263,14 @@ declare module Plottable {
*/
constructor(scale: D3.Scale.Scale);
/**
+ * Modify the domain on the scale so that it includes the extent of all
+ * perspectives it depends on. Extent: The (min, max) pair for a
+ * QuantitiativeScale, all covered strings for an OrdinalScale.
+ * Perspective: A combination of a DataSource and an Accessor that
+ * represents a view in to the data.
+ */
+ public autorangeDomain(): Scale;
+ /**
* Returns the range value corresponding to a given domain value.
*
* @param value {any} A domain value to be scaled.
@@ -212,7 +280,10 @@ declare module Plottable {
/**
* Retrieves the current domain, or sets the Scale's domain to the specified values.
*
- * @param {any[]} [values] The new value for the domain.
+ * @param {any[]} [values] The new value for the domain. This array may
+ * contain more than 2 values if the scale type allows it (e.g.
+ * ordinal scales). Other scales such as quantitative scales accept
+ * only a 2-value extent array.
* @returns {any[]|Scale} The current domain, or the calling Scale (if values is supplied).
*/
public domain(): any[];
@@ -231,42 +302,9 @@ declare module Plottable {
* @returns {Scale} A copy of the calling Scale.
*/
public copy(): Scale;
- /**
- * Registers a callback to be called when the scale's domain is changed.
- *
- * @param {IBroadcasterCallback} callback A callback to be called when the Scale's domain changes.
- * @returns {Scale} The Calling Scale.
- */
- public registerListener(callback: IBroadcasterCallback): Scale;
- /**
- * Expands the Scale's domain to cover the data given.
- * Passes an accessor through to the native d3 code.
- *
- * @param data The data to operate on.
- * @param [accessor] The accessor to get values out of the data
- * @returns {Scale} The Scale.
- */
- public widenDomainOnData(data: any[], accessor?: IAccessor): Scale;
- }
- class OrdinalScale extends Scale {
- /**
- * Creates a new OrdinalScale. Domain and Range are set later.
- *
- * @constructor
- */
- constructor();
- public domain(): any[];
- public domain(values: any[]): Scale;
- /**
- * Returns the range of pixels spanned by the scale, or sets the range.
- *
- * @param {number[]} [values] The pixel range to set on the scale.
- * @returns {number[]|OrdinalScale} The pixel range, or the calling OrdinalScale.
- */
- public range(): any[];
- public range(values: number[]): Scale;
- public widenDomainOnData(data: any[], accessor?: IAccessor): OrdinalScale;
}
+}
+declare module Plottable {
class QuantitiveScale extends Scale {
/**
* Creates a new QuantitiveScale.
@@ -275,6 +313,7 @@ declare module Plottable {
* @param {D3.Scale.QuantitiveScale} scale The D3 QuantitiveScale backing the QuantitiveScale.
*/
constructor(scale: D3.Scale.QuantitiveScale);
+ public autorangeDomain(): QuantitiveScale;
/**
* Retrieves the domain value corresponding to a supplied range value.
*
@@ -288,22 +327,8 @@ declare module Plottable {
* @returns {QuantitiveScale} A copy of the calling QuantitiveScale.
*/
public copy(): QuantitiveScale;
- /**
- * Expands the QuantitiveScale's domain to cover the new region.
- *
- * @param {number[]} newDomain The additional domain to be covered by the QuantitiveScale.
- * @returns {QuantitiveScale} The scale.
- */
- public widenDomain(newDomain: number[]): QuantitiveScale;
- /**
- * Expands the QuantitiveScale's domain to cover the data given.
- * Passes an accessor through to the native d3 code.
- *
- * @param data The data to operate on.
- * @param [accessor] The accessor to get values out of the data.
- * @returns {QuantitiveScale} The scale.
- */
- public widenDomainOnData(data: any[], accessor?: IAccessor): QuantitiveScale;
+ public domain(): any[];
+ public domain(values: any[]): QuantitiveScale;
/**
* Sets or gets the QuantitiveScale's output interpolator
*
@@ -355,6 +380,8 @@ declare module Plottable {
*/
public padDomain(padProportion?: number): QuantitiveScale;
}
+}
+declare module Plottable {
class LinearScale extends QuantitiveScale {
/**
* Creates a new LinearScale.
@@ -371,16 +398,121 @@ declare module Plottable {
*/
public copy(): LinearScale;
}
+}
+declare module Plottable {
+ class OrdinalScale extends Scale {
+ /**
+ * Creates a new OrdinalScale. Domain and Range are set later.
+ *
+ * @constructor
+ */
+ constructor();
+ /**
+ * Retrieves the current domain, or sets the Scale's domain to the specified values.
+ *
+ * @param {any[]} [values] The new values for the domain. This array may contain more than 2 values.
+ * @returns {any[]|Scale} The current domain, or the calling Scale (if values is supplied).
+ */
+ public domain(): any[];
+ public domain(values: any[]): OrdinalScale;
+ /**
+ * Returns the range of pixels spanned by the scale, or sets the range.
+ *
+ * @param {number[]} [values] The pixel range to set on the scale.
+ * @returns {number[]|OrdinalScale} The pixel range, or the calling OrdinalScale.
+ */
+ public range(): any[];
+ public range(values: number[]): OrdinalScale;
+ /**
+ * Returns the width of the range band. Only valid when rangeType is set to "bands".
+ *
+ * @returns {number} The range band width or 0 if rangeType isn't "bands".
+ */
+ public rangeBand(): number;
+ /**
+ * Returns the range type, or sets the range type.
+ *
+ * @param {string} [rangeType] Either "points" or "bands" indicating the
+ * d3 method used to generate range bounds.
+ * @param {number} [outerPadding] The padding outside the range,
+ * proportional to the range step.
+ * @param {number} [innerPadding] The padding between bands in the range,
+ * proportional to the range step. This parameter is only used in
+ * "bands" type ranges.
+ * @returns {string|OrdinalScale} The current range type, or the calling
+ * OrdinalScale.
+ */
+ public rangeType(): string;
+ public rangeType(rangeType: string, outerPadding?: number, innerPadding?: number): OrdinalScale;
+ }
+}
+declare module Plottable {
class ColorScale extends Scale {
/**
* Creates a ColorScale.
*
* @constructor
- * @param {string} [scaleType] the type of color scale to create (Category10/Category20/Category20b/Category20c)
+ * @param {string} [scaleType] the type of color scale to create
+ * (Category10/Category20/Category20b/Category20c).
*/
constructor(scaleType?: string);
}
}
+declare module Plottable {
+ class InterpolatedColorScale extends LinearScale {
+ /**
+ * Converts the string array into a linear d3 scale.
+ *
+ * d3 doesn't accept more than 2 range values unless we use a ordinal
+ * scale. So, in order to interpolate smoothly between the full color
+ * range, we must override the interpolator and compute the color values
+ * manually.
+ *
+ * @param {string[]} [colors] an array of strings representing color
+ * values in hex ("#FFFFFF") or keywords ("white").
+ * @returns a linear d3 scale.
+ */
+ /**
+ * Creates a InterpolatedColorScale.
+ *
+ * @constructor
+ * @param {string|string[]} [scaleType] the type of color scale to create
+ * (reds/blues/posneg). Default is "reds". An array of color values
+ * with at least 2 values may also be passed (e.g. ["#FF00FF", "red",
+ * "dodgerblue"], in which case the resulting scale will interpolate
+ * linearly between the color values across the domain.
+ */
+ constructor(scaleType?: any);
+ }
+}
+declare module Plottable {
+ class DataSource extends Broadcaster {
+ /**
+ * Creates a new DataSource.
+ *
+ * @constructor
+ * @param {any[]} data
+ * @param {any} metadata An object containing additional information.
+ */
+ constructor(data?: any[], metadata?: any);
+ /**
+ * Retrieves the current data from the DataSource, or sets the data.
+ *
+ * @param {any[]} [data] The new data.
+ * @returns {any[]|DataSource} The current data, or the calling DataSource.
+ */
+ public data(): any[];
+ public data(data: any[]): DataSource;
+ /**
+ * Retrieves the current metadata from the DataSource, or sets the metadata.
+ *
+ * @param {any[]} [metadata] The new metadata.
+ * @returns {any[]|DataSource} The current metadata, or the calling DataSource.
+ */
+ public metadata(): any;
+ public metadata(metadata: any): DataSource;
+ }
+}
declare module Plottable {
interface IKeyEventListenerCallback {
(e: D3.Event): any;
@@ -446,32 +578,6 @@ declare module Plottable {
*/
public clearBox(): AreaInteraction;
}
- class ZoomCallbackGenerator {
- /**
- * Adds listen-update pair of X scales.
- *
- * @param {QuantitiveScale} listenerScale An X scale to listen for events on.
- * @param {QuantitiveScale} [targetScale] An X scale to update when events occur.
- * If not supplied, listenerScale will be updated when an event occurs.
- * @returns {ZoomCallbackGenerator} The calling ZoomCallbackGenerator.
- */
- public addXScale(listenerScale: QuantitiveScale, targetScale?: QuantitiveScale): ZoomCallbackGenerator;
- /**
- * Adds listen-update pair of Y scales.
- *
- * @param {QuantitiveScale} listenerScale A Y scale to listen for events on.
- * @param {QuantitiveScale} [targetScale] A Y scale to update when events occur.
- * If not supplied, listenerScale will be updated when an event occurs.
- * @returns {ZoomCallbackGenerator} The calling ZoomCallbackGenerator.
- */
- public addYScale(listenerScale: QuantitiveScale, targetScale?: QuantitiveScale): ZoomCallbackGenerator;
- /**
- * Generates a callback that can be passed to Interactions.
- *
- * @returns {(area: SelectionArea) => void} A callback that updates the scales previously specified.
- */
- public getCallback(): (area: SelectionArea) => void;
- }
class MousemoveInteraction extends Interaction {
constructor(componentToListenTo: Component);
public mousemove(x: number, y: number): void;
@@ -507,11 +613,6 @@ declare module Plottable {
*/
public callback(cb: () => any): KeyInteraction;
}
- class CrosshairsInteraction extends MousemoveInteraction {
- constructor(renderer: NumericXYRenderer);
- public mousemove(x: number, y: number): void;
- public rescale(): void;
- }
}
declare module Plottable {
class Label extends Component {
@@ -539,28 +640,34 @@ declare module Plottable {
}
}
declare module Plottable {
+ interface _IProjector {
+ accessor: IAccessor;
+ scale?: Scale;
+ }
class Renderer extends Component {
public renderArea: D3.Selection;
public element: D3.Selection;
public scales: Scale[];
+ };
/**
* Creates a Renderer.
*
* @constructor
- * @param {IDataset} [dataset] The dataset associated with the Renderer.
+ * @param {any[]|DataSource} [dataset] The data or DataSource to be associated with this Renderer.
*/
- constructor(dataset?: any);
+ constructor();
+ constructor(dataset: any[]);
+ constructor(dataset: DataSource);
/**
- * Sets a new dataset on the Renderer.
+ * Retrieves the current DataSource, or sets a DataSource if the Renderer doesn't yet have one.
*
- * @param {IDataset} dataset The new dataset to be associated with the Renderer.
- * @returns {Renderer} The calling Renderer.
+ * @param {DataSource} [source] The DataSource the Renderer should use, if it doesn't yet have one.
+ * @return {DataSource|Renderer} The current DataSource or the calling Renderer.
*/
- public dataset(dataset: IDataset): Renderer;
- public metadata(metadata: IMetadata): Renderer;
- public data(data: any[]): Renderer;
- public colorAccessor(a: IAccessor): Renderer;
- public autorange(): Renderer;
+ public dataSource(): DataSource;
+ public dataSource(source: DataSource): Renderer;
+ public project(attrToSet: string, accessor: any, scale?: Scale): Renderer;
+ };
}
}
declare module Plottable {
@@ -568,101 +675,54 @@ declare module Plottable {
public dataSelection: D3.UpdateSelection;
public xScale: Scale;
public yScale: Scale;
- public autorangeDataOnLayout: boolean;
/**
* Creates an XYRenderer.
*
* @constructor
- * @param {IDataset} dataset The dataset to render.
+ * @param {any[]|DataSource} [dataset] The data or DataSource to be associated with this Renderer.
* @param {Scale} xScale The x scale to use.
* @param {Scale} yScale The y scale to use.
* @param {IAccessor} [xAccessor] A function for extracting x values from the data.
* @param {IAccessor} [yAccessor] A function for extracting y values from the data.
*/
- constructor(dataset: any, xScale: Scale, yScale: Scale, xAccessor?: IAccessor, yAccessor?: IAccessor);
- public xAccessor(accessor: any): XYRenderer;
- public yAccessor(accessor: any): XYRenderer;
- /**
- * Autoranges the scales over the data.
- * Actual behavior is dependent on the scales.
- */
- public autorange(): XYRenderer;
- }
-}
-declare module Plottable {
- class NumericXYRenderer extends XYRenderer {
- public dataSelection: D3.UpdateSelection;
- public xScale: QuantitiveScale;
- public yScale: QuantitiveScale;
- public autorangeDataOnLayout: boolean;
- /**
- * Creates an NumericXYRenderer.
- *
- * @constructor
- * @param {IDataset} dataset The dataset to render.
- * @param {QuantitiveScale} xScale The x scale to use.
- * @param {QuantitiveScale} yScale The y scale to use.
- * @param {IAccessor} [xAccessor] A function for extracting x values from the data.
- * @param {IAccessor} [yAccessor] A function for extracting y values from the data.
- */
- constructor(dataset: any, xScale: QuantitiveScale, yScale: QuantitiveScale, xAccessor?: IAccessor, yAccessor?: IAccessor);
- /**
- * Converts a SelectionArea with pixel ranges to one with data ranges.
- *
- * @param {SelectionArea} pixelArea The selected area, in pixels.
- * @returns {SelectionArea} The corresponding selected area in the domains of the scales.
- */
- public invertXYSelectionArea(pixelArea: SelectionArea): SelectionArea;
- /**
- * Gets the data in a selected area.
- *
- * @param {SelectionArea} dataArea The selected area.
- * @returns {D3.UpdateSelection} The data in the selected area.
- */
- public getSelectionFromArea(dataArea: SelectionArea): D3.UpdateSelection;
- /**
- * Gets the indices of data in a selected area
- *
- * @param {SelectionArea} dataArea The selected area.
- * @returns {number[]} An array of the indices of datapoints in the selected area.
- */
- public getDataIndicesFromArea(dataArea: SelectionArea): number[];
+ constructor(dataset: any, xScale: Scale, yScale: Scale, xAccessor?: any, yAccessor?: any);
+ public project(attrToSet: string, accessor: any, scale?: Scale): XYRenderer;
}
}
declare module Plottable {
- class CircleRenderer extends NumericXYRenderer {
+ class CircleRenderer extends XYRenderer {
/**
* Creates a CircleRenderer.
*
* @constructor
* @param {IDataset} dataset The dataset to render.
- * @param {QuantitiveScale} xScale The x scale to use.
- * @param {QuantitiveScale} yScale The y scale to use.
+ * @param {Scale} xScale The x scale to use.
+ * @param {Scale} yScale The y scale to use.
* @param {IAccessor} [xAccessor] A function for extracting x values from the data.
* @param {IAccessor} [yAccessor] A function for extracting y values from the data.
* @param {IAccessor} [rAccessor] A function for extracting radius values from the data.
*/
- constructor(dataset: any, xScale: QuantitiveScale, yScale: QuantitiveScale, xAccessor?: any, yAccessor?: any, rAccessor?: any);
- public rAccessor(a: any): CircleRenderer;
+ constructor(dataset: any, xScale: Scale, yScale: Scale, xAccessor?: any, yAccessor?: any, rAccessor?: any);
+ public project(attrToSet: string, accessor: any, scale?: Scale): CircleRenderer;
}
}
declare module Plottable {
- class LineRenderer extends NumericXYRenderer {
+ class LineRenderer extends XYRenderer {
/**
* Creates a LineRenderer.
*
* @constructor
* @param {IDataset} dataset The dataset to render.
- * @param {QuantitiveScale} xScale The x scale to use.
- * @param {QuantitiveScale} yScale The y scale to use.
+ * @param {Scale} xScale The x scale to use.
+ * @param {Scale} yScale The y scale to use.
* @param {IAccessor} [xAccessor] A function for extracting x values from the data.
* @param {IAccessor} [yAccessor] A function for extracting y values from the data.
*/
- constructor(dataset: any, xScale: QuantitiveScale, yScale: QuantitiveScale, xAccessor?: IAccessor, yAccessor?: IAccessor);
+ constructor(dataset: any, xScale: Scale, yScale: Scale, xAccessor?: IAccessor, yAccessor?: IAccessor);
}
}
declare module Plottable {
- class BarRenderer extends NumericXYRenderer {
+ class BarRenderer extends XYRenderer {
public barPaddingPx: number;
public dxAccessor: any;
/**
@@ -670,31 +730,84 @@ declare module Plottable {
*
* @constructor
* @param {IDataset} dataset The dataset to render.
- * @param {QuantitiveScale} xScale The x scale to use.
- * @param {QuantitiveScale} yScale The y scale to use.
+ * @param {Scale} xScale The x scale to use.
+ * @param {Scale} yScale The y scale to use.
* @param {IAccessor} [xAccessor] A function for extracting the start position of each bar from the data.
* @param {IAccessor} [dxAccessor] A function for extracting the width of each bar from the data.
* @param {IAccessor} [yAccessor] A function for extracting height of each bar from the data.
*/
- constructor(dataset: any, xScale: QuantitiveScale, yScale: QuantitiveScale, xAccessor?: IAccessor, dxAccessor?: IAccessor, yAccessor?: IAccessor);
- public autorange(): BarRenderer;
+ constructor(dataset: any, xScale: Scale, yScale: Scale, xAccessor?: IAccessor, dxAccessor?: IAccessor, yAccessor?: IAccessor);
}
}
declare module Plottable {
- class SquareRenderer extends NumericXYRenderer {
+ class SquareRenderer extends XYRenderer {
/**
* Creates a SquareRenderer.
*
* @constructor
* @param {IDataset} dataset The dataset to render.
- * @param {QuantitiveScale} xScale The x scale to use.
- * @param {QuantitiveScale} yScale The y scale to use.
+ * @param {Scale} xScale The x scale to use.
+ * @param {Scale} yScale The y scale to use.
* @param {IAccessor} [xAccessor] A function for extracting x values from the data.
* @param {IAccessor} [yAccessor] A function for extracting y values from the data.
* @param {IAccessor} [rAccessor] A function for extracting radius values from the data.
*/
- constructor(dataset: any, xScale: QuantitiveScale, yScale: QuantitiveScale, xAccessor?: IAccessor, yAccessor?: IAccessor, rAccessor?: IAccessor);
- public rAccessor(a: any): SquareRenderer;
+ constructor(dataset: any, xScale: Scale, yScale: Scale, xAccessor?: IAccessor, yAccessor?: IAccessor, rAccessor?: IAccessor);
+ }
+}
+declare module Plottable {
+ class CategoryBarRenderer extends XYRenderer {
+ public xScale: OrdinalScale;
+ /**
+ * Creates a CategoryBarRenderer.
+ *
+ * @constructor
+ * @param {IDataset} dataset The dataset to render.
+ * @param {OrdinalScale} xScale The x scale to use.
+ * @param {Scale} yScale The y scale to use.
+ * @param {IAccessor} [xAccessor] A function for extracting the start position of each bar from the data.
+ * @param {IAccessor} [widthAccessor] A function for extracting the width position of each bar, in pixels, from the data.
+ * @param {IAccessor} [yAccessor] A function for extracting height of each bar from the data.
+ */
+ constructor(dataset: any, xScale: OrdinalScale, yScale: Scale, xAccessor?: IAccessor, widthAccessor?: IAccessor, yAccessor?: IAccessor);
+ /**
+ * Selects the bar under the given pixel position.
+ *
+ * @param {number} x The pixel x position.
+ * @param {number} y The pixel y position.
+ * @param {boolean} [select] Whether or not to select the bar (by classing it "selected");
+ * @return {D3.Selection} The selected bar, or null if no bar was selected.
+ */
+ public selectBar(x: number, y: number, select?: boolean): D3.Selection;
+ /**
+ * Deselects all bars.
+ */
+ public deselectAll(): void;
+ }
+}
+declare module Plottable {
+ class GridRenderer extends XYRenderer {
+ public colorScale: Scale;
+ public xScale: OrdinalScale;
+ public yScale: OrdinalScale;
+ /**
+ * Creates a GridRenderer.
+ *
+ * @constructor
+ * @param {IDataset} dataset The dataset to render.
+ * @param {OrdinalScale} xScale The x scale to use.
+ * @param {OrdinalScale} yScale The y scale to use.
+ * @param {ColorScale|InterpolatedColorScale} colorScale The color scale to use for each grid
+ * cell.
+ * @param {IAccessor|string|number} [xAccessor] An accessor for extracting
+ * the x position of each grid cell from the data.
+ * @param {IAccessor|string|number} [yAccessor] An accessor for extracting
+ * the y position of each grid cell from the data.
+ * @param {IAccessor|string|number} [valueAccessor] An accessor for
+ * extracting value of each grid cell from the data. This value will
+ * be pass through the color scale to determine the color of the cell.
+ */
+ constructor(dataset: any, xScale: OrdinalScale, yScale: OrdinalScale, colorScale: Scale, xAccessor?: any, yAccessor?: any, valueAccessor?: any);
}
}
declare module Plottable {
@@ -800,61 +913,6 @@ declare module Plottable {
public addCenterComponent(c: Component): StandardChart;
}
}
-declare module Plottable {
- class CategoryXYRenderer extends XYRenderer {
- public dataSelection: D3.UpdateSelection;
- public xScale: OrdinalScale;
- public yScale: QuantitiveScale;
- /**
- * Creates a CategoryXYRenderer with an Ordinal x scale and Quantitive y scale.
- *
- * @constructor
- * @param {IDataset} dataset The dataset to render.
- * @param {OrdinalScale} xScale The x scale to use.
- * @param {QuantitiveScale} yScale The y scale to use.
- * @param {IAccessor} [xAccessor] A function for extracting x values from the data.
- * @param {IAccessor} [yAccessor] A function for extracting y values from the data.
- */
- constructor(dataset: any, xScale: OrdinalScale, yScale: QuantitiveScale, xAccessor?: IAccessor, yAccessor?: IAccessor);
- }
-}
-declare module Plottable {
- class CategoryBarRenderer extends CategoryXYRenderer {
- /**
- * Creates a CategoryBarRenderer.
- *
- * @constructor
- * @param {IDataset} dataset The dataset to render.
- * @param {OrdinalScale} xScale The x scale to use.
- * @param {QuantitiveScale} yScale The y scale to use.
- * @param {IAccessor} [xAccessor] A function for extracting the start position of each bar from the data.
- * @param {IAccessor} [widthAccessor] A function for extracting the width position of each bar, in pixels, from the data.
- * @param {IAccessor} [yAccessor] A function for extracting height of each bar from the data.
- */
- constructor(dataset: any, xScale: OrdinalScale, yScale: QuantitiveScale, xAccessor?: IAccessor, widthAccessor?: IAccessor, yAccessor?: IAccessor);
- /**
- * Sets the width accessor.
- *
- * @param {any} accessor The new width accessor.
- * @returns {CategoryBarRenderer} The calling CategoryBarRenderer.
- */
- public widthAccessor(accessor: any): CategoryBarRenderer;
- public autorange(): CategoryBarRenderer;
- /**
- * Selects the bar under the given pixel position.
- *
- * @param {number} x The pixel x position.
- * @param {number} y The pixel y position.
- * @param {boolean} [select] Whether or not to select the bar (by classing it "selected");
- * @return {D3.Selection} The selected bar, or null if no bar was selected.
- */
- public selectBar(x: number, y: number, select?: boolean): D3.Selection;
- /**
- * Deselects all bars.
- */
- public deselectAll(): void;
- }
-}
declare module Plottable {
class Axis extends Component {
static yWidth: number;
@@ -977,6 +1035,9 @@ declare module Plottable {
interface IAccessor {
(datum: any, index?: number, metadata?: any): any;
}
+ interface IAppliedAccessor {
+ (datum: any, index: number): any;
+ }
interface SelectionArea {
xMin: number;
xMax: number;
@@ -988,9 +1049,6 @@ declare module Plottable {
data: SelectionArea;
}
interface IBroadcasterCallback {
- (broadcaster: IBroadcaster, ...args: any[]): any;
- }
- interface IBroadcaster {
- registerListener: (cb: IBroadcasterCallback) => IBroadcaster;
+ (broadcaster: Broadcaster, ...args: any[]): any;
}
}
diff --git a/plottable.js b/plottable.js
index 09500a06c0..7aca75a566 100644
--- a/plottable.js
+++ b/plottable.js
@@ -26,6 +26,26 @@ var Plottable;
}
Utils.getBBox = getBBox;
+ function _getParsedStyleValue(style, prop) {
+ var value = style.getPropertyValue(prop);
+ if (value == null) {
+ return 0;
+ }
+ return parseFloat(value);
+ }
+
+ function getElementWidth(elem) {
+ var style = window.getComputedStyle(elem);
+ return _getParsedStyleValue(style, "width") + _getParsedStyleValue(style, "padding-left") + _getParsedStyleValue(style, "padding-right") + _getParsedStyleValue(style, "border-left-width") + _getParsedStyleValue(style, "border-right-width");
+ }
+ Utils.getElementWidth = getElementWidth;
+
+ function getElementHeight(elem) {
+ var style = window.getComputedStyle(elem);
+ return _getParsedStyleValue(style, "height") + _getParsedStyleValue(style, "padding-top") + _getParsedStyleValue(style, "padding-bottom") + _getParsedStyleValue(style, "border-top-width") + _getParsedStyleValue(style, "border-bottom-width");
+ }
+ Utils.getElementHeight = getElementHeight;
+
/**
* Truncates a text string to a max length, given the element in which to draw the text
*
@@ -100,6 +120,131 @@ var Plottable;
return width;
}
Utils.getSVGPixelWidth = getSVGPixelWidth;
+
+ 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;
+ };
+ }
+ ;
+ }
+ Utils.accessorize = accessorize;
+
+ function applyAccessor(accessor, dataSource) {
+ var activatedAccessor = accessorize(accessor);
+ return function (d, i) {
+ return activatedAccessor(d, i, dataSource.metadata());
+ };
+ }
+ Utils.applyAccessor = applyAccessor;
+
+ function uniq(strings) {
+ var seen = {};
+ strings.forEach(function (s) {
+ return seen[s] = true;
+ });
+ return d3.keys(seen);
+ }
+ Utils.uniq = uniq;
+
+ /**
+ * 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() {
+ this.keyValuePairs = [];
+ }
+ /**
+ * Set a new key/value pair in the store.
+ *
+ * @param {any} Key to set in the store
+ * @param {any} Value to set in the store
+ * @return {boolean} True if key already in store, false otherwise
+ */
+ StrictEqualityAssociativeArray.prototype.set = function (key, value) {
+ for (var i = 0; i < this.keyValuePairs.length; i++) {
+ if (this.keyValuePairs[i][0] === key) {
+ this.keyValuePairs[i][1] = value;
+ return true;
+ }
+ }
+ this.keyValuePairs.push([key, value]);
+ return false;
+ };
+
+ StrictEqualityAssociativeArray.prototype.get = function (key) {
+ for (var i = 0; i < this.keyValuePairs.length; i++) {
+ if (this.keyValuePairs[i][0] === key) {
+ return this.keyValuePairs[i][1];
+ }
+ }
+ return undefined;
+ };
+
+ StrictEqualityAssociativeArray.prototype.has = function (key) {
+ for (var i = 0; i < this.keyValuePairs.length; i++) {
+ if (this.keyValuePairs[i][0] === key) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ StrictEqualityAssociativeArray.prototype.values = function () {
+ return this.keyValuePairs.map(function (x) {
+ return x[1];
+ });
+ };
+
+ StrictEqualityAssociativeArray.prototype.delete = function (key) {
+ for (var i = 0; i < this.keyValuePairs.length; i++) {
+ if (this.keyValuePairs[i][0] === key) {
+ this.keyValuePairs.splice(i, 1);
+ return true;
+ }
+ }
+ return false;
+ };
+ return StrictEqualityAssociativeArray;
+ })();
+ Utils.StrictEqualityAssociativeArray = StrictEqualityAssociativeArray;
+
+ var IDCounter = (function () {
+ function IDCounter() {
+ this.counter = {};
+ }
+ IDCounter.prototype.setDefault = function (id) {
+ if (this.counter[id] == null) {
+ this.counter[id] = 0;
+ }
+ };
+
+ IDCounter.prototype.increment = function (id) {
+ this.setDefault(id);
+ return ++this.counter[id];
+ };
+
+ IDCounter.prototype.decrement = function (id) {
+ this.setDefault(id);
+ return --this.counter[id];
+ };
+
+ IDCounter.prototype.get = function (id) {
+ this.setDefault(id);
+ return this.counter[id];
+ };
+ return IDCounter;
+ })();
+ Utils.IDCounter = IDCounter;
})(Plottable.Utils || (Plottable.Utils = {}));
var Utils = Plottable.Utils;
})(Plottable || (Plottable = {}));
@@ -135,8 +280,87 @@ var Plottable;
///
var Plottable;
(function (Plottable) {
- var Component = (function () {
+ var PlottableObject = (function () {
+ function PlottableObject() {
+ this._plottableID = PlottableObject.nextID++;
+ }
+ PlottableObject.nextID = 0;
+ return PlottableObject;
+ })();
+ Plottable.PlottableObject = PlottableObject;
+})(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 Broadcaster = (function (_super) {
+ __extends(Broadcaster, _super);
+ function Broadcaster() {
+ _super.apply(this, arguments);
+ this.listener2Callback = new Plottable.Utils.StrictEqualityAssociativeArray();
+ }
+ /**
+ * Registers a callback to be called when the broadcast method is called. Also takes a listener which
+ * is used to support deregistering the same callback later, by passing in the same listener.
+ * If there is already a callback associated with that listener, then the callback will be replaced.
+ *
+ * @param listener The listener associated with the callback.
+ * @param {IBroadcasterCallback} callback A callback to be called when the Scale's domain changes.
+ * @returns {Broadcaster} this object
+ */
+ Broadcaster.prototype.registerListener = function (listener, callback) {
+ this.listener2Callback.set(listener, callback);
+ return this;
+ };
+
+ /**
+ * Call all listening callbacks, optionally with arguments passed through.
+ *
+ * @param ...args A variable number of optional arguments
+ * @returns {Broadcaster} this object
+ */
+ Broadcaster.prototype._broadcast = function () {
+ var _this = this;
+ var args = [];
+ for (var _i = 0; _i < (arguments.length - 0); _i++) {
+ args[_i] = arguments[_i + 0];
+ }
+ this.listener2Callback.values().forEach(function (callback) {
+ return callback(_this, args);
+ });
+ return this;
+ };
+
+ /**
+ * Registers deregister the callback associated with a listener.
+ *
+ * @param listener The listener to deregister.
+ * @returns {Broadcaster} this object
+ */
+ Broadcaster.prototype.deregisterListener = function (listener) {
+ var listenerWasFound = this.listener2Callback.delete(listener);
+ if (listenerWasFound) {
+ return this;
+ } else {
+ throw new Error("Attempted to deregister listener, but listener not found");
+ }
+ };
+ return Broadcaster;
+ })(Plottable.PlottableObject);
+ Plottable.Broadcaster = Broadcaster;
+})(Plottable || (Plottable = {}));
+///
+var Plottable;
+(function (Plottable) {
+ var Component = (function (_super) {
+ __extends(Component, _super);
function Component() {
+ _super.apply(this, arguments);
this.interactionsToRegister = [];
this.boxes = [];
this.clipPathEnabled = false;
@@ -166,6 +390,9 @@ var Plottable;
// 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.element = element.append("g");
this.isTopLevelComponent = true;
} else {
@@ -216,8 +443,10 @@ var Plottable;
// we are the root node, retrieve height/width from root SVG
xOrigin = 0;
yOrigin = 0;
- availableWidth = parseFloat(this.rootSVG.attr("width"));
- availableHeight = parseFloat(this.rootSVG.attr("height"));
+
+ var elem = this.rootSVG.node();
+ availableWidth = Plottable.Utils.getElementWidth(elem);
+ availableHeight = Plottable.Utils.getElementHeight(elem);
} else {
throw new Error("null arguments cannot be passed to _computeLayout() on a non-root node");
}
@@ -348,9 +577,8 @@ var Plottable;
Component.prototype.generateClipPath = function () {
// The clip path will prevent content from overflowing its component space.
- var clipPathId = Component.clipPathId++;
- this.element.attr("clip-path", "url(#clipPath" + clipPathId + ")");
- var clipPathParent = this.boxContainer.append("clipPath").attr("id", "clipPath" + clipPathId);
+ this.element.attr("clip-path", "url(#clipPath" + this._plottableID + ")");
+ var clipPathParent = this.boxContainer.append("clipPath").attr("id", "clipPath" + this._plottableID);
this.addBox("clip-rect", clipPathParent);
};
@@ -463,21 +691,15 @@ var Plottable;
return cg;
}
};
- Component.clipPathId = 0;
return Component;
- })();
+ })(Plottable.PlottableObject);
Plottable.Component = Component;
})(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 () {
+ var Scale = (function (_super) {
+ __extends(Scale, _super);
/**
* Creates a new Scale.
*
@@ -485,9 +707,74 @@ var Plottable;
* @param {D3.Scale.Scale} scale The D3 scale backing the Scale.
*/
function Scale(scale) {
- this._broadcasterCallbacks = [];
+ _super.call(this);
+ this._autoDomain = true;
+ this.rendererID2Perspective = {};
+ this.dataSourceReferenceCounter = new Plottable.Utils.IDCounter();
+ this.isAutorangeUpToDate = false;
+ this._autoNice = false;
+ this._autoPad = false;
this._d3Scale = scale;
}
+ Scale.prototype._getCombinedExtent = function () {
+ var perspectives = d3.values(this.rendererID2Perspective);
+ var extents = perspectives.map(function (p) {
+ var source = p.dataSource;
+ var accessor = p.accessor;
+ return source._getExtent(accessor);
+ });
+ return extents;
+ };
+
+ /**
+ * Modify the domain on the scale so that it includes the extent of all
+ * perspectives it depends on. Extent: The (min, max) pair for a
+ * QuantitiativeScale, all covered strings for an OrdinalScale.
+ * Perspective: A combination of a DataSource and an Accessor that
+ * represents a view in to the data.
+ */
+ Scale.prototype.autorangeDomain = function () {
+ this.isAutorangeUpToDate = true;
+ this._setDomain(this._getCombinedExtent());
+ return this;
+ };
+
+ Scale.prototype._autoDomainIfNeeded = function () {
+ if (!this.isAutorangeUpToDate && this._autoDomain) {
+ this.autorangeDomain();
+ }
+ };
+
+ Scale.prototype._addPerspective = function (rendererIDAttr, dataSource, accessor) {
+ var _this = this;
+ if (this.rendererID2Perspective[rendererIDAttr] != null) {
+ this._removePerspective(rendererIDAttr);
+ }
+ this.rendererID2Perspective[rendererIDAttr] = { dataSource: dataSource, accessor: accessor };
+
+ var dataSourceID = dataSource._plottableID;
+ if (this.dataSourceReferenceCounter.increment(dataSourceID) === 1) {
+ dataSource.registerListener(this, function () {
+ return _this.isAutorangeUpToDate = false;
+ });
+ }
+
+ this.isAutorangeUpToDate = false;
+ return this;
+ };
+
+ Scale.prototype._removePerspective = function (rendererIDAttr) {
+ var dataSource = this.rendererID2Perspective[rendererIDAttr].dataSource;
+ var dataSourceID = dataSource._plottableID;
+ if (this.dataSourceReferenceCounter.decrement(dataSourceID) === 0) {
+ dataSource.deregisterListener(this);
+ }
+
+ delete this.rendererID2Perspective[rendererIDAttr];
+ this.isAutorangeUpToDate = false;
+ return this;
+ };
+
/**
* Returns the range value corresponding to a given domain value.
*
@@ -495,24 +782,29 @@ var Plottable;
* @returns {any} The range value corresponding to the supplied domain value.
*/
Scale.prototype.scale = function (value) {
+ this._autoDomainIfNeeded();
return this._d3Scale(value);
};
Scale.prototype.domain = function (values) {
- var _this = this;
if (values == null) {
+ this._autoDomainIfNeeded();
return this._d3Scale.domain();
} else {
- this._d3Scale.domain(values);
- this._broadcasterCallbacks.forEach(function (b) {
- return b(_this);
- });
+ this._autoDomain = false;
+ this._setDomain(values);
return this;
}
};
+ Scale.prototype._setDomain = function (values) {
+ this._d3Scale.domain(values);
+ this._broadcast();
+ };
+
Scale.prototype.range = function (values) {
if (values == null) {
+ this._autoDomainIfNeeded();
return this._d3Scale.range();
} else {
this._d3Scale.range(values);
@@ -528,104 +820,13 @@ var Plottable;
Scale.prototype.copy = function () {
return new Scale(this._d3Scale.copy());
};
-
- /**
- * Registers a callback to be called when the scale's domain is changed.
- *
- * @param {IBroadcasterCallback} callback A callback to be called when the Scale's domain changes.
- * @returns {Scale} The Calling Scale.
- */
- Scale.prototype.registerListener = function (callback) {
- this._broadcasterCallbacks.push(callback);
- return this;
- };
-
- /**
- * Expands the Scale's domain to cover the data given.
- * Passes an accessor through to the native d3 code.
- *
- * @param data The data to operate on.
- * @param [accessor] The accessor to get values out of the data
- * @returns {Scale} The Scale.
- */
- Scale.prototype.widenDomainOnData = function (data, accessor) {
- // no-op; implementation is sublcass-dependent
- return this;
- };
return Scale;
- })();
+ })(Plottable.Broadcaster);
Plottable.Scale = Scale;
-
- var OrdinalScale = (function (_super) {
- __extends(OrdinalScale, _super);
- /**
- * Creates a new OrdinalScale. Domain and Range are set later.
- *
- * @constructor
- */
- function OrdinalScale() {
- _super.call(this, d3.scale.ordinal());
- this.END_PADDING = 0.5;
- this._range = [0, 1];
- }
- OrdinalScale.prototype.domain = function (values) {
- var _this = this;
- if (values == null) {
- return this._d3Scale.domain();
- } else {
- this._d3Scale.domain(values);
- this._broadcasterCallbacks.forEach(function (b) {
- return b(_this);
- });
- this._d3Scale.rangePoints(this.range(), 2 * this.END_PADDING); // d3 scale takes total padding
- return this;
- }
- };
-
- OrdinalScale.prototype.range = function (values) {
- if (values == null) {
- return this._range;
- } else {
- this._range = values;
- this._d3Scale.rangePoints(values, 2 * this.END_PADDING); // d3 scale takes total padding
- return this;
- }
- };
-
- OrdinalScale.prototype.widenDomainOnData = function (data, accessor) {
- var changed = false;
- var newDomain = this.domain();
- var a;
- if (accessor == null) {
- a = function (d, i) {
- return d;
- };
- } else if (typeof (accessor) === "string") {
- a = function (d, i) {
- return d[accessor];
- };
- } else if (typeof (accessor) === "function") {
- a = accessor;
- } else {
- a = function (d, i) {
- return accessor;
- };
- }
- data.map(a).forEach(function (d) {
- if (newDomain.indexOf(d) === -1) {
- newDomain.push(d);
- changed = true;
- }
- });
- if (changed) {
- this.domain(newDomain);
- }
- return this;
- };
- return OrdinalScale;
- })(Scale);
- Plottable.OrdinalScale = OrdinalScale;
-
+})(Plottable || (Plottable = {}));
+///
+var Plottable;
+(function (Plottable) {
var QuantitiveScale = (function (_super) {
__extends(QuantitiveScale, _super);
/**
@@ -638,6 +839,28 @@ var Plottable;
_super.call(this, scale);
this.lastRequestedTickCount = 10;
}
+ QuantitiveScale.prototype._getCombinedExtent = function () {
+ var extents = _super.prototype._getCombinedExtent.call(this);
+ var starts = extents.map(function (e) {
+ return e[0];
+ });
+ var ends = extents.map(function (e) {
+ return e[1];
+ });
+ return [d3.min(starts), d3.max(ends)];
+ };
+
+ QuantitiveScale.prototype.autorangeDomain = function () {
+ _super.prototype.autorangeDomain.call(this);
+ if (this._autoPad) {
+ this.padDomain();
+ }
+ if (this._autoNice) {
+ this.nice();
+ }
+ return this;
+ };
+
/**
* Retrieves the domain value corresponding to a supplied range value.
*
@@ -657,31 +880,8 @@ var Plottable;
return new QuantitiveScale(this._d3Scale.copy());
};
- /**
- * Expands the QuantitiveScale's domain to cover the new region.
- *
- * @param {number[]} newDomain The additional domain to be covered by the QuantitiveScale.
- * @returns {QuantitiveScale} The scale.
- */
- QuantitiveScale.prototype.widenDomain = function (newDomain) {
- var currentDomain = this.domain();
- var wideDomain = [Math.min(newDomain[0], currentDomain[0]), Math.max(newDomain[1], currentDomain[1])];
- this.domain(wideDomain);
- return this;
- };
-
- /**
- * Expands the QuantitiveScale's domain to cover the data given.
- * Passes an accessor through to the native d3 code.
- *
- * @param data The data to operate on.
- * @param [accessor] The accessor to get values out of the data.
- * @returns {QuantitiveScale} The scale.
- */
- QuantitiveScale.prototype.widenDomainOnData = function (data, accessor) {
- var extent = d3.extent(data, accessor);
- this.widenDomain(extent);
- return this;
+ QuantitiveScale.prototype.domain = function (values) {
+ return _super.prototype.domain.call(this, values);
};
QuantitiveScale.prototype.interpolate = function (factory) {
@@ -717,7 +917,7 @@ var Plottable;
*/
QuantitiveScale.prototype.nice = function (count) {
this._d3Scale.nice(count);
- this.domain(this._d3Scale.domain()); // nice() can change the domain, so update all listeners
+ this._setDomain(this._d3Scale.domain()); // nice() can change the domain, so update all listeners
return this;
};
@@ -756,18 +956,20 @@ var Plottable;
var currentDomain = this.domain();
var extent = currentDomain[1] - currentDomain[0];
var newDomain = [currentDomain[0] - padProportion / 2 * extent, currentDomain[1] + padProportion / 2 * extent];
- this.domain(newDomain);
+ this._setDomain(newDomain);
return this;
};
return QuantitiveScale;
- })(Scale);
+ })(Plottable.Scale);
Plottable.QuantitiveScale = QuantitiveScale;
-
+})(Plottable || (Plottable = {}));
+///
+var Plottable;
+(function (Plottable) {
var LinearScale = (function (_super) {
__extends(LinearScale, _super);
function LinearScale(scale) {
_super.call(this, scale == null ? d3.scale.linear() : scale);
- this.domain([Infinity, -Infinity]);
}
/**
* Creates a copy of the LinearScale with the same domain and range but without any registered listeners.
@@ -778,16 +980,100 @@ var Plottable;
return new LinearScale(this._d3Scale.copy());
};
return LinearScale;
- })(QuantitiveScale);
+ })(Plottable.QuantitiveScale);
Plottable.LinearScale = LinearScale;
+})(Plottable || (Plottable = {}));
+///
+var Plottable;
+(function (Plottable) {
+ var OrdinalScale = (function (_super) {
+ __extends(OrdinalScale, _super);
+ /**
+ * Creates a new OrdinalScale. Domain and Range are set later.
+ *
+ * @constructor
+ */
+ function OrdinalScale() {
+ _super.call(this, d3.scale.ordinal());
+ this._range = [0, 1];
+ this._rangeType = "points";
+ // Padding as a proportion of the spacing between domain values
+ this._innerPadding = 0.3;
+ this._outerPadding = 0.5;
+ }
+ OrdinalScale.prototype._getCombinedExtent = function () {
+ var extents = _super.prototype._getCombinedExtent.call(this);
+ var concatenatedExtents = [];
+ extents.forEach(function (e) {
+ concatenatedExtents = concatenatedExtents.concat(e);
+ });
+ return Plottable.Utils.uniq(concatenatedExtents);
+ };
+
+ OrdinalScale.prototype.domain = function (values) {
+ return _super.prototype.domain.call(this, values);
+ };
+
+ OrdinalScale.prototype._setDomain = function (values) {
+ _super.prototype._setDomain.call(this, values);
+ this.range(this.range()); // update range
+ };
+
+ OrdinalScale.prototype.range = function (values) {
+ if (values == null) {
+ this._autoDomainIfNeeded();
+ return this._range;
+ } else {
+ this._range = values;
+ if (this._rangeType === "points") {
+ this._d3Scale.rangePoints(values, 2 * this._outerPadding); // d3 scale takes total padding
+ } else if (this._rangeType === "bands") {
+ this._d3Scale.rangeBands(values, this._innerPadding, this._outerPadding);
+ }
+ return this;
+ }
+ };
+
+ /**
+ * Returns the width of the range band. Only valid when rangeType is set to "bands".
+ *
+ * @returns {number} The range band width or 0 if rangeType isn't "bands".
+ */
+ OrdinalScale.prototype.rangeBand = function () {
+ this._autoDomainIfNeeded();
+ return this._d3Scale.rangeBand();
+ };
+ OrdinalScale.prototype.rangeType = function (rangeType, outerPadding, innerPadding) {
+ if (rangeType == null) {
+ return this._rangeType;
+ } else {
+ if (!(rangeType === "points" || rangeType === "bands")) {
+ throw new Error("Unsupported range type: " + rangeType);
+ }
+ this._rangeType = rangeType;
+ if (outerPadding != null)
+ this._outerPadding = outerPadding;
+ if (innerPadding != null)
+ this._innerPadding = innerPadding;
+ return this;
+ }
+ };
+ return OrdinalScale;
+ })(Plottable.Scale);
+ Plottable.OrdinalScale = OrdinalScale;
+})(Plottable || (Plottable = {}));
+///
+var Plottable;
+(function (Plottable) {
var ColorScale = (function (_super) {
__extends(ColorScale, _super);
/**
* Creates a ColorScale.
*
* @constructor
- * @param {string} [scaleType] the type of color scale to create (Category10/Category20/Category20b/Category20c)
+ * @param {string} [scaleType] the type of color scale to create
+ * (Category10/Category20/Category20b/Category20c).
*/
function ColorScale(scaleType) {
var scale;
@@ -822,9 +1108,186 @@ var Plottable;
_super.call(this, scale);
}
return ColorScale;
- })(Scale);
+ })(Plottable.Scale);
Plottable.ColorScale = ColorScale;
})(Plottable || (Plottable = {}));
+///
+var Plottable;
+(function (Plottable) {
+ var InterpolatedColorScale = (function (_super) {
+ __extends(InterpolatedColorScale, _super);
+ /**
+ * Creates a InterpolatedColorScale.
+ *
+ * @constructor
+ * @param {string|string[]} [scaleType] the type of color scale to create
+ * (reds/blues/posneg). Default is "reds". An array of color values
+ * with at least 2 values may also be passed (e.g. ["#FF00FF", "red",
+ * "dodgerblue"], in which case the resulting scale will interpolate
+ * linearly between the color values across the domain.
+ */
+ function InterpolatedColorScale(scaleType) {
+ var scale;
+ if (scaleType instanceof Array) {
+ scale = InterpolatedColorScale.INTERPOLATE_COLORS(scaleType);
+ } else {
+ switch (scaleType) {
+ case "blues":
+ scale = InterpolatedColorScale.INTERPOLATE_COLORS(InterpolatedColorScale.COLOR_SCALES["blues"]);
+ break;
+ case "posneg":
+ scale = InterpolatedColorScale.INTERPOLATE_COLORS(InterpolatedColorScale.COLOR_SCALES["posneg"]);
+ break;
+ case "reds":
+ default:
+ scale = InterpolatedColorScale.INTERPOLATE_COLORS(InterpolatedColorScale.COLOR_SCALES["reds"]);
+ break;
+ }
+ }
+ _super.call(this, scale);
+ }
+ /**
+ * Converts the string array into a linear d3 scale.
+ *
+ * d3 doesn't accept more than 2 range values unless we use a ordinal
+ * scale. So, in order to interpolate smoothly between the full color
+ * range, we must override the interpolator and compute the color values
+ * manually.
+ *
+ * @param {string[]} [colors] an array of strings representing color
+ * values in hex ("#FFFFFF") or keywords ("white").
+ * @returns a linear d3 scale.
+ */
+ InterpolatedColorScale.INTERPOLATE_COLORS = function (colors) {
+ if (colors.length < 2)
+ throw new Error("Color scale arrays must have at least two elements.");
+ return d3.scale.linear().range([0, 1]).interpolate(function (ignored) {
+ return function (t) {
+ // Clamp t parameter to [0,1]
+ t = Math.max(0, Math.min(1, t));
+
+ // Determine indices for colors
+ var tScaled = t * (colors.length - 1);
+ var i0 = Math.floor(tScaled);
+ var i1 = Math.ceil(tScaled);
+ var frac = (tScaled - i0);
+
+ // Interpolate in the L*a*b color space
+ return d3.interpolateLab(colors[i0], colors[i1])(frac);
+ };
+ });
+ };
+ InterpolatedColorScale.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"
+ ]
+ };
+ return InterpolatedColorScale;
+ })(Plottable.LinearScale);
+ Plottable.InterpolatedColorScale = InterpolatedColorScale;
+})(Plottable || (Plottable = {}));
+///
+var Plottable;
+(function (Plottable) {
+ var DataSource = (function (_super) {
+ __extends(DataSource, _super);
+ /**
+ * Creates a new DataSource.
+ *
+ * @constructor
+ * @param {any[]} data
+ * @param {any} metadata An object containing additional information.
+ */
+ function DataSource(data, metadata) {
+ if (typeof data === "undefined") { data = []; }
+ if (typeof metadata === "undefined") { metadata = {}; }
+ _super.call(this);
+ this._data = data;
+ this._metadata = metadata;
+ this.accessor2cachedExtent = new Plottable.Utils.StrictEqualityAssociativeArray();
+ }
+ DataSource.prototype.data = function (data) {
+ if (data == null) {
+ return this._data;
+ } else {
+ this._data = data;
+ this.accessor2cachedExtent = new Plottable.Utils.StrictEqualityAssociativeArray();
+ this._broadcast();
+ return this;
+ }
+ };
+
+ DataSource.prototype.metadata = function (metadata) {
+ if (metadata == null) {
+ return this._metadata;
+ } else {
+ this._metadata = metadata;
+ this.accessor2cachedExtent = new Plottable.Utils.StrictEqualityAssociativeArray();
+ this._broadcast();
+ return this;
+ }
+ };
+
+ DataSource.prototype._getExtent = function (accessor) {
+ var cachedExtent = this.accessor2cachedExtent.get(accessor);
+ if (cachedExtent === undefined) {
+ cachedExtent = this.computeExtent(accessor);
+ this.accessor2cachedExtent.set(accessor, cachedExtent);
+ }
+ return cachedExtent;
+ };
+
+ DataSource.prototype.computeExtent = function (accessor) {
+ var appliedAccessor = Plottable.Utils.applyAccessor(accessor, this);
+ var mappedData = this._data.map(appliedAccessor);
+ if (typeof (appliedAccessor(this._data[0], 0)) === "string") {
+ return Plottable.Utils.uniq(mappedData);
+ } else {
+ return d3.extent(mappedData);
+ }
+ };
+ return DataSource;
+ })(Plottable.Broadcaster);
+ Plottable.DataSource = DataSource;
+})(Plottable || (Plottable = {}));
///
var Plottable;
(function (Plottable) {
@@ -1051,73 +1514,6 @@ var Plottable;
})(Interaction);
Plottable.AreaInteraction = AreaInteraction;
- var ZoomCallbackGenerator = (function () {
- function ZoomCallbackGenerator() {
- this.xScaleMappings = [];
- this.yScaleMappings = [];
- }
- /**
- * Adds listen-update pair of X scales.
- *
- * @param {QuantitiveScale} listenerScale An X scale to listen for events on.
- * @param {QuantitiveScale} [targetScale] An X scale to update when events occur.
- * If not supplied, listenerScale will be updated when an event occurs.
- * @returns {ZoomCallbackGenerator} The calling ZoomCallbackGenerator.
- */
- ZoomCallbackGenerator.prototype.addXScale = function (listenerScale, targetScale) {
- if (targetScale == null) {
- targetScale = listenerScale;
- }
- this.xScaleMappings.push([listenerScale, targetScale]);
- return this;
- };
-
- /**
- * Adds listen-update pair of Y scales.
- *
- * @param {QuantitiveScale} listenerScale A Y scale to listen for events on.
- * @param {QuantitiveScale} [targetScale] A Y scale to update when events occur.
- * If not supplied, listenerScale will be updated when an event occurs.
- * @returns {ZoomCallbackGenerator} The calling ZoomCallbackGenerator.
- */
- ZoomCallbackGenerator.prototype.addYScale = function (listenerScale, targetScale) {
- if (targetScale == null) {
- targetScale = listenerScale;
- }
- this.yScaleMappings.push([listenerScale, targetScale]);
- return this;
- };
-
- ZoomCallbackGenerator.prototype.updateScale = function (referenceScale, targetScale, pixelMin, pixelMax) {
- var originalDomain = referenceScale.domain();
- var newDomain = [referenceScale.invert(pixelMin), referenceScale.invert(pixelMax)];
- var sameDirection = (newDomain[0] < newDomain[1]) === (originalDomain[0] < originalDomain[1]);
- if (!sameDirection) {
- newDomain.reverse();
- }
- targetScale.domain(newDomain);
- };
-
- /**
- * Generates a callback that can be passed to Interactions.
- *
- * @returns {(area: SelectionArea) => void} A callback that updates the scales previously specified.
- */
- ZoomCallbackGenerator.prototype.getCallback = function () {
- var _this = this;
- return function (area) {
- _this.xScaleMappings.forEach(function (sm) {
- _this.updateScale(sm[0], sm[1], area.xMin, area.xMax);
- });
- _this.yScaleMappings.forEach(function (sm) {
- _this.updateScale(sm[0], sm[1], area.yMin, area.yMax);
- });
- };
- };
- return ZoomCallbackGenerator;
- })();
- Plottable.ZoomCallbackGenerator = ZoomCallbackGenerator;
-
var MousemoveInteraction = (function (_super) {
__extends(MousemoveInteraction, _super);
function MousemoveInteraction(componentToListenTo) {
@@ -1219,60 +1615,6 @@ var Plottable;
return KeyInteraction;
})(Interaction);
Plottable.KeyInteraction = KeyInteraction;
-
- var CrosshairsInteraction = (function (_super) {
- __extends(CrosshairsInteraction, _super);
- function CrosshairsInteraction(renderer) {
- var _this = this;
- _super.call(this, renderer);
- this.renderer = renderer;
- renderer.xScale.registerListener(function () {
- return _this.rescale();
- });
- renderer.yScale.registerListener(function () {
- return _this.rescale();
- });
- }
- CrosshairsInteraction.prototype._anchor = function (hitBox) {
- _super.prototype._anchor.call(this, hitBox);
- var container = this.renderer.foregroundContainer.append("g").classed("crosshairs", true);
- this.circle = container.append("circle").classed("centerpoint", true);
- this.xLine = container.append("path").classed("x-line", true);
- this.yLine = container.append("path").classed("y-line", true);
- this.circle.attr("r", 5);
- };
-
- CrosshairsInteraction.prototype.mousemove = function (x, y) {
- this.lastx = x;
- this.lasty = y;
- var domainX = this.renderer.xScale.invert(x);
- var data = this.renderer._data;
- var xA = this.renderer._getAppliedAccessor(this.renderer._xAccessor);
- var yA = this.renderer._getAppliedAccessor(this.renderer._yAccessor);
- var dataIndex = Plottable.OSUtils.sortedIndex(domainX, data, xA);
- dataIndex = dataIndex > 0 ? dataIndex - 1 : 0;
- var dataPoint = data[dataIndex];
-
- var dataX = xA(dataPoint, dataIndex);
- var dataY = yA(dataPoint, dataIndex);
- var pixelX = this.renderer.xScale.scale(dataX);
- var pixelY = this.renderer.yScale.scale(dataY);
- this.circle.attr("cx", pixelX).attr("cy", pixelY);
-
- var width = this.renderer.availableWidth;
- var height = this.renderer.availableHeight;
- this.xLine.attr("d", "M 0 " + pixelY + " L " + width + " " + pixelY);
- this.yLine.attr("d", "M " + pixelX + " 0 L " + pixelX + " " + height);
- };
-
- CrosshairsInteraction.prototype.rescale = function () {
- if (this.lastx != null) {
- this.mousemove(this.lastx, this.lasty);
- }
- };
- return CrosshairsInteraction;
- })(MousemoveInteraction);
- Plottable.CrosshairsInteraction = CrosshairsInteraction;
})(Plottable || (Plottable = {}));
///
var Plottable;
@@ -1401,24 +1743,17 @@ var Plottable;
})(Label);
Plottable.AxisLabel = AxisLabel;
})(Plottable || (Plottable = {}));
-///
+///
var Plottable;
(function (Plottable) {
var Renderer = (function (_super) {
__extends(Renderer, _super);
- // A perf-efficient approach to rendering scale changes would be to transform
- // the container rather than re-render. In the event that the data is changed,
- // it will be necessary to do a regular rerender.
- /**
- * Creates a Renderer.
- *
- * @constructor
- * @param {IDataset} [dataset] The dataset associated with the Renderer.
- */
function Renderer(dataset) {
+ var _this = this;
_super.call(this);
this._animate = false;
this._hasRendered = false;
+ this._projectors = {};
this._rerenderUpdateSelection = false;
// A perf-efficient manner of rendering would be to calculate attributes only
// on new nodes, and assume that old nodes (ie the update selection) can
@@ -1430,58 +1765,81 @@ var Plottable;
this._fixedWidth = false;
this._fixedHeight = false;
this.classed("renderer", true);
+
if (dataset != null) {
- if (dataset.data == null) {
- this.data(dataset);
+ if (typeof dataset.data === "function") {
+ this._dataSource = dataset;
} else {
- this.data(dataset.data);
- if (dataset.metadata != null) {
- this.metadata(dataset.metadata);
- }
+ this._dataSource = new Plottable.DataSource(dataset);
}
+ this._dataSource.registerListener(this, function () {
+ return _this._render();
+ });
+ } else {
+ this._dataSource = new Plottable.DataSource();
}
- this.colorAccessor(Renderer.defaultColorAccessor);
}
- /**
- * Sets a new dataset on the Renderer.
- *
- * @param {IDataset} dataset The new dataset to be associated with the Renderer.
- * @returns {Renderer} The calling Renderer.
- */
- Renderer.prototype.dataset = function (dataset) {
- this.data(dataset.data);
- this.metadata(dataset.metadata);
- return this;
+ Renderer.prototype.dataSource = function (source) {
+ var _this = this;
+ if (source == null) {
+ return this._dataSource;
+ } else if (this._dataSource == null) {
+ this._dataSource = source;
+ this._dataSource.registerListener(this, function () {
+ return _this._render();
+ });
+ return this;
+ } else {
+ throw new Error("Can't set a new DataSource on the Renderer if it already has one.");
+ }
};
- Renderer.prototype.metadata = function (metadata) {
- var oldCSSClass = this._metadata != null ? this._metadata.cssClass : null;
- this.classed(oldCSSClass, false);
- this._metadata = metadata;
- this.classed(this._metadata.cssClass, true);
- this._rerenderUpdateSelection = true;
+ Renderer.prototype.project = function (attrToSet, accessor, scale) {
+ var _this = this;
+ var rendererIDAttr = this._plottableID + attrToSet;
+ var currentProjection = this._projectors[attrToSet];
+ var existingScale = (currentProjection != null) ? currentProjection.scale : null;
+ if (scale == null) {
+ scale = existingScale;
+ }
+ if (existingScale != null) {
+ existingScale._removePerspective(rendererIDAttr);
+ existingScale.deregisterListener(this);
+ }
+ if (scale != null) {
+ scale._addPerspective(rendererIDAttr, this.dataSource(), accessor);
+ scale.registerListener(this, function () {
+ return _this._render();
+ });
+ }
+ this._projectors[attrToSet] = { accessor: accessor, scale: scale };
this._requireRerender = true;
+ this._rerenderUpdateSelection = true;
return this;
};
- Renderer.prototype.data = function (data) {
- this._data = data;
- this._requireRerender = true;
- return this;
+ Renderer.prototype._generateAttrToProjector = function () {
+ var _this = this;
+ var h = {};
+ d3.keys(this._projectors).forEach(function (a) {
+ var projector = _this._projectors[a];
+ var accessor = Plottable.Utils.applyAccessor(projector.accessor, _this.dataSource());
+ var scale = projector.scale;
+ var fn = scale == null ? accessor : function (d, i) {
+ return scale.scale(accessor(d, i));
+ };
+ h[a] = fn;
+ });
+ return h;
};
Renderer.prototype._render = function () {
- this._hasRendered = true;
- this._paint();
- this._requireRerender = false;
- this._rerenderUpdateSelection = false;
- return this;
- };
-
- Renderer.prototype.colorAccessor = function (a) {
- this._colorAccessor = a;
- this._requireRerender = true;
- this._rerenderUpdateSelection = true;
+ if (this.element != null) {
+ this._hasRendered = true;
+ this._paint();
+ this._requireRerender = false;
+ this._rerenderUpdateSelection = false;
+ }
return this;
};
@@ -1489,33 +1847,11 @@ var Plottable;
// no-op
};
- Renderer.prototype.autorange = function () {
- // no-op
- return this;
- };
-
Renderer.prototype._anchor = function (element) {
_super.prototype._anchor.call(this, element);
this.renderArea = this.content.append("g").classed("render-area", true);
return this;
};
-
- Renderer.prototype._getAppliedAccessor = function (accessor) {
- var _this = this;
- if (typeof (accessor) === "function") {
- return function (d, i) {
- return accessor(d, i, _this._metadata);
- };
- } else if (typeof (accessor) === "string") {
- return function (d, i) {
- return d[accessor];
- };
- } else {
- return function (d, i) {
- return accessor;
- };
- }
- };
Renderer.defaultColorAccessor = function (d) {
return "#1f77b4";
};
@@ -1523,7 +1859,7 @@ var Plottable;
})(Plottable.Component);
Plottable.Renderer = Renderer;
})(Plottable || (Plottable = {}));
-///
+///
var Plottable;
(function (Plottable) {
var XYRenderer = (function (_super) {
@@ -1532,42 +1868,38 @@ var Plottable;
* Creates an XYRenderer.
*
* @constructor
- * @param {IDataset} dataset The dataset to render.
+ * @param {any[]|DataSource} [dataset] The data or DataSource to be associated with this Renderer.
* @param {Scale} xScale The x scale to use.
* @param {Scale} yScale The y scale to use.
* @param {IAccessor} [xAccessor] A function for extracting x values from the data.
* @param {IAccessor} [yAccessor] A function for extracting y values from the data.
*/
function XYRenderer(dataset, xScale, yScale, xAccessor, yAccessor) {
- var _this = this;
+ if (typeof xAccessor === "undefined") { xAccessor = "x"; }
+ if (typeof yAccessor === "undefined") { yAccessor = "y"; }
_super.call(this, dataset);
- this.autorangeDataOnLayout = true;
this.classed("xy-renderer", true);
- this._xAccessor = (xAccessor != null) ? xAccessor : "x"; // default
- this._yAccessor = (yAccessor != null) ? yAccessor : "y"; // default
-
- this.xScale = xScale;
- this.yScale = yScale;
-
- this.xScale.registerListener(function () {
- return _this.rescale();
- });
- this.yScale.registerListener(function () {
- return _this.rescale();
- });
+ this.project("x", xAccessor, xScale);
+ this.project("y", yAccessor, yScale);
}
- XYRenderer.prototype.xAccessor = function (accessor) {
- this._xAccessor = accessor;
- this._requireRerender = true;
- this._rerenderUpdateSelection = true;
- return this;
- };
+ XYRenderer.prototype.project = function (attrToSet, accessor, scale) {
+ _super.prototype.project.call(this, attrToSet, accessor, scale);
- XYRenderer.prototype.yAccessor = function (accessor) {
- this._yAccessor = accessor;
- this._requireRerender = true;
- this._rerenderUpdateSelection = true;
+ // 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") {
+ this._xAccessor = this._projectors["x"].accessor;
+ this.xScale = this._projectors["x"].scale;
+ this.xScale._autoNice = true;
+ this.xScale._autoPad = true;
+ }
+ if (attrToSet === "y") {
+ this._yAccessor = this._projectors["y"].accessor;
+ this.yScale = this._projectors["y"].scale;
+ this.yScale._autoNice = true;
+ this.yScale._autoPad = true;
+ }
return this;
};
@@ -1576,119 +1908,19 @@ var Plottable;
_super.prototype._computeLayout.call(this, xOffset, yOffset, availableWidth, availableHeight);
this.xScale.range([0, this.availableWidth]);
this.yScale.range([this.availableHeight, 0]);
- if (this.autorangeDataOnLayout) {
- this.autorange();
- }
- return this;
- };
-
- /**
- * Autoranges the scales over the data.
- * Actual behavior is dependent on the scales.
- */
- XYRenderer.prototype.autorange = function () {
- var _this = this;
- _super.prototype.autorange.call(this);
- var data = this._data;
- var xA = function (d) {
- return _this._getAppliedAccessor(_this._xAccessor)(d, null);
- };
- this.xScale.widenDomainOnData(data, xA);
-
- var yA = function (d) {
- return _this._getAppliedAccessor(_this._yAccessor)(d, null);
- };
- this.yScale.widenDomainOnData(data, yA);
return this;
};
-
- XYRenderer.prototype.rescale = function () {
- if (this.element != null && this._hasRendered) {
- this._render();
- }
- };
- return XYRenderer;
- })(Plottable.Renderer);
- Plottable.XYRenderer = XYRenderer;
-})(Plottable || (Plottable = {}));
-///
-var Plottable;
-(function (Plottable) {
- var NumericXYRenderer = (function (_super) {
- __extends(NumericXYRenderer, _super);
- /**
- * Creates an NumericXYRenderer.
- *
- * @constructor
- * @param {IDataset} dataset The dataset to render.
- * @param {QuantitiveScale} xScale The x scale to use.
- * @param {QuantitiveScale} yScale The y scale to use.
- * @param {IAccessor} [xAccessor] A function for extracting x values from the data.
- * @param {IAccessor} [yAccessor] A function for extracting y values from the data.
- */
- function NumericXYRenderer(dataset, xScale, yScale, xAccessor, yAccessor) {
- _super.call(this, dataset, xScale, yScale, xAccessor, yAccessor);
- this.autorangeDataOnLayout = true;
- this.classed("numeric-xy-renderer", true);
- }
- /**
- * Converts a SelectionArea with pixel ranges to one with data ranges.
- *
- * @param {SelectionArea} pixelArea The selected area, in pixels.
- * @returns {SelectionArea} The corresponding selected area in the domains of the scales.
- */
- NumericXYRenderer.prototype.invertXYSelectionArea = function (pixelArea) {
- var xMin = this.xScale.invert(pixelArea.xMin);
- var xMax = this.xScale.invert(pixelArea.xMax);
- var yMin = this.yScale.invert(pixelArea.yMin);
- var yMax = this.yScale.invert(pixelArea.yMax);
- var dataArea = { xMin: xMin, xMax: xMax, yMin: yMin, yMax: yMax };
- return dataArea;
- };
-
- NumericXYRenderer.prototype.getDataFilterFunction = function (dataArea) {
- var xA = this._getAppliedAccessor(this._xAccessor);
- var yA = this._getAppliedAccessor(this._yAccessor);
- var filterFunction = function (d, i) {
- var x = xA(d, i);
- var y = yA(d, i);
- return Plottable.Utils.inRange(x, dataArea.xMin, dataArea.xMax) && Plottable.Utils.inRange(y, dataArea.yMin, dataArea.yMax);
- };
- return filterFunction;
- };
-
- /**
- * Gets the data in a selected area.
- *
- * @param {SelectionArea} dataArea The selected area.
- * @returns {D3.UpdateSelection} The data in the selected area.
- */
- NumericXYRenderer.prototype.getSelectionFromArea = function (dataArea) {
- var filterFunction = this.getDataFilterFunction(dataArea);
- return this.dataSelection.filter(filterFunction);
- };
-
- /**
- * Gets the indices of data in a selected area
- *
- * @param {SelectionArea} dataArea The selected area.
- * @returns {number[]} An array of the indices of datapoints in the selected area.
- */
- NumericXYRenderer.prototype.getDataIndicesFromArea = function (dataArea) {
- var filterFunction = this.getDataFilterFunction(dataArea);
- var results = [];
- this._data.forEach(function (d, i) {
- if (filterFunction(d, i)) {
- results.push(i);
- }
- });
- return results;
+
+ XYRenderer.prototype.rescale = function () {
+ if (this.element != null && this._hasRendered) {
+ this._render();
+ }
};
- return NumericXYRenderer;
- })(Plottable.XYRenderer);
- Plottable.NumericXYRenderer = NumericXYRenderer;
+ return XYRenderer;
+ })(Plottable.Renderer);
+ Plottable.XYRenderer = XYRenderer;
})(Plottable || (Plottable = {}));
-///
+///
var Plottable;
(function (Plottable) {
var CircleRenderer = (function (_super) {
@@ -1698,46 +1930,45 @@ var Plottable;
*
* @constructor
* @param {IDataset} dataset The dataset to render.
- * @param {QuantitiveScale} xScale The x scale to use.
- * @param {QuantitiveScale} yScale The y scale to use.
+ * @param {Scale} xScale The x scale to use.
+ * @param {Scale} yScale The y scale to use.
* @param {IAccessor} [xAccessor] A function for extracting x values from the data.
* @param {IAccessor} [yAccessor] A function for extracting y values from the data.
* @param {IAccessor} [rAccessor] A function for extracting radius values from the data.
*/
function CircleRenderer(dataset, xScale, yScale, xAccessor, yAccessor, rAccessor) {
_super.call(this, dataset, xScale, yScale, xAccessor, yAccessor);
- this._rAccessor = (rAccessor != null) ? rAccessor : CircleRenderer.defaultRAccessor;
+
+ /* this._rAccessor = (rAccessor != null) ? rAccessor : CircleRenderer.defaultRAccessor;*/
this.classed("circle-renderer", true);
+ this.project("r", 3);
+ this.project("fill", "#00ffaa");
}
- CircleRenderer.prototype.rAccessor = function (a) {
- this._rAccessor = a;
- this._requireRerender = true;
- this._rerenderUpdateSelection = true;
+ CircleRenderer.prototype.project = function (attrToSet, accessor, scale) {
+ attrToSet = attrToSet === "cx" ? "x" : attrToSet;
+ attrToSet = attrToSet === "cy" ? "y" : attrToSet;
+ _super.prototype.project.call(this, attrToSet, accessor, scale);
return this;
};
CircleRenderer.prototype._paint = function () {
- var _this = this;
_super.prototype._paint.call(this);
- var cx = function (d, i) {
- return _this.xScale.scale(_this._getAppliedAccessor(_this._xAccessor)(d, i));
- };
- var cy = function (d, i) {
- return _this.yScale.scale(_this._getAppliedAccessor(_this._yAccessor)(d, i));
- };
- var r = this._getAppliedAccessor(this._rAccessor);
- var color = this._getAppliedAccessor(this._colorAccessor);
- this.dataSelection = this.renderArea.selectAll("circle").data(this._data);
+ var attrToProjector = this._generateAttrToProjector();
+ attrToProjector["cx"] = attrToProjector["x"];
+ attrToProjector["cy"] = attrToProjector["y"];
+ delete attrToProjector["x"];
+ delete attrToProjector["y"];
+
+ this.dataSelection = this.renderArea.selectAll("circle").data(this._dataSource.data());
this.dataSelection.enter().append("circle");
- this.dataSelection.attr("cx", cx).attr("cy", cy).attr("r", r).attr("fill", color);
+ this.dataSelection.attr(attrToProjector);
this.dataSelection.exit().remove();
};
- CircleRenderer.defaultRAccessor = 3;
return CircleRenderer;
- })(Plottable.NumericXYRenderer);
+ })(Plottable.XYRenderer);
Plottable.CircleRenderer = CircleRenderer;
})(Plottable || (Plottable = {}));
-///
+///
var Plottable;
(function (Plottable) {
var LineRenderer = (function (_super) {
@@ -1747,8 +1978,8 @@ var Plottable;
*
* @constructor
* @param {IDataset} dataset The dataset to render.
- * @param {QuantitiveScale} xScale The x scale to use.
- * @param {QuantitiveScale} yScale The y scale to use.
+ * @param {Scale} xScale The x scale to use.
+ * @param {Scale} yScale The y scale to use.
* @param {IAccessor} [xAccessor] A function for extracting x values from the data.
* @param {IAccessor} [yAccessor] A function for extracting y values from the data.
*/
@@ -1763,24 +1994,19 @@ var Plottable;
};
LineRenderer.prototype._paint = function () {
- var _this = this;
_super.prototype._paint.call(this);
- var xA = this._getAppliedAccessor(this._xAccessor);
- var yA = this._getAppliedAccessor(this._yAccessor);
- var cA = this._getAppliedAccessor(this._colorAccessor);
- this.line = d3.svg.line().x(function (d, i) {
- return _this.xScale.scale(xA(d, i));
- }).y(function (d, i) {
- return _this.yScale.scale(yA(d, i));
- });
- this.dataSelection = this.path.datum(this._data);
- this.path.attr("d", this.line).attr("stroke", cA);
+ var attrToProjector = this._generateAttrToProjector();
+ this.line = d3.svg.line().x(attrToProjector["x"]).y(attrToProjector["y"]);
+ this.dataSelection = this.path.datum(this._dataSource.data());
+ delete attrToProjector["x"];
+ delete attrToProjector["y"];
+ this.path.attr("d", this.line).attr(attrToProjector);
};
return LineRenderer;
- })(Plottable.NumericXYRenderer);
+ })(Plottable.XYRenderer);
Plottable.LineRenderer = LineRenderer;
})(Plottable || (Plottable = {}));
-///
+///
var Plottable;
(function (Plottable) {
var BarRenderer = (function (_super) {
@@ -1790,8 +2016,8 @@ var Plottable;
*
* @constructor
* @param {IDataset} dataset The dataset to render.
- * @param {QuantitiveScale} xScale The x scale to use.
- * @param {QuantitiveScale} yScale The y scale to use.
+ * @param {Scale} xScale The x scale to use.
+ * @param {Scale} yScale The y scale to use.
* @param {IAccessor} [xAccessor] A function for extracting the start position of each bar from the data.
* @param {IAccessor} [dxAccessor] A function for extracting the width of each bar from the data.
* @param {IAccessor} [yAccessor] A function for extracting height of each bar from the data.
@@ -1800,67 +2026,46 @@ var Plottable;
_super.call(this, dataset, xScale, yScale, xAccessor, yAccessor);
this.barPaddingPx = 1;
this.classed("bar-renderer", true);
-
- this.dxAccessor = (dxAccessor != null) ? dxAccessor : BarRenderer.defaultDxAccessor;
+ this.project("dx", "dx");
}
- BarRenderer.prototype.autorange = function () {
- _super.prototype.autorange.call(this);
- var xA = this._getAppliedAccessor(this._xAccessor);
- var dxA = this._getAppliedAccessor(this.dxAccessor);
- var x2Accessor = function (d) {
- return xA(d, null) + dxA(d, null);
- };
- var x2Extent = d3.extent(this._data, x2Accessor);
- this.xScale.widenDomain(x2Extent);
- return this;
- };
-
BarRenderer.prototype._paint = function () {
var _this = this;
_super.prototype._paint.call(this);
var yRange = this.yScale.range();
var maxScaledY = Math.max(yRange[0], yRange[1]);
- this.dataSelection = this.renderArea.selectAll("rect").data(this._data);
+ this.dataSelection = this.renderArea.selectAll("rect").data(this._dataSource.data());
var xdr = this.xScale.domain()[1] - this.xScale.domain()[0];
var xrr = this.xScale.range()[1] - this.xScale.range()[0];
this.dataSelection.enter().append("rect");
- var xA = this._getAppliedAccessor(this._xAccessor);
- var xFunction = function (d, i) {
- var x = xA(d, i);
- var scaledX = _this.xScale.scale(x);
- return scaledX + _this.barPaddingPx;
- };
+ var attrToProjector = this._generateAttrToProjector();
- var yA = this._getAppliedAccessor(this._yAccessor);
- var yFunction = function (d, i) {
- var y = yA(d, i);
- var scaledY = _this.yScale.scale(y);
- return scaledY;
+ var xF = attrToProjector["x"];
+ attrToProjector["x"] = function (d, i) {
+ return xF(d, i) + _this.barPaddingPx;
};
- var dxA = this._getAppliedAccessor(this.dxAccessor);
- var widthFunction = function (d, i) {
+ var dxA = Plottable.Utils.applyAccessor(this._projectors["dx"].accessor, this.dataSource());
+ attrToProjector["width"] = function (d, i) {
var dx = dxA(d, i);
var scaledDx = _this.xScale.scale(dx);
var scaledOffset = _this.xScale.scale(0);
return scaledDx - scaledOffset - 2 * _this.barPaddingPx;
};
- var heightFunction = function (d, i) {
- return maxScaledY - yFunction(d, i);
+ attrToProjector["height"] = function (d, i) {
+ return maxScaledY - attrToProjector["y"](d, i);
};
- this.dataSelection.attr("x", xFunction).attr("y", yFunction).attr("width", widthFunction).attr("height", heightFunction).attr("fill", this._getAppliedAccessor(this._colorAccessor));
+ this.dataSelection.attr(attrToProjector);
this.dataSelection.exit().remove();
};
- BarRenderer.defaultDxAccessor = "dx";
return BarRenderer;
- })(Plottable.NumericXYRenderer);
+ })(Plottable.XYRenderer);
Plottable.BarRenderer = BarRenderer;
})(Plottable || (Plottable = {}));
-///
+///
var Plottable;
(function (Plottable) {
var SquareRenderer = (function (_super) {
@@ -1870,49 +2075,199 @@ var Plottable;
*
* @constructor
* @param {IDataset} dataset The dataset to render.
- * @param {QuantitiveScale} xScale The x scale to use.
- * @param {QuantitiveScale} yScale The y scale to use.
+ * @param {Scale} xScale The x scale to use.
+ * @param {Scale} yScale The y scale to use.
* @param {IAccessor} [xAccessor] A function for extracting x values from the data.
* @param {IAccessor} [yAccessor] A function for extracting y values from the data.
* @param {IAccessor} [rAccessor] A function for extracting radius values from the data.
*/
function SquareRenderer(dataset, xScale, yScale, xAccessor, yAccessor, rAccessor) {
_super.call(this, dataset, xScale, yScale, xAccessor, yAccessor);
- this._rAccessor = (rAccessor != null) ? rAccessor : SquareRenderer.defaultRAccessor;
+ this.project("r", 3);
this.classed("square-renderer", true);
}
- SquareRenderer.prototype.rAccessor = function (a) {
- this._rAccessor = a;
- this._requireRerender = true;
- this._rerenderUpdateSelection = true;
- return this;
- };
-
SquareRenderer.prototype._paint = function () {
- var _this = this;
_super.prototype._paint.call(this);
- var xA = this._getAppliedAccessor(this._xAccessor);
- var yA = this._getAppliedAccessor(this._yAccessor);
- var rA = this._getAppliedAccessor(this._rAccessor);
- var cA = this._getAppliedAccessor(this._colorAccessor);
- var xFn = function (d, i) {
- return _this.xScale.scale(xA(d, i)) - rA(d, i);
+ var attrToProjector = this._generateAttrToProjector();
+ var xF = attrToProjector["x"];
+ var yF = attrToProjector["y"];
+ var rF = attrToProjector["r"];
+ attrToProjector["x"] = function (d, i) {
+ return xF(d, i) - rF(d, i);
};
-
- var yFn = function (d, i) {
- return _this.yScale.scale(yA(d, i)) - rA(d, i);
+ attrToProjector["y"] = function (d, i) {
+ return yF(d, i) - rF(d, i);
};
- this.dataSelection = this.renderArea.selectAll("rect").data(this._data);
+ this.dataSelection = this.renderArea.selectAll("rect").data(this._dataSource.data());
this.dataSelection.enter().append("rect");
- this.dataSelection.attr("x", xFn).attr("y", yFn).attr("width", rA).attr("height", rA).attr("fill", cA);
+ this.dataSelection.attr(attrToProjector);
this.dataSelection.exit().remove();
};
SquareRenderer.defaultRAccessor = 3;
return SquareRenderer;
- })(Plottable.NumericXYRenderer);
+ })(Plottable.XYRenderer);
Plottable.SquareRenderer = SquareRenderer;
})(Plottable || (Plottable = {}));
+///
+var Plottable;
+(function (Plottable) {
+ var CategoryBarRenderer = (function (_super) {
+ __extends(CategoryBarRenderer, _super);
+ /**
+ * Creates a CategoryBarRenderer.
+ *
+ * @constructor
+ * @param {IDataset} dataset The dataset to render.
+ * @param {OrdinalScale} xScale The x scale to use.
+ * @param {Scale} yScale The y scale to use.
+ * @param {IAccessor} [xAccessor] A function for extracting the start position of each bar from the data.
+ * @param {IAccessor} [widthAccessor] A function for extracting the width position of each bar, in pixels, from the data.
+ * @param {IAccessor} [yAccessor] A function for extracting height of each bar from the data.
+ */
+ function CategoryBarRenderer(dataset, xScale, yScale, xAccessor, widthAccessor, yAccessor) {
+ _super.call(this, dataset, xScale, yScale, xAccessor, yAccessor);
+ this.classed("bar-renderer", true);
+ this._animate = true;
+ this.project("width", 10);
+ }
+ CategoryBarRenderer.prototype._paint = function () {
+ var _this = this;
+ _super.prototype._paint.call(this);
+ var yRange = this.yScale.range();
+ var maxScaledY = Math.max(yRange[0], yRange[1]);
+ var xA = Plottable.Utils.applyAccessor(this._xAccessor, this.dataSource());
+
+ this.dataSelection = this.renderArea.selectAll("rect").data(this._dataSource.data(), xA);
+ this.dataSelection.enter().append("rect");
+
+ var attrToProjector = this._generateAttrToProjector();
+
+ var rangeType = this.xScale.rangeType();
+ if (rangeType === "points") {
+ var xF = attrToProjector["x"];
+ var widthF = attrToProjector["width"];
+ attrToProjector["x"] = function (d, i) {
+ return xF(d, i) - widthF(d, i) / 2;
+ };
+ } else {
+ attrToProjector["width"] = function (d, i) {
+ return _this.xScale.rangeBand();
+ };
+ }
+
+ var heightFunction = function (d, i) {
+ return maxScaledY - attrToProjector["y"](d, i);
+ };
+ attrToProjector["height"] = heightFunction;
+
+ var updateSelection = this.dataSelection;
+ if (this._animate) {
+ updateSelection = updateSelection.transition();
+ }
+ updateSelection.attr(attrToProjector);
+ this.dataSelection.exit().remove();
+ };
+
+ /**
+ * Selects the bar under the given pixel position.
+ *
+ * @param {number} x The pixel x position.
+ * @param {number} y The pixel y position.
+ * @param {boolean} [select] Whether or not to select the bar (by classing it "selected");
+ * @return {D3.Selection} The selected bar, or null if no bar was selected.
+ */
+ CategoryBarRenderer.prototype.selectBar = function (x, y, select) {
+ if (typeof select === "undefined") { select = true; }
+ var selectedBar = null;
+
+ this.dataSelection.each(function (d) {
+ var bbox = this.getBBox();
+ if (bbox.x <= x && x <= bbox.x + bbox.width && bbox.y <= y && y <= bbox.y + bbox.height) {
+ selectedBar = d3.select(this);
+ }
+ });
+
+ if (selectedBar != null) {
+ selectedBar.classed("selected", select);
+ }
+
+ return selectedBar;
+ };
+
+ /**
+ * Deselects all bars.
+ */
+ CategoryBarRenderer.prototype.deselectAll = function () {
+ this.dataSelection.classed("selected", false);
+ };
+ return CategoryBarRenderer;
+ })(Plottable.XYRenderer);
+ Plottable.CategoryBarRenderer = CategoryBarRenderer;
+})(Plottable || (Plottable = {}));
+///
+var Plottable;
+(function (Plottable) {
+ var GridRenderer = (function (_super) {
+ __extends(GridRenderer, _super);
+ /**
+ * Creates a GridRenderer.
+ *
+ * @constructor
+ * @param {IDataset} dataset The dataset to render.
+ * @param {OrdinalScale} xScale The x scale to use.
+ * @param {OrdinalScale} yScale The y scale to use.
+ * @param {ColorScale|InterpolatedColorScale} colorScale The color scale to use for each grid
+ * cell.
+ * @param {IAccessor|string|number} [xAccessor] An accessor for extracting
+ * the x position of each grid cell from the data.
+ * @param {IAccessor|string|number} [yAccessor] An accessor for extracting
+ * the y position of each grid cell from the data.
+ * @param {IAccessor|string|number} [valueAccessor] An accessor for
+ * extracting value of each grid cell from the data. This value will
+ * be pass through the color scale to determine the color of the cell.
+ */
+ function GridRenderer(dataset, xScale, yScale, colorScale, xAccessor, yAccessor, valueAccessor) {
+ _super.call(this, dataset, xScale, yScale, xAccessor, yAccessor);
+ this.classed("grid-renderer", true);
+
+ // The x and y scales should render in bands with no padding
+ this.xScale.rangeType("bands", 0, 0);
+ this.yScale.rangeType("bands", 0, 0);
+
+ this.colorScale = colorScale;
+ this.project("fill", valueAccessor, colorScale);
+ }
+ GridRenderer.prototype._paint = function () {
+ _super.prototype._paint.call(this);
+
+ this.dataSelection = this.renderArea.selectAll("rect").data(this._dataSource.data());
+ this.dataSelection.enter().append("rect");
+
+ var xStep = this.xScale.rangeBand();
+ var yr = this.yScale.range();
+ var yStep = this.yScale.rangeBand();
+ var yMax = Math.max(yr[0], yr[1]) - yStep;
+
+ var attrToProjector = this._generateAttrToProjector();
+ attrToProjector["width"] = function () {
+ return xStep;
+ };
+ attrToProjector["height"] = function () {
+ return yStep;
+ };
+ var yAttr = attrToProjector["y"];
+ attrToProjector["y"] = function (d, i) {
+ return yMax - yAttr(d, i);
+ };
+
+ this.dataSelection.attr(attrToProjector);
+ this.dataSelection.exit().remove();
+ };
+ return GridRenderer;
+ })(Plottable.XYRenderer);
+ Plottable.GridRenderer = GridRenderer;
+})(Plottable || (Plottable = {}));
///
var Plottable;
(function (Plottable) {
@@ -2201,7 +2556,7 @@ var Plottable;
this.rescaleInProgress = false;
this.scales = scales;
this.scales.forEach(function (s) {
- return s.registerListener(function (sx) {
+ return s.registerListener(_this, function (sx) {
return _this.rescale(sx);
});
});
@@ -2411,169 +2766,35 @@ var Plottable;
})(Plottable.Table);
Plottable.StandardChart = StandardChart;
})(Plottable || (Plottable = {}));
-///
-var Plottable;
-(function (Plottable) {
- var CategoryXYRenderer = (function (_super) {
- __extends(CategoryXYRenderer, _super);
- /**
- * Creates a CategoryXYRenderer with an Ordinal x scale and Quantitive y scale.
- *
- * @constructor
- * @param {IDataset} dataset The dataset to render.
- * @param {OrdinalScale} xScale The x scale to use.
- * @param {QuantitiveScale} yScale The y scale to use.
- * @param {IAccessor} [xAccessor] A function for extracting x values from the data.
- * @param {IAccessor} [yAccessor] A function for extracting y values from the data.
- */
- function CategoryXYRenderer(dataset, xScale, yScale, xAccessor, yAccessor) {
- _super.call(this, dataset, xScale, yScale, xAccessor, yAccessor);
- this.classed("category-renderer", true);
- }
- return CategoryXYRenderer;
- })(Plottable.XYRenderer);
- Plottable.CategoryXYRenderer = CategoryXYRenderer;
-})(Plottable || (Plottable = {}));
-///
-var Plottable;
-(function (Plottable) {
- var CategoryBarRenderer = (function (_super) {
- __extends(CategoryBarRenderer, _super);
- /**
- * Creates a CategoryBarRenderer.
- *
- * @constructor
- * @param {IDataset} dataset The dataset to render.
- * @param {OrdinalScale} xScale The x scale to use.
- * @param {QuantitiveScale} yScale The y scale to use.
- * @param {IAccessor} [xAccessor] A function for extracting the start position of each bar from the data.
- * @param {IAccessor} [widthAccessor] A function for extracting the width position of each bar, in pixels, from the data.
- * @param {IAccessor} [yAccessor] A function for extracting height of each bar from the data.
- */
- function CategoryBarRenderer(dataset, xScale, yScale, xAccessor, widthAccessor, yAccessor) {
- _super.call(this, dataset, xScale, yScale, xAccessor, yAccessor);
- this.classed("bar-renderer", true);
- this._animate = true;
- this._widthAccessor = (widthAccessor != null) ? widthAccessor : 10; // default width is 10px
- }
- /**
- * Sets the width accessor.
- *
- * @param {any} accessor The new width accessor.
- * @returns {CategoryBarRenderer} The calling CategoryBarRenderer.
- */
- CategoryBarRenderer.prototype.widthAccessor = function (accessor) {
- this._widthAccessor = accessor;
- this._requireRerender = true;
- this._rerenderUpdateSelection = true;
- return this;
- };
-
- CategoryBarRenderer.prototype._paint = function () {
- var _this = this;
- _super.prototype._paint.call(this);
- var yRange = this.yScale.range();
- var maxScaledY = Math.max(yRange[0], yRange[1]);
- var xA = this._getAppliedAccessor(this._xAccessor);
-
- this.dataSelection = this.renderArea.selectAll("rect").data(this._data, xA);
- this.dataSelection.enter().append("rect");
-
- var widthFunction = this._getAppliedAccessor(this._widthAccessor);
-
- var xFunction = function (d, i) {
- var x = xA(d, i);
- var scaledX = _this.xScale.scale(x);
- return scaledX - widthFunction(d, i) / 2;
- };
-
- var yA = this._getAppliedAccessor(this._yAccessor);
- var yFunction = function (d, i) {
- var y = yA(d, i);
- var scaledY = _this.yScale.scale(y);
- return scaledY;
- };
-
- var heightFunction = function (d, i) {
- return maxScaledY - yFunction(d, i);
- };
-
- var updateSelection = this.dataSelection.attr("fill", this._getAppliedAccessor(this._colorAccessor));
- if (this._animate) {
- updateSelection = updateSelection.transition();
- }
- updateSelection.attr("x", xFunction).attr("y", yFunction).attr("width", widthFunction).attr("height", heightFunction);
- this.dataSelection.exit().remove();
- };
-
- CategoryBarRenderer.prototype.autorange = function () {
- _super.prototype.autorange.call(this);
- var yDomain = this.yScale.domain();
- if (yDomain[1] < 0 || yDomain[0] > 0) {
- var newDomain = [Math.min(0, yDomain[0]), Math.max(0, yDomain[1])];
- this.yScale.domain(newDomain);
- }
- return this;
- };
-
- /**
- * Selects the bar under the given pixel position.
- *
- * @param {number} x The pixel x position.
- * @param {number} y The pixel y position.
- * @param {boolean} [select] Whether or not to select the bar (by classing it "selected");
- * @return {D3.Selection} The selected bar, or null if no bar was selected.
- */
- CategoryBarRenderer.prototype.selectBar = function (x, y, select) {
- if (typeof select === "undefined") { select = true; }
- var selectedBar = null;
-
- this.dataSelection.each(function (d) {
- var bbox = this.getBBox();
- if (bbox.x <= x && x <= bbox.x + bbox.width && bbox.y <= y && y <= bbox.y + bbox.height) {
- selectedBar = d3.select(this);
- }
- });
-
- if (selectedBar != null) {
- selectedBar.classed("selected", select);
- }
-
- return selectedBar;
- };
-
- /**
- * Deselects all bars.
- */
- CategoryBarRenderer.prototype.deselectAll = function () {
- this.dataSelection.classed("selected", false);
- };
- return CategoryBarRenderer;
- })(Plottable.CategoryXYRenderer);
- Plottable.CategoryBarRenderer = CategoryBarRenderer;
-})(Plottable || (Plottable = {}));
///
///
+///
+///
///
-///
+///
+///
+///
+///
+///
+///
+///
//grunt-start
///
///
///
///
-///
-///
-///
-///
-///
-///
-///
+///
+///
+///
+///
+///
+///
+///
+///
///
///
///
///
-///
-///
//grunt-end
///
var Plottable;
@@ -2601,7 +2822,7 @@ var Plottable;
formatter = d3.format(".3s");
}
this.d3Axis.tickFormat(formatter);
- this.axisScale.registerListener(function () {
+ this.axisScale.registerListener(this, function () {
return _this.rescale();
});
}
@@ -3062,12 +3283,12 @@ var Plottable;
this.xScale = xScale;
this.yScale = yScale;
if (this.xScale != null) {
- this.xScale.registerListener(function () {
+ this.xScale.registerListener(this, function () {
return _this.redrawXLines();
});
}
if (this.yScale != null) {
- this.yScale.registerListener(function () {
+ this.yScale.registerListener(this, function () {
return _this.redrawYLines();
});
}
diff --git a/test/tests.js b/test/tests.js
index 0eec9964ec..d2f0213d6a 100644
--- a/test/tests.js
+++ b/test/tests.js
@@ -2,17 +2,19 @@
function generateSVG(width, height) {
if (typeof width === "undefined") { width = 400; }
if (typeof height === "undefined") { height = 400; }
- var parent;
+ var parent = getSVGParent();
+ return parent.append("svg").attr("width", width).attr("height", height);
+}
+
+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]);
- parent = lastSuite.selectAll("ul");
+ return lastSuite.selectAll("ul");
} else {
- parent = d3.select("body");
+ return d3.select("body");
}
- var svg = parent.append("svg").attr("width", width).attr("height", height);
- return svg;
}
function getTranslate(element) {
@@ -53,16 +55,14 @@ function makeLinearSeries(n) {
function makePoint(x) {
return { x: x, y: x };
}
- var data = d3.range(n).map(makePoint);
- return { data: data, metadata: { cssClass: "linear-series" } };
+ return d3.range(n).map(makePoint);
}
function makeQuadraticSeries(n) {
function makeQuadraticPoint(x) {
return { x: x, y: x * x };
}
- var data = d3.range(n).map(makeQuadraticPoint);
- return { data: data, metadata: { cssClass: "quadratic-series" } };
+ return d3.range(n).map(makeQuadraticPoint);
}
var MultiTestVerifier = (function () {
@@ -179,6 +179,68 @@ describe("Axes", function () {
///
var assert = chai.assert;
+describe("Broadcasters", function () {
+ var b;
+ var called;
+ var cb;
+
+ beforeEach(function () {
+ b = new Plottable.Broadcaster();
+ 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 never called");
+ });
+
+ it("arguments are passed through to callback", function () {
+ var g2 = {};
+ var g3 = "foo";
+ var cb = function (a1, rest) {
+ assert.equal(b, a1, "broadcaster passed through");
+ assert.equal(g2, rest[0], "arg1 passed through");
+ assert.equal(g3, rest[1], "arg2 passed through");
+ called = true;
+ };
+ b.registerListener(null, cb);
+ b._broadcast(g2, g3);
+ assert.isTrue(called, "the cb was called");
+ });
+
+ it("deregistering an unregistered listener throws an error", function () {
+ assert.throws(function () {
+ return b.deregisterListener({});
+ });
+ });
+});
+///
+var assert = chai.assert;
+
describe("ComponentGroups", function () {
it("components in componentGroups overlap", function () {
var c1 = new Plottable.Component().rowMinimum(10).colMinimum(10);
@@ -378,6 +440,31 @@ describe("Component behavior", function () {
svg.remove();
});
+ it("computeLayout works with CSS layouts", function () {
+ // Manually size parent
+ var parent = d3.select(svg.node().parentNode);
+ parent.style("width", "100px").style("height", "200px");
+
+ // Remove width/height attributes and style with CSS
+ svg.attr("width", null).attr("height", null);
+ svg.style("width", "50%").style("height", "50%");
+
+ c._anchor(svg)._computeLayout();
+ assert.equal(c.availableWidth, 50, "computeLayout defaulted width to svg width");
+ assert.equal(c.availableHeight, 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");
+
+ svg.style("width", "25%").style("height", "25%");
+ c._computeLayout();
+ assert.equal(c.availableWidth, 25, "computeLayout updated width to new svg width");
+ assert.equal(c.availableHeight, 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");
+
+ svg.remove();
+ });
+
it("computeLayout will not default when attached to non-root node", function () {
var g = svg.append("g");
c._anchor(g);
@@ -486,9 +573,8 @@ describe("Component behavior", function () {
it("clipPath works as expected", function () {
assert.isFalse(c.clipPathEnabled, "clipPathEnabled defaults to false");
c.clipPathEnabled = true;
- var expectedClipPathID = Plottable.Component.clipPathId;
+ var expectedClipPathID = c._plottableID;
c._anchor(svg)._computeLayout(0, 0, 100, 100)._render();
- assert.equal(Plottable.Component.clipPathId, expectedClipPathID + 1, "clipPathId incremented");
var expectedClipPathURL = "url(#clipPath" + expectedClipPathID + ")";
assert.equal(c.element.attr("clip-path"), expectedClipPathURL, "the element has clip-path url attached");
var clipRect = c.boxContainer.select(".clip-rect");
@@ -497,6 +583,15 @@ describe("Component behavior", function () {
svg.remove();
});
+ it("componentID works as expected", function () {
+ var expectedID = Plottable.PlottableObject.nextID;
+ var c1 = new Plottable.Component();
+ assert.equal(c1._plottableID, expectedID, "component id on next component was as expected");
+ var c2 = new Plottable.Component();
+ assert.equal(c2._plottableID, expectedID + 1, "future components increment appropriately");
+ svg.remove();
+ });
+
it("boxes work as expected", function () {
assert.throws(function () {
return c.addBox("pre-anchor");
@@ -621,6 +716,64 @@ describe("Coordinators", function () {
///
var assert = chai.assert;
+describe("DataSource", function () {
+ it("Updates listeners when the data is changed", function () {
+ var ds = new Plottable.DataSource();
+
+ var newData = [1, 2, 3];
+
+ var callbackCalled = false;
+ var callback = function (broadcaster) {
+ assert.equal(broadcaster, ds, "Callback received the DataSource as the first argument");
+ assert.deepEqual(ds.data(), newData, "DataSource arrives with correct data");
+ callbackCalled = true;
+ };
+ ds.registerListener(null, callback);
+
+ ds.data(newData);
+ assert.isTrue(callbackCalled, "callback was called when the data was changed");
+ });
+
+ it("Updates listeners when the metadata is changed", function () {
+ var ds = new Plottable.DataSource();
+
+ var newMetadata = "blargh";
+
+ var callbackCalled = false;
+ var callback = function (broadcaster) {
+ assert.equal(broadcaster, ds, "Callback received the DataSource as the first argument");
+ assert.deepEqual(ds.metadata(), newMetadata, "DataSource arrives with correct metadata");
+ callbackCalled = true;
+ };
+ ds.registerListener(null, callback);
+
+ ds.metadata(newMetadata);
+ assert.isTrue(callbackCalled, "callback was called when the metadata was changed");
+ });
+
+ it("_getExtent works as expected", function () {
+ var data = [1, 2, 3, 4, 1];
+ var metadata = { foo: 11 };
+ var dataSource = new Plottable.DataSource(data, metadata);
+ var a1 = function (d, i, m) {
+ return d + i - 2;
+ };
+ assert.deepEqual(dataSource._getExtent(a1), [-1, 5], "extent for numerical data works properly");
+ var a2 = function (d, i, m) {
+ return d + m.foo;
+ };
+ assert.deepEqual(dataSource._getExtent(a2), [12, 15], "extent uses metadata appropriately");
+ dataSource.metadata({ foo: -1 });
+ assert.deepEqual(dataSource._getExtent(a2), [0, 3], "metadata change is reflected in extent results");
+ var a3 = function (d, i, m) {
+ return "_" + d;
+ };
+ assert.deepEqual(dataSource._getExtent(a3), ["_1", "_2", "_3", "_4"], "extent works properly on string domains (no repeats)");
+ });
+});
+///
+var assert = chai.assert;
+
describe("Gridlines", function () {
it("Gridlines and axis tick marks align", function () {
var svg = generateSVG(640, 480);
@@ -703,8 +856,8 @@ describe("Interactions", 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.LinearScale();
- var yScale = new Plottable.LinearScale();
+ var xScale = new Plottable.LinearScale().domain([0, 11]);
+ var yScale = new Plottable.LinearScale().domain([11, 0]);
var svg = generateSVG();
var dataset = makeLinearSeries(11);
@@ -741,8 +894,8 @@ describe("Interactions", function () {
var expectedXDragChange = -dragDistancePixelX * getSlope(xScale);
var expectedYDragChange = -dragDistancePixelY * getSlope(yScale);
- assert.equal(xDomainAfter[0] - xDomainBefore[0], expectedXDragChange, "x domain changed by the correct amount");
- assert.equal(yDomainAfter[0] - yDomainBefore[0], expectedYDragChange, "y domain changed by the correct amount");
+ 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");
svg.remove();
});
@@ -765,7 +918,7 @@ describe("Interactions", function () {
before(function () {
svg = generateSVG(svgWidth, svgHeight);
- dataset = makeLinearSeries(10);
+ dataset = new Plottable.DataSource(makeLinearSeries(10));
xScale = new Plottable.LinearScale();
yScale = new Plottable.LinearScale();
renderer = new Plottable.CircleRenderer(dataset, xScale, yScale);
@@ -820,99 +973,6 @@ describe("Interactions", function () {
});
});
- describe("BrushZoomInteraction", function () {
- it("Zooms in correctly on drag", function () {
- var xScale = new Plottable.LinearScale();
- var yScale = new Plottable.LinearScale();
-
- var svgWidth = 400;
- var svgHeight = 400;
- var svg = generateSVG(svgWidth, svgHeight);
- var dataset = makeLinearSeries(11);
- var renderer = new Plottable.CircleRenderer(dataset, xScale, yScale);
- renderer.renderTo(svg);
-
- var xDomainBefore = xScale.domain();
- var yDomainBefore = yScale.domain();
-
- var dragstartX = 10;
- var dragstartY = 210;
- var dragendX = 190;
- var dragendY = 390;
-
- var expectedXDomain = [xScale.invert(dragstartX), xScale.invert(dragendX)];
- var expectedYDomain = [yScale.invert(dragendY), yScale.invert(dragstartY)];
-
- var indicesCallbackCalled = false;
- var interaction;
- var indicesCallback = function (indices) {
- indicesCallbackCalled = true;
- interaction.clearBox();
- assert.deepEqual(indices, [1, 2, 3, 4], "the correct points were selected");
- };
- var zoomCallback = new Plottable.ZoomCallbackGenerator().addXScale(xScale).addYScale(yScale).getCallback();
- var callback = function (a) {
- var dataArea = renderer.invertXYSelectionArea(a);
- var indices = renderer.getDataIndicesFromArea(dataArea);
- indicesCallback(indices);
- zoomCallback(a);
- };
- interaction = new Plottable.AreaInteraction(renderer).callback(callback);
- interaction.registerWithComponent();
-
- fakeDragSequence(interaction, dragstartX, dragstartY, dragendX, dragendY);
- assert.isTrue(indicesCallbackCalled, "indicesCallback was called");
- assert.deepEqual(xScale.domain(), expectedXDomain, "X scale domain was updated correctly");
- assert.deepEqual(yScale.domain(), expectedYDomain, "Y scale domain was updated correclty");
-
- svg.remove();
- });
- });
-
- describe("CrosshairsInteraction", function () {
- it("Crosshairs manifest basic functionality", function () {
- var svg = generateSVG(400, 400);
- var dp = function (x, y) {
- return { x: x, y: y };
- };
- var data = [dp(0, 0), dp(20, 10), dp(40, 40)];
- var dataset = { metadata: { cssClass: "foo" }, data: data };
- var xScale = new Plottable.LinearScale();
- var yScale = new Plottable.LinearScale();
- var circleRenderer = new Plottable.CircleRenderer(dataset, xScale, yScale);
- var crosshairs = new Plottable.CrosshairsInteraction(circleRenderer);
- crosshairs.registerWithComponent();
- circleRenderer.renderTo(svg);
-
- var crosshairsG = circleRenderer.foregroundContainer.select(".crosshairs");
- var circle = crosshairsG.select("circle");
- var xLine = crosshairsG.select(".x-line");
- var yLine = crosshairsG.select(".y-line");
-
- crosshairs.mousemove(0, 0);
- assert.equal(circle.attr("cx"), 0, "the crosshairs are at x=0");
- assert.equal(circle.attr("cy"), 400, "the crosshairs are at y=400");
- assert.equal(xLine.attr("d"), "M 0 400 L 400 400", "the xLine behaves properly at y=400");
- assert.equal(yLine.attr("d"), "M 0 0 L 0 400", "the yLine behaves properly at x=0");
-
- crosshairs.mousemove(30, 0);
-
- // It should stay in the same position
- assert.equal(circle.attr("cx"), 0, "the crosshairs are at x=0 still");
- assert.equal(circle.attr("cy"), 400, "the crosshairs are at y=400 still");
- assert.equal(xLine.attr("d"), "M 0 400 L 400 400", "the xLine behaves properly at y=400");
- assert.equal(yLine.attr("d"), "M 0 0 L 0 400", "the yLine behaves properly at x=0");
-
- crosshairs.mousemove(300, 0);
- assert.equal(circle.attr("cx"), 200, "the crosshairs are at x=200");
- assert.equal(circle.attr("cy"), 300, "the crosshairs are at y=300");
- assert.equal(xLine.attr("d"), "M 0 300 L 400 300", "the xLine behaves properly at y=300");
- assert.equal(yLine.attr("d"), "M 200 0 L 200 400", "the yLine behaves properly at x=200");
-
- svg.remove();
- });
- });
-
describe("KeyInteraction", function () {
it("Triggers the callback only when the Component is moused over and appropriate key is pressed", function () {
var svg = generateSVG(400, 400);
@@ -1243,45 +1303,37 @@ describe("Renderers", function () {
assert.isTrue(r.clipPathEnabled, "clipPathEnabled defaults to true");
});
- it("Base renderer functionality works", function () {
+ it("Base Renderer functionality works", function () {
var svg = generateSVG(400, 300);
- var d1 = { data: ["foo"], metadata: { cssClass: "bar" } };
+ var d1 = new Plottable.DataSource(["foo"], { cssClass: "bar" });
var r = new Plottable.Renderer(d1);
r._anchor(svg)._computeLayout();
var renderArea = r.content.select(".render-area");
assert.isNotNull(renderArea.node(), "there is a render-area");
- assert.isTrue(r.element.classed("bar"), "the element is classed w/ metadata.cssClass");
- assert.deepEqual(r._data, d1.data, "the data is set properly");
- assert.deepEqual(r._metadata, d1.metadata, "the metadata is set properly");
- var d2 = { data: ["bar"], metadata: { cssClass: "boo" } };
- r.dataset(d2);
- assert.isFalse(r.element.classed("bar"), "the element is no longer classed bar");
- assert.isTrue(r.element.classed("boo"), "the element is now classed boo");
- assert.deepEqual(r._data, d2.data, "the data is set properly");
- assert.deepEqual(r._metadata, d2.metadata, "the metadata is set properly");
+
+ var d2 = new Plottable.DataSource(["bar"], { cssClass: "boo" });
+ assert.throws(function () {
+ return r.dataSource(d2);
+ }, Error);
+
svg.remove();
});
- it("rerenderUpdateSelection and requireRerender flags updated appropriately", function () {
+ it("Renderer automatically generates a DataSource if only data is provided", function () {
+ var data = ["foo", "bar"];
+ var r = new Plottable.Renderer(data);
+ var dataSource = r.dataSource();
+ assert.isNotNull(dataSource, "A DataSource was automatically generated");
+ assert.deepEqual(dataSource.data(), data, "The generated DataSource has the correct data");
+ });
+
+ it("Renderer.project works as intended", function () {
var r = new Plottable.Renderer();
- var svg = generateSVG();
- r.renderTo(svg);
- assert.isFalse(r._rerenderUpdateSelection, "don't need to rerender update");
- assert.isFalse(r._requireRerender, "dont require rerender");
- var metadata = {};
- r.metadata(metadata);
- assert.isTrue(r._rerenderUpdateSelection, "rerenderingUpdate req after metadata set");
- assert.isTrue(r._requireRerender, "rerender required when metadata set");
-
- r.renderTo(svg);
- assert.isFalse(r._rerenderUpdateSelection, "don't need to rerender update after render");
- assert.isFalse(r._requireRerender, "dont require rerender after render");
-
- var data = [];
- r.data(data);
- assert.isFalse(r._rerenderUpdateSelection, "don't need to rerender update after setting data");
- assert.isTrue(r._requireRerender, "rerender required when data set");
- svg.remove();
+ var s = new Plottable.LinearScale().domain([0, 1]).range([0, 10]);
+ r.project("attr", "a", s);
+ var attrToProjector = r._generateAttrToProjector();
+ var projector = attrToProjector["attr"];
+ assert.equal(projector({ "a": 0.5 }, 0), 5, "projector works as intended");
});
});
@@ -1290,6 +1342,8 @@ describe("Renderers", function () {
var svg = generateSVG(400, 400);
var xScale = new Plottable.LinearScale();
var yScale = new Plottable.LinearScale();
+ 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) {
@@ -1298,11 +1352,8 @@ describe("Renderers", function () {
var yAccessor = function (d, i, m) {
return m.bar;
};
- var dataset = { data: data, metadata: metadata };
- var renderer = new Plottable.CircleRenderer(dataset, xScale, yScale, xAccessor, yAccessor);
- renderer.autorangeDataOnLayout = false;
- xScale.domain([0, 400]);
- yScale.domain([400, 0]);
+ var dataSource = new Plottable.DataSource(data, metadata);
+ var renderer = new Plottable.CircleRenderer(dataSource, xScale, yScale, xAccessor, yAccessor);
renderer.renderTo(svg);
var circles = renderer.renderArea.selectAll("circle");
var c1 = d3.select(circles[0][0]);
@@ -1313,14 +1364,14 @@ describe("Renderers", function () {
assert.closeTo(parseFloat(c2.attr("cy")), 20, 0.01, "second circle cy is correct");
data = [{ x: 2, y: 2 }, { x: 4, y: 4 }];
- renderer.data(data).renderTo(svg);
+ dataSource.data(data);
assert.closeTo(parseFloat(c1.attr("cx")), 2, 0.01, "first circle cx is correct after data change");
assert.closeTo(parseFloat(c1.attr("cy")), 20, 0.01, "first circle cy is correct after data change");
assert.closeTo(parseFloat(c2.attr("cx")), 14, 0.01, "second circle cx is correct after data change");
assert.closeTo(parseFloat(c2.attr("cy")), 20, 0.01, "second circle cy is correct after data change");
metadata = { foo: 0, bar: 0 };
- renderer.metadata(metadata).renderTo(svg);
+ dataSource.metadata(metadata);
assert.closeTo(parseFloat(c1.attr("cx")), 2, 0.01, "first circle cx is correct after metadata change");
assert.closeTo(parseFloat(c1.attr("cy")), 0, 0.01, "first circle cy is correct after metadata change");
assert.closeTo(parseFloat(c2.attr("cx")), 4, 0.01, "second circle cx is correct after metadata change");
@@ -1336,14 +1387,14 @@ describe("Renderers", function () {
var xScale;
var yScale;
var lineRenderer;
- var simpleDataset = { metadata: { cssClass: "simpleDataset" }, data: [{ foo: 0, bar: 0 }, { foo: 1, bar: 1 }] };
+ var simpleDataset = new Plottable.DataSource([{ foo: 0, bar: 0 }, { foo: 1, bar: 1 }]);
var renderArea;
var verifier = new MultiTestVerifier();
before(function () {
svg = generateSVG(500, 500);
- xScale = new Plottable.LinearScale();
- yScale = new Plottable.LinearScale();
+ xScale = new Plottable.LinearScale().domain([0, 1]);
+ yScale = new Plottable.LinearScale().domain([0, 1]);
var xAccessor = function (d) {
return d.foo;
};
@@ -1354,7 +1405,7 @@ describe("Renderers", function () {
return d3.rgb(d.foo, d.bar, i).toString();
};
lineRenderer = new Plottable.LineRenderer(simpleDataset, xScale, yScale, xAccessor, yAccessor);
- lineRenderer.colorAccessor(colorAccessor);
+ lineRenderer.project("stroke", colorAccessor);
lineRenderer.renderTo(svg);
renderArea = lineRenderer.renderArea;
});
@@ -1443,16 +1494,14 @@ describe("Renderers", function () {
before(function () {
svg = generateSVG(SVG_WIDTH, SVG_HEIGHT);
- xScale = new Plottable.LinearScale();
- yScale = new Plottable.LinearScale();
+ xScale = new Plottable.LinearScale().domain([0, 9]);
+ yScale = new Plottable.LinearScale().domain([0, 81]);
circleRenderer = new Plottable.CircleRenderer(quadraticDataset, xScale, yScale);
- circleRenderer.colorAccessor(colorAccessor);
+ circleRenderer.project("fill", colorAccessor);
circleRenderer.renderTo(svg);
});
it("setup is handled properly", function () {
- assert.deepEqual(xScale.domain(), [0, 9], "xScale domain was set by the renderer");
- assert.deepEqual(yScale.domain(), [0, 81], "yScale domain was set by the renderer");
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");
circleRenderer.renderArea.selectAll("circle").each(getCircleRendererVerifier());
@@ -1467,37 +1516,6 @@ describe("Renderers", function () {
verifier.end();
});
- it("invertXYSelectionArea works", function () {
- var actualDataAreaFull = circleRenderer.invertXYSelectionArea(pixelAreaFull);
- assert.deepEqual(actualDataAreaFull, dataAreaFull, "the full data area is as expected");
-
- var actualDataAreaPart = circleRenderer.invertXYSelectionArea(pixelAreaPart);
-
- assert.closeTo(actualDataAreaPart.xMin, dataAreaPart.xMin, 1, "partial xMin is close");
- assert.closeTo(actualDataAreaPart.xMax, dataAreaPart.xMax, 1, "partial xMax is close");
- assert.closeTo(actualDataAreaPart.yMin, dataAreaPart.yMin, 1, "partial yMin is close");
- assert.closeTo(actualDataAreaPart.yMax, dataAreaPart.yMax, 1, "partial yMax is close");
- verifier.end();
- });
-
- it("getSelectionFromArea works", function () {
- var selectionFull = circleRenderer.getSelectionFromArea(dataAreaFull);
- assert.lengthOf(selectionFull[0], 10, "all 10 circles were selected by the full region");
-
- var selectionPartial = circleRenderer.getSelectionFromArea(dataAreaPart);
- assert.lengthOf(selectionPartial[0], 2, "2 circles were selected by the partial region");
- verifier.end();
- });
-
- it("getDataIndicesFromArea works", function () {
- var indicesFull = circleRenderer.getDataIndicesFromArea(dataAreaFull);
- assert.deepEqual(indicesFull, d3.range(10), "all 10 circles were selected by the full region");
-
- var indicesPartial = circleRenderer.getDataIndicesFromArea(dataAreaPart);
- assert.deepEqual(indicesPartial, [6, 7], "2 circles were selected by the partial region");
- verifier.end();
- });
-
describe("after the scale has changed", function () {
before(function () {
xScale.domain([0, 3]);
@@ -1506,37 +1524,6 @@ describe("Renderers", function () {
dataAreaPart = { xMin: 1, xMax: 3, yMin: 6, yMax: 3 };
});
- it("invertXYSelectionArea works", function () {
- var actualDataAreaFull = circleRenderer.invertXYSelectionArea(pixelAreaFull);
- assert.deepEqual(actualDataAreaFull, dataAreaFull, "the full data area is as expected");
-
- var actualDataAreaPart = circleRenderer.invertXYSelectionArea(pixelAreaPart);
-
- assert.closeTo(actualDataAreaPart.xMin, dataAreaPart.xMin, 1, "partial xMin is close");
- assert.closeTo(actualDataAreaPart.xMax, dataAreaPart.xMax, 1, "partial xMax is close");
- assert.closeTo(actualDataAreaPart.yMin, dataAreaPart.yMin, 1, "partial yMin is close");
- assert.closeTo(actualDataAreaPart.yMax, dataAreaPart.yMax, 1, "partial yMax is close");
- verifier.end();
- });
-
- it("getSelectionFromArea works", function () {
- var selectionFull = circleRenderer.getSelectionFromArea(dataAreaFull);
- assert.lengthOf(selectionFull[0], 4, "four circles were selected by the full region");
-
- var selectionPartial = circleRenderer.getSelectionFromArea(dataAreaPart);
- assert.lengthOf(selectionPartial[0], 1, "one circle was selected by the partial region");
- verifier.end();
- });
-
- it("getDataIndicesFromArea works", function () {
- var indicesFull = circleRenderer.getDataIndicesFromArea(dataAreaFull);
- assert.deepEqual(indicesFull, [0, 1, 2, 3], "four circles were selected by the full region");
-
- var indicesPartial = circleRenderer.getDataIndicesFromArea(dataAreaPart);
- assert.deepEqual(indicesPartial, [2], "circle 2 was selected by the partial region");
- verifier.end();
- });
-
it("the circles re-rendered properly", function () {
var renderArea = circleRenderer.renderArea;
var circles = renderArea.selectAll("circle");
@@ -1568,16 +1555,14 @@ describe("Renderers", function () {
// Choosing data with a negative x value is significant, since there is
// a potential failure mode involving the xDomain with an initial
// point below 0
- var dataset = { metadata: { cssClass: "sampleBarData" }, data: [d0, d1] };
+ var dataset = new Plottable.DataSource([d0, d1]);
before(function () {
svg = generateSVG(SVG_WIDTH, SVG_HEIGHT);
- xScale = new Plottable.LinearScale();
- yScale = new Plottable.LinearScale();
+ xScale = new Plottable.LinearScale().domain([-2, 4]);
+ yScale = new Plottable.LinearScale().domain([0, 4]);
barRenderer = new Plottable.BarRenderer(dataset, xScale, yScale);
barRenderer._anchor(svg)._computeLayout();
- var currentYDomain = yScale.domain();
- yScale.domain([0, currentYDomain[1]]);
});
beforeEach(function () {
@@ -1636,15 +1621,13 @@ describe("Renderers", function () {
before(function () {
svg = generateSVG(SVG_WIDTH, SVG_HEIGHT);
- xScale = new Plottable.OrdinalScale();
+ xScale = new Plottable.OrdinalScale().domain(["A", "B"]);
yScale = new Plottable.LinearScale();
- dataset = {
- data: [
- { x: "A", y: 1 },
- { x: "B", y: 2 }
- ],
- metadata: { cssClass: "letters" }
- };
+ var data = [
+ { x: "A", y: 1 },
+ { x: "B", y: 2 }
+ ];
+ dataset = new Plottable.DataSource(data);
renderer = new Plottable.CategoryBarRenderer(dataset, xScale, yScale);
renderer._animate = false;
@@ -1677,6 +1660,82 @@ describe("Renderers", function () {
;
});
});
+
+ describe("Grid Renderer", function () {
+ var verifier = new MultiTestVerifier();
+ var svg;
+ var xScale;
+ var yScale;
+ var colorScale;
+ var renderer;
+
+ var SVG_WIDTH = 400;
+ var SVG_HEIGHT = 200;
+
+ before(function () {
+ svg = generateSVG(SVG_WIDTH, SVG_HEIGHT);
+ xScale = new Plottable.OrdinalScale();
+ yScale = new Plottable.OrdinalScale();
+ colorScale = new Plottable.InterpolatedColorScale(["black", "white"]);
+ var data = [
+ { x: "A", y: "U", magnitude: 0 },
+ { x: "B", y: "U", magnitude: 2 },
+ { x: "A", y: "V", magnitude: 16 },
+ { x: "B", y: "V", magnitude: 8 }
+ ];
+
+ renderer = new Plottable.GridRenderer(data, xScale, yScale, colorScale, "x", "y", "magnitude");
+ renderer.renderTo(svg);
+ });
+
+ beforeEach(function () {
+ verifier.start();
+ });
+
+ it("renders correctly", function () {
+ var renderArea = renderer.renderArea;
+ var cells = renderArea.selectAll("rect")[0];
+ assert.equal(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' x 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' x 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' x 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' x coord is correct");
+ assert.equal(cellBV.attr("fill"), "#777777", "cell 'BV' color is correct");
+
+ verifier.end();
+ });
+
+ after(function () {
+ if (verifier.passed) {
+ svg.remove();
+ }
+ ;
+ });
+ });
});
});
///
@@ -1688,11 +1747,11 @@ describe("Scales", function () {
return true;
};
var scale = new Plottable.Scale(d3.scale.linear());
- scale.registerListener(testCallback);
+ scale.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._broadcasterCallbacks, scaleCopy._broadcasterCallbacks, "Registered callbacks are not copied over");
+ assert.notDeepEqual(scale.listener2Callback, scaleCopy.listener2Callback, "Registered callbacks are not copied over");
});
it("Scale alerts listeners when its domain is updated", function () {
@@ -1702,7 +1761,7 @@ describe("Scales", function () {
assert.equal(broadcaster, scale, "Callback received the calling scale as the first argument");
callbackWasCalled = true;
};
- scale.registerListener(testCallback);
+ scale.registerListener(null, testCallback);
scale.domain([0, 10]);
assert.isTrue(callbackWasCalled, "The registered callback was called");
@@ -1715,22 +1774,165 @@ describe("Scales", function () {
scale.padDomain(0.2);
assert.isTrue(callbackWasCalled, "The registered callback was called when padDomain() is used to set the domain");
});
+ describe("autoranging behavior", function () {
+ var data;
+ var dataSource;
+ var scale;
+ beforeEach(function () {
+ data = [{ foo: 2, bar: 1 }, { foo: 5, bar: -20 }, { foo: 0, bar: 0 }];
+ dataSource = new Plottable.DataSource(data);
+ scale = new Plottable.LinearScale();
+ });
- it("QuantitiveScale.widenDomain() functions correctly", function () {
- var scale = new Plottable.QuantitiveScale(d3.scale.linear());
- assert.deepEqual(scale.domain(), [0, 1], "Initial domain is [0, 1]");
- scale.widenDomain([1, 2]);
- assert.deepEqual(scale.domain(), [0, 2], "Domain was wided to [0, 2]");
- scale.widenDomain([-1, 1]);
- assert.deepEqual(scale.domain(), [-1, 2], "Domain was wided to [-1, 2]");
- scale.widenDomain([0, 1]);
- assert.deepEqual(scale.domain(), [-1, 2], "Domain does not get shrink if \"widened\" to a smaller value");
+ it("scale autoDomain flag is not overwritten without explicitly setting the domain", function () {
+ scale._addPerspective("1", dataSource, "foo");
+ scale.autorangeDomain().padDomain().nice();
+ assert.isTrue(scale._autoDomain, "the autoDomain flag is still set after autoranginging and padding and nice-ing");
+ scale.domain([0, 5]);
+ assert.isFalse(scale._autoDomain, "the autoDomain flag is false after domain explicitly set");
+ });
+
+ it("scale autorange works as expected with single dataSource", function () {
+ assert.isFalse(scale.isAutorangeUpToDate, "isAutorangeUpToDate is false by default");
+ scale._addPerspective("1x", dataSource, "foo");
+ assert.isFalse(scale.isAutorangeUpToDate, "isAutorangeUpToDate set to false after adding perspective");
+ scale.autorangeDomain();
+ assert.isTrue(scale.isAutorangeUpToDate, "isAutorangeUpToDate is true after autoranging");
+ assert.deepEqual(scale.domain(), [0, 5], "scale domain was autoranged properly");
+ data.push({ foo: 100, bar: 200 });
+ dataSource.data(data);
+ dataSource._broadcast();
+ assert.isFalse(scale.isAutorangeUpToDate, "isAutorangeUpToDate set to false after modifying data");
+ scale.autorangeDomain();
+ assert.deepEqual(scale.domain(), [0, 100], "scale domain was autoranged properly");
+ });
+
+ it("scale reference counting works as expected", function () {
+ scale._addPerspective("1x", dataSource, "foo");
+ scale._addPerspective("2x", dataSource, "foo");
+ scale.autorangeDomain();
+ scale._removePerspective("1x");
+ scale.autorangeDomain();
+ assert.isTrue(scale.isAutorangeUpToDate, "scale autorange up to date");
+ dataSource._broadcast();
+ assert.isFalse(scale.isAutorangeUpToDate, "scale was still listening to dataSource after one perspective deregistered");
+ scale._removePerspective("2x");
+ scale.autorangeDomain();
+ assert.isTrue(scale.isAutorangeUpToDate, "scale autorange up to date");
+ dataSource._broadcast();
+ assert.isTrue(scale.isAutorangeUpToDate, "scale not listening to the dataSource after all perspectives removed");
+ });
+
+ it("scale perspectives can be removed appropriately", function () {
+ assert.isTrue(scale._autoDomain, "autoDomain enabled1");
+ scale._addPerspective("1x", dataSource, "foo");
+ scale._addPerspective("2x", dataSource, "bar");
+ assert.isTrue(scale._autoDomain, "autoDomain enabled2");
+ assert.deepEqual(scale.domain(), [-20, 5], "scale domain includes both perspectives");
+ assert.isTrue(scale._autoDomain, "autoDomain enabled3");
+ scale._removePerspective("1x");
+ assert.isTrue(scale._autoDomain, "autoDomain enabled4");
+ assert.deepEqual(scale.domain(), [-20, 1], "only the bar accessor is active");
+ scale._addPerspective("2x", dataSource, "foo");
+ assert.isTrue(scale._autoDomain, "autoDomain enabled5");
+ assert.deepEqual(scale.domain(), [0, 5], "the bar accessor was overwritten");
+ });
});
- it("Linear Scales default to a domain of [Infinity, -Infinity]", function () {
- var scale = new Plottable.LinearScale();
- var domain = scale.domain();
- assert.deepEqual(domain, [Infinity, -Infinity]);
+ describe("Ordinal Scales", function () {
+ it("defaults to \"points\" range type", function () {
+ var scale = new Plottable.OrdinalScale();
+ assert.deepEqual(scale.rangeType(), "points");
+ });
+
+ it("rangeBand returns 0 when in \"points\" mode", function () {
+ var scale = new Plottable.OrdinalScale();
+ assert.deepEqual(scale.rangeType(), "points");
+ assert.deepEqual(scale.rangeBand(), 0);
+ });
+
+ it("rangeBands are updated when we switch to \"bands\" mode", function () {
+ var scale = new Plottable.OrdinalScale();
+ scale.rangeType("bands");
+ assert.deepEqual(scale.rangeType(), "bands");
+ scale.range([0, 2679]);
+
+ scale.domain([1, 2, 3, 4]);
+ assert.deepEqual(scale.rangeBand(), 399);
+
+ scale.domain([1, 2, 3, 4, 5]);
+ assert.deepEqual(scale.rangeBand(), 329);
+ });
+ });
+
+ describe("Color Scales", function () {
+ it("accepts categorical string types and ordinal domain", function () {
+ var scale = new Plottable.ColorScale("10");
+ scale.domain(["yes", "no", "maybe"]);
+ assert.equal("#1f77b4", scale.scale("yes"));
+ assert.equal("#ff7f0e", scale.scale("no"));
+ assert.equal("#2ca02c", scale.scale("maybe"));
+ });
+ });
+
+ describe("Interpolated Color Scales", function () {
+ it("linearly interpolates colors in L*a*b color space", function () {
+ var scale = new Plottable.InterpolatedColorScale("reds");
+ scale.domain([0, 1]);
+ assert.equal("#b10026", scale.scale(1));
+ assert.equal("#d9151f", scale.scale(0.9));
+ });
+
+ it("accepts array types with color hex values", function () {
+ var scale = new Plottable.InterpolatedColorScale(["#000", "#FFF"]);
+ scale.domain([0, 16]);
+ assert.equal("#000000", scale.scale(0));
+ assert.equal("#ffffff", scale.scale(16));
+ assert.equal("#777777", scale.scale(8));
+ });
+
+ it("accepts array types with color names", function () {
+ var scale = new Plottable.InterpolatedColorScale(["black", "white"]);
+ scale.domain([0, 16]);
+ assert.equal("#000000", scale.scale(0));
+ assert.equal("#ffffff", scale.scale(16));
+ assert.equal("#777777", scale.scale(8));
+ });
+
+ it("overflow scale values clamp to range", function () {
+ var scale = new Plottable.InterpolatedColorScale(["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));
+ });
+ });
+
+ describe("Ordinal Scales", function () {
+ it("defaults to \"points\" range type", function () {
+ var scale = new Plottable.OrdinalScale();
+ assert.deepEqual(scale.rangeType(), "points");
+ });
+
+ it("rangeBand returns 0 when in \"points\" mode", function () {
+ var scale = new Plottable.OrdinalScale();
+ assert.deepEqual(scale.rangeType(), "points");
+ assert.deepEqual(scale.rangeBand(), 0);
+ });
+
+ it("rangeBands are updated when we switch to \"bands\" mode", function () {
+ var scale = new Plottable.OrdinalScale();
+ scale.rangeType("bands");
+ assert.deepEqual(scale.rangeType(), "bands");
+ scale.range([0, 2679]);
+
+ scale.domain([1, 2, 3, 4]);
+ assert.deepEqual(scale.rangeBand(), 399);
+
+ scale.domain([1, 2, 3, 4, 5]);
+ assert.deepEqual(scale.rangeBand(), 329);
+ });
});
});
///
@@ -2043,4 +2245,89 @@ describe("Utils", function () {
assert.equal(textEl.text(), " ", "getTextHeight did not modify the text in the element");
svg.remove();
});
+
+ 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.Utils.accessorize(f);
+ assert.equal(f, a1, "function passes through accessorize unchanged");
+
+ var a2 = Plottable.Utils.accessorize("key");
+ assert.equal(a2(datum, 0, null), 4, "key accessor works appropriately");
+
+ var a3 = Plottable.Utils.accessorize("#aaaa");
+ assert.equal(a3(datum, 0, null), "#aaaa", "strings beginning with # are returned as final value");
+
+ var a4 = Plottable.Utils.accessorize(33);
+ assert.equal(a4(datum, 0, null), 33, "numbers are return as final value");
+
+ var a5 = Plottable.Utils.accessorize(datum);
+ assert.equal(a5(datum, 0, null), datum, "objects are return as final value");
+ });
+
+ it("StrictEqualityAssociativeArray works as expected", function () {
+ var s = new Plottable.Utils.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("uniq works as expected", function () {
+ var strings = ["foo", "bar", "foo", "foo", "baz", "bam"];
+ assert.deepEqual(Plottable.Utils.uniq(strings), ["foo", "bar", "baz", "bam"]);
+ });
+
+ it("IDCounter works as expected", function () {
+ var i = new Plottable.Utils.IDCounter();
+ assert.equal(i.get("f"), 0);
+ assert.equal(i.increment("f"), 1);
+ assert.equal(i.increment("g"), 1);
+ assert.equal(i.increment("f"), 2);
+ assert.equal(i.decrement("f"), 1);
+ assert.equal(i.get("f"), 1);
+ assert.equal(i.get("f"), 1);
+ assert.equal(i.decrement(2), -1);
+ });
+
+ it("can get a plain element's size", function () {
+ var parent = getSVGParent();
+ parent.style("width", "300px");
+ parent.style("height", "200px");
+ var parentElem = parent[0][0];
+
+ var width = Plottable.Utils.getElementWidth(parentElem);
+ assert.equal(width, 300, "measured width matches set width");
+ var height = Plottable.Utils.getElementHeight(parentElem);
+ assert.equal(height, 200, "measured height matches set height");
+ });
+
+ it("can get the svg's size", function () {
+ var svg = generateSVG(450, 120);
+ var svgElem = svg[0][0];
+
+ var width = Plottable.Utils.getElementWidth(svgElem);
+ assert.equal(width, 450, "measured width matches set width");
+ var height = Plottable.Utils.getElementHeight(svgElem);
+ assert.equal(height, 120, "measured height matches set height");
+ });
});