避免Plotly Javascript中的重叠刻度标签。

3
我们正在使用Plotly.JS的散点图来显示2D图形数据,覆盖了大范围的X轴,因此我们使用对数比例尺。缩放和平移效果非常好,但有一个小问题:X轴刻度标签很难理解,因为Plotly使用单个数字标签来表示次要(非10的幂)刻度:

default tick format

我可以使用tickFormat: '0.1s'来显示实数(这是用户想要的),而不是单个数字,但在某些情况下,这些标签可能会重叠:

format with 'tickformat: '0.1s''

我还可以添加dtick: 'D2',它仅在位置2和5处显示子刻度,而不是所有数字,但这样就固定了,不再根据缩放进行调整。

理想情况下,我可以指定子刻度标签数字以跳过完全跳过标签(但不是垂直线),而无需诉诸于tickmode: array并手动指定所有刻度标签,并仍然受益于自动刻度调整,具体取决于缩放。例如,如果显示了所有子刻度数字,则我希望在1、2、3、5、7处有刻度标签,结果将如下所示:

my preferred solution, but without specifying ticks manually

其他显示模式(仅数字2和5,或仅幂10)不会改变。是否有办法实现这一点?如果有,该怎么做?如果需要,我可以修补Plotly,但现在我不知道从哪里开始。


1
通常我会通过将标签旋转35-45度来解决这个问题。这样它们仍然都在那里,而且还是可读的。 - Johan Faerch
1
你可以编写一个函数,允许你指定刻度标签的位置。该函数本身将使用 tickmode: array,但你只需要担心编写一次该函数即可。 - Derek O
@JohanFaerch,没错,谢谢你。但是考虑到用户反馈,他们认为旋转并不是最优解决方案,他们宁愿跳过标签也不想为了每个标签都要歪着头(即使有足够的空间)。 :-) - Jens
1
@DerekO,我们以前做过这个,但是这会显著减慢渲染速度,因为在每次重新渲染时都必须重新计算子刻度。此外,在调整大小、平移和缩放操作期间,该函数存在一些非常奇怪的竞争条件。不太想再走这条路了... - Jens
@Jens 好的,没问题(开个玩笑 ;) - Johan Faerch
2个回答

1
通常我会通过将标签旋转35至45度来解决这个问题。这样它们就都在那里并且仍然可读。

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


如果“tickangle = auto”只在需要时旋转,这将解决至少重叠问题。然而,plotly.js已经可以根据可用空间(缩放级别)决定显示多少个刻度线。代码中是否有位置可以微调此决策呢? - Jens

0

好的,我深入研究了Plotly的配置选项,并发现有些选项是根据缩放级别半自动修改的,但这些修改发生的时间是硬编码的。

因此,我提供了一个补丁(适用于Plotly v1.36,因为这是我们目前使用的版本)。我修改了一些硬编码的条件,从1、10、100、...到1、2、5、10、20、50、100、...,在对数轴上标记为1、2、3、4、5、...,并删除了最后一种情况下4、6、8和9的刻度标签,以避免文本重叠。

enter image description here

对于我的应用程序,现在它运行良好,我找不到任何重叠的刻度标签。此外,我还去掉了十的幂之间的单个数字,这使一些用户感到困惑。

--- 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));
     }

此补丁已发布为公共领域,以使其尽可能易于直接集成到Plotly中(也许作为“D3”格式选项?)。


也已经作为PR提交到Plotly.JS的GitHub页面 https://github.com/plotly/plotly.js/issues/6105。 - Jens

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接