Skip to content

Commit fa37487

Browse files
committed
bar: refactor code into functions for each barmode
* Refactored the code for setting bar positions into setGroupPositionsInOverlayMode, setGroupPositionsInGroupMode and setGroupPositionsInStackOrRelativeMode. * Refactored code for stacking bars and computing minDiff into helper class Sieve.
1 parent c150b3d commit fa37487

File tree

2 files changed

+225
-46
lines changed

2 files changed

+225
-46
lines changed

src/traces/bar/set_positions.js

Lines changed: 126 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ var isNumeric = require('fast-isnumeric');
1313

1414
var Registry = require('../../registry');
1515
var Axes = require('../../plots/cartesian/axes');
16-
var Lib = require('../../lib');
16+
var Sieve = require('./sieve.js');
1717

1818
/*
1919
* Bar chart stacking/grouping positioning and autoscaling calculations
@@ -52,24 +52,44 @@ module.exports = function setPositions(gd, plotinfo) {
5252
function setGroupPositions(gd, pa, sa, traces) {
5353
if(!traces.length) return;
5454

55-
if(gd._fullLayout.barmode === 'overlay') {
55+
var barmode = gd._fullLayout.barmode,
56+
overlay = (barmode === 'overlay'),
57+
group = (barmode === 'group');
58+
59+
if(overlay) {
5660
setGroupPositionsInOverlayMode(gd, pa, sa, traces);
5761
}
62+
else if(group) {
63+
setGroupPositionsInGroupMode(gd, pa, sa, traces);
64+
}
5865
else {
59-
setOffsetAndWidth(gd, pa, traces);
60-
setBaseAndSize(gd, sa, traces);
66+
setGroupPositionsInStackOrRelativeMode(gd, pa, sa, traces);
6167
}
6268
}
6369

6470

6571
function setGroupPositionsInOverlayMode(gd, pa, sa, traces) {
66-
// set bar offsets and widths
72+
var barnorm = gd._fullLayout.barnorm,
73+
separateNegativeValues = false,
74+
dontMergeOverlappingData = !barnorm;
75+
76+
// update position axis and set bar offsets and widths
6777
traces.forEach(function(trace) {
68-
setOffsetAndWidth(gd, pa, [trace]);
78+
var sieve = new Sieve(
79+
[trace], separateNegativeValues, dontMergeOverlappingData
80+
),
81+
minDiff = sieve.minDiff,
82+
distinctPositions = sieve.distinctPositions;
83+
84+
setOffsetAndWidth(gd, pa, sieve);
85+
86+
Axes.minDtick(pa, minDiff, distinctPositions[0]);
87+
Axes.expand(pa, distinctPositions, {vpad: minDiff / 2});
6988
});
7089

71-
// for overlaid bars,
72-
// just make sure the size axis includes zero,
90+
// update size axis and set bar bases and sizes
91+
//
92+
// make sure the size axis includes zero,
7393
// along with the tops of each bar,
7494
// and store these bar tops in calcdata
7595
var sLetter = getAxisLetter(sa),
@@ -81,53 +101,112 @@ function setGroupPositionsInOverlayMode(gd, pa, sa, traces) {
81101
}
82102

83103

84-
// bar position offset and width calculation
85-
// traces is a list of traces (in calcdata) to look at together
86-
// to find the maximum width bars that won't overlap
87-
// for stacked or grouped bars, this is all vertical or horizontal
88-
// bars for overlaid bars, call this individually on each trace.
89-
function setOffsetAndWidth(gd, pa, traces) {
104+
function setGroupPositionsInGroupMode(gd, pa, sa, traces) {
90105
var fullLayout = gd._fullLayout,
91-
pLetter = getAxisLetter(pa),
92-
i, trace,
93-
j, bar;
106+
barnorm = fullLayout.barnorm,
107+
separateNegativeValues = false,
108+
dontMergeOverlappingData = !barnorm,
109+
sieve = new Sieve(
110+
traces, separateNegativeValues, dontMergeOverlappingData
111+
),
112+
minDiff = sieve.minDiff,
113+
distinctPositions = sieve.distinctPositions;
94114

95-
// make list of bar positions
96-
var positions = [];
97-
for(i = 0; i < traces.length; i++) {
98-
trace = traces[i];
99-
for(j = 0; j < trace.length; j++) {
100-
bar = trace[j];
101-
positions.push(bar.p);
102-
}
103-
}
115+
// set bar offsets and widths
116+
setOffsetAndWidthInGroupMode(gd, pa, sieve);
104117

105-
var dv = Lib.distinctVals(positions),
106-
distinctPositions = dv.vals,
107-
minDiff = dv.minDiff;
118+
// update position axis
119+
Axes.minDtick(pa, minDiff, distinctPositions[0]);
120+
Axes.expand(pa, distinctPositions, {vpad: minDiff / 2});
108121

109-
// check if there are any overlapping positions;
110-
// if there aren't, let them have full width even if mode is group
111-
var overlap = false;
112-
if(fullLayout.barmode === 'group') {
113-
overlap = (positions.length !== distinctPositions.length);
114-
}
122+
// set bar bases and sizes
123+
setBaseAndSize(gd, sa, sieve);
124+
}
125+
126+
127+
function setGroupPositionsInStackOrRelativeMode(gd, pa, sa, traces) {
128+
var fullLayout = gd._fullLayout,
129+
barmode = fullLayout.barmode,
130+
stack = (barmode === 'stack'),
131+
relative = (barmode === 'relative'),
132+
barnorm = gd._fullLayout.barnorm,
133+
separateNegativeValues = relative,
134+
dontMergeOverlappingData = !(barnorm || stack || relative),
135+
sieve = new Sieve(
136+
traces, separateNegativeValues, dontMergeOverlappingData
137+
),
138+
minDiff = sieve.minDiff,
139+
distinctPositions = sieve.distinctPositions;
115140

116-
// check forced minimum dtick
117-
Axes.minDtick(pa, minDiff, distinctPositions[0], overlap);
141+
// set bar offsets and widths
142+
setOffsetAndWidth(gd, pa, sieve);
118143

119-
// position axis autorange - always tight fitting
144+
// update position axis
145+
Axes.minDtick(pa, minDiff, distinctPositions[0]);
120146
Axes.expand(pa, distinctPositions, {vpad: minDiff / 2});
121147

122-
// computer bar widths and position offsets
123-
var barGroupWidth = minDiff * (1 - fullLayout.bargap),
148+
// set bar bases and sizes
149+
setBaseAndSize(gd, sa, sieve);
150+
}
151+
152+
153+
function setOffsetAndWidth(gd, pa, sieve) {
154+
var fullLayout = gd._fullLayout,
155+
pLetter = getAxisLetter(pa),
156+
traces = sieve.traces,
157+
bargap = fullLayout.bargap,
158+
bargroupgap = fullLayout.bargroupgap,
159+
minDiff = sieve.minDiff;
160+
161+
// set bar offsets and widths
162+
var barGroupWidth = minDiff * (1 - bargap),
163+
barWidthPlusGap = barGroupWidth,
164+
barWidth = barWidthPlusGap * (1 - bargroupgap);
165+
166+
// computer bar group center and bar offset
167+
var offsetFromCenter = -barWidth / 2,
168+
barCenter = 0;
169+
170+
for(var i = 0; i < traces.length; i++) {
171+
var trace = traces[i];
172+
173+
// store bar width and offset for this trace
174+
var t = trace[0].t;
175+
t.barwidth = barWidth;
176+
t.poffset = offsetFromCenter;
177+
t.bargroupwidth = barGroupWidth;
178+
179+
// store the bar center in each calcdata item
180+
for(var j = 0; j < trace.length; j++) {
181+
var bar = trace[j];
182+
bar[pLetter] = bar.p + barCenter;
183+
}
184+
}
185+
}
186+
187+
188+
function setOffsetAndWidthInGroupMode(gd, pa, sieve) {
189+
var fullLayout = gd._fullLayout,
190+
pLetter = getAxisLetter(pa),
191+
traces = sieve.traces,
192+
bargap = fullLayout.bargap,
193+
bargroupgap = fullLayout.bargroupgap,
194+
positions = sieve.positions,
195+
distinctPositions = sieve.distinctPositions,
196+
minDiff = sieve.minDiff;
197+
198+
// if there aren't any overlapping positions,
199+
// let them have full width even if mode is group
200+
var overlap = (positions.length !== distinctPositions.length);
201+
202+
var barGroupWidth = minDiff * (1 - bargap),
124203
barWidthPlusGap = (overlap) ?
125204
barGroupWidth / traces.length :
126205
barGroupWidth,
127-
barWidth = barWidthPlusGap * (1 - fullLayout.bargroupgap);
206+
barWidth = barWidthPlusGap * (1 - bargroupgap);
128207

129-
for(i = 0; i < traces.length; i++) {
130-
trace = traces[i];
208+
for(var i = 0; i < traces.length; i++) {
209+
var trace = traces[i];
131210

132211
// computer bar group center and bar offset
133212
var offsetFromCenter = (overlap) ?
@@ -142,17 +221,18 @@ function setOffsetAndWidth(gd, pa, traces) {
142221
t.bargroupwidth = barGroupWidth;
143222

144223
// store the bar center in each calcdata item
145-
for(j = 0; j < trace.length; j++) {
146-
bar = trace[j];
224+
for(var j = 0; j < trace.length; j++) {
225+
var bar = trace[j];
147226
bar[pLetter] = bar.p + barCenter;
148227
}
149228
}
150229
}
151230

152231

153-
function setBaseAndSize(gd, sa, traces) {
232+
function setBaseAndSize(gd, sa, sieve) {
154233
var fullLayout = gd._fullLayout,
155234
sLetter = getAxisLetter(sa),
235+
traces = sieve.traces,
156236
i, trace,
157237
j, bar;
158238

src/traces/bar/sieve.js

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/**
2+
* Copyright 2012-2016, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
module.exports = Sieve;
12+
13+
var Lib = require('../../lib');
14+
15+
/**
16+
* Helper class to sieve data from traces into bins
17+
*
18+
* @class
19+
* @param {Array} traces
20+
* Array of calculated traces
21+
* @param {boolean} [separateNegativeValues]
22+
* If true, then split data at the same position into a bar
23+
* for positive values and another for negative values
24+
* @param {boolean} [dontMergeOverlappingData]
25+
* If true, then don't merge overlapping bars into a single bar
26+
*/
27+
function Sieve(traces, separateNegativeValues, dontMergeOverlappingData) {
28+
this.traces = traces;
29+
this.separateNegativeValues = separateNegativeValues;
30+
this.dontMergeOverlappingData = dontMergeOverlappingData;
31+
32+
var positions = [];
33+
for(var i = 0; i < traces.length; i++) {
34+
var trace = traces[i];
35+
for(var j = 0; j < trace.length; j++) {
36+
var bar = trace[j];
37+
positions.push(bar.p);
38+
}
39+
}
40+
this.positions = positions;
41+
42+
var dv = Lib.distinctVals(this.positions);
43+
this.distinctPositions = dv.vals;
44+
this.minDiff = dv.minDiff;
45+
46+
this.binWidth = this.minDiff;
47+
48+
this.bins = {};
49+
}
50+
51+
/**
52+
* Sieve datum
53+
*
54+
* @method
55+
* @param {number} position
56+
* @param {number} value
57+
* @returns {number} Previous bin value
58+
*/
59+
Sieve.prototype.put = function put(position, value) {
60+
var label = this.getLabel(position, value),
61+
oldValue = this.bins[label] || 0;
62+
63+
this.bins[label] = oldValue + value;
64+
65+
return oldValue;
66+
};
67+
68+
/**
69+
* Get current bin value for a given datum
70+
*
71+
* @method
72+
* @param {number} position Position of datum
73+
* @param {number} [value] Value of datum
74+
* (required if this.separateNegativeValues is true)
75+
* @returns {number} Current bin value
76+
*/
77+
Sieve.prototype.get = function put(position, value) {
78+
var label = this.getLabel(position, value);
79+
return this.bins[label] || 0;
80+
};
81+
82+
/**
83+
* Get bin label for a given datum
84+
*
85+
* @method
86+
* @param {number} position Position of datum
87+
* @param {number} [value] Value of datum
88+
* (required if this.separateNegativeValues is true)
89+
* @returns {string} Bin label
90+
* (prefixed with a 'v' if value is negative and this.separateNegativeValues is
91+
* true; otherwise prefixed with '^')
92+
*/
93+
Sieve.prototype.getLabel = function getLabel(position, value) {
94+
var prefix = (value < 0 && this.separateNegativeValues) ? 'v' : '^',
95+
label = (this.dontMergeOverlappingData) ?
96+
position :
97+
Math.round(position / this.binWidth);
98+
return prefix + label;
99+
};

0 commit comments

Comments
 (0)