diff --git a/Gruntfile.js b/Gruntfile.js
index a2e5da6cd5..dc35d72bfd 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -66,7 +66,7 @@ module.exports = function(grunt) {
},
"rebuild": {
"tasks": ["dev-compile"],
- "files": ["src/*.ts"]
+ "files": ["src/**/*.ts"]
},
"tests": {
"tasks": ["ts:test", "tslint"],
@@ -78,9 +78,9 @@ module.exports = function(grunt) {
}
},
blanket_mocha: {
- all: ['test/tests.html'],
+ all: ['test/coverage.html'],
options: {
- threshold: 85
+ threshold: 80
}
},
connect: {
@@ -147,4 +147,5 @@ module.exports = function(grunt) {
grunt.registerTask("launch", ["connect", "dev-compile", "watch"]);
grunt.registerTask("test", ["dev-compile", "blanket_mocha"]);
+ grunt.registerTask("bm", ["blanket_mocha"]);
};
diff --git a/bower.json b/bower.json
index dac42c07fb..19b9dec25e 100644
--- a/bower.json
+++ b/bower.json
@@ -1,6 +1,6 @@
{
"name": "plottable",
- "version": "0.7.0",
+ "version": "0.8.0",
"ignore": [
"**/*",
"!plottable.js",
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/examples/exampleUtil.ts b/examples/exampleUtil.ts
index 8207ecf88d..8ad53bc798 100644
--- a/examples/exampleUtil.ts
+++ b/examples/exampleUtil.ts
@@ -1,4 +1,4 @@
-function makeRandomData(numPoints, scaleFactor=1): Plottable.IDataset {
+function makeRandomData(numPoints, scaleFactor=1): any[] {
var data = [];
for (var i = 0; i < numPoints; i++) {
var x = Math.random();
@@ -6,7 +6,7 @@ function makeRandomData(numPoints, scaleFactor=1): Plottable.IDataset {
data.push(r);
}
data.sort((a: any, b: any) => a.x - b.x);
- return {data: data, metadata: {cssClass: "random-data"}};
+ return data;
}
function makeNormallyDistributedData(n=100, xMean?, xStdDev?, yMean?, yStdDev?) {
@@ -53,7 +53,7 @@ function binByVal(data: any[], accessor: Plottable.IAccessor, range=[0,100], nBi
})
return bins;
}
-function makeRandomBucketData(numBuckets: number, bucketWidth: number, maxValue = 10): Plottable.IDataset {
+function makeRandomBucketData(numBuckets: number, bucketWidth: number, maxValue = 10): any[] {
var data = [];
for (var i=0; i < numBuckets; i++) {
data.push({
@@ -62,10 +62,7 @@ function makeRandomBucketData(numBuckets: number, bucketWidth: number, maxValue
y: Math.round(Math.random() * maxValue)
});
}
- return {
- "data": data,
- metadata: {cssClass: "random-buckets"}
- };
+ return data;
}
function generateHeightWeightData(n: number) {
@@ -81,8 +78,5 @@ function generateHeightWeightData(n: number) {
});
}
- return {
- data: data,
- metadata: {cssClass: "height-weight-data"}
- };
+ return data;
}
diff --git a/examples/main-page/commit-dashboard.js b/examples/main-page/commit-dashboard.js
index 51896a6333..7bc35a8183 100644
--- a/examples/main-page/commit-dashboard.js
+++ b/examples/main-page/commit-dashboard.js
@@ -45,15 +45,15 @@ function commitDashboard(dataManager, svg) {
var scatterDateAxis = new Plottable.XAxis(timeScale, "bottom", dateFormatter);
var rScale = new Plottable.QuantitiveScale(d3.scale.log())
- .range([2, 12])
- .widenDomainOnData(commits, linesAddedAccessor);
+ .range([2, 12]);
function radiusAccessor(d) { return rScale.scale(linesAddedAccessor(d)); }
var scatterRenderer = new Plottable.CircleRenderer(commits, timeScale, scatterYScale)
- .xAccessor("date")
- .yAccessor(hourAccessor)
- .rAccessor(radiusAccessor)
- .colorAccessor(function(d) { return contributorColorScale.scale(d.name); });
+ .project("x", "date")
+ .project("y", hourAccessor)
+ .project("r", linesAddedAccessor, rScale)
+ .project("fill", "name", contributorColorScale);
+ window.scatterRenderer = scatterRenderer;
var scatterGridlines = new Plottable.Gridlines(timeScale, scatterYScale);
var scatterRenderArea = scatterGridlines.merge(scatterRenderer);
@@ -68,17 +68,14 @@ function commitDashboard(dataManager, svg) {
var tscRenderers = {};
dataManager.directories.forEach(function(dir) {
var timeSeries = directoryTimeSeries[dir];
- var directoryDataset = {
- data: timeSeries,
- metadata: {}
- };
- var lineRenderer = new Plottable.LineRenderer(directoryDataset, timeScale, tscYScale);
- lineRenderer.xAccessor(function(d) { return d[0]; })
- .yAccessor(function(d) { return d[1]; })
- .colorAccessor(function(d) { return directoryColorScale.scale(dir); });
+ var lineRenderer = new Plottable.LineRenderer(timeSeries, timeScale, tscYScale);
+ lineRenderer.project("x", function(d) { return d[0]; })
+ .project("y", function(d) { return d[1]; })
+ .project("stroke", function() {return dir}, directoryColorScale);
lineRenderer.classed(dir, true);
tscRenderers[dir] = lineRenderer;
tscRenderArea = tscRenderArea.merge(lineRenderer);
+ window.lineRenderer = lineRenderer;
});
var loadTSCData = function() {
@@ -102,17 +99,17 @@ function commitDashboard(dataManager, svg) {
// ----- /Legends -----
// ----- Bar1: Lines changed by contributor -----
- var contributorBarYScale = new Plottable.LinearScale();
- var contributorBarYAxis = new Plottable.YAxis(contributorBarYScale, "right");
var contributorBarXScale = new Plottable.OrdinalScale().domain(dataManager.contributors);
+ var contributorBarYScale = new Plottable.LinearScale();
var contributorBarXAxis = new Plottable.XAxis(contributorBarXScale, "bottom", function(d) { return d});
+ var contributorBarYAxis = new Plottable.YAxis(contributorBarYScale, "right");
contributorBarXAxis.classed("no-tick-labels", true).rowMinimum(5);
var contributorBarRenderer = new Plottable.CategoryBarRenderer(linesByContributor,
contributorBarXScale,
contributorBarYScale);
- contributorBarRenderer.widthAccessor(40);
- contributorBarRenderer.colorAccessor(function(d) { return contributorColorScale.scale(d.name); });
- contributorBarRenderer.xAccessor("name").yAccessor(linesAddedAccessor);
+ contributorBarRenderer.project("width", 40)
+ .project("fill", "name", contributorColorScale)
+ .project("x", "name").project("y", linesAddedAccessor);
var contributorGridlines = new Plottable.Gridlines(null, contributorBarYScale);
var contributorBarChart = new Plottable.Table([
[contributorBarRenderer.merge(contributorGridlines), contributorBarYAxis],
@@ -129,9 +126,10 @@ function commitDashboard(dataManager, svg) {
var directoryBarRenderer = new Plottable.CategoryBarRenderer(linesByDirectory,
directoryBarXScale,
directoryBarYScale);
- directoryBarRenderer.widthAccessor(40);
- directoryBarRenderer.colorAccessor(function(d) { return directoryColorScale.scale(d.directory); });
- directoryBarRenderer.xAccessor("directory").yAccessor(linesAddedAccessor);
+ directoryBarRenderer.project("width", 40)
+ .project("fill", "directory", directoryColorScale)
+ .project("x", "directory")
+ .project("y", linesAddedAccessor);
var directoryGridlines = new Plottable.Gridlines(null, directoryBarYScale);
var directoryBarChart = new Plottable.Table([
[directoryBarRenderer.merge(directoryGridlines), directoryBarYAxis],
diff --git a/license_header.txt b/license_header.txt
index 68467d16bc..51ac8518a3 100644
--- a/license_header.txt
+++ b/license_header.txt
@@ -1,5 +1,5 @@
/*!
-Plottable v0.7.0 (https://github.com/palantir/plottable)
+Plottable v0.8.0 (https://github.com/palantir/plottable)
Copyright 2014 Palantir Technologies
Licensed under MIT (https://github.com/palantir/plottable/blob/master/LICENSE)
*/
diff --git a/package.json b/package.json
index ea999a97f6..5580bfdbb9 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "plottable.js",
- "version": "0.7.0",
+ "version": "0.8.0",
"description": "Build flexible, performant, interactive charts using D3",
"repository": {
"type": "git",
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/src/axis.ts b/src/axis.ts
index 54d36d248a..846f687f0e 100644
--- a/src/axis.ts
+++ b/src/axis.ts
@@ -28,7 +28,7 @@ module Plottable {
formatter = d3.format(".3s");
}
this.d3Axis.tickFormat(formatter);
- this.axisScale.registerListener(() => this.rescale());
+ this.axisScale.registerListener(this, () => this.rescale());
}
public _anchor(element: D3.Selection) {
diff --git a/src/barRenderer.ts b/src/barRenderer.ts
deleted file mode 100644
index 692f51e219..0000000000
--- a/src/barRenderer.ts
+++ /dev/null
@@ -1,88 +0,0 @@
-///
-
-module Plottable {
- export class BarRenderer extends NumericXYRenderer {
- private static defaultDxAccessor = "dx";
- public barPaddingPx = 1;
-
- public dxAccessor: any;
-
- /**
- * Creates a BarRenderer.
- *
- * @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 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) {
- super(dataset, xScale, yScale, xAccessor, yAccessor);
- this.classed("bar-renderer", true);
-
- this.dxAccessor = (dxAccessor != null) ? dxAccessor : BarRenderer.defaultDxAccessor;
- }
-
- public autorange() {
- super.autorange();
- var xA = this._getAppliedAccessor(this._xAccessor);
- var dxA = this._getAppliedAccessor(this.dxAccessor);
- var x2Accessor = (d: any) => xA(d, null) + dxA(d, null);
- var x2Extent: number[] = d3.extent(this._data, x2Accessor);
- this.xScale.widenDomain(x2Extent);
- return this;
- }
-
- public _paint() {
- super._paint();
- var yRange = this.yScale.range();
- var maxScaledY = Math.max(yRange[0], yRange[1]);
-
- this.dataSelection = this.renderArea.selectAll("rect").data(this._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 = (d: any, i: number) => {
- var x = xA(d, i);
- var scaledX = this.xScale.scale(x);
- return scaledX + this.barPaddingPx;
- };
-
- var yA = this._getAppliedAccessor(this._yAccessor);
- var yFunction = (d: any, i: number) => {
- var y = yA(d, i);
- var scaledY = this.yScale.scale(y);
- return scaledY;
- };
-
- var dxA = this._getAppliedAccessor(this.dxAccessor);
- var widthFunction = (d: any, i: number) => {
- 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 = (d: any, i: number) => {
- return maxScaledY - yFunction(d, i);
- };
-
- this.dataSelection
- .attr("x", xFunction)
- .attr("y", yFunction)
- .attr("width", widthFunction)
- .attr("height", heightFunction)
- .attr("fill", this._getAppliedAccessor(this._colorAccessor));
- this.dataSelection.exit().remove();
- }
- }
-}
diff --git a/src/broadcaster.ts b/src/broadcaster.ts
new file mode 100644
index 0000000000..013a6b59df
--- /dev/null
+++ b/src/broadcaster.ts
@@ -0,0 +1,51 @@
+///
+
+module Plottable {
+ interface IListenerCallbackPair {
+ l: any;
+ c: IBroadcasterCallback;
+ }
+ export class Broadcaster extends PlottableObject {
+ private listener2Callback = new 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
+ */
+ public registerListener(listener: any, callback: IBroadcasterCallback) {
+ 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
+ */
+ public _broadcast(...args: any[]) {
+ this.listener2Callback.values().forEach((callback) => callback(this, args));
+ return this;
+ }
+
+ /**
+ * Registers deregister the callback associated with a listener.
+ *
+ * @param listener The listener to deregister.
+ * @returns {Broadcaster} this object
+ */
+ public deregisterListener(listener: any) {
+ var listenerWasFound = this.listener2Callback.delete(listener);
+ if (listenerWasFound) {
+ return this;
+ } else {
+ throw new Error("Attempted to deregister listener, but listener not found");
+ }
+ }
+ }
+}
diff --git a/src/categoryXYRenderer.ts b/src/categoryXYRenderer.ts
deleted file mode 100644
index bb7714b0dc..0000000000
--- a/src/categoryXYRenderer.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-///
-
-module Plottable {
- export class CategoryXYRenderer extends XYRenderer {
- public dataSelection: D3.UpdateSelection;
- public xScale: OrdinalScale;
- public yScale: QuantitiveScale;
- public _xAccessor: any;
- public _yAccessor: any;
-
- /**
- * 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) {
- super(dataset, xScale, yScale, xAccessor, yAccessor);
- this.classed("category-renderer", true);
- }
- }
-}
diff --git a/src/circleRenderer.ts b/src/circleRenderer.ts
deleted file mode 100644
index 821bb68ca3..0000000000
--- a/src/circleRenderer.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-///
-
-module Plottable {
- export class CircleRenderer extends NumericXYRenderer {
- private _rAccessor: any;
- private static defaultRAccessor = 3;
-
- /**
- * 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 {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) {
- super(dataset, xScale, yScale, xAccessor, yAccessor);
- this._rAccessor = (rAccessor != null) ? rAccessor : CircleRenderer.defaultRAccessor;
- this.classed("circle-renderer", true);
- }
-
- public rAccessor(a: any) {
- this._rAccessor = a;
- this._requireRerender = true;
- this._rerenderUpdateSelection = true;
- return this;
- }
-
- public _paint() {
- super._paint();
- var cx = (d: any, i: number) => this.xScale.scale(this._getAppliedAccessor(this._xAccessor)(d, i));
- var cy = (d: any, i: number) => 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);
- this.dataSelection.enter().append("circle");
- this.dataSelection.attr("cx", cx)
- .attr("cy", cy)
- .attr("r", r)
- .attr("fill", color);
- this.dataSelection.exit().remove();
- }
- }
-}
diff --git a/src/component.ts b/src/component.ts
index 3526f2ceb2..ad913706ee 100644
--- a/src/component.ts
+++ b/src/component.ts
@@ -1,8 +1,7 @@
///
module Plottable {
- export class Component {
- private static clipPathId = 0; // Used for unique namespacing for the clipPaths
+ export class Component extends PlottableObject {
public element: D3.Selection;
public content: D3.Selection;
private hitBox: D3.Selection;
@@ -222,10 +221,9 @@ module Plottable {
private generateClipPath() {
// The clip path will prevent content from overflowing its component space.
- var clipPathId = Component.clipPathId++;
- this.element.attr("clip-path", "url(#clipPath" + clipPathId + ")");
+ this.element.attr("clip-path", "url(#clipPath" + this._plottableID + ")");
var clipPathParent = this.boxContainer.append("clipPath")
- .attr("id", "clipPath" + clipPathId);
+ .attr("id", "clipPath" + this._plottableID);
this.addBox("clip-rect", clipPathParent);
}
diff --git a/src/coordinator.ts b/src/coordinator.ts
index 8d820703a4..8435287cd6 100644
--- a/src/coordinator.ts
+++ b/src/coordinator.ts
@@ -17,7 +17,7 @@ module Plottable {
*/
constructor(scales: Scale[]) {
this.scales = scales;
- this.scales.forEach((s) => s.registerListener((sx: Scale) => this.rescale(sx)));
+ this.scales.forEach((s) => s.registerListener(this, (sx: Scale) => this.rescale(sx)));
}
public rescale(scale: Scale) {
diff --git a/src/dataSource.ts b/src/dataSource.ts
new file mode 100644
index 0000000000..827065c34c
--- /dev/null
+++ b/src/dataSource.ts
@@ -0,0 +1,83 @@
+///
+
+module Plottable {
+ interface ICachedExtent {
+ accessor: IAccessor;
+ extent: any[];
+ }
+ export class DataSource extends Broadcaster {
+ private _data: any[];
+ private _metadata: any;
+ private accessor2cachedExtent: Utils.StrictEqualityAssociativeArray;
+ /**
+ * Creates a new DataSource.
+ *
+ * @constructor
+ * @param {any[]} data
+ * @param {any} metadata An object containing additional information.
+ */
+ constructor(data: any[] = [], metadata: any = {}) {
+ super();
+ this._data = data;
+ this._metadata = metadata;
+ this.accessor2cachedExtent = new Utils.StrictEqualityAssociativeArray();
+ }
+
+ /**
+ * 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;
+ public data(data?: any[]): any {
+ if (data == null) {
+ return this._data;
+ } else {
+ this._data = data;
+ this.accessor2cachedExtent = new Utils.StrictEqualityAssociativeArray();
+ this._broadcast();
+ return this;
+ }
+ }
+
+ /**
+ * 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;
+ public metadata(metadata?: any): any {
+ if (metadata == null) {
+ return this._metadata;
+ } else {
+ this._metadata = metadata;
+ this.accessor2cachedExtent = new Utils.StrictEqualityAssociativeArray();
+ this._broadcast();
+ return this;
+ }
+ }
+
+ public _getExtent(accessor: IAccessor): any[] {
+ var cachedExtent = this.accessor2cachedExtent.get(accessor);
+ if (cachedExtent === undefined) {
+ cachedExtent = this.computeExtent(accessor);
+ this.accessor2cachedExtent.set(accessor, cachedExtent);
+ }
+ return cachedExtent;
+ }
+
+ private computeExtent(accessor: IAccessor): any[] {
+ var appliedAccessor = Utils.applyAccessor(accessor, this);
+ var mappedData = this._data.map(appliedAccessor);
+ if (typeof(appliedAccessor(this._data[0], 0)) === "string") {
+ return Utils.uniq(mappedData);
+ } else {
+ return d3.extent(mappedData);
+ }
+ }
+ }
+}
diff --git a/src/gridlines.ts b/src/gridlines.ts
index 56d541da07..bb908eef44 100644
--- a/src/gridlines.ts
+++ b/src/gridlines.ts
@@ -20,10 +20,10 @@ module Plottable {
this.xScale = xScale;
this.yScale = yScale;
if (this.xScale != null) {
- this.xScale.registerListener(() => this.redrawXLines());
+ this.xScale.registerListener(this, () => this.redrawXLines());
}
if (this.yScale != null) {
- this.yScale.registerListener(() => this.redrawYLines());
+ this.yScale.registerListener(this, () => this.redrawYLines());
}
}
diff --git a/src/interaction.ts b/src/interaction.ts
index 0041c1277a..b1557d178a 100644
--- a/src/interaction.ts
+++ b/src/interaction.ts
@@ -184,67 +184,6 @@ module Plottable {
}
}
- export class ZoomCallbackGenerator {
- private xScaleMappings: QuantitiveScale[][] = [];
- private yScaleMappings: QuantitiveScale[][] = [];
-
- /**
- * 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 {
- 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.
- */
- public addYScale(listenerScale: QuantitiveScale, targetScale?: QuantitiveScale): ZoomCallbackGenerator {
- if (targetScale == null) {
- targetScale = listenerScale;
- }
- this.yScaleMappings.push([listenerScale, targetScale]);
- return this;
- }
-
- private updateScale(referenceScale: QuantitiveScale, targetScale: QuantitiveScale, pixelMin: number, pixelMax: number) {
- 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.
- */
- public getCallback() {
- return (area: SelectionArea) => {
- this.xScaleMappings.forEach((sm: QuantitiveScale[]) => {
- this.updateScale(sm[0], sm[1], area.xMin, area.xMax);
- });
- this.yScaleMappings.forEach((sm: QuantitiveScale[]) => {
- this.updateScale(sm[0], sm[1], area.yMin, area.yMax);
- });
- };
- }
- }
-
export class MousemoveInteraction extends Interaction {
constructor(componentToListenTo: Component) {
super(componentToListenTo);
@@ -343,60 +282,4 @@ module Plottable {
return this;
}
}
-
-
- export class CrosshairsInteraction extends MousemoveInteraction {
- private renderer: NumericXYRenderer;
-
- private circle: D3.Selection;
- private xLine: D3.Selection;
- private yLine: D3.Selection;
- private lastx: number;
- private lasty: number;
-
- constructor(renderer: NumericXYRenderer) {
- super(renderer);
- this.renderer = renderer;
- renderer.xScale.registerListener(() => this.rescale());
- renderer.yScale.registerListener(() => this.rescale());
- }
-
- public _anchor(hitBox: D3.Selection) {
- super._anchor(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);
- }
-
- public mousemove(x: number, y: number) {
- 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 = 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);
- }
-
- public rescale() {
- if (this.lastx != null) {
- this.mousemove(this.lastx, this.lasty);
- }
- }
- }
}
diff --git a/src/interfaces.ts b/src/interfaces.ts
index 84c4fc2cca..138c3ae6a0 100644
--- a/src/interfaces.ts
+++ b/src/interfaces.ts
@@ -13,6 +13,10 @@ module Plottable {
(datum: any, index?: number, metadata?: any): any;
};
+ export interface IAppliedAccessor {
+ (datum: any, index: number) : any;
+ }
+
export interface SelectionArea {
xMin: number;
xMax: number;
@@ -26,10 +30,6 @@ module Plottable {
}
export interface IBroadcasterCallback {
- (broadcaster: IBroadcaster, ...args: any[]): any;
- }
-
- export interface IBroadcaster {
- registerListener: (cb: IBroadcasterCallback) => IBroadcaster;
+ (broadcaster: Broadcaster, ...args: any[]): any;
}
}
diff --git a/src/lineRenderer.ts b/src/lineRenderer.ts
deleted file mode 100644
index 9d039fdf54..0000000000
--- a/src/lineRenderer.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-///
-
-module Plottable {
- export class LineRenderer extends NumericXYRenderer {
- private path: D3.Selection;
- private line: D3.Svg.Line;
-
-/**
- * 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 {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) {
- super(dataset, xScale, yScale, xAccessor, yAccessor);
- this.classed("line-renderer", true);
- }
-
- public _anchor(element: D3.Selection) {
- super._anchor(element);
- this.path = this.renderArea.append("path").classed("line", true);
- return this;
- }
-
- public _paint() {
- super._paint();
- var xA = this._getAppliedAccessor(this._xAccessor);
- var yA = this._getAppliedAccessor(this._yAccessor);
- var cA = this._getAppliedAccessor(this._colorAccessor);
- this.line = d3.svg.line()
- .x((d: any, i: number) => this.xScale.scale(xA(d, i)))
- .y((d: any, i: number) => this.yScale.scale(yA(d, i)));
- this.dataSelection = this.path.datum(this._data);
- this.path.attr("d", this.line).attr("stroke", cA);
- }
- }
-}
diff --git a/src/numericXYRenderer.ts b/src/numericXYRenderer.ts
deleted file mode 100644
index 903a7d5947..0000000000
--- a/src/numericXYRenderer.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-///
-
-module Plottable {
- export class NumericXYRenderer extends XYRenderer {
- public dataSelection: D3.UpdateSelection;
- public xScale: QuantitiveScale;
- public yScale: QuantitiveScale;
- public _xAccessor: any;
- public _yAccessor: any;
- public autorangeDataOnLayout = true;
-
- /**
- * 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) {
- super(dataset, xScale, yScale, xAccessor, yAccessor);
- 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.
- */
- public invertXYSelectionArea(pixelArea: SelectionArea): SelectionArea {
- 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;
- }
-
- private getDataFilterFunction(dataArea: SelectionArea): (d: any, i: number) => boolean {
- var xA = this._getAppliedAccessor(this._xAccessor);
- var yA = this._getAppliedAccessor(this._yAccessor);
- var filterFunction = (d: any, i: number) => {
- var x: number = xA(d, i);
- var y: number = yA(d, i);
- return Utils.inRange(x, dataArea.xMin, dataArea.xMax) && 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.
- */
- public getSelectionFromArea(dataArea: SelectionArea) {
- 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.
- */
- public getDataIndicesFromArea(dataArea: SelectionArea): number[] {
- var filterFunction = this.getDataFilterFunction(dataArea);
- var results: number[] = [];
- this._data.forEach((d, i) => {
- if (filterFunction(d, i)) {
- results.push(i);
- }
- });
- return results;
- }
- }
-}
diff --git a/src/plottableObject.ts b/src/plottableObject.ts
new file mode 100644
index 0000000000..3c928c3a6f
--- /dev/null
+++ b/src/plottableObject.ts
@@ -0,0 +1,8 @@
+///
+
+module Plottable {
+ export class PlottableObject {
+ private static nextID = 0;
+ public _plottableID = PlottableObject.nextID++;
+ }
+}
diff --git a/src/reference.ts b/src/reference.ts
index 164cc3edc4..bcc7503775 100644
--- a/src/reference.ts
+++ b/src/reference.ts
@@ -1,27 +1,30 @@
///
///
+///
+///
///
///
///
///
///
///
+///
+///
//grunt-start
///
///
///
///
-///
-///
-///
-///
-///
-///
-///
+///
+///
+///
+///
+///
+///
+///
+///
///
///
///
///
-///
-///
//grunt-end
diff --git a/src/renderer.ts b/src/renderer.ts
deleted file mode 100644
index 4345eff799..0000000000
--- a/src/renderer.ts
+++ /dev/null
@@ -1,120 +0,0 @@
-///
-
-module Plottable {
- export class Renderer extends Component {
- public _data: any[];
- public _metadata: IMetadata;
- public renderArea: D3.Selection;
- public element: D3.Selection;
- public scales: Scale[];
- public _colorAccessor: IAccessor;
- public _animate = false;
- public _hasRendered = false;
- private static defaultColorAccessor = (d: any) => "#1f77b4";
-
- public _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
- // maintain their current attributes. If we change the metadata or an
- // accessor function, then this property will not be true, and we will need
- // to recompute attributes on the entire update selection.
-
- public _requireRerender = false;
- // 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.
- */
- constructor(dataset?: any) {
- super();
- this.clipPathEnabled = true;
- this._fixedWidth = false;
- this._fixedHeight = false;
- this.classed("renderer", true);
- if (dataset != null) {
- if (dataset.data == null) {
- this.data(dataset);
- } else {
- this.data(dataset.data);
- if (dataset.metadata != null) {
- this.metadata(dataset.metadata);
- }
- }
- }
- 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.
- */
- public dataset(dataset: IDataset): Renderer {
- this.data(dataset.data);
- this.metadata(dataset.metadata);
- return this;
- }
-
- public metadata(metadata: IMetadata): Renderer {
- 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;
- this._requireRerender = true;
- return this;
- }
-
- public data(data: any[]): Renderer {
- this._data = data;
- this._requireRerender = true;
- return this;
- }
-
- public _render(): Renderer {
- this._hasRendered = true;
- this._paint();
- this._requireRerender = false;
- this._rerenderUpdateSelection = false;
- return this;
- }
-
- public colorAccessor(a: IAccessor): Renderer {
- this._colorAccessor = a;
- this._requireRerender = true;
- this._rerenderUpdateSelection = true;
- return this;
- }
-
- public _paint() {
- // no-op
- }
-
- public autorange() {
- // no-op
- return this;
- }
-
- public _anchor(element: D3.Selection) {
- super._anchor(element);
- this.renderArea = this.content.append("g").classed("render-area", true);
- return this;
- }
-
- public _getAppliedAccessor(accessor: any): (d: any, i: number) => any {
- if (typeof(accessor) === "function") {
- return (d: any, i: number) => accessor(d, i, this._metadata);
- } else if (typeof(accessor) === "string") {
- return (d: any, i: number) => d[accessor];
- } else {
- return (d: any, i: number) => accessor;
- }
- }
- }
-}
diff --git a/src/renderers/barRenderer.ts b/src/renderers/barRenderer.ts
new file mode 100644
index 0000000000..7c4f27d053
--- /dev/null
+++ b/src/renderers/barRenderer.ts
@@ -0,0 +1,62 @@
+///
+
+module Plottable {
+ export class BarRenderer extends XYRenderer {
+ public barPaddingPx = 1;
+
+ public dxAccessor: any;
+
+ /**
+ * Creates a BarRenderer.
+ *
+ * @constructor
+ * @param {IDataset} dataset The dataset to render.
+ * @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: Scale,
+ yScale: Scale,
+ xAccessor?: IAccessor,
+ dxAccessor?: IAccessor,
+ yAccessor?: IAccessor) {
+ super(dataset, xScale, yScale, xAccessor, yAccessor);
+ this.classed("bar-renderer", true);
+ this.project("dx", "dx");
+ }
+
+ public _paint() {
+ super._paint();
+ var yRange = this.yScale.range();
+ var maxScaledY = Math.max(yRange[0], yRange[1]);
+
+ 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 attrToProjector = this._generateAttrToProjector();
+
+ var xF = attrToProjector["x"];
+ attrToProjector["x"] = (d: any, i: number) => xF(d, i) + this.barPaddingPx;
+
+ var dxA = Utils.applyAccessor(this._projectors["dx"].accessor, this.dataSource());
+ attrToProjector["width"] = (d: any, i: number) => {
+ var dx = dxA(d, i);
+ var scaledDx = this.xScale.scale(dx);
+ var scaledOffset = this.xScale.scale(0);
+ return scaledDx - scaledOffset - 2 * this.barPaddingPx;
+ };
+
+ attrToProjector["height"] = (d: any, i: number) => {
+ return maxScaledY - attrToProjector["y"](d, i);
+ };
+
+ this.dataSelection.attr(attrToProjector);
+ this.dataSelection.exit().remove();
+ }
+ }
+}
diff --git a/src/categoryBarRenderer.ts b/src/renderers/categoryBarRenderer.ts
similarity index 53%
rename from src/categoryBarRenderer.ts
rename to src/renderers/categoryBarRenderer.ts
index 63b21d3fd7..56de9cc6da 100644
--- a/src/categoryBarRenderer.ts
+++ b/src/renderers/categoryBarRenderer.ts
@@ -1,109 +1,64 @@
-///
+///
module Plottable {
- export class CategoryBarRenderer extends CategoryXYRenderer {
- private _widthAccessor: any;
-
+ export 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 {QuantitiveScale} yScale The y 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: QuantitiveScale,
+ yScale: Scale,
xAccessor?: IAccessor,
widthAccessor?: IAccessor,
yAccessor?: IAccessor) {
super(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.
- */
- public widthAccessor(accessor: any) {
- this._widthAccessor = accessor;
- this._requireRerender = true;
- this._rerenderUpdateSelection = true;
- return this;
+ this.project("width", 10);
}
public _paint() {
super._paint();
var yRange = this.yScale.range();
var maxScaledY = Math.max(yRange[0], yRange[1]);
- var xA = this._getAppliedAccessor(this._xAccessor);
+ var xA = Utils.applyAccessor(this._xAccessor, this.dataSource());
- this.dataSelection = this.renderArea.selectAll("rect").data(this._data, xA);
+ this.dataSelection = this.renderArea.selectAll("rect").data(this._dataSource.data(), xA);
this.dataSelection.enter().append("rect");
- var rangeType = this.xScale.rangeType();
+ var attrToProjector = this._generateAttrToProjector();
- var widthFunction: (d:any, i: number) => number;
+ var rangeType = this.xScale.rangeType();
if (rangeType === "points"){
- widthFunction = this._getAppliedAccessor(this._widthAccessor);
+ var xF = attrToProjector["x"];
+ var widthF = attrToProjector["width"];
+ attrToProjector["x"] = (d: any, i: number) => xF(d, i) - widthF(d, i) / 2;
} else {
- widthFunction = (d:any, i: number) => this.xScale.rangeBand();
+ attrToProjector["width"] = (d: any, i: number) => this.xScale.rangeBand();
}
- var xFunction = (d: any, i: number) => {
- var x = xA(d, i);
- var scaledX = this.xScale.scale(x);
- if (rangeType === "points") {
- return scaledX - widthFunction(d, i)/2;
- } else {
- return scaledX;
- }
- };
-
- var yA = this._getAppliedAccessor(this._yAccessor);
- var yFunction = (d: any, i: number) => {
- var y = yA(d, i);
- var scaledY = this.yScale.scale(y);
- return scaledY;
- };
-
var heightFunction = (d: any, i: number) => {
- return maxScaledY - yFunction(d, i);
+ return maxScaledY - attrToProjector["y"](d, i);
};
+ attrToProjector["height"] = heightFunction;
- var updateSelection: any = this.dataSelection
- .attr("fill", this._getAppliedAccessor(this._colorAccessor));
-
+ var updateSelection: any = this.dataSelection;
if (this._animate) {
updateSelection = updateSelection.transition();
}
-
- updateSelection
- .attr("x", xFunction)
- .attr("y", yFunction)
- .attr("width", widthFunction)
- .attr("height", heightFunction);
+ updateSelection.attr(attrToProjector);
this.dataSelection.exit().remove();
}
- public autorange() {
- super.autorange();
- var yDomain = this.yScale.domain();
- if (yDomain[1] < 0 || yDomain[0] > 0) { // domain does not include 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.
*
diff --git a/src/renderers/circleRenderer.ts b/src/renderers/circleRenderer.ts
new file mode 100644
index 0000000000..d792188220
--- /dev/null
+++ b/src/renderers/circleRenderer.ts
@@ -0,0 +1,47 @@
+///
+
+module Plottable {
+ export class CircleRenderer extends XYRenderer {
+
+ /**
+ * Creates a CircleRenderer.
+ *
+ * @constructor
+ * @param {IDataset} dataset The dataset to render.
+ * @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: Scale, yScale: Scale,
+ xAccessor?: any, yAccessor?: any, rAccessor?: any) {
+ super(dataset, xScale, yScale, xAccessor, yAccessor);
+/* this._rAccessor = (rAccessor != null) ? rAccessor : CircleRenderer.defaultRAccessor;*/
+ this.classed("circle-renderer", true);
+ this.project("r", 3);
+ this.project("fill", "#00ffaa");
+ }
+
+ public project(attrToSet: string, accessor: any, scale?: Scale) {
+ attrToSet = attrToSet === "cx" ? "x" : attrToSet;
+ attrToSet = attrToSet === "cy" ? "y" : attrToSet;
+ super.project(attrToSet, accessor, scale);
+ return this;
+ }
+
+ public _paint() {
+ super._paint();
+ 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(attrToProjector);
+ this.dataSelection.exit().remove();
+ }
+ }
+}
diff --git a/src/renderers/gridRenderer.ts b/src/renderers/gridRenderer.ts
new file mode 100644
index 0000000000..71d793829f
--- /dev/null
+++ b/src/renderers/gridRenderer.ts
@@ -0,0 +1,65 @@
+///
+
+module Plottable {
+ export 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) {
+ super(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);
+ }
+
+ public _paint() {
+ super._paint();
+
+ 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"] = () => xStep;
+ attrToProjector["height"] = () => yStep;
+ var yAttr = attrToProjector["y"];
+ attrToProjector["y"] = (d: any, i: number) => yMax - yAttr(d,i);
+
+ this.dataSelection.attr(attrToProjector);
+ this.dataSelection.exit().remove();
+ }
+ }
+}
diff --git a/src/renderers/lineRenderer.ts b/src/renderers/lineRenderer.ts
new file mode 100644
index 0000000000..9f20d24367
--- /dev/null
+++ b/src/renderers/lineRenderer.ts
@@ -0,0 +1,41 @@
+///
+
+module Plottable {
+ export class LineRenderer extends XYRenderer {
+ private path: D3.Selection;
+ private line: D3.Svg.Line;
+
+/**
+ * Creates a LineRenderer.
+ *
+ * @constructor
+ * @param {IDataset} dataset The dataset to render.
+ * @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) {
+ super(dataset, xScale, yScale, xAccessor, yAccessor);
+ this.classed("line-renderer", true);
+ }
+
+ public _anchor(element: D3.Selection) {
+ super._anchor(element);
+ this.path = this.renderArea.append("path").classed("line", true);
+ return this;
+ }
+
+ public _paint() {
+ super._paint();
+ 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);
+ }
+ }
+}
diff --git a/src/renderers/renderer.ts b/src/renderers/renderer.ts
new file mode 100644
index 0000000000..2e6fe3aef0
--- /dev/null
+++ b/src/renderers/renderer.ts
@@ -0,0 +1,134 @@
+///
+
+module Plottable {
+ export interface _IProjector {
+ accessor: IAccessor;
+ scale?: Scale;
+ }
+
+ export class Renderer extends Component {
+ public _dataSource: DataSource;
+
+ public renderArea: D3.Selection;
+ public element: D3.Selection;
+ public scales: Scale[];
+ public _colorAccessor: IAccessor;
+ public _animate = false;
+ public _hasRendered = false;
+ private static defaultColorAccessor = (d: any) => "#1f77b4";
+ public _projectors: { [attrToSet: string]: _IProjector; } = {};
+
+ public _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
+ // maintain their current attributes. If we change the metadata or an
+ // accessor function, then this property will not be true, and we will need
+ // to recompute attributes on the entire update selection.
+
+ public _requireRerender = false;
+ // 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 {any[]|DataSource} [dataset] The data or DataSource to be associated with this Renderer.
+ */
+ constructor();
+ constructor(dataset: any[]);
+ constructor(dataset: DataSource);
+ constructor(dataset?: any) {
+ super();
+ this.clipPathEnabled = true;
+ this._fixedWidth = false;
+ this._fixedHeight = false;
+ this.classed("renderer", true);
+
+ if (dataset != null) {
+ if (typeof dataset.data === "function") { // DataSource
+ this._dataSource = dataset;
+ } else {
+ this._dataSource = new DataSource(dataset);
+ }
+ this._dataSource.registerListener(this, () => this._render());
+ } else {
+ this._dataSource = new DataSource();
+ }
+ }
+
+ /**
+ * Retrieves the current DataSource, or sets a DataSource if the Renderer doesn't yet have one.
+ *
+ * @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 dataSource(): DataSource;
+ public dataSource(source: DataSource): Renderer;
+ public dataSource(source?: DataSource): any {
+ if (source == null) {
+ return this._dataSource;
+ } else if (this._dataSource == null) {
+ this._dataSource = source;
+ this._dataSource.registerListener(this, () => this._render());
+ return this;
+ } else {
+ throw new Error("Can't set a new DataSource on the Renderer if it already has one.");
+ }
+ }
+
+ public project(attrToSet: string, accessor: any, scale?: Scale) {
+ 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, () => this._render());
+ }
+ this._projectors[attrToSet] = {accessor: accessor, scale: scale};
+ this._requireRerender = true;
+ this._rerenderUpdateSelection = true;
+ return this;
+ }
+
+ public _generateAttrToProjector(): { [attrToSet: string]: IAppliedAccessor; } {
+ var h: { [attrName: string]: IAppliedAccessor; } = {};
+ d3.keys(this._projectors).forEach((a) => {
+ var projector = this._projectors[a];
+ var accessor = Utils.applyAccessor(projector.accessor, this.dataSource());
+ var scale = projector.scale;
+ var fn = scale == null ? accessor : (d: any, i: number) => scale.scale(accessor(d, i));
+ h[a] = fn;
+ });
+ return h;
+ }
+
+ public _render(): Renderer {
+ if (this.element != null) {
+ this._hasRendered = true;
+ this._paint();
+ this._requireRerender = false;
+ this._rerenderUpdateSelection = false;
+ }
+ return this;
+ }
+
+ public _paint() {
+ // no-op
+ }
+
+ public _anchor(element: D3.Selection) {
+ super._anchor(element);
+ this.renderArea = this.content.append("g").classed("render-area", true);
+ return this;
+ }
+ }
+}
diff --git a/src/renderers/squareRenderer.ts b/src/renderers/squareRenderer.ts
new file mode 100644
index 0000000000..27193ac240
--- /dev/null
+++ b/src/renderers/squareRenderer.ts
@@ -0,0 +1,41 @@
+///
+
+module Plottable {
+ export class SquareRenderer extends XYRenderer {
+ private _rAccessor: any;
+ private static defaultRAccessor = 3;
+
+ /**
+ * Creates a SquareRenderer.
+ *
+ * @constructor
+ * @param {IDataset} dataset The dataset to render.
+ * @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: Scale, yScale: Scale,
+ xAccessor?: IAccessor, yAccessor?: IAccessor, rAccessor?: IAccessor) {
+ super(dataset, xScale, yScale, xAccessor, yAccessor);
+ this.project("r", 3);
+ this.classed("square-renderer", true);
+ }
+
+ public _paint() {
+ super._paint();
+ var attrToProjector = this._generateAttrToProjector();
+ var xF = attrToProjector["x"];
+ var yF = attrToProjector["y"];
+ var rF = attrToProjector["r"];
+ attrToProjector["x"] = (d: any, i: number) => xF(d, i) - rF(d, i);
+ attrToProjector["y"] = (d: any, i: number) => yF(d, i) - rF(d, i);
+
+ this.dataSelection = this.renderArea.selectAll("rect").data(this._dataSource.data());
+ this.dataSelection.enter().append("rect");
+ this.dataSelection.attr(attrToProjector);
+ this.dataSelection.exit().remove();
+ }
+ }
+}
diff --git a/src/renderers/xyRenderer.ts b/src/renderers/xyRenderer.ts
new file mode 100644
index 0000000000..7cd4f4de6e
--- /dev/null
+++ b/src/renderers/xyRenderer.ts
@@ -0,0 +1,62 @@
+///
+
+module Plottable {
+ export class XYRenderer extends Renderer {
+ public dataSelection: D3.UpdateSelection;
+ public xScale: Scale;
+ public yScale: Scale;
+ public _xAccessor: any;
+ public _yAccessor: any;
+
+ /**
+ * Creates an XYRenderer.
+ *
+ * @constructor
+ * @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: any = "x", yAccessor: any = "y") {
+ super(dataset);
+ this.classed("xy-renderer", true);
+
+ this.project("x", xAccessor, xScale);
+ this.project("y", yAccessor, yScale);
+ }
+
+ public project(attrToSet: string, accessor: any, scale?: Scale) {
+ super.project(attrToSet, accessor, scale);
+ // 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;
+ }
+
+ public _computeLayout(xOffset?: number, yOffset?: number, availableWidth?: number, availableHeight? :number) {
+ this._hasRendered = false;
+ super._computeLayout(xOffset, yOffset, availableWidth, availableHeight);
+ this.xScale.range([0, this.availableWidth]);
+ this.yScale.range([this.availableHeight, 0]);
+ return this;
+ }
+
+ private rescale() {
+ if (this.element != null && this._hasRendered) {
+ this._render();
+ }
+ }
+ }
+}
diff --git a/src/scales/colorScale.ts b/src/scales/colorScale.ts
index ab26570bd4..bf8a76fd0c 100644
--- a/src/scales/colorScale.ts
+++ b/src/scales/colorScale.ts
@@ -2,11 +2,13 @@
module Plottable {
export 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) {
var scale: D3.Scale.Scale;
diff --git a/src/scales/interpolatedColorScale.ts b/src/scales/interpolatedColorScale.ts
new file mode 100644
index 0000000000..f0e8fb400b
--- /dev/null
+++ b/src/scales/interpolatedColorScale.ts
@@ -0,0 +1,113 @@
+///
+
+module Plottable {
+ export class InterpolatedColorScale extends LinearScale {
+ private static COLOR_SCALES = {
+ reds : [
+ "#FFFFFF", // white
+ "#FFF6E1",
+ "#FEF4C0",
+ "#FED976",
+ "#FEB24C",
+ "#FD8D3C",
+ "#FC4E2A",
+ "#E31A1C",
+ "#B10026" // red
+ ],
+ blues : [
+ "#FFFFFF", // white
+ "#CCFFFF",
+ "#A5FFFD",
+ "#85F7FB",
+ "#6ED3EF",
+ "#55A7E0",
+ "#417FD0",
+ "#2545D3",
+ "#0B02E1" // blue
+ ],
+ posneg : [
+ "#0B02E1", // blue
+ "#2545D3",
+ "#417FD0",
+ "#55A7E0",
+ "#6ED3EF",
+ "#85F7FB",
+ "#A5FFFD",
+ "#CCFFFF",
+ "#FFFFFF", // white
+ "#FFF6E1",
+ "#FEF4C0",
+ "#FED976",
+ "#FEB24C",
+ "#FD8D3C",
+ "#FC4E2A",
+ "#E31A1C",
+ "#B10026" // red
+ ]
+ };
+
+ /**
+ * 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.
+ */
+ private static INTERPOLATE_COLORS(colors:string[]): D3.Scale.LinearScale {
+ if (colors.length < 2) throw new Error("Color scale arrays must have at least two elements.");
+ return d3.scale.linear()
+ .range([0, 1])
+ .interpolate((ignored:any): any => {
+ return (t: any): any => {
+ // 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);
+ };
+ });
+ }
+
+ /**
+ * 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) {
+ var scale: D3.Scale.LinearScale;
+ 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(scale);
+ }
+ }
+}
diff --git a/src/scales/linearScale.ts b/src/scales/linearScale.ts
index 7f72350c8b..38d15a89ad 100644
--- a/src/scales/linearScale.ts
+++ b/src/scales/linearScale.ts
@@ -13,7 +13,6 @@ module Plottable {
constructor(scale: D3.Scale.LinearScale);
constructor(scale?: any) {
super(scale == null ? d3.scale.linear() : scale);
- this.domain([Infinity, -Infinity]);
}
/**
diff --git a/src/scales/ordinalScale.ts b/src/scales/ordinalScale.ts
index e84bfaf632..9a44a21b39 100644
--- a/src/scales/ordinalScale.ts
+++ b/src/scales/ordinalScale.ts
@@ -3,12 +3,13 @@
module Plottable {
export class OrdinalScale extends Scale {
public _d3Scale: D3.Scale.OrdinalScale;
- // Padding as a proportion of the spacing between domain values
- private INNER_PADDING = 0.3;
- private OUTER_PADDING = 0.5;
private _range = [0, 1];
private _rangeType: string = "points";
+ // Padding as a proportion of the spacing between domain values
+ private _innerPadding: number = 0.3;
+ private _outerPadding: number = 0.5;
+
/**
* Creates a new OrdinalScale. Domain and Range are set later.
*
@@ -18,17 +19,30 @@ module Plottable {
super(d3.scale.ordinal());
}
+ public _getCombinedExtent(): any [] {
+ var extents = super._getCombinedExtent();
+ var concatenatedExtents: string[] = [];
+ extents.forEach((e) => {
+ concatenatedExtents = concatenatedExtents.concat(e);
+ });
+ return Utils.uniq(concatenatedExtents);
+ }
+
+ /**
+ * 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[]): Scale;
+ public domain(values: any[]): OrdinalScale;
public domain(values?: any[]): any {
- if (values == null) {
- return this._d3Scale.domain();
- } else {
- this._d3Scale.domain(values);
- this._broadcasterCallbacks.forEach((b) => b(this));
- this.range(this.range()); // update range
- return this;
- }
+ return super.domain(values);
+ }
+
+ public _setDomain(values: any[]) {
+ super._setDomain(values);
+ this.range(this.range()); // update range
}
/**
@@ -41,13 +55,14 @@ module Plottable {
public range(values: number[]): OrdinalScale;
public range(values?: number[]): any {
if (values == null) {
+ this._autoDomainIfNeeded();
return this._range;
} else {
this._range = values;
- if (this._rangeType === "points"){
- this._d3Scale.rangePoints(values, 2*this.OUTER_PADDING); // d3 scale takes total padding
+ 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.INNER_PADDING, this.OUTER_PADDING);
+ this._d3Scale.rangeBands(values, this._innerPadding, this._outerPadding);
}
return this;
}
@@ -59,56 +74,37 @@ module Plottable {
* @returns {number} The range band width or 0 if rangeType isn't "bands".
*/
public rangeBand() : number {
- if (this._rangeType === "bands") {
- return this._d3Scale.rangeBand();
- } else {
- return 0;
- }
+ this._autoDomainIfNeeded();
+ return this._d3Scale.rangeBand();
}
/**
* 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.
- * @returns {string|OrdinalScale} The current range type, or the calling OrdinalScale.
+ * @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) : OrdinalScale;
- public rangeType(rangeType?: string) : any {
- if (rangeType == null){
+ public rangeType(rangeType: string, outerPadding?: number, innerPadding?: number) : OrdinalScale;
+ public rangeType(rangeType?: string, outerPadding?: number, innerPadding?: number) : any {
+ if (rangeType == null) {
return this._rangeType;
} else {
- if(!(rangeType === "points" || rangeType === "bands")){
+ 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;
}
}
-
- public widenDomainOnData(data: any[], accessor?: IAccessor): OrdinalScale {
- var changed = false;
- var newDomain = this.domain();
- var a: (d: any, i: number) => any;
- if (accessor == null) {
- a = (d, i) => d;
- } else if (typeof(accessor) === "string") {
- a = (d, i) => d[accessor];
- } else if (typeof(accessor) === "function") {
- a = accessor;
- } else {
- a = (d, i) => accessor;
- }
- data.map(a).forEach((d) => {
- if (newDomain.indexOf(d) === -1) {
- newDomain.push(d);
- changed = true;
- }
- });
- if (changed) {
- this.domain(newDomain);
- }
- return this;
- }
}
}
diff --git a/src/scales/quantitiveScale.ts b/src/scales/quantitiveScale.ts
index ff164bc034..66b468c2fe 100644
--- a/src/scales/quantitiveScale.ts
+++ b/src/scales/quantitiveScale.ts
@@ -15,6 +15,24 @@ module Plottable {
super(scale);
}
+ public _getCombinedExtent(): any [] {
+ var extents = super._getCombinedExtent();
+ var starts: number[] = extents.map((e) => e[0]);
+ var ends: number[] = extents.map((e) => e[1]);
+ return [d3.min(starts), d3.max(ends)];
+ }
+
+ public autorangeDomain() {
+ super.autorangeDomain();
+ if (this._autoPad) {
+ this.padDomain();
+ }
+ if (this._autoNice) {
+ this.nice();
+ }
+ return this;
+ }
+
/**
* Retrieves the domain value corresponding to a supplied range value.
*
@@ -34,31 +52,10 @@ module 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.
- */
- public widenDomain(newDomain: number[]) {
- 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.
- */
- public widenDomainOnData(data: any[], accessor?: IAccessor) {
- var extent = d3.extent(data, accessor);
- this.widenDomain(extent);
- return this;
+ public domain(): any[];
+ public domain(values: any[]): QuantitiveScale;
+ public domain(values?: any[]): any {
+ return super.domain(values); // need to override type sig to enable method chaining :/
}
/**
@@ -110,7 +107,7 @@ module Plottable {
*/
public nice(count?: number) {
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;
}
@@ -148,7 +145,7 @@ module 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;
}
}
diff --git a/src/scales/scale.ts b/src/scales/scale.ts
index 1d0fcb7ebb..e9d4200a95 100644
--- a/src/scales/scale.ts
+++ b/src/scales/scale.ts
@@ -1,10 +1,18 @@
///
module Plottable {
- export class Scale implements IBroadcaster {
+ interface IPerspective {
+ dataSource: DataSource;
+ accessor: IAccessor;
+ }
+ export class Scale extends Broadcaster {
public _d3Scale: D3.Scale.Scale;
- public _broadcasterCallbacks: IBroadcasterCallback[] = [];
-
+ public _autoDomain = true;
+ private rendererID2Perspective: {[rendererID: string]: IPerspective} = {};
+ private dataSourceReferenceCounter = new Utils.IDCounter();
+ private isAutorangeUpToDate = false;
+ public _autoNice = false;
+ public _autoPad = false;
/**
* Creates a new Scale.
*
@@ -12,9 +20,66 @@ module Plottable {
* @param {D3.Scale.Scale} scale The D3 scale backing the Scale.
*/
constructor(scale: D3.Scale.Scale) {
+ super();
this._d3Scale = scale;
}
+ public _getCombinedExtent(): any[] {
+ var perspectives: IPerspective[] = d3.values(this.rendererID2Perspective);
+ var extents = perspectives.map((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.
+ */
+ public autorangeDomain() {
+ this.isAutorangeUpToDate = true;
+ this._setDomain(this._getCombinedExtent());
+ return this;
+ }
+
+ public _autoDomainIfNeeded() {
+ if (!this.isAutorangeUpToDate && this._autoDomain) {
+ this.autorangeDomain();
+ }
+ }
+
+ public _addPerspective(rendererIDAttr: string, dataSource: DataSource, accessor: any) {
+ 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, () => this.isAutorangeUpToDate = false );
+ }
+
+ this.isAutorangeUpToDate = false;
+ return this;
+ }
+
+ public _removePerspective(rendererIDAttr: string) {
+ 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.
*
@@ -22,27 +87,37 @@ module Plottable {
* @returns {any} The range value corresponding to the supplied domain value.
*/
public scale(value: any) {
+ this._autoDomainIfNeeded();
return this._d3Scale(value);
}
/**
* 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[];
public domain(values: any[]): Scale;
public domain(values?: any[]): any {
if (values == null) {
+ this._autoDomainIfNeeded();
return this._d3Scale.domain();
} else {
- this._d3Scale.domain(values);
- this._broadcasterCallbacks.forEach((b) => b(this));
+ this._autoDomain = false;
+ this._setDomain(values);
return this;
}
}
+ public _setDomain(values: any[]) {
+ this._d3Scale.domain(values);
+ this._broadcast();
+ }
+
/**
* Retrieves the current range, or sets the Scale's range to the specified values.
*
@@ -53,6 +128,7 @@ module Plottable {
public range(values: any[]): Scale;
public range(values?: any[]): any {
if (values == null) {
+ this._autoDomainIfNeeded();
return this._d3Scale.range();
} else {
this._d3Scale.range(values);
@@ -68,29 +144,5 @@ module Plottable {
public copy(): Scale {
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.
- */
- public registerListener(callback: IBroadcasterCallback) {
- 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.
- */
- public widenDomainOnData(data: any[], accessor?: IAccessor): Scale {
- // no-op; implementation is sublcass-dependent
- return this;
- }
}
}
diff --git a/src/squareRenderer.ts b/src/squareRenderer.ts
deleted file mode 100644
index 201b78841c..0000000000
--- a/src/squareRenderer.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-///
-
-module Plottable {
- export class SquareRenderer extends NumericXYRenderer {
- private _rAccessor: any;
- private static defaultRAccessor = 3;
-
- /**
- * 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 {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) {
- super(dataset, xScale, yScale, xAccessor, yAccessor);
- this._rAccessor = (rAccessor != null) ? rAccessor : SquareRenderer.defaultRAccessor;
- this.classed("square-renderer", true);
- }
-
-
- public rAccessor(a: any) {
- this._rAccessor = a;
- this._requireRerender = true;
- this._rerenderUpdateSelection = true;
- return this;
- }
-
- public _paint() {
- super._paint();
- 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 = (d: any, i: number) =>
- this.xScale.scale(xA(d, i)) - rA(d, i);
-
- var yFn = (d: any, i: number) =>
- this.yScale.scale(yA(d, i)) - rA(d, i);
-
- this.dataSelection = this.renderArea.selectAll("rect").data(this._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.exit().remove();
- }
- }
-}
diff --git a/src/utils.ts b/src/utils.ts
index 8e076cadbc..d82cff7853 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -116,5 +116,109 @@ module Plottable {
return width;
}
+ export function accessorize(accessor: any): IAccessor {
+ if (typeof(accessor) === "function") {
+ return ( accessor);
+ } else if (typeof(accessor) === "string" && accessor[0] !== "#") {
+ return (d: any, i: number, s: any) => d[accessor];
+ } else {
+ return (d: any, i: number, s: any) => accessor;
+ };
+ }
+
+ export function applyAccessor(accessor: IAccessor, dataSource: DataSource) {
+ var activatedAccessor = accessorize(accessor);
+ return (d: any, i: number) => activatedAccessor(d, i, dataSource.metadata());
+ }
+
+ export function uniq(strings: string[]): string[] {
+ var seen: {[s: string]: boolean} = {};
+ strings.forEach((s) => seen[s] = true);
+ return d3.keys(seen);
+ }
+
+ /**
+ * An associative array that can be keyed by anything (inc objects).
+ * Uses pointer equality checks which is why this works.
+ * This power has a price: everything is linear time since it is actually backed by an array...
+ */
+ export class StrictEqualityAssociativeArray {
+ private keyValuePairs: any[][] = [];
+
+ /**
+ * 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) {
+ 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;
+ }
+
+ public get(key: any): any {
+ for (var i=0; i x[1]);
+ }
+
+ public delete(key: any): boolean {
+ for (var i=0; i < this.keyValuePairs.length; i++) {
+ if (this.keyValuePairs[i][0] === key) {
+ this.keyValuePairs.splice(i, 1);
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ export class IDCounter {
+ private counter: {[id: string]: number} = {};
+
+ private setDefault(id: any) {
+ if (this.counter[id] == null) {
+ this.counter[id] = 0;
+ }
+ }
+
+ public increment(id: any): number {
+ this.setDefault(id);
+ return ++this.counter[id];
+ }
+
+ public decrement(id: any): number {
+ this.setDefault(id);
+ return --this.counter[id];
+ }
+
+ public get(id: any): number {
+ this.setDefault(id);
+ return this.counter[id];
+ }
+ }
}
}
diff --git a/src/xyRenderer.ts b/src/xyRenderer.ts
deleted file mode 100644
index 916bad4bc3..0000000000
--- a/src/xyRenderer.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-///
-
-module Plottable {
- export class XYRenderer extends Renderer {
- public dataSelection: D3.UpdateSelection;
- public xScale: Scale;
- public yScale: Scale;
- public _xAccessor: any;
- public _yAccessor: any;
- public autorangeDataOnLayout = true;
-
- /**
- * Creates an XYRenderer.
- *
- * @constructor
- * @param {IDataset} dataset The dataset to render.
- * @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) {
- super(dataset);
- 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(() => this.rescale());
- this.yScale.registerListener(() => this.rescale());
- }
-
- public xAccessor(accessor: any) {
- this._xAccessor = accessor;
- this._requireRerender = true;
- this._rerenderUpdateSelection = true;
- return this;
- }
-
- public yAccessor(accessor: any) {
- this._yAccessor = accessor;
- this._requireRerender = true;
- this._rerenderUpdateSelection = true;
- return this;
- }
-
- public _computeLayout(xOffset?: number, yOffset?: number, availableWidth?: number, availableHeight? :number) {
- this._hasRendered = false;
- super._computeLayout(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.
- */
- public autorange() {
- super.autorange();
- var data = this._data;
- var xA = (d: any) => this._getAppliedAccessor(this._xAccessor)(d, null);
- this.xScale.widenDomainOnData(data, xA);
-
- var yA = (d: any) => this._getAppliedAccessor(this._yAccessor)(d, null);
- this.yScale.widenDomainOnData(data, yA);
- return this;
- }
-
- private rescale() {
- if (this.element != null && this._hasRendered) {
- this._render();
- }
- }
- }
-}
diff --git a/test/broadcasterTests.ts b/test/broadcasterTests.ts
new file mode 100644
index 0000000000..d63e36fc3a
--- /dev/null
+++ b/test/broadcasterTests.ts
@@ -0,0 +1,57 @@
+///
+
+var assert = chai.assert;
+
+describe("Broadcasters", () => {
+ var b: Plottable.Broadcaster;
+ var called: boolean;
+ var cb: any;
+
+ beforeEach(() => {
+ b = new Plottable.Broadcaster();
+ called = false;
+ cb = () => {called = true;};
+ });
+ it("listeners are called by the broadcast method", () => {
+ b.registerListener(null, cb);
+ b._broadcast();
+ assert.isTrue(called, "callback was called");
+ });
+
+ it("same listener can only be associated with one callback", () => {
+ var called2 = false;
+ var cb2 = () => {called2 = true;};
+ var listener = {};
+ b.registerListener(listener, cb);
+ b.registerListener(listener, cb2);
+ b._broadcast();
+ assert.isFalse(called, "first (overwritten) callback not called");
+ assert.isTrue(called2, "second callback was called");
+ });
+
+ it("listeners can be deregistered", () => {
+ var listener = {};
+ b.registerListener(listener, cb);
+ b.deregisterListener(listener);
+ b._broadcast();
+ assert.isFalse(called, "callback was never called");
+ });
+
+ it("arguments are passed through to callback", () => {
+ var g2 = {};
+ var g3 = "foo";
+ var cb = (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", () => {
+ assert.throws(() => b.deregisterListener({}) );
+ });
+});
diff --git a/test/componentTests.ts b/test/componentTests.ts
index 097920e837..2c7197cf7b 100644
--- a/test/componentTests.ts
+++ b/test/componentTests.ts
@@ -161,7 +161,6 @@ describe("Component behavior", () => {
svg.remove();
});
-
it("component defaults are as expected", () => {
assert.equal(c.rowMinimum(), 0, "rowMinimum defaults to 0");
assert.equal(c.colMinimum(), 0, "colMinimum defaults to 0");
@@ -183,9 +182,8 @@ describe("Component behavior", () => {
it("clipPath works as expected", () => {
assert.isFalse(c.clipPathEnabled, "clipPathEnabled defaults to false");
c.clipPathEnabled = true;
- var expectedClipPathID: number = ( 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");
@@ -194,6 +192,15 @@ describe("Component behavior", () => {
svg.remove();
});
+ it("componentID works as expected", () => {
+ 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", () => {
assert.throws(() => ( c).addBox("pre-anchor"), Error, "Adding boxes before anchoring is currently disallowed");
c.renderTo(svg);
diff --git a/test/coverage.html b/test/coverage.html
new file mode 100644
index 0000000000..062fe10b38
--- /dev/null
+++ b/test/coverage.html
@@ -0,0 +1,48 @@
+
+
+
+
+ Plottable.js Tests
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/dataSourceTests.ts b/test/dataSourceTests.ts
new file mode 100644
index 0000000000..843b78b4dd
--- /dev/null
+++ b/test/dataSourceTests.ts
@@ -0,0 +1,53 @@
+///
+
+var assert = chai.assert;
+
+describe("DataSource", () => {
+ it("Updates listeners when the data is changed", () => {
+ var ds = new Plottable.DataSource();
+
+ var newData = [ 1, 2, 3 ];
+
+ var callbackCalled = false;
+ var callback: Plottable.IBroadcasterCallback = (broadcaster: Plottable.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", () => {
+ var ds = new Plottable.DataSource();
+
+ var newMetadata = "blargh";
+
+ var callbackCalled = false;
+ var callback: Plottable.IBroadcasterCallback = (broadcaster: Plottable.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", () => {
+ var data = [1,2,3,4,1];
+ var metadata = {foo: 11};
+ var dataSource = new Plottable.DataSource(data, metadata);
+ var a1 = (d: number, i: number, m: any) => d + i - 2;
+ assert.deepEqual(dataSource._getExtent(a1), [-1, 5], "extent for numerical data works properly");
+ var a2 = (d: number, i: number, m: any) => 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 = (d: number, i: number, m: any) => "_" + d;
+ assert.deepEqual(dataSource._getExtent(a3), ["_1", "_2", "_3", "_4"], "extent works properly on string domains (no repeats)");
+ });
+});
diff --git a/test/interactionTests.ts b/test/interactionTests.ts
index fc251c45e0..1461499b36 100644
--- a/test/interactionTests.ts
+++ b/test/interactionTests.ts
@@ -33,8 +33,8 @@ describe("Interactions", () => {
it("Pans properly", () => {
// The only difference between pan and zoom is internal to d3
// Simulating zoom events is painful, so panning will suffice here
- var xScale = new Plottable.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);
@@ -70,8 +70,8 @@ describe("Interactions", () => {
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();
});
@@ -81,7 +81,7 @@ describe("Interactions", () => {
var svgWidth = 400;
var svgHeight = 400;
var svg: D3.Selection;
- var dataset: Plottable.IDataset;
+ var dataset: Plottable.DataSource;
var xScale: Plottable.QuantitiveScale;
var yScale: Plottable.QuantitiveScale;
var renderer: Plottable.XYRenderer;
@@ -94,7 +94,7 @@ describe("Interactions", () => {
before(() => {
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);
@@ -150,96 +150,6 @@ describe("Interactions", () => {
});
});
- describe("BrushZoomInteraction", () => {
- it("Zooms in correctly on drag", () =>{
- 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)]; // reversed because Y scale is
-
- var indicesCallbackCalled = false;
- var interaction: any;
- var indicesCallback = (indices: number[]) => {
- 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 = (a: Plottable.SelectionArea) => {
- 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", () => {
- it("Crosshairs manifest basic functionality", () => {
- var svg = generateSVG(400, 400);
- var dp = (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", () => {
it("Triggers the callback only when the Component is moused over and appropriate key is pressed", () => {
var svg = generateSVG(400, 400);
diff --git a/test/quicktests/quicktest-categoryRenderer.html b/test/quicktests/quicktest-categoryRenderer.html
index 04d9d18eed..1d419f6232 100644
--- a/test/quicktests/quicktest-categoryRenderer.html
+++ b/test/quicktests/quicktest-categoryRenderer.html
@@ -33,14 +33,14 @@
var xScale = new Plottable.OrdinalScale();
var xAxis = new Plottable.XAxis(xScale, "bottom", function(d) { return d; } );
- var yScale = new Plottable.LinearScale();
+ var yScale = new Plottable.LinearScale().domain([0, 15]);
var yAxis = new Plottable.YAxis(yScale, "left");
// yAxis.showEndTickLabels(true);
var barRenderer = new Plottable.CategoryBarRenderer(data, xScale, yScale);
- barRenderer.xAccessor("name");
- barRenderer.widthAccessor(50);
- barRenderer.yAccessor("age");
+ barRenderer.project("x", "name", xScale);
+ // barRenderer.widthAccessor(50);
+ barRenderer.project("y", "age", yScale);
var basicTable = new Plottable.Table().addComponent(0, 0, yAxis)
.addComponent(0, 1, barRenderer)
diff --git a/test/quicktests/quicktest-gridRenderer.html b/test/quicktests/quicktest-gridRenderer.html
new file mode 100644
index 0000000000..4217bbdd31
--- /dev/null
+++ b/test/quicktests/quicktest-gridRenderer.html
@@ -0,0 +1,74 @@
+
+
+ Grid Renderer Quicktest
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/rendererTests.ts b/test/rendererTests.ts
index a70581b903..7fa5264dfa 100644
--- a/test/rendererTests.ts
+++ b/test/rendererTests.ts
@@ -12,63 +12,51 @@ describe("Renderers", () => {
assert.isTrue(r.clipPathEnabled, "clipPathEnabled defaults to true");
});
- it("Base renderer functionality works", () => {
+ it("Base Renderer functionality works", () => {
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(() => r.dataSource(d2), Error);
+
svg.remove();
});
- it("rerenderUpdateSelection and requireRerender flags updated appropriately", () => {
+ it("Renderer automatically generates a DataSource if only data is provided", () => {
+ 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", () => {
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");
});
});
describe("XYRenderer functionality", () => {
-
it("the accessors properly access data, index, and metadata", () => {
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 = (d, i?, m?) => d.x + i * m.foo;
var yAccessor = (d, i?, m?) => 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]);
@@ -79,14 +67,14 @@ describe("Renderers", () => {
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");
@@ -102,19 +90,19 @@ describe("Renderers", () => {
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(() => {
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 = (d) => d.foo;
var yAccessor = (d) => d.bar;
var colorAccessor = (d, i, m) => 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;
});
@@ -196,16 +184,14 @@ describe("Renderers", () => {
before(() => {
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", () => {
- 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());
@@ -220,37 +206,6 @@ describe("Renderers", () => {
verifier.end();
});
- it("invertXYSelectionArea works", () => {
- 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", () => {
- 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", () => {
- 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", () => {
before(() => {
xScale.domain([0, 3]);
@@ -259,37 +214,6 @@ describe("Renderers", () => {
dataAreaPart = {xMin: 1, xMax: 3, yMin: 6, yMax: 3};
});
- it("invertXYSelectionArea works", () => {
- 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", () => {
- 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", () => {
- 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", () => {
var renderArea = circleRenderer.renderArea;
var circles = renderArea.selectAll("circle");
@@ -317,16 +241,14 @@ describe("Renderers", () => {
// 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(() => {
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(() => {
@@ -373,7 +295,7 @@ describe("Renderers", () => {
describe("Category Bar Renderer", () => {
var verifier = new MultiTestVerifier();
var svg: D3.Selection;
- var dataset: Plottable.IDataset;
+ var dataset: Plottable.DataSource;
var xScale: Plottable.OrdinalScale;
var yScale: Plottable.LinearScale;
var renderer: Plottable.CategoryBarRenderer;
@@ -382,15 +304,13 @@ describe("Renderers", () => {
before(() => {
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;
@@ -420,5 +340,79 @@ describe("Renderers", () => {
if (verifier.passed) {svg.remove();};
});
});
+
+ describe("Grid Renderer", () => {
+ var verifier = new MultiTestVerifier();
+ var svg: D3.Selection;
+ var xScale: Plottable.OrdinalScale;
+ var yScale: Plottable.OrdinalScale;
+ var colorScale: Plottable.InterpolatedColorScale;
+ var renderer: Plottable.GridRenderer;
+
+ var SVG_WIDTH = 400;
+ var SVG_HEIGHT = 200;
+
+ before(() => {
+ 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(() => {
+ verifier.start();
+ });
+
+ it("renders correctly", () => {
+ 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(() => {
+ if (verifier.passed) {svg.remove();};
+ });
+ });
+
});
});
diff --git a/test/scaleTests.ts b/test/scaleTests.ts
index b9124ea23d..70cf81a607 100644
--- a/test/scaleTests.ts
+++ b/test/scaleTests.ts
@@ -4,26 +4,26 @@ var assert = chai.assert;
describe("Scales", () => {
it("Scale's copy() works correctly", () => {
- var testCallback: Plottable.IBroadcasterCallback = (broadcaster: Plottable.IBroadcaster) => {
+ var testCallback: Plottable.IBroadcasterCallback = (broadcaster: Plottable.Broadcaster) => {
return true; // doesn't do anything
};
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,
+ assert.notDeepEqual(( scale).listener2Callback, ( scaleCopy).listener2Callback,
"Registered callbacks are not copied over");
});
it("Scale alerts listeners when its domain is updated", () => {
var scale = new Plottable.QuantitiveScale(d3.scale.linear());
var callbackWasCalled = false;
- var testCallback: Plottable.IBroadcasterCallback = (broadcaster: Plottable.IBroadcaster) => {
+ var testCallback: Plottable.IBroadcasterCallback = (broadcaster: Plottable.Broadcaster) => {
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");
@@ -36,22 +36,69 @@ describe("Scales", () => {
scale.padDomain(0.2);
assert.isTrue(callbackWasCalled, "The registered callback was called when padDomain() is used to set the domain");
});
+ describe("autoranging behavior", () => {
+ var data: any[];
+ var dataSource: Plottable.DataSource;
+ var scale: Plottable.LinearScale;
+ beforeEach(() => {
+ 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", () => {
- 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", () => {
+ 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", () => {
+ 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("Linear Scales default to a domain of [Infinity, -Infinity]", () => {
- var scale = new Plottable.LinearScale();
- var domain = scale.domain();
- assert.deepEqual(domain, [Infinity, -Infinity]);
+ it("scale reference counting works as expected", () => {
+ 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", () => {
+ 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");
+ });
});
describe("Ordinal Scales", () => {
@@ -80,4 +127,73 @@ describe("Scales", () => {
});
});
+ describe("Color Scales", () => {
+ it("accepts categorical string types and ordinal domain", () => {
+ 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", () => {
+ it("linearly interpolates colors in L*a*b color space", () => {
+ 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", () => {
+ 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", () => {
+ 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", () => {
+ 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", () => {
+ it("defaults to \"points\" range type", () => {
+ var scale = new Plottable.OrdinalScale();
+ assert.deepEqual(scale.rangeType(), "points");
+ });
+
+ it("rangeBand returns 0 when in \"points\" mode", () => {
+ 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", () => {
+ 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);
+ });
+ });
});
diff --git a/test/testUtils.ts b/test/testUtils.ts
index 352e9391b6..2380ea1f37 100644
--- a/test/testUtils.ts
+++ b/test/testUtils.ts
@@ -50,20 +50,19 @@ function assertWidthHeight(el: D3.Selection, widthExpected, heightExpected, mess
assert.equal(height, heightExpected, "height: " + message);
}
-function makeLinearSeries(n: number): Plottable.IDataset {
+
+function makeLinearSeries(n: number): {x: number; y:number;}[] {
function makePoint(x: number) {
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: number): Plottable.IDataset {
+function makeQuadraticSeries(n: number): {x: number; y:number;}[] {
function makeQuadraticPoint(x: number) {
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);
}
class MultiTestVerifier {
diff --git a/test/tests.html b/test/tests.html
index be7066dfdc..dff21a6067 100644
--- a/test/tests.html
+++ b/test/tests.html
@@ -21,7 +21,7 @@
-
+
@@ -33,8 +33,7 @@
mocha.checkLeaks();
if (window.mochaPhantomJS) {
mochaPhantomJS.run();
- }
- else {
+ } else {
mocha.run();
}
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");
+ });
});
diff --git a/test/utilsTests.ts b/test/utilsTests.ts
index 031789e108..ab3c5f50b5 100644
--- a/test/utilsTests.ts
+++ b/test/utilsTests.ts
@@ -61,6 +61,67 @@ describe("Utils", () => {
svg.remove();
});
+ it("accessorize works properly", () => {
+ var datum = {"foo": 2, "bar": 3, "key": 4};
+
+ var f = (d: any, i: number, m: any) => d + i;
+ var a1 = Plottable.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", () => {
+ 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", () => {
+ var strings = ["foo", "bar", "foo", "foo", "baz", "bam"];
+ assert.deepEqual(Plottable.Utils.uniq(strings), ["foo", "bar", "baz", "bam"]);
+ });
+
+ it("IDCounter works as expected", () => {
+ 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", () => {
var parent = getSVGParent();
parent.style("width", "300px");
@@ -83,4 +144,3 @@ describe("Utils", () => {
assert.equal(height, 120, "measured height matches set height");
});
});
-
diff --git a/tslint.json b/tslint.json
index 461cfb1bf5..5da961c424 100644
--- a/tslint.json
+++ b/tslint.json
@@ -1,6 +1,6 @@
{
"rules": {
- "class-name": true,
+ "class-name": false,
"curly": true,
"eofline": true,
"forin": true,
diff --git a/typings/d3/d3.d.ts b/typings/d3/d3.d.ts
index dc1bdbed71..1259223861 100644
--- a/typings/d3/d3.d.ts
+++ b/typings/d3/d3.d.ts
@@ -242,7 +242,7 @@ declare module D3 {
*
* @param map Array of objects to get the values from
*/
- values(map: any[]): any[];
+ values(map: any): any[];
/**
* List the key-value entries of an associative array.
*