Skip to content

Commit 8d36d81

Browse files
committed
factor out bin_label_vals so histogram2d can use it too
1 parent 7686096 commit 8d36d81

File tree

2 files changed

+170
-155
lines changed

2 files changed

+170
-155
lines changed
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/**
2+
* Copyright 2012-2017, 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+
10+
'use strict';
11+
12+
var numConstants = require('../../constants/numerical');
13+
var oneYear = numConstants.ONEAVGYEAR;
14+
var oneMonth = numConstants.ONEAVGMONTH;
15+
var oneDay = numConstants.ONEDAY;
16+
var oneHour = numConstants.ONEHOUR;
17+
var oneMin = numConstants.ONEMIN;
18+
var oneSec = numConstants.ONESEC;
19+
var tickIncrement = require('../../plots/cartesian/axes').tickIncrement;
20+
21+
22+
/*
23+
* make a function that will find rounded bin edges
24+
* @param {number} leftGap: how far from the left edge of any bin is the closest data value?
25+
* @param {number} rightGap: how far from the right edge of any bin is the closest data value?
26+
* @param {Array[number]} binEdges: the actual edge values used in binning
27+
* @param {object} pa: the position axis
28+
* @param {string} calendar: the data calendar
29+
*
30+
* @return {function(v, isRightEdge)}:
31+
* find the start (isRightEdge is falsy) or end (truthy) label value for a bin edge `v`
32+
*/
33+
module.exports = function getBinSpanLabelRound(leftGap, rightGap, binEdges, pa, calendar) {
34+
// the rounding digit is the largest digit that changes in *all* of 4 regions:
35+
// - inside the rightGap before binEdges[0] (shifted 10% to the left)
36+
// - inside the leftGap after binEdges[0] (expanded by 10% of rightGap on each end)
37+
// - same for binEdges[1]
38+
var dv0 = -1.1 * rightGap;
39+
var dv1 = -0.1 * rightGap;
40+
var dv2 = leftGap - dv1;
41+
var edge0 = binEdges[0];
42+
var edge1 = binEdges[1];
43+
var regions = [
44+
[edge0 + dv0, edge0 + dv1],
45+
[edge0 + dv1, edge0 + dv2],
46+
[edge1 + dv0, edge1 + dv1],
47+
[edge1 + dv1, edge1 + dv2]
48+
];
49+
var digit = Infinity;
50+
for(var i = 0; i < regions.length; i++) {
51+
digit = Math.min(digit, biggestDigitChanged(regions[i], pa, calendar));
52+
}
53+
54+
if(pa.type === 'date' && digit > oneDay) {
55+
var dashExclude = (digit === oneYear) ? 1 : 6;
56+
var increment = (digit === oneYear) ? 'M12' : 'M1';
57+
58+
return function(v, isRightEdge) {
59+
var dateStr = pa.c2d(v, oneYear, calendar);
60+
var dashPos = dateStr.indexOf('-', dashExclude);
61+
if(dashPos > 0) dateStr = dateStr.substr(0, dashPos);
62+
var roundedV = pa.d2c(dateStr, calendar);
63+
64+
if(roundedV < v) {
65+
var nextV = tickIncrement(roundedV, increment, false, calendar);
66+
if((roundedV + nextV) / 2 < v + leftGap) roundedV = nextV;
67+
}
68+
69+
if(isRightEdge) {
70+
return tickIncrement(roundedV, increment, true, calendar);
71+
}
72+
73+
return roundedV;
74+
};
75+
}
76+
77+
return function(v, isRightEdge) {
78+
var roundedV = digit * Math.round(v / digit);
79+
// if we rounded down and we could round up and still be < leftGap
80+
// (or what leftGap values round to), do that
81+
// TODO: is the `digit / 2` correct or is that too much leeway?
82+
if(roundedV + (digit / 10) < v && roundedV + (digit * 0.9) < v + leftGap) {
83+
roundedV += digit;
84+
}
85+
// finally for the right edge back off one digit - but only if we can do that
86+
// and not clip off any data that's potentially in the bin
87+
if(isRightEdge) {
88+
roundedV -= digit;
89+
}
90+
return roundedV;
91+
};
92+
};
93+
94+
/*
95+
* Find the largest digit that changes within a (calcdata) region [v1, v2]
96+
* if dates, "digit" means date/time part when it's bigger than a second
97+
* returns the unit value to round to this digit, eg 0.01 to round to hundredths, or
98+
* 100 to round to hundreds. returns oneMonth or oneYear for month or year rounding,
99+
* so that Math.min will work, rather than 'M1' and 'M12'
100+
*/
101+
function biggestDigitChanged(region, pa, calendar) {
102+
var v1 = region[0];
103+
var v2 = region[1];
104+
105+
// are we crossing zero? can't say anything.
106+
// in principle this doesn't apply to dates but turns out this doesn't matter.
107+
if(v1 * v2 <= 0) return Infinity;
108+
109+
var dv = Math.abs(v2 - v1);
110+
var isDate = pa.type === 'date';
111+
var digit = biggestGuaranteedDigitChanged(dv, isDate);
112+
// see if a larger digit also changed
113+
for(var i = 0; i < 10; i++) {
114+
// numbers: next digit needs to be >10x but <100x then gets rounded down.
115+
// dates: next digit can be as much as 60x (then rounded down)
116+
var nextDigit = biggestGuaranteedDigitChanged(digit * 80, isDate);
117+
// if we get to years, the chain stops
118+
if(digit === nextDigit) break;
119+
if(didDigitChange(nextDigit, v1, v2, isDate, pa, calendar)) digit = nextDigit;
120+
else break;
121+
}
122+
return digit;
123+
}
124+
125+
/*
126+
* Find the largest digit that *definitely* changes in a region [v, v + dv] for any v
127+
* for nonuniform date regions (months/years) pick the largest
128+
*/
129+
function biggestGuaranteedDigitChanged(dv, isDate) {
130+
if(isDate && dv > oneSec) {
131+
// this is supposed to be the biggest *guaranteed* change
132+
// so compare to the longest month and year across any calendar,
133+
// (TODO: does 1.1 do that? I think so...)
134+
// and we'll iterate back up later
135+
// note: does not support rounding larger than one year. We could add
136+
// that if anyone wants it, but seems unusual and not strictly necessary.
137+
if(dv > oneDay) {
138+
if(dv > oneYear * 1.1) return oneYear;
139+
if(dv > oneMonth * 1.1) return oneMonth;
140+
return oneDay;
141+
}
142+
143+
if(dv > oneHour) return oneHour;
144+
if(dv > oneMin) return oneMin;
145+
return oneSec;
146+
}
147+
return Math.pow(10, Math.floor(Math.log(dv) / Math.LN10));
148+
}
149+
150+
function didDigitChange(digit, v1, v2, isDate, pa, calendar) {
151+
if(isDate && digit > oneDay) {
152+
var dateParts1 = dateParts(v1, pa, calendar);
153+
var dateParts2 = dateParts(v2, pa, calendar);
154+
var parti = (digit === oneYear) ? 0 : 1;
155+
return dateParts1[parti] !== dateParts2[parti];
156+
157+
}
158+
return Math.floor(v2 / digit) - Math.floor(v1 / digit) > 0.1;
159+
}
160+
161+
function dateParts(v, pa, calendar) {
162+
var parts = pa.c2d(v, oneYear, calendar).split('-');
163+
if(parts[0] === '') {
164+
parts.unshift();
165+
parts[0] = '-' + parts[0];
166+
}
167+
return parts;
168+
}

src/traces/histogram/calc.js

Lines changed: 2 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,8 @@ var binFunctions = require('./bin_functions');
1919
var normFunctions = require('./norm_functions');
2020
var doAvg = require('./average');
2121
var cleanBins = require('./clean_bins');
22-
var numConstants = require('../../constants/numerical');
23-
var oneYear = numConstants.ONEAVGYEAR;
24-
var oneMonth = numConstants.ONEAVGMONTH;
25-
var oneDay = numConstants.ONEDAY;
26-
var oneHour = numConstants.ONEHOUR;
27-
var oneMin = numConstants.ONEMIN;
28-
var oneSec = numConstants.ONESEC;
22+
var oneMonth = require('../../constants/numerical').ONEAVGMONTH;
23+
var getBinSpanLabelRound = require('./bin_label_vals');
2924

3025

3126
module.exports = function calc(gd, trace) {
@@ -201,154 +196,6 @@ module.exports = function calc(gd, trace) {
201196
return cd;
202197
};
203198

204-
/*
205-
* make a function that will find rounded bin edges
206-
* @param {number} leftGap: how far from the left edge of any bin is the closest data value?
207-
* @param {number} rightGap: how far from the right edge of any bin is the closest data value?
208-
* @param {Array[number]} binEdges: the actual edge values used in binning
209-
* @param {object} pa: the position axis
210-
* @param {string} calendar: the data calendar
211-
*
212-
* @return {function(v, isRightEdge)}:
213-
* find the start (isRightEdge is falsy) or end (truthy) label value for a bin edge `v`
214-
*/
215-
function getBinSpanLabelRound(leftGap, rightGap, binEdges, pa, calendar) {
216-
// the rounding digit is the largest digit that changes in *all* of 4 regions:
217-
// - inside the rightGap before binEdges[0] (shifted 10% to the left)
218-
// - inside the leftGap after binEdges[0] (expanded by 10% of rightGap on each end)
219-
// - same for binEdges[1]
220-
var dv0 = -1.1 * rightGap;
221-
var dv1 = -0.1 * rightGap;
222-
var dv2 = leftGap - dv1;
223-
var edge0 = binEdges[0];
224-
var edge1 = binEdges[1];
225-
var regions = [
226-
[edge0 + dv0, edge0 + dv1],
227-
[edge0 + dv1, edge0 + dv2],
228-
[edge1 + dv0, edge1 + dv1],
229-
[edge1 + dv1, edge1 + dv2]
230-
];
231-
var digit = Infinity;
232-
for(var i = 0; i < regions.length; i++) {
233-
digit = Math.min(digit, biggestDigitChanged(regions[i], pa, calendar));
234-
}
235-
236-
if(pa.type === 'date' && digit > oneDay) {
237-
var dashExclude = (digit === oneYear) ? 1 : 6;
238-
var increment = (digit === oneYear) ? 'M12' : 'M1';
239-
240-
return function(v, isRightEdge) {
241-
var dateStr = pa.c2d(v, oneYear, calendar);
242-
var dashPos = dateStr.indexOf('-', dashExclude);
243-
if(dashPos > 0) dateStr = dateStr.substr(0, dashPos);
244-
var roundedV = pa.d2c(dateStr, calendar);
245-
246-
if(roundedV < v) {
247-
var nextV = Axes.tickIncrement(roundedV, increment, false, calendar);
248-
if((roundedV + nextV) / 2 < v + leftGap) roundedV = nextV;
249-
}
250-
251-
if(isRightEdge) {
252-
return Axes.tickIncrement(roundedV, increment, true, calendar);
253-
}
254-
255-
return roundedV;
256-
};
257-
}
258-
259-
return function(v, isRightEdge) {
260-
var roundedV = digit * Math.round(v / digit);
261-
// if we rounded down and we could round up and still be < leftGap
262-
// (or what leftGap values round to), do that
263-
// TODO: is the `digit / 2` correct or is that too much leeway?
264-
if(roundedV < v && roundedV + (digit / 2) < v + leftGap) {
265-
roundedV += digit;
266-
}
267-
// finally for the right edge back off one digit - but only if we can do that
268-
// and not clip off any data that's potentially in the bin
269-
if(isRightEdge) {
270-
roundedV -= digit;
271-
}
272-
return roundedV;
273-
};
274-
}
275-
276-
/*
277-
* Find the largest digit that changes within a (calcdata) region [v1, v2]
278-
* if dates, "digit" means date/time part when it's bigger than a second
279-
* returns the unit value to round to this digit, eg 0.01 to round to hundredths, or
280-
* 100 to round to hundreds. returns oneMonth or oneYear for month or year rounding,
281-
* so that Math.min will work, rather than 'M1' and 'M12'
282-
*/
283-
function biggestDigitChanged(region, pa, calendar) {
284-
var v1 = region[0];
285-
var v2 = region[1];
286-
287-
// are we crossing zero? can't say anything.
288-
// in principle this doesn't apply to dates but turns out this doesn't matter.
289-
if(v1 * v2 <= 0) return Infinity;
290-
291-
var dv = Math.abs(v2 - v1);
292-
var isDate = pa.type === 'date';
293-
var digit = biggestGuaranteedDigitChanged(dv, isDate);
294-
// see if a larger digit also changed
295-
for(var i = 0; i < 10; i++) {
296-
// numbers: next digit needs to be >10x but <100x then gets rounded down.
297-
// dates: next digit can be as much as 60x (then rounded down)
298-
var nextDigit = biggestGuaranteedDigitChanged(digit * 80, isDate);
299-
// if we get to years, the chain stops
300-
if(digit === nextDigit) break;
301-
if(didDigitChange(nextDigit, v1, v2, isDate, pa, calendar)) digit = nextDigit;
302-
else break;
303-
}
304-
return digit;
305-
}
306-
307-
/*
308-
* Find the largest digit that *definitely* changes in a region [v, v + dv] for any v
309-
* for nonuniform date regions (months/years) pick the largest
310-
*/
311-
function biggestGuaranteedDigitChanged(dv, isDate) {
312-
if(isDate && dv > oneSec) {
313-
// this is supposed to be the biggest *guaranteed* change
314-
// so compare to the longest month and year across any calendar,
315-
// (TODO: does 1.1 do that? I think so...)
316-
// and we'll iterate back up later
317-
// note: does not support rounding larger than one year. We could add
318-
// that if anyone wants it, but seems unusual and not strictly necessary.
319-
if(dv > oneDay) {
320-
if(dv > oneYear * 1.1) return oneYear;
321-
if(dv > oneMonth * 1.1) return oneMonth;
322-
return oneDay;
323-
}
324-
325-
if(dv > oneHour) return oneHour;
326-
if(dv > oneMin) return oneMin;
327-
return oneSec;
328-
}
329-
return Math.pow(10, Math.floor(Math.log(dv) / Math.LN10));
330-
}
331-
332-
function didDigitChange(digit, v1, v2, isDate, pa, calendar) {
333-
if(isDate && digit > oneDay) {
334-
var dateParts1 = dateParts(v1, pa, calendar);
335-
var dateParts2 = dateParts(v2, pa, calendar);
336-
var parti = (digit === oneYear) ? 0 : 1;
337-
return dateParts1[parti] !== dateParts2[parti];
338-
339-
}
340-
return Math.floor(v2 / digit) - Math.floor(v1 / digit) > 0.1;
341-
}
342-
343-
function dateParts(v, pa, calendar) {
344-
var parts = pa.c2d(v, oneYear, calendar).split('-');
345-
if(parts[0] === '') {
346-
parts.unshift();
347-
parts[0] = '-' + parts[0];
348-
}
349-
return parts;
350-
}
351-
352199
/*
353200
* calcAllAutoBins: we want all histograms on the same axes to share bin specs
354201
* if they're grouped or stacked. If the user has explicitly specified differing

0 commit comments

Comments
 (0)