Skip to content

Commit 10912bd

Browse files
committed
Merge branch 'master' into misc-geo-fixes
2 parents 75930b9 + 863e8d0 commit 10912bd

27 files changed

+882
-29
lines changed

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@
142142
"karma-firefox-launcher": "^1.0.1",
143143
"karma-ie-launcher": "^1.0.0",
144144
"karma-jasmine": "^1.1.2",
145-
"karma-jasmine-spec-tags": "^1.1.0",
145+
"karma-jasmine-spec-tags": "^1.0.1",
146146
"karma-spec-reporter": "0.0.32",
147147
"karma-verbose-reporter": "0.0.6",
148148
"karma-viewport": "^1.0.4",

src/components/legend/style.js

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -356,12 +356,27 @@ module.exports = function style(s, gd) {
356356

357357
pts.each(function() {
358358
var p = d3.select(this);
359-
var w = boundLineWidth(undefined, trace.line, MAX_MARKER_LINE_WIDTH, CST_MARKER_LINE_WIDTH);
360359

361-
p.style('stroke-width', w + 'px')
362-
.call(Color.fill, trace.fillcolor);
360+
if((trace.boxpoints === 'all' || trace.points === 'all') &&
361+
Color.opacity(trace.fillcolor) === 0 && Color.opacity((trace.line || {}).color) === 0
362+
) {
363+
var tMod = Lib.minExtend(trace, {
364+
marker: {
365+
size: constantItemSizing ? CST_MARKER_SIZE : Lib.constrain(trace.marker.size, 2, 16),
366+
sizeref: 1,
367+
sizemin: 1,
368+
sizemode: 'diameter'
369+
}
370+
});
371+
pts.call(Drawing.pointStyle, tMod, gd);
372+
} else {
373+
var w = boundLineWidth(undefined, trace.line, MAX_MARKER_LINE_WIDTH, CST_MARKER_LINE_WIDTH);
363374

364-
if(w) Color.stroke(p, trace.line.color);
375+
p.style('stroke-width', w + 'px')
376+
.call(Color.fill, trace.fillcolor);
377+
378+
if(w) Color.stroke(p, trace.line.color);
379+
}
365380
});
366381
}
367382

src/lib/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ var statsModule = require('./stats');
7575
lib.aggNums = statsModule.aggNums;
7676
lib.len = statsModule.len;
7777
lib.mean = statsModule.mean;
78+
lib.median = statsModule.median;
7879
lib.midRange = statsModule.midRange;
7980
lib.variance = statsModule.variance;
8081
lib.stdev = statsModule.stdev;

src/lib/stats.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,15 @@ exports.stdev = function(data, len, mean) {
7474
return Math.sqrt(exports.variance(data, len, mean));
7575
};
7676

77+
/**
78+
* median of a finite set of numbers
79+
* reference page: https://en.wikipedia.org/wiki/Median#Finite_set_of_numbers
80+
**/
81+
exports.median = function(data) {
82+
var b = data.slice().sort();
83+
return exports.interp(b, 0.5);
84+
};
85+
7786
/**
7887
* interp() computes a percentile (quantile) for a given distribution.
7988
* We interpolate the distribution (to compute quantiles, we follow method #10 here:

src/plot_api/plot_schema.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,8 @@ function getTraceAttributes(type) {
503503

504504
var out = {
505505
meta: _module.meta || {},
506+
categories: _module.categories || {},
507+
type: type,
506508
attributes: formatAttributes(attributes),
507509
};
508510

src/plots/cartesian/layout_attributes.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -817,8 +817,13 @@ module.exports = {
817817
categoryorder: {
818818
valType: 'enumerated',
819819
values: [
820-
'trace', 'category ascending', 'category descending', 'array'
821-
/* , 'value ascending', 'value descending'*/ // value ascending / descending to be implemented later
820+
'trace', 'category ascending', 'category descending', 'array',
821+
'total ascending', 'total descending',
822+
'min ascending', 'min descending',
823+
'max ascending', 'max descending',
824+
'sum ascending', 'sum descending',
825+
'mean ascending', 'mean descending',
826+
'median ascending', 'median descending'
822827
],
823828
dflt: 'trace',
824829
role: 'info',
@@ -828,11 +833,12 @@ module.exports = {
828833
'By default, plotly uses *trace*, which specifies the order that is present in the data supplied.',
829834
'Set `categoryorder` to *category ascending* or *category descending* if order should be determined by',
830835
'the alphanumerical order of the category names.',
831-
/* 'Set `categoryorder` to *value ascending* or *value descending* if order should be determined by the',
832-
'numerical order of the values.',*/ // // value ascending / descending to be implemented later
833836
'Set `categoryorder` to *array* to derive the ordering from the attribute `categoryarray`. If a category',
834837
'is not found in the `categoryarray` array, the sorting behavior for that attribute will be identical to',
835-
'the *trace* mode. The unspecified categories will follow the categories in `categoryarray`.'
838+
'the *trace* mode. The unspecified categories will follow the categories in `categoryarray`.',
839+
'Set `categoryorder` to *total ascending* or *total descending* if order should be determined by the',
840+
'numerical order of the values.',
841+
'Similarly, the order can be determined by the min, max, sum, mean or median of all the values.'
836842
].join(' ')
837843
},
838844
categoryarray: {

src/plots/cartesian/select.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,15 @@ function prepSelect(e, startX, startY, dragOptions, mode) {
8585
var searchTraces = determineSearchTraces(gd, dragOptions.xaxes,
8686
dragOptions.yaxes, dragOptions.subplot);
8787

88+
// in v2 (once log ranges are fixed),
89+
// we'll be able to p2r here for all axis types
90+
function p2r(ax, v) {
91+
return ax.type === 'log' ? ax.p2d(v) : ax.p2r(v);
92+
}
93+
8894
function axValue(ax) {
8995
var index = (ax._id.charAt(0) === 'y') ? 1 : 0;
90-
return function(v) { return ax.p2d(v[index]); };
96+
return function(v) { return p2r(ax, v[index]); };
9197
}
9298

9399
function ascending(a, b) { return a - b; }
@@ -107,8 +113,8 @@ function prepSelect(e, startX, startY, dragOptions, mode) {
107113
var axLetter = ax._id.charAt(0);
108114

109115
ranges[ax._id] = [
110-
ax.p2d(poly[axLetter + 'min']),
111-
ax.p2d(poly[axLetter + 'max'])
116+
p2r(ax, poly[axLetter + 'min']),
117+
p2r(ax, poly[axLetter + 'max'])
112118
].sort(ascending);
113119
}
114120
};

src/plots/cartesian/set_convert.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,37 @@ module.exports = function setConvert(ax, fullLayout) {
612612
}
613613
};
614614

615+
// sort the axis (and all the matching ones) by _initialCategories
616+
// returns the indices of the traces affected by the reordering
617+
ax.sortByInitialCategories = function() {
618+
var affectedTraces = [];
619+
var emptyCategories = function() {
620+
ax._categories = [];
621+
ax._categoriesMap = {};
622+
};
623+
624+
emptyCategories();
625+
626+
if(ax._initialCategories) {
627+
for(var j = 0; j < ax._initialCategories.length; j++) {
628+
setCategoryIndex(ax._initialCategories[j]);
629+
}
630+
}
631+
632+
affectedTraces = affectedTraces.concat(ax._traceIndices);
633+
634+
// Propagate to matching axes
635+
var group = ax._matchGroup;
636+
for(var axId2 in group) {
637+
if(axId === axId2) continue;
638+
var ax2 = fullLayout[axisIds.id2name(axId2)];
639+
ax2._categories = ax._categories;
640+
ax2._categoriesMap = ax._categoriesMap;
641+
affectedTraces = affectedTraces.concat(ax2._traceIndices);
642+
}
643+
return affectedTraces;
644+
};
645+
615646
// Propagate localization into the axis so that
616647
// methods in Axes can use it w/o having to pass fullLayout
617648
// Default (non-d3) number formatting uses separators directly

src/plots/cartesian/type_defaults.js

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -80,14 +80,8 @@ function setAutoType(ax, data) {
8080
ax.type = autoType(boxPositions, calendar, opts);
8181
} else if(d0.type === 'splom') {
8282
var dimensions = d0.dimensions;
83-
var diag = d0._diag;
84-
for(i = 0; i < dimensions.length; i++) {
85-
var dim = dimensions[i];
86-
if(dim.visible && (diag[i][0] === id || diag[i][1] === id)) {
87-
ax.type = autoType(dim.values, calendar, opts);
88-
break;
89-
}
90-
}
83+
var dim = dimensions[d0._axesDim[id]];
84+
if(dim.visible) ax.type = autoType(dim.values, calendar, opts);
9185
} else {
9286
ax.type = autoType(d0[axLetter] || [d0[axLetter + '0']], calendar, opts);
9387
}

src/plots/plots.js

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2847,10 +2847,196 @@ plots.doCalcdata = function(gd, traces) {
28472847

28482848
doCrossTraceCalc(gd);
28492849

2850+
// Sort axis categories per value if specified
2851+
var sorted = sortAxisCategoriesByValue(axList, gd);
2852+
if(sorted.length) {
2853+
// If a sort operation was performed, run calc() again
2854+
for(i = 0; i < sorted.length; i++) calci(sorted[i], true);
2855+
for(i = 0; i < sorted.length; i++) calci(sorted[i], false);
2856+
doCrossTraceCalc(gd);
2857+
}
2858+
28502859
Registry.getComponentMethod('fx', 'calc')(gd);
28512860
Registry.getComponentMethod('errorbars', 'calc')(gd);
28522861
};
28532862

2863+
var sortAxisCategoriesByValueRegex = /(total|sum|min|max|mean|median) (ascending|descending)/;
2864+
2865+
function sortAxisCategoriesByValue(axList, gd) {
2866+
var affectedTraces = [];
2867+
var i, j, k, l, o;
2868+
2869+
function zMapCategory(type, ax, value) {
2870+
var axLetter = ax._id.charAt(0);
2871+
if(type === 'histogram2dcontour') {
2872+
var counterAxLetter = ax._counterAxes[0];
2873+
var counterAx = axisIDs.getFromId(gd, counterAxLetter);
2874+
2875+
var xCategorical = axLetter === 'x' || (counterAxLetter === 'x' && counterAx.type === 'category');
2876+
var yCategorical = axLetter === 'y' || (counterAxLetter === 'y' && counterAx.type === 'category');
2877+
2878+
return function(o, l) {
2879+
if(o === 0 || l === 0) return -1; // Skip first row and column
2880+
if(xCategorical && o === value[l].length - 1) return -1;
2881+
if(yCategorical && l === value.length - 1) return -1;
2882+
2883+
return (axLetter === 'y' ? l : o) - 1;
2884+
};
2885+
} else {
2886+
return function(o, l) {
2887+
return axLetter === 'y' ? l : o;
2888+
};
2889+
}
2890+
}
2891+
2892+
var aggFn = {
2893+
'min': function(values) {return Lib.aggNums(Math.min, null, values);},
2894+
'max': function(values) {return Lib.aggNums(Math.max, null, values);},
2895+
'sum': function(values) {return Lib.aggNums(function(a, b) { return a + b;}, null, values);},
2896+
'total': function(values) {return Lib.aggNums(function(a, b) { return a + b;}, null, values);},
2897+
'mean': function(values) {return Lib.mean(values);},
2898+
'median': function(values) {return Lib.median(values);}
2899+
};
2900+
2901+
for(i = 0; i < axList.length; i++) {
2902+
var ax = axList[i];
2903+
if(ax.type !== 'category') continue;
2904+
2905+
// Order by value
2906+
var match = ax.categoryorder.match(sortAxisCategoriesByValueRegex);
2907+
if(match) {
2908+
var aggregator = match[1];
2909+
var order = match[2];
2910+
2911+
// Store values associated with each category
2912+
var categoriesValue = [];
2913+
for(j = 0; j < ax._categories.length; j++) {
2914+
categoriesValue.push([ax._categories[j], []]);
2915+
}
2916+
2917+
// Collect values across traces
2918+
for(j = 0; j < ax._traceIndices.length; j++) {
2919+
var traceIndex = ax._traceIndices[j];
2920+
var fullTrace = gd._fullData[traceIndex];
2921+
var axLetter = ax._id.charAt(0);
2922+
2923+
// Skip over invisible traces
2924+
if(fullTrace.visible !== true) continue;
2925+
2926+
var type = fullTrace.type;
2927+
if(Registry.traceIs(fullTrace, 'histogram')) delete fullTrace._autoBinFinished;
2928+
2929+
var cd = gd.calcdata[traceIndex];
2930+
for(k = 0; k < cd.length; k++) {
2931+
var cdi = cd[k];
2932+
var cat, catIndex, value;
2933+
2934+
if(type === 'splom') {
2935+
// If `splom`, collect values across dimensions
2936+
// Find which dimension the current axis is representing
2937+
var currentDimensionIndex = fullTrace._axesDim[ax._id];
2938+
2939+
// Apply logic to associated x axis if it's defined
2940+
if(axLetter === 'y') {
2941+
var associatedXAxisID = fullTrace._diag[currentDimensionIndex][0];
2942+
if(associatedXAxisID) ax = gd._fullLayout[axisIDs.id2name(associatedXAxisID)];
2943+
}
2944+
2945+
var categories = cdi.trace.dimensions[currentDimensionIndex].values;
2946+
for(l = 0; l < categories.length; l++) {
2947+
cat = categories[l];
2948+
catIndex = ax._categoriesMap[cat];
2949+
2950+
// Collect associated values at index `l` over all other dimensions
2951+
for(o = 0; o < cdi.trace.dimensions.length; o++) {
2952+
if(o === currentDimensionIndex) continue;
2953+
var dimension = cdi.trace.dimensions[o];
2954+
categoriesValue[catIndex][1].push(dimension.values[l]);
2955+
}
2956+
}
2957+
} else if(type === 'scattergl') {
2958+
// If `scattergl`, collect all values stashed under cdi.t
2959+
for(l = 0; l < cdi.t.x.length; l++) {
2960+
if(axLetter === 'x') {
2961+
cat = cdi.t.x[l];
2962+
catIndex = cat;
2963+
value = cdi.t.y[l];
2964+
}
2965+
2966+
if(axLetter === 'y') {
2967+
cat = cdi.t.y[l];
2968+
catIndex = cat;
2969+
value = cdi.t.x[l];
2970+
}
2971+
categoriesValue[catIndex][1].push(value);
2972+
}
2973+
// must clear scene 'batches', so that 2nd
2974+
// _module.calc call starts from scratch
2975+
if(cdi.t && cdi.t._scene) {
2976+
delete cdi.t._scene.dirty;
2977+
}
2978+
} else if(cdi.hasOwnProperty('z')) {
2979+
// If 2dMap, collect values in `z`
2980+
value = cdi.z;
2981+
var mapping = zMapCategory(fullTrace.type, ax, value);
2982+
2983+
for(l = 0; l < value.length; l++) {
2984+
for(o = 0; o < value[l].length; o++) {
2985+
catIndex = mapping(o, l);
2986+
if(catIndex + 1) categoriesValue[catIndex][1].push(value[l][o]);
2987+
}
2988+
}
2989+
} else {
2990+
// For all other 2d cartesian traces
2991+
if(axLetter === 'x') {
2992+
cat = cdi.p + 1 ? cdi.p : cdi.x;
2993+
value = cdi.s || cdi.v || cdi.y;
2994+
} else if(axLetter === 'y') {
2995+
cat = cdi.p + 1 ? cdi.p : cdi.y;
2996+
value = cdi.s || cdi.v || cdi.x;
2997+
}
2998+
if(!Array.isArray(value)) value = [value];
2999+
for(l = 0; l < value.length; l++) {
3000+
categoriesValue[cat][1].push(value[l]);
3001+
}
3002+
}
3003+
}
3004+
}
3005+
3006+
ax._categoriesValue = categoriesValue;
3007+
3008+
var categoriesAggregatedValue = [];
3009+
for(j = 0; j < categoriesValue.length; j++) {
3010+
categoriesAggregatedValue.push([
3011+
categoriesValue[j][0],
3012+
aggFn[aggregator](categoriesValue[j][1])
3013+
]);
3014+
}
3015+
3016+
// Sort by aggregated value
3017+
categoriesAggregatedValue.sort(function(a, b) {
3018+
return a[1] - b[1];
3019+
});
3020+
3021+
ax._categoriesAggregatedValue = categoriesAggregatedValue;
3022+
3023+
// Set new category order
3024+
ax._initialCategories = categoriesAggregatedValue.map(function(c) {
3025+
return c[0];
3026+
});
3027+
3028+
// Reverse if descending
3029+
if(order === 'descending') {
3030+
ax._initialCategories.reverse();
3031+
}
3032+
3033+
// Sort all matching axes
3034+
affectedTraces = affectedTraces.concat(ax.sortByInitialCategories());
3035+
}
3036+
}
3037+
return affectedTraces;
3038+
}
3039+
28543040
function setupAxisCategories(axList, fullData) {
28553041
for(var i = 0; i < axList.length; i++) {
28563042
var ax = axList[i];

0 commit comments

Comments
 (0)