【问题标题】:Adaptive gridlines自适应网格线
【发布时间】:2025-12-02 00:00:02
【问题描述】:

我想使用网格线在二维图形上创建millimeter graphing paper 的效果,以显示多变量函数如何依赖于 1 个变量。不同变量的尺度差异很大,所以我的幼稚方法(我以前使用过)似乎不起作用。

我目前拥有的示例:

<< ErrorBarPlots`
Cmb[x_, y_, ex_, ey_] := {{N[x], N[y]}, ErrorBar[ex, ey]};
SetAttributes[Cmb, Listable];

ELP[x_, y_, ex_, ey_, name_] :=
 ErrorListPlot[
  Cmb[x, y, ex, ey],
  PlotRange -> FromTo[x, y],
  PlotLabel -> name,
  Joined -> True, Frame -> True, GridLines -> GetGrid,
  ImageSize -> {600}
 ]

FromTo(我想在框架中留出 5% 的边距)和 GetGrid 都不能完全按照我的意愿工作。

在某些轴上,变量相差 10 的多个数量级。而且我不希望一个轴比另一个轴有多个 10 个网格线的数量级。最重要的是,我希望网格线与刻度线对齐。

样本数据:

ELP[
  {4124961/25000000, 27573001/100000000, 9162729/25000000, 44635761/
   100000000, 15737089/25000000, 829921/1562500, 4405801/4000000, 
   23068809/25000000, 329386201/100000000, 58079641/100000000},
  {1/10, 1/5, 3/10, 2/5, 3/5, 1/2, 1/2, 1/2, 1/2, 1/2},
  {2031/(250000 Sqrt[10]), 5251/(500000 Sqrt[10]), 3027/(
   250000 Sqrt[10]), 6681/(500000 Sqrt[10]), 3967/(250000 Sqrt[10]), 
   911/(62500 Sqrt[10]), 2099/(100000 Sqrt[10]), 4803/(
   250000 Sqrt[10]), 18149/(500000 Sqrt[10]), 7621/(500000 Sqrt[10])},
  {1/2000, 1/1000, 3/2000, 1/500, 3/1000, 1/400, 1/400, 1/400, 1/400, 
   1/400},
  "T2, m"
]

会导致:

还有我天真的 GetGrid,它在某种意义上是有效的:

FromTo[x_, y_] := Module[{dx, dy},
   dx = (Max[x] - Min[x])*0.1;
   dy = (Max[y] - Min[y])*0.1;
   {{Min[x] - dx, Max[x] + dx}, {Min[y] - dy, Max[y] + dy}}];
GetGrid[min_, max_] := Module[{step, i},
  step = (max - min)/100;
  Table[
   {min + i*step,
    If[Equal[Mod[i, 10], 0],
     Directive[Gray, Thick, Opacity[0.5]],
     If[Equal[Mod[i, 5], 0],
      Directive[Gray, Opacity[0.5]],
      Directive[LightGray, Opacity[0.5]]
      ]]},
   {i, 1, 100}]
  ]

问题

如何使 GridLines 与刻度对齐?

编辑:使用

GetTicks[x_, y_] := Module[{dx, dy},
   dx = (Max[x] - Min[x])*0.1;
   dy = (Max[y] - Min[y])*0.1;
   {
    Min[x] - dx + Table[i*dx*1.2, {i, 1, 9}],
    Min[y] - dy + Table[i*dy*1.2, {i, 1, 9}]
    }];

ELP[x_, y_, ex_, ey_, name_] :=
 ErrorListPlot[
  Cmb[x, y, ex, ey],
  PlotRange -> FromTo[x, y],
  PlotLabel -> name,
  Joined -> True, Frame -> True, GridLines -> GetGrid, 
  FrameTicks -> GetTicks[x, y],
  ImageSize -> {600},
  AspectRatio -> 1
  ]

我可以得到:

这好多了。但我想移动网格而不是刻度。

编辑:@Sjoerd C. de Vries

您的解决方案完成了我想要归档的工作。我还注意到,如果我取样本数据的前 5 个元素,那么绘图将是(对元素进行排序并添加回归线)。

注意最左边的元素就像离网一样。

【问题讨论】:

  • 您可能还想插入 Needs["ErrorBarPlots"] for completeness. Do you intend the gridlines to form a square raster? In that case you have to play with AspectRatio`。
  • @Sjoerd C. de Vries:是的,你的权利。
  • 如果你想指定刻度线的位置,我建议使用LevelScheme。它的CustomTicks 包远远优于内置功能,而且您不必自己编写容易出错的代码来设置自定义规范。
  • @rcollyer : 有趣的资源。
  • 显然,自定义网格线应该在 LevelScheme 的下一个版本中。不过,我不知道它是否能够支持三个级别的样式。

标签: wolfram-mathematica


【解决方案1】:

不要使用 FrameTicks,但要正确移动网格。这是第一种方法。晚餐等待。

getGrid[min_, max_] :=
 Module[{step, i},
  Print[{min, max}];
  step = 1/100;
  Table[
   {
    Floor[min, 0.1] + i*step,
    If[Equal[Mod[i, 10], 0], Directive[Gray, Thick, Opacity[0.5]],
     If[Equal[Mod[i, 5], 0], Directive[Gray, Opacity[0.5]],
      Directive[LightGray, Opacity[0.5]]
      ]
     ]
    },
   {i, 1, (Ceiling[max, 0.1] - Floor[min, 0.1])/step // Round}
   ]
  ]

使用适合网格的 AspectRatio(可能是 x 和 y 范围的比率)


饭后更新

为了使它对不同的值范围更健壮(根据您的评论),我生成了ListPlot 选择的刻度,并以此为基础:

getGrid[min_, max_] :=
 Module[{step, i,j},
  i = Cases[(Ticks /. 
       AbsoluteOptions[ListPlot[{{min, min}, {max, max}}], 
        Ticks])[[1]], {a_, ___, {_, AbsoluteThickness[0.25`]}} :> a];
  step = i[[2]] - i[[1]];
  Table[
   {
    i[[1]] + j*step/10,
    If[Equal[Mod[j, 10], 0], Directive[Gray, Thick, Opacity[0.5]],
     If[Equal[Mod[j, 5], 0], Directive[Gray, Opacity[0.5]],
      Directive[LightGray, Opacity[0.5]]
      ]
     ]
    },
   {j, 0, 10 Length[i]}
   ]
  ]

并获得产生方形栅格的纵横比

getAspect[{{minX_, maxX_}, {minY_, maxY_}}] :=
 Module[{stepx, stepy, i, rx, ry},
   i = (Ticks /.AbsoluteOptions[ListPlot[{{minX, minY}, {maxX, maxY}}], Ticks]);
   rx = Cases[i[[1]], {a_, ___, {_, AbsoluteThickness[0.25`]}} :> a];
   stepx = rx[[2]] - rx[[1]];
   ry = Cases[i[[2]], {a_, ___, {_, AbsoluteThickness[0.25`]}} :> a];
   stepy = ry[[2]] - ry[[1]];
  ((maxY - minY)/stepy)/((maxX - minX)/stepx)
  ]

测试

ELP[x_, y_, ex_, ey_, name_] := 
 ErrorListPlot[Cmb[x, y, ex, ey], PlotLabel -> name, Joined -> True, 
  Frame -> True, GridLines -> getGrid, ImageSize -> {600}, 
  PlotRangePadding -> 0, AspectRatio -> getAspect[FromTo[x, y]], 
  PlotRange -> FromTo[x, y]]


ELP[{4124961/25000000, 27573001/100000000, 9162729/25000000, 
  44635761/100000000, 15737089/25000000, 829921/1562500, 
  4405801/4000000, 23068809/25000000, 329386201/100000000, 
  58079641/100000000}, {1/10, 1/5, 3/10, 2/5, 3/5, 1/2, 1/2, 1/2, 1/2,
   1/2}, {2031/(250000 Sqrt[10]), 5251/(500000 Sqrt[10]), 
  3027/(250000 Sqrt[10]), 1/100000 6681/(500000 Sqrt[10]), 
  3967/(250000 Sqrt[10]), 911/(62500 Sqrt[10]), 
  2099/(100000 Sqrt[10]), 4803/(250000 Sqrt[10]), 
  18149/(500000 Sqrt[10]), 7621/(500000 Sqrt[10])}, {1/2000, 1/1000, 
  3/2000, 1/500, 3/1000, 1/400, 1/400, 1/400, 1/400, 1/400}, "T2, m"]

这里我将 y 值除以 20 并将 x 值乘以 10000 以显示网格仍然很好:


最终更新(我希望)

这使用 FindDivisions 作为suggested by belisarius。但是,我按照 Margus 的要求使用了毫米纸的三层线结构标准:

getGrid[x_, y_] := 
 FindDivisions[{x, y}, {10, 2, 5}] /. {r_, s_, t_} :> 
   Join[
     {#, Directive[Gray, Thick, Opacity[0.5]]} & /@ r, 
     {#, Directive[Gray, Opacity[0.5]]} & /@ Union[Flatten[s]], 
     {#, Directive[LightGray, Opacity[0.5]]} & /@ Union[Flatten[t]]
   ]

getAspect[{{minX_, maxX_}, {minY_, maxY_}}] :=
 Module[{stepx, stepy},
  stepx = (#[[2]] - #[[1]]) &@FindDivisions[{minX, maxX}, 10];
  stepy = (#[[2]] - #[[1]]) &@FindDivisions[{minY, maxY}, 10];
 ((maxY - minY)/stepy)/((maxX - minX)/stepx)
  ]

警告!!!

我刚刚注意到,如果你在 MMA 中有这个:

然后你把它复制到 SO(只是 ctrl-c ctrl-v),你会得到这个:

(maxY - minY)/stepy/(maxX - minX)/stepx  

数学上不等价。应该是这样的:

((maxY - minY)*stepx)/((maxX - minX)*stepy)

我在上面的代码中更正了这个问题,但是在我的计算机上正常工作时,它已经发布了半天错误。觉得提这个就好了。

【讨论】:

  • @Sjoerd C. de Vries :我尝试过这样的方法,但使用 CeilingFloor 函数适得其反,因为在某些变量上,平均变化约为 10^4 和其他 10^- 4.
  • @Sjoerd C. de Vries :如果我设置AspectRatio -&gt; (Max[y] - Min[y])/(Max[x] - Min[x]),并且变量大小与 10 的顺序相同,那么我认为它甚至可以工作(至少在我测试过的情况下)。但这并不是我想要做的。
  • @Margus WRT 你的第一条评论:检查我的更新;它应该适用于各种数据范围。 WRT评论#2:那不起作用。如果 y 很大而 x 很小怎么办?
  • @Margus 将我的更新更改为包含使用 FromTo 范围输入的自动 AspectRatio。
  • @Sjoerd 有人需要将此警告作为问题发布。如果你不这样做,我会的。
【解决方案2】:

我认为 FindDivisions[ ] 是您所追求的:

FindDivisions[{xmin,xmax},n] 查找大约 n 个“nice”数字的列表,这些数字将 xmin 到 xmax 周围的间隔分成等距的部分。

getTicks[x_, y_] := Flatten@FindDivisions[#, {10}] & /@ FromTo[x, y]
getGrid  [x_,y_] := FindDivisions[{x,y},{10,5}]/.
                          {r__,{s__}}:>Join@@{s,{#,{Gray,Thick}}&/@r} 

【讨论】:

  • +1 我们确定 FindDivisions[] 产生与 Ticks->Automatic 相同的刻度吗?我在文档中找不到这个。如果不是,这不是一个有保证的解决方案。但我猜 FindDivisions[] 暴露了 mma 本身使用的例程。不错,不知道这个这就是我认为 mma 初始屏幕应该有一个随机的今日功能的原因之一,以增加此类偶然发现的数量。由于您的 mm 论文仍然不是 Margus 所要求的(方形网格需要三个细分),我将在我的答案中使用您的代码进行更新。
  • @Sjoerd “保证”是一个强有力的词。我尝试了几个 max-min 集,它 似乎Ticks->Automatic 那样执行。这显然是提供此功能的意图。 WRT 每日功能,我宁愿认真地重新组织文档,因为导航帮助几乎无法访问许多功能。
  • @belisarius 我希望在日志图中使用对数刻度的规定,但似乎 FindDivisions[] 没有。至于第二条评论:我同意帮助可能会更好,但我通常会找到解决方法。只要我知道有一个功能可以完成我的任务,我通常就能找到它。问题是知道这样的功能存在。也许你应该假设你想要的每个功能都存在......
  • @Sjoerd 我(谦虚地)认为文档问题远大于“我能找到自己的路”。我一直在寻找像 reference.wolfram.com/mathematica/TetGenLink/guide/… 这样的东西,它们隐藏在不知名的链接后面,可以节省我几个月的工作时间。
  • @belisarius 确实,这太棒了。从来不知道我们在引擎盖下有这个。我想知道里面还有什么。
【解决方案3】:

如果您对FrameTicksGridlines 使用相同的函数,它们会排成一行。

请参阅 FrameTicksGridLines。我想你需要ImageMargins 来作为边框。

【讨论】:

  • -1 他已经得到了 Frame->true。他也在使用 GridLines。并且 FrameMargins 不是 Plot 的选项
  • @Sjoerd 看来我读他的答案太快了。但我的意思是,他确实需要将 FrameTicks 与 GridLines 协调起来。我没有看到对 FrameTicks 的任何引用。
  • 好的,我删除了-1。你确定 ImageMargins 吗?我有一种预感,它可能是 PlotRangePadding。
  • @Sjoerd 就在收到您对 FrameMargins 的(合理的)批评之前,我尝试了 FrameMargins 和 ImageMargins,发现前者不起作用,但后者起作用。所以我修改了我原来的建议,改为推荐 ImageMargins。
  • 我不能使用FrameTicks -&gt; GetGrid,但是看看它是否适用于某些情况会很有趣。