Makie:散点图的非重叠标签放置算法

2024-01-03

我使用 CairoMakie 散点图 XY 数据集,但使用标签作为标记:

using CairoMakie


x = [0, 0.5, 0.50]
y = [0, 0.5, 0.51]
lbls = ["O", "A", "B"]

fig = Figure()
ax = Axis(fig[1,1])
scatter!(ax, x, y, marker=:circle, markersize=10, color=:red)
foreach(i -> text!(ax, position=(x[i], y[i]), lbls[i]), 1:3)

display(fig)

这会产生下图:

因为积分A and B彼此非常接近,各自的标签重叠。 CairoMakie 是否有一种算法来放置标签以避免标签重叠?

我知道 Gadfly 有这个能力Geom.label但我希望我不必使用单独的包来绘制此类图表。我也知道在 CairoMakie,我可以使用诸如position and offset以标签不重叠的方式调整标签位置,但我无法对我的情况下的每个数据集执行此操作。

有人可以帮忙吗?或者也许有一个用 Julia 编写的标签放置算法?谢谢。


令人惊讶的是,已经写了几篇关于非重叠标签放置算法的数学论文,但我既没有数学背景,也没有时间研究这些论文,更不用说将算法翻译成代码了。 Python 和 R 都有此类算法的代码。

不管怎样,下面的 Julia 代码对于我的情况来说已经足够了,但对于所有情况和每个人的情况来说不一定足够全面。

using DataFrames
using CairoMakie
using Random


function allpairs(n::Int)
    arr = collect(Iterators.product(1:n, 1:n))
    row, col = size(arr)
    combo = []
    for r ∈ 1:row-1, c ∈ (r + 1):col
        push!(combo, arr[r, c])
    end
    combo
end


angle(x, y) = atan(y[2] - y[1], x[2] - x[1])


dist(x, y) = sqrt((x[2] - x[1])^2 + (y[2] - y[1])^2)


function label_dist(x, y, δw, δh)
    x1, x2 = x[1], x[2]
    y1, y2 = y[1], y[2]
    x1b, x2b = x1 + δw, x2 + δw
    y1b, y2b = y1 + δh, y2 + δh

    left = x2b < x1
    right = x1b < x2
    bottom = y2b < y1
    top = y1b < y2

    d = top && left ? dist((x1, x2b), (y1b, y2)) :
        left && bottom ? dist((x1, x2b), (y1, y2b)) :
        bottom && right ? dist((x1b, x2), (y1, y2b)) :
        right && top ? dist((x1b, x2), (y1b, y2)) :
        left ? x1 - x2b :
        right ? x2 - x1b :
        bottom ? y1 - y2b :
        top ? y2 - y1b :
        0.0
    d
end


function shift!(x, y, idx, r, angle)
    x[idx[1]] -= r * cos(angle)
    y[idx[1]] -= r * sin(angle)
    x[idx[2]] += r * cos(angle)
    y[idx[2]] += r * sin(angle)
end


function label_pos(x, y;               # label positions (original/untransformed scale)
                   min_dist=0.002,     # min. distance between labels (on a 0-1 scale)
                   shift_size=0.002,   # distance to shift labels (on a 0-1 scale)
                   width=0.05,         # width of label (on a 0-1 scale)
                   height=0.03,        # height of label (on a 0-1 scale)
                   attempts::Int=100)  # no. of tries to place labels
    xlims = extrema(x)
    Δx = xlims[2] - xlims[1]
    ylims = extrema(y)
    Δy = ylims[2] - ylims[1]

    xt = (x .- xlims[1]) ./ Δx  # map to a 0-1 scale
    yt = (y .- ylims[1]) ./ Δy

    combo = allpairs(length(xt))
    iter = true
    i = 1
    while iter
        pts = map(combo) do p
            idx = [p[1], p[2]]
            d = label_dist(xt[idx], yt[idx], width, height)
            θ = angle(xt[idx], yt[idx])
            (idx=idx, dist=d, angle=θ)
        end

        pos = findall(p -> p.dist < min_dist, pts)
        foreach(p -> shift!(xt, yt, p.idx, shift_size, p.angle), pts[pos])
        i += 1
        iter = (i <= attempts) && !isempty(pos)
    end

    xt = xt .* Δx .+ xlims[1]   # revert to original scale
    yt = yt .* Δy .+ ylims[1]

    (; x=xt, y=yt)
end


xlims = (0, 1)
ylims = (-10, 10)

Δxl = xlims[2] - xlims[1]
Δyl = ylims[2] - ylims[1]

n = 150      # no. of points to generate
seed = 392348
Random.seed!(seed)
xs = xlims[1] .+ rand(n) .* Δxl
ys = ylims[1] .+ rand(n) .* Δyl

labels = map(i -> "$i", eachindex(xs))

xt = xs[:]
yt = ys[:]
xt2 = xs[:]
yt2 = ys[:]

xt, yt = label_pos(xt, yt)

xlims2 = (xlims[1] - Δxl * 0.05, xlims[2] + Δxl * 0.05)
ylims2 = (ylims[1] - Δyl * 0.05, ylims[2] + Δyl * 0.05)

fig = Figure(resolution=(600,1200))  # must be square and preferably >500 px
axs = Axis(fig[1,1])
scatter!(axs, xs, ys, marker=:circle, markersize=5, color=:red)
axs.title = "non-overlap label placement"

foreach(eachindex(labels)) do i
    text!(axs, position=(xt[i], yt[i]), labels[i], textsize=15, align=(:right, :bottom))
end

axs2 = Axis(fig[2,1])
scatter!(axs2, xs, ys, marker=:circle, markersize=5, color=:red)
axs2.title = "default label placement"

limits!(axs, xlims2, ylims2)
limits!(axs2, xlims2, ylims2)

foreach(eachindex(labels)) do i
    text!(axs2, position=(xt2[i], yt2[i]), labels[i], textsize=15, align=(:right, :bottom))
end

display(fig)

该代码生成以下 2 个图表:

它远非近乎完美或优化,但它足够充分地梳理标签,以便我可以将图形保存为 SVG,然后使用矢量绘图应用程序在需要时手动调整标签。

这是一个开始,因为我相信聪明的人可以优化这段代码。我希望这对其他人有帮助。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Makie:散点图的非重叠标签放置算法 的相关文章

  • 使用 ggplot 为长格式数据创建散点图

    我研究了一个长格式的 data frame 在每种情况下都有不同数量的实体 如下所示 Condition rating Control 2 1596456 Control 0 2385878 Control 3 0042808 Contro
  • Julia:如何让多个工作人员访问模块中的函数?

    我有以下测试模块 MyMod jl 来在 Julia 中存储一些测试函数 一些核心函数是串行编写的 其他函数并行调用核心函数 module MyMod export Dummy distribute data getfrom recombi
  • @distributed 似乎有效,函数返回很不稳定

    我正在学习如何在 Julia 中进行并行计算 我在用着 sync distributed在 3x 嵌套的开始处for循环并行化事物 参见底部的代码 从线路上看println errCmp row col 我可以观察数组的所有元素errCmp
  • 估算缺失数据,同时强制相关系数保持不变

    考虑以下 excel 数据集 m r 2 0 3 3 0 8 4 0 1 3 2 1 5 2 2 3 1 9 2 5 1 2 3 0 2 0 2 6 我的目标是使用以下条件填充缺失值 将上述两列之间的成对相关性表示为 R 大约 0 68 将
  • 当 Jupyter 单元包含函数、循环或其他块时,是否可以将其拆分为多个单元?

    Jupyter 的一项功能是能够一次执行一个单元 如果一个单元格有很多语句 通常可以 希望 将其拆分为更小的单语句单元格 除非涉及块 例如 if for def 等 这个问题之前以不同的方式提出过 在Jupyter中逐步执行算法 https
  • 如何在 Julia 中保存文件

    在某些时候 我认为 Julia v0 7 你可以做 save savepath thingtosave为了使用 Julia 保存文件 我尝试在 v0 7 上运行它 看看是否收到弃用警告 但即使在 0 7 上 它也说 save未定义 如何使用
  • 如何制作任意级别的嵌套for循环

    我可以像这样做一个两级嵌套循环 for i1 in 1 n for i2 in 1 n do something with i1 i2 如何将其扩展到任意级别的嵌套循环 例如 我可以在 Python 中执行此操作以循环 n m 的笛卡尔积
  • 并行模拟写入同一文件

    我的目标是在集群上并行运行 10 000 个左右的 Julia 编码模拟 每个模拟独立于所有其他模拟 每个模拟都有一个要输出的数字 以及有关哪个模拟产生该数字的 3 列信息 因此 强制每个模拟打印在单独的文件上对我来说听起来有点愚蠢 我可以
  • 在 Julia 中解压缩元组数组

    假设我有一个元组数组 arr 1 2 3 4 5 6 使用 python 我可以做zip arr 1 3 5 2 4 6 朱莉娅中与此等效的是什么 作为 splatting 的替代方案 因为这非常慢 您可以执行以下操作 unzip a ma
  • Julia:如何更新到软件包的最新版本(即 Flux)

    I have Julia 1 1 在本例中 我想更新到软件包的最新版本Flux 8 3 0根据Flux jl 的文档 https fluxml ai Flux jl stable 当我打字时 Pkg status Flux I get St
  • 如何在 Julia 中引用结构本身

    我有这个代码 struct MyStruct text String function MyStruct text String text text do other things end end 当我写这篇文章时 我意识到朱莉娅没有认识到
  • 添加图例到散点图

    这个问题已经被问到了 但我想找到一个更清晰的解决方案 给定 X 是 100x2 数据 标签是标签向量 从 1 到 9 我绘制散点图如下 pl scatter X 0 X 1 c labels pl show 如何仅用一行代码添加图例来解释颜
  • 如何在matplotlib中绘制极坐标hist2d/hexbin?

    我有一个随机向量 随机长度和随机角度 想通过绘制其近似 PDF 概率密度函数 hist2d or hexbin 不幸的是 它们似乎不适用于极坐标图 以下代码不会产生任何结果 import numpy as np import matplot
  • Julia 中基准和时间宏的区别

    我最近发现两个宏之间存在巨大差异 benchmark 和 time 在内存分配信息和时间方面 例如 benchmark quadgk x gt x 0 1 BenchmarkTools Trial memory estimate 560 b
  • Julia 中的 Refs 和 Broadcasting 之间有什么联系

    对于两个对象A and B我们之前可以得到向量 A A A B 与代码A A B 从 Julia 0 7 中的弃用警告来看 执行此操作的新方法似乎是使用第一个 A 的引用 所以它变成Ref A A B 参考文献和广播操作之间似乎没有很强的联
  • 使用“kde”函数进行 R 中的 5-D 核密度估计

    我想通过使用 R 的 ks 库中的 kde 函数来执行 5 维数据 x y z 时间 大小 的核密度估计 在它的手册中 它说它可以执行核密度估计1 至 6 维数据 手册第 24 页 http cran r project org web p
  • 读入 Julia 中的数组

    我对 Julia 比较陌生 正在寻找一种有效的方法来从文本文件中读取并将每个 列 存储在数组中 我有 2 列 但通用解决方案也很棒 例如 我想要输入 1 2 3 4 5 6 被读入两个数组 例如 x 和 y 使得 x 1 3 5 和 y 2
  • Julia:将 1x1 数组从内积转换为数字

    从内积运算而不是 1x1 数组中获取数字的最佳方法是什么 还有比这更好的方法吗 1 2 3 4 5 6 1 如果可能的话 我不会手动进行内积 我会使用dot i e dot 1 2 3 4 5 6 我注意到你实际上并没有向量 相反你有1x3
  • Julia DataFrame 中列的累积和

    在 Python Pandas 中 如果我想用现有列的累积和创建一个新列 我会这样做 df cumulative sum df scores cumsum 在 Julia 中执行此操作的等效方法是什么 您可以使用基本方法cumsum计算向量
  • 为什么这两种不同的构造数组的方式会产生不同的行为?

    当我以两种不同的方式构造一个 2 元素数组时 例如a and b 当我将一个元素添加到内部数组之一时 我得到两个不同的结果 这也会发生在append 根据构建每个之后的输出 我希望它们完全相同 julia gt a 2 element Ar

随机推荐

  • Jenkins 管道将所有参数传递给下游作业

    我有一个名为的管道作业buildall看起来像这样 pipeline stages stage job1 build job job1 The buildalljob有25个参数 我想通过所有buildall的参数降至job1 有没有一种简
  • 在 WebView 中显示 URL

    在我的 JsonParsing 中 我从这个 json 中获取了 url 我需要像在 web 视图中一样显示该 Url 链接 我该怎么做 Code在这儿 TextView tv TextView findViewById R id text
  • iOS:MapView 注释未显示图钉

    由于某些奇怪的原因 viewForAnnotation 仅适用于 viewDidLoad 中设置的引脚 这是一个测试引脚 在其他地方加载的引脚在按下时不会被注释 我已经设置了代表 我认为这与mapView调用中的标识符有关 但我不确定如何解
  • Sprite 套件中的 iCarousel

    解释 我正在尝试构建一个类似于 Crossy Road 的角色选择菜单 如您所见here http www therockstargamers com wp content uploads 2015 04 crossy road chara
  • MVC Web API 绑定模型到派生类

    我正在研究如何将模型绑定到 MVC Web API 中的派生类 我遇到的问题是我认为我已经找到了大约 5 种方法 我所拥有的是 型号 gt 模型库 模型A 模型库 模型B 模型库 然后控制器容器该方法 Post ModelBase mode
  • XNA 的自动 XNB 序列化支持哪些类型?

    我已阅读 Shawn Harvgreave 关于自动序列化的博客文章和关于内容管道概述的 MSDN 文章 但我找不到支持的类型列表 引用MSDN 从 XNA Game Studio 3 1 开始 自定义数据的序列化 对于不支持的简单类型 X
  • 如何从 Rails 中的哈希列表中删除嵌套键

    我现在正在尝试几个小时来删除哈希列表的嵌套哈希键 我看到许多解决方案非嵌套哈希 如下所示 sample hash key1 gt value1 key2 gt value2 sample hash except key1 这导致 key2
  • 使用 web api HttpResponseMessage 输出图像

    我正在尝试使用以下代码从 asp net web api 输出图像 但响应正文长度始终为 0 public HttpResponseMessage GetImage HttpResponseMessage response new Http
  • Python对不同维度变量进行曲面拟合得到未知参数?

    我有一个包含 x 和 y 作为自变量的函数 我想将参数拟合到数据和函数并绘制曲面图 我看到如果变量有两个不同的维度 我可以使用np meshgrid x y 那么我如何找到参数a b c呢 我的代码如下所示 import matplotli
  • Python 中的分块矩阵赋值

    从这个mwe a np zeros 5 5 b np zeros 2 2 a np matrix a b np matrix b b 0 0 4 b 1 1 9 b 0 1 7 indice 2 3 1 c a indice indice
  • 领域逻辑与数据验证

    我正在忙着阅读并享受 Mark Seemann 所著的 Net 中的依赖注入 我很难解释确切的上下文 所以如果你熟悉这本书 请只关心这个问题 我的问题与第 2 章第 49 页中的两个 Product 类有关 其中一个位于域层 一个位于数据访
  • jquery 绑定多个锚点

    我想将相同的事件绑定到锚链接列表 为什么这不起作用 Markup a href contactRoles class fg button fg button icon right ui widget ui state default ui
  • 如何防止 R 显示 exp 和 log 值 inf 和零

    我将计算矩阵正态密度以在对数似然公式中使用它们 在我的计算中 我需要计算大数 数千万 的指数 我意识到 当 i gt 710 时 R 会返回 exp i 的无穷大 无论如何 我可以手动强制 R 不显示无穷大 或者其日志不相应地为 0 吗 感
  • 查找元素更改值的索引 pandas dataframe

    关于这个问题 答案 https stackoverflow com questions 19125661 find index where elements change value numpy 有没有一种方法可以为 pandas 数据帧结
  • 进度条适用于 Android API 23,但不适用于 21 或 22

    这是 xml 部分
  • 重新发明标签控件

    我需要从头开始重新发明 重新创建标签控件 以添加我自己的魔力 是的 我知道你在想什么 如果你不这么想 你不应该这样想吗 有人能指出我正确的方向吗 谢谢 重新创建标签的全部目的是我想要完全控制它如何绘制到屏幕上 这样我也可以拥有它的 KeyD
  • ILMerge 问题错误代码 1

    我知道 ilmerge 是一个控制台应用程序 但当我运行它时 它运行一秒钟然后关闭 下面是我尝试使用的预构建代码 它给出了这个问题 Error 1 The command ilmerge out F Users Tom Desktop Ne
  • Cocoa osx NSTableview 改变行高亮颜色

    在我的应用程序中 我有一个基于视图的 NSTableView 其中有一列 行的突出显示颜色设置为常规 蓝色 我需要将该颜色更改为我的自定义颜色 在界面生成器中 我尝试更改它 但唯一的选项是 无 常规和源列表 我尝试了这个帖子解决方案但没有成
  • 将算法从 C# 转换为 VB.NET 失败

    我正在尝试将以下算法从 C 转换为 VB NET 但我所拥有的 VB NET 生成的结果与我的 C 算法不同 有人可以告诉我在转换过程中哪里出了问题吗 public static IEnumerable
  • Makie:散点图的非重叠标签放置算法

    我使用 CairoMakie 散点图 XY 数据集 但使用标签作为标记 using CairoMakie x 0 0 5 0 50 y 0 0 5 0 51 lbls O A B fig Figure ax Axis fig 1 1 sca