Skip to content

Optimize performance of setCategoryIndex #1544

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Apr 10, 2017
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 23 additions & 7 deletions src/plots/cartesian/set_convert.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,22 +126,36 @@ module.exports = function setConvert(ax, fullLayout) {
*/
function setCategoryIndex(v) {
if(v !== null && v !== undefined) {
var c = ax._categories.indexOf(v);
if(c === -1) {
if(ax._categoriesMap === undefined) {
ax._categoriesMap = {};
}

if(ax._categoriesMap[v] !== undefined) {
return ax._categoriesMap[v];
} else {
ax._categories.push(v);
return ax._categories.length - 1;

var curLength = ax._categories.length - 1;
ax._categoriesMap[v] = curLength;

return curLength;
}
return c;
}
return BADNUM;
}

function getCategoryIndex(v) {
// d2l/d2c variant that that won't add categories but will also
// allow numbers to be mapped to the linearized axis positions
var index = ax._categories.indexOf(v);
if(index !== -1) return index;
if(typeof v === 'number') return v;
var index;
if(ax._categoriesMap) {
index = ax._categoriesMap[v] ? ax._categoriesMap : undefined;
if(index !== undefined) return index;
} else {
index = ax._categories.indexOf(v);
if(index !== -1) return index;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

If this block is only here to fix that test, and never gets hit in real code (which looks to be the case to me, since ax._categories and ax._categoriesMap always get populated simultaneously) then we should leave out the else block here, and just make the test more realistic by creating the corresponding _categoriesMap in the mock ax object.

For the record, this made me curious "do we still need ax._categories at all?" But you were right to not remove it, as getCategoryName (ax.c2d etc, ie hover info) still needs the reverse mapping to be fast.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Totally agree. I will make the change on the mock object instead.

Yeah I tried to completely replacing the _categories array with the map, but there will be some cases we need a reverse mapping.

Actually, comparing to maintaining a pair of synced-up array and map, I feel there must be some better data structure for a performant categories collection. But I was sort of lazy and did not give it another think anymore.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I feel there must be some better data structure for a performant categories collection.

Good point, I'm sure there is, though it won't be built in so is unlikely to improve performance, it would just clean up the API... lets not worry about it for now, this two-part solution is light and easy enough to use here but we can look into it further if this comes up in enough other places.

if(typeof v === 'number') { return v; }
}

function l2p(v) {
Expand Down Expand Up @@ -325,6 +339,8 @@ module.exports = function setConvert(ax, fullLayout) {

// TODO cleaner way to handle this case
if(!ax._categories) ax._categories = [];
// Add a map to optimize the performance of category collection
if(!ax._categoriesMap) ax._categoriesMap = {};

// make sure we have a domain (pull it in from the axis
// this one is overlaying if necessary)
Expand Down
9 changes: 9 additions & 0 deletions src/plots/plots.js
Original file line number Diff line number Diff line change
Expand Up @@ -1950,6 +1950,13 @@ plots.doCalcdata = function(gd, traces) {
// to be filled in later by ax.d2c
for(i = 0; i < axList.length; i++) {
axList[i]._categories = axList[i]._initialCategories.slice();

// Build the lookup map for initialized categories
axList[i]._categoriesMap = {};
for(j = 0; j < axList[i]._categories.length; j++) {
axList[i]._categoriesMap[axList[i]._categories[j]] = j;
}

if(axList[i].type === 'category') hasCategoryAxis = true;
}

Expand Down Expand Up @@ -1994,6 +2001,8 @@ plots.doCalcdata = function(gd, traces) {
axList[i]._min = [];
axList[i]._max = [];
axList[i]._categories = [];
// Reset the look up map
axList[i]._categoriesMap = {};
}
}

Expand Down
8 changes: 4 additions & 4 deletions src/traces/scatter/plot.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,13 @@ module.exports = function plot(gd, plotinfo, cdscatter, transitionOpts, makeOnCo
// Sort the traces, once created, so that the ordering is preserved even when traces
// are shown and hidden. This is needed since we're not just wiping everything out
// and recreating on every update.
for(i = 0, uids = []; i < cdscatter.length; i++) {
uids[i] = cdscatter[i][0].trace.uid;
for(i = 0, uids = {}; i < cdscatter.length; i++) {
uids[cdscatter[i][0].trace.uid] = i;
}

scatterlayer.selectAll('g.trace').sort(function(a, b) {
var idx1 = uids.indexOf(a[0].trace.uid);
var idx2 = uids.indexOf(b[0].trace.uid);
var idx1 = uids[a[0].trace.uid];
var idx2 = uids[b[0].trace.uid];
return idx1 > idx2 ? 1 : -1;
});

Expand Down
1 change: 1 addition & 0 deletions test/jasmine/tests/axes_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1804,6 +1804,7 @@ describe('Test axes', function() {

function _autoBin(x, ax, nbins) {
ax._categories = [];
ax._categoriesMap = {};
Axes.setConvert(ax);

var d = ax.makeCalcdata({ x: x }, 'x');
Expand Down