Jens
Jens

Reputation: 1398

Skip overlapping tick labels in Plotly Javascript

We are using scatter plots in Plotly.JS to display 2D graph data over a large X range, so we use logarithmic scaling. Zooming and panning works very well, except for one small issue: the X tick labels are confusing because Plotly uses single-digit labels for minor (non-powers of 10) ticks:

default tick format

I can use tickFormat: '0.1s' to show real numbers (which is what users want) instead of single digits, but then there are cases where these labels can overlap:

format with 'tickformat: '0.1s''

I can also add dtick: 'D2' which only displays subticks at positions 2 and 5 and not all digits, but this is then fixed and doesn't adjust to scaling any more.

Ideally, I could specify subtick-label digits where to skip the label (but not the vertical line) completely, without having to resort to tickmode: array and having to specify all tick labels manually, and still benefit from automatic tick adjustment depending on scaling. For example, if all subtick digits are displayed, I would say I'd like to have tick labels at positions 1, 2, 3, 5, 7, the result would look like this:

my preferred solution, but without specifying ticks manually

The other display modes (digits 2 & 5 only, or just the power of 10) would not change. Is there a way to do this? If so, how? I'm not afraid of patching Plotly if required, but right now I don't know where to start looking.

Upvotes: 3

Views: 2067

Answers (2)

Jens
Jens

Reputation: 1398

OK, so I looked deeply into the Plotly configuration options and there are options that are semi-automatically modified 'live' depending on zoom levels, but the conditions when those modifications happen are hardcoded.

So here's a patch (for Plotly v1.36, because that's what we currently use). I modified some of the hardcoded conditions when to change from 1,10,100,... to 1,2,5,10,20,50,100,... to 1,2,3,4,5,... labeling on logarithmic axes and removed the tick labels at 4, 6, 8 and 9 for the last case to avoid text overlapping.

enter image description here

For my application, this now works well and I could not find any more overlapping of tick labels. Also, I got rid of the single digits between powers of ten which confused some users.

--- a/plotly-latest.v1.36.js    2021-05-24 21:45:28.000000000 +0100
+++ b/plotly-latest.v1.36.js    2022-02-02 10:21:08.000000000 +0100
@@ -127302,13 +127302,13 @@
         if(!nt) {
             if(ax.type === 'category') {
                 minPx = ax.tickfont ? (ax.tickfont.size || 12) * 1.2 : 15;
                 nt = ax._length / minPx;
             }
             else {
-                minPx = ax._id.charAt(0) === 'y' ? 40 : 80;
+                minPx = ax._id.charAt(0) === 'y' ? 40 : 100;
                 nt = Lib.constrain(ax._length / minPx, 4, 9) + 1;
             }
 
             // radial axes span half their domain,
             // multiply nticks value by two to get correct number of auto ticks.
             if(ax._name === 'radialaxis') nt *= 2;
@@ -127395,14 +127395,21 @@
     // Start with it cleared and mark that we're in calcTicks (ie calculating a
     // whole string of these so we should care what the previous date head was!)
     ax._prevDateHead = '';
     ax._inCalcTicks = true;
 
     var ticksOut = new Array(vals.length);
-    for(var i = 0; i < vals.length; i++) ticksOut[i] = axes.tickText(ax, vals[i]);
+    // if scaling == log, skip some intermediate tick labels to avoid overlapping text
+    var skipTexts = /^[4689]/;
+    var text;
+    for(var i = 0; i < vals.length; i++) {
+      text = axes.tickText(ax, vals[i]);
+      if(ax.type == "log" && ax.dtick == "D1" && text.text.match(skipTexts)) text.text = "";
+      ticksOut[i] = text;
+    }
     ax._inCalcTicks = false;
 
     return ticksOut;
 };
 
 function arrayTicks(ax) {
@@ -127535,18 +127542,20 @@
 
             // ticks on a linear scale, labeled fully
             roughDTick = Math.abs(Math.pow(10, rng[1]) -
                 Math.pow(10, rng[0])) / nt;
             base = getBase(10);
             ax.dtick = 'L' + roundDTick(roughDTick, base, roundBase10);
+            ax.tickformat = '';
         }
         else {
             // include intermediates between powers of 10,
             // labeled with small digits
             // ax.dtick = "D2" (show 2 and 5) or "D1" (show all digits)
-            ax.dtick = (roughDTick > 0.3) ? 'D2' : 'D1';
+            ax.dtick = (roughDTick > 0.4) ? 'D2' : 'D1';
+            ax.tickformat = '0.1s';
+            ax.hoverformat = '0.2s'; // Workaround to fix hoverinfo label formatting
         }
     }
     else if(ax.type === 'category') {
         ax.tick0 = 0;
         ax.dtick = Math.ceil(Math.max(roughDTick, 1));
     }

The patch is hereby released as Public Domain to make it as easy as possible to use this for direct integration into Plotly (maybe as a "D3" formatting option?).

Upvotes: 0

Johan Faerch
Johan Faerch

Reputation: 1196

Usually I solve this by rotating the labels by some 35-45 degrees. That way they are all there and still readable.

https://plotly.com/javascript/reference/#layout-xaxis-tickangle

tickangle
Parent: layout.xaxis
Type: angle
Default: "auto"

Sets the angle of the tick labels with respect to the horizontal. For example, a `tickangle` of -90 draws the tick labels vertically.

Angled X labels

Upvotes: 1

Related Questions