Skip to content

Commit bd24421

Browse files
authored
Merge pull request #1191 from plotly/tickvals-edge-cases
tickvals / ticktext edge cases
2 parents c130932 + c70f04a commit bd24421

File tree

3 files changed

+285
-34
lines changed

3 files changed

+285
-34
lines changed

src/plots/cartesian/axes.js

Lines changed: 56 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -611,8 +611,6 @@ axes.autoBin = function(data, ax, nbins, is2d) {
611611
// in any case, set tickround to # of digits to round tick labels to,
612612
// or codes to this effect for log and date scales
613613
axes.calcTicks = function calcTicks(ax) {
614-
if(ax.tickmode === 'array') return arrayTicks(ax);
615-
616614
var rng = ax.range.map(ax.r2l);
617615

618616
// calculate max number of (auto) ticks to display based on plot size
@@ -629,6 +627,11 @@ axes.calcTicks = function calcTicks(ax) {
629627
nt = Lib.constrain(ax._length / minPx, 4, 9) + 1;
630628
}
631629
}
630+
631+
// add a couple of extra digits for filling in ticks when we
632+
// have explicit tickvals without tick text
633+
if(ax.tickmode === 'array') nt *= 100;
634+
632635
axes.autoTicks(ax, Math.abs(rng[1] - rng[0]) / nt);
633636
// check for a forced minimum dtick
634637
if(ax._minDtick > 0 && ax.dtick < ax._minDtick * 2) {
@@ -645,6 +648,10 @@ axes.calcTicks = function calcTicks(ax) {
645648
// now figure out rounding of tick values
646649
autoTickRound(ax);
647650

651+
// now that we've figured out the auto values for formatting
652+
// in case we're missing some ticktext, we can break out for array ticks
653+
if(ax.tickmode === 'array') return arrayTicks(ax);
654+
648655
// find the first tick
649656
ax._tmin = axes.tickFirst(ax);
650657

@@ -672,11 +679,11 @@ axes.calcTicks = function calcTicks(ax) {
672679
// show the exponent only on the last one
673680
ax._tmax = vals[vals.length - 1];
674681

675-
// for showing date suffixes: ax._prevSuffix holds what we showed most
676-
// recently. Start with it cleared and mark that we're in calcTicks (ie
677-
// calculating a whole string of these so we should care what the previous
678-
// suffix was!)
679-
ax._prevSuffix = '';
682+
// for showing the rest of a date when the main tick label is only the
683+
// latter part: ax._prevDateHead holds what we showed most recently.
684+
// Start with it cleared and mark that we're in calcTicks (ie calculating a
685+
// whole string of these so we should care what the previous date head was!)
686+
ax._prevDateHead = '';
680687
ax._inCalcTicks = true;
681688

682689
var ticksOut = new Array(vals.length);
@@ -704,8 +711,17 @@ function arrayTicks(ax) {
704711
// except with more precision to the numbers
705712
if(!Array.isArray(text)) text = [];
706713

714+
// make sure showing ticks doesn't accidentally add new categories
715+
var tickVal2l = ax.type === 'category' ? ax.d2l_noadd : ax.d2l;
716+
717+
// array ticks on log axes always show the full number
718+
// (if no explicit ticktext overrides it)
719+
if(ax.type === 'log' && String(ax.dtick).charAt(0) !== 'L') {
720+
ax.dtick = 'L' + Math.pow(10, Math.floor(Math.min(ax.range[0], ax.range[1])) - 1);
721+
}
722+
707723
for(i = 0; i < vals.length; i++) {
708-
vali = ax.d2l(vals[i]);
724+
vali = tickVal2l(vals[i]);
709725
if(vali > tickMin && vali < tickMax) {
710726
if(text[i] === undefined) ticksOut[j] = axes.tickText(ax, vali);
711727
else ticksOut[j] = tickTextObj(ax, vali, String(text[i]));
@@ -1030,13 +1046,14 @@ axes.tickText = function(ax, x, hover) {
10301046
hideexp,
10311047
arrayMode = ax.tickmode === 'array',
10321048
extraPrecision = hover || arrayMode,
1033-
i;
1049+
i,
1050+
tickVal2l = ax.type === 'category' ? ax.d2l_noadd : ax.d2l;
10341051

10351052
if(arrayMode && Array.isArray(ax.ticktext)) {
10361053
var rng = ax.range.map(ax.r2l),
10371054
minDiff = Math.abs(rng[1] - rng[0]) / 10000;
10381055
for(i = 0; i < ax.ticktext.length; i++) {
1039-
if(Math.abs(x - ax.d2l(ax.tickvals[i])) < minDiff) break;
1056+
if(Math.abs(x - tickVal2l(ax.tickvals[i])) < minDiff) break;
10401057
}
10411058
if(i < ax.ticktext.length) {
10421059
out.text = String(ax.ticktext[i]);
@@ -1089,12 +1106,11 @@ function tickTextObj(ax, x, text) {
10891106
function formatDate(ax, out, hover, extraPrecision) {
10901107
var x = out.x,
10911108
tr = ax._tickround,
1092-
trOriginal = tr,
10931109
d = new Date(x),
1094-
// suffix completes the full date info, to be included
1110+
// headPart completes the full date info, to be included
10951111
// with only the first tick or if any info before what's
10961112
// shown has changed
1097-
suffix,
1113+
headPart,
10981114
tt;
10991115
if(hover && ax.hoverformat) {
11001116
tt = modDateFormat(ax.hoverformat, x);
@@ -1113,12 +1129,12 @@ function formatDate(ax, out, hover, extraPrecision) {
11131129
else if(tr === 'm') tt = monthFormat(d);
11141130
else {
11151131
if(tr === 'd') {
1116-
if(!hover) suffix = '<br>' + yearFormat(d);
1132+
headPart = yearFormat(d);
11171133

11181134
tt = dayFormat(d);
11191135
}
11201136
else {
1121-
if(!hover) suffix = '<br>' + yearMonthDayFormat(d);
1137+
headPart = yearMonthDayFormat(d);
11221138

11231139
tt = minuteFormat(d);
11241140
if(tr !== 'M') {
@@ -1128,17 +1144,34 @@ function formatDate(ax, out, hover, extraPrecision) {
11281144
.substr(1);
11291145
}
11301146
}
1131-
else if(trOriginal === 'd') {
1132-
// for hover on axes with day ticks, minuteFormat (which
1133-
// only includes %H:%M) isn't enough, you want the date too
1134-
tt = dayFormat(d) + ' ' + tt;
1135-
}
11361147
}
11371148
}
11381149
}
1139-
if(suffix && (!ax._inCalcTicks || (suffix !== ax._prevSuffix))) {
1140-
tt += suffix;
1141-
ax._prevSuffix = suffix;
1150+
if(hover || ax.tickmode === 'array') {
1151+
// we get extra precision in array mode or hover,
1152+
// but it may be useless, strip it off
1153+
if(tt === '00:00:00' || tt === '00:00') {
1154+
tt = headPart;
1155+
headPart = '';
1156+
}
1157+
else if(tt.length === 8) {
1158+
// strip off seconds if they're zero (zero fractional seconds
1159+
// are already omitted)
1160+
tt = tt.replace(/:00$/, '');
1161+
}
1162+
}
1163+
1164+
if(headPart) {
1165+
if(hover) {
1166+
// hover puts it all on one line, so headPart works best up front
1167+
// except for year headPart: turn this into "Jan 1, 2000" etc.
1168+
if(tr === 'd') tt += ', ' + headPart;
1169+
else tt = headPart + (tt ? ', ' + tt : '');
1170+
}
1171+
else if(!ax._inCalcTicks || (headPart !== ax._prevDateHead)) {
1172+
tt += '<br>' + headPart;
1173+
ax._prevDateHead = headPart;
1174+
}
11421175
}
11431176
out.text = tt;
11441177
}

src/plots/cartesian/set_convert.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,14 @@ module.exports = function setConvert(ax) {
313313
return c === -1 ? BADNUM : c;
314314
};
315315

316+
ax.d2l_noadd = function(v) {
317+
// d2c variant that that won't add categories but will also
318+
// allow numbers to be mapped to the linearized axis positions
319+
var index = ax._categories.indexOf(v);
320+
if(index !== -1) return index;
321+
if(typeof v === 'number') return v;
322+
};
323+
316324
ax.d2l = ax.d2c;
317325
ax.r2l = num;
318326
ax.l2r = num;

0 commit comments

Comments
 (0)