Skip to content

Commit 259eafa

Browse files
committed
implement axis breaks setConvert logic
- add abstractions: + ax._breaks, the disjoint breaks inside the range though ax.locateBreaks + ax._lBreaks, length of these breaks in value space + ax._m2, l2p slope (same for all intervals) + ax._B, set of l2p offsets (one for each of the (N+1) piecewise intervals) - adapt l2p and p2l for axis breaks - add jasmine tests for setConvert and hover labels
1 parent baf753a commit 259eafa

File tree

3 files changed

+734
-5
lines changed

3 files changed

+734
-5
lines changed

src/plots/cartesian/set_convert.js

Lines changed: 248 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ var numConstants = require('../../constants/numerical');
2222
var FP_SAFE = numConstants.FP_SAFE;
2323
var BADNUM = numConstants.BADNUM;
2424
var LOG_CLIP = numConstants.LOG_CLIP;
25+
var ONEDAY = numConstants.ONEDAY;
26+
var ONEHOUR = numConstants.ONEHOUR;
27+
var ONEMIN = numConstants.ONEMIN;
28+
var ONESEC = numConstants.ONESEC;
2529

2630
var constants = require('./constants');
2731
var axisIds = require('./axis_ids');
@@ -170,14 +174,73 @@ module.exports = function setConvert(ax, fullLayout) {
170174
if(isNumeric(v)) return +v;
171175
}
172176

173-
function l2p(v) {
177+
// include 2 fractional digits on pixel, for PDF zooming etc
178+
function _l2p(v, m, b) { return d3.round(b + m * v, 2); }
179+
180+
function _p2l(px, m, b) { return (px - b) / m; }
181+
182+
var l2p = function l2p(v) {
174183
if(!isNumeric(v)) return BADNUM;
184+
return _l2p(v, ax._m, ax._b);
185+
};
175186

176-
// include 2 fractional digits on pixel, for PDF zooming etc
177-
return d3.round(ax._b + ax._m * v, 2);
178-
}
187+
var p2l = function(px) {
188+
return _p2l(px, ax._m, ax._b);
189+
};
179190

180-
function p2l(px) { return (px - ax._b) / ax._m; }
191+
if(ax.breaks) {
192+
if(axLetter === 'y') {
193+
l2p = function(v) {
194+
if(!isNumeric(v)) return BADNUM;
195+
if(!ax._breaks.length) return _l2p(v, ax._m, ax._b);
196+
197+
var b = ax._B[0];
198+
for(var i = 0; i < ax._breaks.length; i++) {
199+
var brk = ax._breaks[i];
200+
if(v <= brk.min) b = ax._B[i + 1];
201+
else if(v > brk.max) break;
202+
}
203+
return _l2p(v, -ax._m2, b);
204+
};
205+
p2l = function(px) {
206+
if(!isNumeric(px)) return BADNUM;
207+
if(!ax._breaks.length) return _p2l(px, ax._m, ax._b);
208+
209+
var b = ax._B[0];
210+
for(var i = 0; i < ax._breaks.length; i++) {
211+
var brk = ax._breaks[i];
212+
if(px >= brk.pmin) b = ax._B[i + 1];
213+
else if(px < brk.pmax) break;
214+
}
215+
return _p2l(px, -ax._m2, b);
216+
};
217+
} else {
218+
l2p = function(v) {
219+
if(!isNumeric(v)) return BADNUM;
220+
if(!ax._breaks.length) return _l2p(v, ax._m, ax._b);
221+
222+
var b = ax._B[0];
223+
for(var i = 0; i < ax._breaks.length; i++) {
224+
var brk = ax._breaks[i];
225+
if(v >= brk.max) b = ax._B[i + 1];
226+
else if(v < brk.min) break;
227+
}
228+
return _l2p(v, ax._m2, b);
229+
};
230+
p2l = function(px) {
231+
if(!isNumeric(px)) return BADNUM;
232+
if(!ax._breaks.length) return _p2l(px, ax._m, ax._b);
233+
234+
var b = ax._B[0];
235+
for(var i = 0; i < ax._breaks.length; i++) {
236+
var brk = ax._breaks[i];
237+
if(px >= brk.pmax) b = ax._B[i + 1];
238+
else if(px < brk.pmin) break;
239+
}
240+
return _p2l(px, ax._m2, b);
241+
};
242+
}
243+
}
181244

182245
// conversions among c/l/p are fairly simple - do them together for all axis types
183246
ax.c2l = (ax.type === 'log') ? toLog : ensureNumber;
@@ -486,6 +549,51 @@ module.exports = function setConvert(ax, fullLayout) {
486549
ax._b = -ax._m * rl0;
487550
}
488551

552+
// set of "N" disjoint breaks inside the range
553+
ax._breaks = [];
554+
// length of these breaks in value space
555+
ax._lBreaks = 0;
556+
// l2p slope (same for all intervals)
557+
ax._m2 = 0;
558+
// set of l2p offsets (one for each of the (N+1) piecewise intervals)
559+
ax._B = [];
560+
561+
if(ax.breaks) {
562+
var i, brk;
563+
564+
ax._breaks = ax.locateBreaks(rl0, rl1);
565+
566+
if(ax._breaks.length) {
567+
for(i = 0; i < ax._breaks.length; i++) {
568+
brk = ax._breaks[i];
569+
ax._lBreaks += (brk.max - brk.min);
570+
}
571+
572+
ax._m2 = ax._length / (rl1 - rl0 - ax._lBreaks);
573+
574+
if(axLetter === 'y') {
575+
ax._breaks.reverse();
576+
// N.B. top to bottom (negative coord, positive px direction)
577+
ax._B.push(ax._m2 * rl1);
578+
} else {
579+
ax._B.push(-ax._m2 * rl0);
580+
}
581+
582+
for(i = 0; i < ax._breaks.length; i++) {
583+
brk = ax._breaks[i];
584+
ax._B.push(ax._B[ax._B.length - 1] - ax._m2 * (brk.max - brk.min));
585+
}
586+
587+
// fill pixel (i.e. 'p') min/max here,
588+
// to not have to loop through the _breaks twice during `p2l`
589+
for(i = 0; i < ax._breaks.length; i++) {
590+
brk = ax._breaks[i];
591+
brk.pmin = l2p(brk.min);
592+
brk.pmax = l2p(brk.max);
593+
}
594+
}
595+
}
596+
489597
if(!isFinite(ax._m) || !isFinite(ax._b) || ax._length < 0) {
490598
fullLayout._replotting = false;
491599
throw new Error('Something went wrong with axis scaling');
@@ -565,6 +673,141 @@ module.exports = function setConvert(ax, fullLayout) {
565673
}
566674
return v;
567675
};
676+
677+
ax.locateBreaks = function(r0, r1) {
678+
var i, bnds, b0, b1;
679+
680+
var breaksOut = [];
681+
if(!ax.breaks) return breaksOut;
682+
683+
var breaksIn;
684+
if(ax.type === 'date') {
685+
breaksIn = ax.breaks.slice().sort(function(a, b) {
686+
if(a.pattern === '%w' && b.pattern === '%H') return -1;
687+
else if(b.pattern === '%w' && a.pattern === '%H') return 1;
688+
return 0;
689+
});
690+
} else {
691+
breaksIn = ax.breaks;
692+
}
693+
694+
var addBreak = function(min, max) {
695+
min = Lib.constrain(min, r0, r1);
696+
max = Lib.constrain(max, r0, r1);
697+
if(min === max) return;
698+
699+
var isNewBreak = true;
700+
for(var j = 0; j < breaksOut.length; j++) {
701+
var brkj = breaksOut[j];
702+
if(min > brkj.max || max < brkj.min) {
703+
// potentially a new break
704+
} else {
705+
if(min < brkj.min) {
706+
brkj.min = min;
707+
}
708+
if(max > brkj.max) {
709+
brkj.max = max;
710+
}
711+
isNewBreak = false;
712+
}
713+
}
714+
if(isNewBreak) {
715+
breaksOut.push({min: min, max: max});
716+
}
717+
};
718+
719+
for(i = 0; i < breaksIn.length; i++) {
720+
var brk = breaksIn[i];
721+
722+
if(brk.enabled) {
723+
var op = brk.operation;
724+
var op0 = op.charAt(0);
725+
var op1 = op.charAt(1);
726+
727+
if(brk.bounds) {
728+
if(brk.pattern) {
729+
bnds = Lib.simpleMap(brk.bounds, cleanNumber);
730+
if(bnds[0] === bnds[1] && op === '()') continue;
731+
732+
// r0 value as date
733+
var r0Date = new Date(r0);
734+
// r0 value for break pattern
735+
var r0Pattern;
736+
// delta between r0 and first break in break pattern values
737+
var r0PatternDelta;
738+
// delta between break bounds in ms
739+
var bndDelta;
740+
// step in ms between breaks
741+
var step;
742+
// tracker to position bounds
743+
var t;
744+
745+
switch(brk.pattern) {
746+
case '%w':
747+
b0 = bnds[0] + (op0 === '(' ? 1 : 0);
748+
b1 = bnds[1];
749+
r0Pattern = r0Date.getUTCDay();
750+
r0PatternDelta = b0 - r0Pattern;
751+
bndDelta = (b1 >= b0 ? b1 - b0 : (b1 + 7) - b0) * ONEDAY;
752+
if(op1 === ']') bndDelta += ONEDAY;
753+
step = 7 * ONEDAY;
754+
755+
t = r0 + r0PatternDelta * ONEDAY -
756+
r0Date.getUTCHours() * ONEHOUR -
757+
r0Date.getUTCMinutes() * ONEMIN -
758+
r0Date.getUTCSeconds() * ONESEC -
759+
r0Date.getUTCMilliseconds();
760+
break;
761+
case '%H':
762+
b0 = bnds[0];
763+
b1 = bnds[1];
764+
r0Pattern = r0Date.getUTCHours();
765+
r0PatternDelta = b0 - r0Pattern;
766+
bndDelta = (b1 >= b0 ? b1 - b0 : (b1 + 24) - b0) * ONEHOUR;
767+
step = ONEDAY;
768+
769+
t = r0 + r0PatternDelta * ONEHOUR -
770+
r0Date.getUTCMinutes() * ONEMIN -
771+
r0Date.getUTCSeconds() * ONESEC -
772+
r0Date.getUTCMilliseconds();
773+
break;
774+
}
775+
776+
while(t <= r1) {
777+
// TODO we need to remove decimal (most often found
778+
// in auto ranges) for this to work correctly,
779+
// should this be Math.floor, Math.ceil or
780+
// Math.round ??
781+
addBreak(Math.floor(t), Math.floor(t + bndDelta));
782+
t += step;
783+
}
784+
} else {
785+
bnds = Lib.simpleMap(brk.bounds, ax.r2l);
786+
if(bnds[0] <= bnds[1]) {
787+
b0 = bnds[0];
788+
b1 = bnds[1];
789+
} else {
790+
b0 = bnds[1];
791+
b1 = bnds[0];
792+
}
793+
addBreak(b0, b1);
794+
}
795+
} else {
796+
var vals = Lib.simpleMap(brk.values, ax.d2c);
797+
for(var j = 0; j < vals.length; j++) {
798+
b0 = vals[j];
799+
b1 = b0 + brk.dvalue;
800+
addBreak(b0, b1);
801+
}
802+
}
803+
}
804+
}
805+
806+
breaksOut.sort(function(a, b) { return a.min - b.min; });
807+
808+
return breaksOut;
809+
};
810+
568811
// makeCalcdata: takes an x or y array and converts it
569812
// to a position on the axis object "ax"
570813
// inputs:

0 commit comments

Comments
 (0)