Skip to content

Commit

Permalink
doLocked on entityNearest (#3404)
Browse files Browse the repository at this point in the history
also emit sourcemap for plottable.js
  • Loading branch information
hellochar authored Sep 12, 2017
1 parent 1ab5618 commit fc8311d
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 61 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ build/
test/tests.js
test/tests.js.map
plottable.js
plottable.js.map
plottable.min.js

# Typescript
Expand Down
123 changes: 62 additions & 61 deletions src/plots/barPlot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,80 +398,81 @@ export class Bar<X, Y> extends XYPlot<X, Y> {
/**
* Returns the PlotEntity nearest to the query point according to the following algorithm:
* - If the query point is inside a bar, returns the PlotEntity for that bar.
* - Otherwise, gets the nearest PlotEntity by the primary direction (X for vertical, Y for horizontal),
* - Otherwise, gets the nearest PlotEntity by the positional direction (X for vertical, Y for horizontal),
* breaking ties with the secondary direction.
* Returns undefined if no PlotEntity can be found.
*
* @param {Point} queryPoint
* @returns {PlotEntity} The nearest PlotEntity, or undefined if no PlotEntity can be found.
*/
public entityNearest(queryPoint: Point): IPlotEntity {
return this._computeBarPixelThickness.doLocked(() => {
const queryPtPrimary = this._isVertical ? queryPoint.x : queryPoint.y;
const queryPtSecondary = this._isVertical ? queryPoint.y : queryPoint.x;

// SVGRects are positioned with sub-pixel accuracy (the default unit
// for the x, y, height & width attributes), but user selections (e.g. via
// mouse events) usually have pixel accuracy. We add a tolerance of 0.5 pixels.
const tolerance = 0.5;

// PERF: precompute all these values to prevent recomputing for each entity
const chartBounds = this.bounds();
const chartWidth = chartBounds.bottomRight.x - chartBounds.topLeft.x;
const chartHeight = chartBounds.bottomRight.y - chartBounds.topLeft.y;
const xRange = { min: 0, max: chartWidth };
const yRange = { min: 0, max: chartHeight };
const originalPositionProjector = Plot._scaledAccessor(this.length());
const scaledBaseline = this.length().scale.scale(this.baselineValue());
const plotPointFactory = (datum: any, index: number, dataset: Dataset, rect: IEntityBounds) => {
return this._pixelPointBar(originalPositionProjector(datum, index, dataset), scaledBaseline, rect);
};

let minPrimaryDist = Infinity;
let minSecondaryDist = Infinity;
let closest: ILightweightPlotEntity;
this._getEntityStore().entities().forEach((entity: ILightweightPlotEntity) => {
const barBBox = this._entityBounds(entity);
if (!Utils.DOM.intersectsBBox(xRange, yRange, barBBox)) {
return;
}

const queryPtPrimary = this._isVertical ? queryPoint.x : queryPoint.y;
const queryPtSecondary = this._isVertical ? queryPoint.y : queryPoint.x;

// SVGRects are positioned with sub-pixel accuracy (the default unit
// for the x, y, height & width attributes), but user selections (e.g. via
// mouse events) usually have pixel accuracy. We add a tolerance of 0.5 pixels.
const tolerance = 0.5;

// PERF: precompute all these values to prevent recomputing for each entity
const chartBounds = this.bounds();
const chartWidth = chartBounds.bottomRight.x - chartBounds.topLeft.x;
const chartHeight = chartBounds.bottomRight.y - chartBounds.topLeft.y;
const xRange = { min: 0, max: chartWidth };
const yRange = { min: 0, max: chartHeight };
const originalPositionProjector = (this._isVertical ? Plot._scaledAccessor(this.y()) : Plot._scaledAccessor(this.x()));
const scaledBaseline = (<Scale<any, any>> (this._isVertical ? this.y().scale : this.x().scale)).scale(this.baselineValue());
const plotPointFactory = (datum: any, index: number, dataset: Dataset, rect: IEntityBounds) => {
return this._pixelPointBar(originalPositionProjector(datum, index, dataset), scaledBaseline, rect);
};

let minPrimaryDist = Infinity;
let minSecondaryDist = Infinity;
let closest: ILightweightPlotEntity;
this._getEntityStore().entities().forEach((entity: ILightweightPlotEntity) => {
const barBBox = this._entityBounds(entity);
if (!Utils.DOM.intersectsBBox(xRange, yRange, barBBox)) {
return;
}
let primaryDist = 0;
let secondaryDist = 0;

// if we're inside a bar, distance in both directions should stay 0
if (!Utils.DOM.intersectsBBox(queryPoint.x, queryPoint.y, barBBox, tolerance)) {
const plotPt = plotPointFactory(entity.datum, entity.index, entity.dataset, barBBox);
const plotPtPrimary = this._isVertical ? plotPt.x : plotPt.y;
primaryDist = Math.abs(queryPtPrimary - plotPtPrimary);

// compute this bar's min and max along the secondary axis
const barMinSecondary = this._isVertical ? barBBox.y : barBBox.x;
const barMaxSecondary = barMinSecondary + (this._isVertical ? barBBox.height : barBBox.width);

if (queryPtSecondary >= barMinSecondary - tolerance && queryPtSecondary <= barMaxSecondary + tolerance) {
// if we're within a bar's secondary axis span, it is closest in that direction
secondaryDist = 0;
} else {
const plotPtSecondary = this._isVertical ? plotPt.y : plotPt.x;
secondaryDist = Math.abs(queryPtSecondary - plotPtSecondary);
}
}

let primaryDist = 0;
let secondaryDist = 0;

// if we're inside a bar, distance in both directions should stay 0
if (!Utils.DOM.intersectsBBox(queryPoint.x, queryPoint.y, barBBox, tolerance)) {
const plotPt = plotPointFactory(entity.datum, entity.index, entity.dataset, barBBox);
const plotPtPrimary = this._isVertical ? plotPt.x : plotPt.y;
primaryDist = Math.abs(queryPtPrimary - plotPtPrimary);

// compute this bar's min and max along the secondary axis
const barMinSecondary = this._isVertical ? barBBox.y : barBBox.x;
const barMaxSecondary = barMinSecondary + (this._isVertical ? barBBox.height : barBBox.width);

if (queryPtSecondary >= barMinSecondary - tolerance && queryPtSecondary <= barMaxSecondary + tolerance) {
// if we're within a bar's secondary axis span, it is closest in that direction
secondaryDist = 0;
} else {
const plotPtSecondary = this._isVertical ? plotPt.y : plotPt.x;
secondaryDist = Math.abs(queryPtSecondary - plotPtSecondary);
// if we find a closer bar, record its distance and start new closest lists
if (primaryDist < minPrimaryDist
|| primaryDist === minPrimaryDist && secondaryDist < minSecondaryDist) {
closest = entity;
minPrimaryDist = primaryDist;
minSecondaryDist = secondaryDist;
}
}
});

// if we find a closer bar, record its distance and start new closest lists
if (primaryDist < minPrimaryDist
|| primaryDist === minPrimaryDist && secondaryDist < minSecondaryDist) {
closest = entity;
minPrimaryDist = primaryDist;
minSecondaryDist = secondaryDist;
if (closest !== undefined) {
return this._lightweightPlotEntityToPlotEntity(closest);
} else {
return undefined;
}
});

if (closest !== undefined) {
return this._lightweightPlotEntityToPlotEntity(closest);
} else {
return undefined;
}
}

/**
Expand Down
1 change: 1 addition & 0 deletions webpackConfig/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Licensed under MIT (https://github.com/palantir/plottable/blob/master/LICENSE)`;
* a dependency on a module named "plottable" (both in AMD and CommonJS).
*/
module.exports = {
devtool: "source-map",
entry: "./build/src/index.js",
output: {
filename: "plottable.js",
Expand Down

1 comment on commit fc8311d

@blueprint-bot
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doLocked on entityNearest (#3404)

Demo: quicktests | fiddle

Please sign in to comment.