Engee 文档

WGLMakie

该页面正在翻译中。

WGLMakie是基于web的后端,现在主要在Julia中实现。 WGLMakie使用https://github.com/SimonDanisch/Bonito.jl[鲣鱼]生成html和JavaScript以显示图。 在JavaScript方面,我们使用https://threejs.org/[ThreeJS]和https://en.wikipedia.org/wiki/WebGL[WebGL]渲染图。 将更多的实现迁移到JavaScript是目前的目标,它将为我们提供更好的JavaScript API,以及更多的交互,而无需运行Julia服务器。

警告WGLMakie可以被认为是实验性的,因为JavaScript API还不稳定,笔记本集成还不完美,但所有的情节类型都应该工作,因此所有的食谱,但有一些警告

浏览器支持

伊朱利亚
  • Bonito现在使用IJulia连接,因此即使在复杂的代理设置中也可以使用,而无需任何额外的设置

  • 不支持重新加载页面,如果重新加载,则需要重新执行所有单元格并确保 页() 被首先执行。

JupyterHub/Jupyterlab/活页夹

  • WGLMakie主要应该使用websocket连接。 鲣鱼试图https://github.com/SimonDanisch/Bonito.jl/blob/master/src/server-defaults.jl[推断代理设置]需要连接到julia进程。 在本地jupyterlab实例上,这应该没问题。 在托管实例上,可能需要https://jupyter-server-proxy.readthedocs.io/en/latest/arbitrary-ports-hosts.html#with-jupyterhub[脧锚脧赂`jupyter-服务器-代理`]安装,然后执行类似 页面(;listen_port=9091,proxy_url="<jhub-instance>.com/user/<username>/proxy/9091"). 另请参阅: 第2464期 第2405期

冥王星

  • 仍然使用Bonito的Websocket连接,因此需要为远程服务器进行额外的设置。

  • 不支持重新加载页面,如果重新加载,则需要重新执行所有单元格并确保 页() 被首先执行。

  • 静态html导出尚未完全工作

朱利亚胡布

  • 浏览器中的VSCode应该开箱即用。

  • 朱利亚胡布的冥王星仍然有一个https://github.com/SimonDanisch/Bonito.jl/issues/140[问题]与WebSocket连接。 所以,你会看到一个情节,但互动不起作用。

浏览器支持

有些浏览器可能只有Webgl1.0,或者需要额外的步骤来启用WebGL,但一般来说,所有现代浏览器都在https://www.lambdatest.com/web-technologies/webgl2[移动和桌面应支持WebGL2.0]。 Safari用户可能需要https://discussions.apple.com/thread/8655829[启用]WebGL,虽然。 如果您最终停留在WebGL1.0上,则主要缺少的功能将是 卷数 & 轮廓(体积).

激活和屏幕配置

通过调用激活后端 WGLMakie.启动!() 具有以下选项:

WGLMakie.activate!(; screen_config...)

将WGLMakie设置为当前活动的后端,并允许快速设置 屏幕_config. 请注意, 屏幕_config 也可以通过永久设置 Makie。set_theme!(WGLMakie=(screen_config。..,)).

可以通过的参数 屏幕_config:

  • 帧速率=30:将帧速率(每秒帧数)设置为更高的数字以获得更流畅的动画,或设置为更低的数字以使用更少的资源。

  • resize_to=无:调整画布到父元素的大小 resize_to=:父,或身体,如果 resize_to=:身体. 默认值 什么都没有,将调整什么。 元组也是允许的,只有宽度/高度的值是相同的。

输出

您可以在Pluto,IJulia,Webpages和Documenter中使用Bonito和WGLMakie创建交互式应用程序和仪表板,在实时网页上提供它们,或将它们导出为静态HTML。

本教程将贯穿不同的模式和期望的限制。

页面

页() 可以用来重置多页输出所需的鲣鱼状态,就像它的情况一样 文件中心 或各种笔记本(Ijulia/冥王星/等)。 以前,有必要始终插入并显示 页面 打电话给笔记本,但现在打电话给 页() 是可选的,不需要显示。 它所做的是纯粹重置新的多页输出的状态,这通常是针对 文件中心,它在一个Julia会话中创建多个页面,或者您可以使用它来重置笔记本中的状态,例如在页面重新加载后。 页面(可导出=true,离线=true) 可用于强制内联所有数据和js依赖关系,以便所有内容都可以在单个HTML对象中加载,而无需运行Julia进程。 默认值应该已经被这样选择,例如文档中心,所以这应该主要用于例如。 冥王星 离线导出(目前不完全支持,但应该很快)。

以下是如何在Franklin中使用this的示例:

using WGLMakie
using Bonito, Markdown
Page() # for Franklin, you still need to configure
WGLMakie.activate!()
Makie.inline!(true) # Make sure to inline plots into Documenter output!
scatter(1:4, color=1:4)

正如你所看到的,输出是完全静态的,因为我们没有一个正在运行的Julia服务器,就像Pluto一样。 为了使情节具有交互性,我们需要在JS中编写WGLMakie的更多部分,这是一项持续的努力。 正如你所看到的,交互性已经继续为3D工作:

N = 60
function xy_data(x, y)
    r = sqrt(x^2 + y^2)
    r == 0.0 ? 1f0 : (sin(r)/r)
end
l = range(-10, stop = 10, length = N)
z = Float32[xy_data(x, y) for x in l, y in l]
surface(
    -1..1, -1..1, z,
    colormap = :Spectral
)

有几种方法可以在静态导出中保持与绘图交互。

记录状态图

Bonito允许为满足以下接口的所有小部件记录statemap:

# must be true to be found inside the DOM
is_widget(x) = true
# Updating the widget isn't dependent on any other state (only thing supported right now)
is_independant(x) = true
# The values a widget can iterate
function value_range end
# updating the widget with a certain value (usually an observable)
function update_value!(x, value) end

目前,只有滑块重载接口:

using Observables

App() do session::Session
    n = 10
    index_slider = Slider(1:n)
    volume = rand(n, n, n)
    slice = map(index_slider) do idx
        return volume[:, :, idx]
    end
    fig = Figure()
    ax, cplot = contour(fig[1, 1], volume)
    rectplot = linesegments!(ax, Rect(-1, -1, 12, 12), linewidth=2, color=:red)
    on(index_slider) do idx
        translate!(rectplot, 0,0,idx)
    end
    heatmap(fig[1, 2], slice)
    slider = DOM.div("z-index: ", index_slider, index_slider.value)
    return Bonito.record_states(session, DOM.div(slider, fig))
end

直接执行Javascript

Bonito可以轻松构建整个HTML和JS应用程序。 例如,您可以直接注册在更改时运行的JavaScript函数。

using Bonito

App() do session::Session
    s1 = Slider(1:100)
    slider_val = DOM.p(s1[]) # initialize with current value
    # call the `on_update` function whenever s1.value changes in JS:
    onjs(session, s1.value, js"""function on_update(new_value) {
        //interpolating of DOM nodes and other Julia values work mostly as expected:
        const p_element = $(slider_val)
        p_element.innerText = new_value
    }
    """)

    return DOM.div("slider 1: ", s1, slider_val)
end

也可以将图插值到JS中,并通过JS更新这些图。 问题是,还没有一个惊人的界面。 返回的对象直接是一个三个对象,所有的plot属性都转换为Javascript类型。 好消息是,所有属性都应该在 三烯。材料。制服,或 三烯。几何。属性. 接下来,我们应该在WGLMakie中创建一个API,这使得它像在Julia中一样简单: 情节。属性=值. 但是,虽然这还没有到位,但记录返回的对象可以很容易地弄清楚该怎么做-顺便说一句,JS控制台+日志记录是惊人的,并且可以很容易地在记录对象后玩

using Bonito: on_document_load
using WGLMakie

App() do session::Session
    s1 = Slider(1:100)
    slider_val = DOM.p(s1[]) # initialize with current value

    fig, ax, splot = scatter(1:4)

    # With on_document_load one can run JS after everything got loaded.
    # This is an alternative to `evaljs`, which we can't use here,
    # since it gets run asap, which means the plots won't be found yet.
    on_document_load(session, js"""
        // you get a promise for an array of plots, when interpolating into JS:
        $(splot).then(plots=>{
            // just one plot for atomics like scatter, but for recipes it can be multiple plots
            const scatter_plot = plots[0]
            // open the console with ctr+shift+i, to inspect the values
            // tip - you can right click on the log and store the actual variable as a global, and directly interact with it to change the plot.
            console.log(scatter_plot)
            console.log(scatter_plot.material.uniforms)
            console.log(scatter_plot.geometry.attributes)
        })
    """)

    # with the above, we can find out that the positions are stored in `offset`
    # (*sigh*, this is because threejs special cases `position` attributes so it can't be used)
    # Now, lets go and change them when using the slider :)
    onjs(session, s1.value, js"""function on_update(new_value) {
        $(splot).then(plots=>{
            const scatter_plot = plots[0]
            // change first point x + y value
            scatter_plot.geometry.attributes.pos.array[0] = (new_value/100) * 4
            scatter_plot.geometry.attributes.pos.array[1] = (new_value/100) * 4
            // this always needs to be set of geometry attributes after an update
            scatter_plot.geometry.attributes.pos.needsUpdate = true
        })
    }
    """)
    # and for got measures, add a slider to change the color:
    color_slider = Slider(LinRange(0, 1, 100))
    onjs(session, color_slider.value, js"""function on_update(hue) {
        $(splot).then(plots=>{
            const scatter_plot = plots[0]
            const color = new THREE.Color()
            color.setHSL(hue, 1.0, 0.5)
            scatter_plot.material.uniforms.color.value.x = color.r
            scatter_plot.material.uniforms.color.value.y = color.g
            scatter_plot.material.uniforms.color.value.z = color.b
        })
    }""")

    markersize = Slider(1:100)
    onjs(session, markersize.value, js"""function on_update(size) {
        $(splot).then(plots=>{
            const scatter_plot = plots[0]
            scatter_plot.material.uniforms.markersize.value.x = size
            scatter_plot.material.uniforms.markersize.value.y = size
        })
    }""")
    return DOM.div(s1, color_slider, markersize, fig)
end

这总结了静态页面中与WGLMakie交互的当前状态。

离线工具提示

麦琪数据探测仪 与WGLMakie工作得很好,但它需要一个正在运行的Julia进程来显示和更新工具提示。

还有一种方法可以直接在Javascript中显示工具提示,它需要插入到HTML dom中。 这意味着,我们实际上需要使用 鲣鱼。应用程序 返回 多姆 对象:

App() do session
    f, ax, pl = scatter(1:4, markersize=100, color=Float32[0.3, 0.4, 0.5, 0.6])
    custom_info = ["a", "b", "c", "d"]
    on_click_callback = js"""(plot, index) => {
        // the plot object is currently just the raw THREEJS mesh
        console.log(plot)
        // Which can be used to extract e.g. position or color:
        const {pos, color} = plot.geometry.attributes
        console.log(pos)
        console.log(color)
        const x = pos.array[index*2] // everything is a flat array in JS
        const y = pos.array[index*2+1]
        const c = Math.round(color.array[index] * 10) / 10 // rounding to a digit in JS
        const custom = $(custom_info)[index]
        // return either a string, or an HTMLNode:
        return "Point: <" + x + ", " + y + ">, value: " + c + " custom: " + custom
    }
    """

    # ToolTip(figurelike, js_callback; plots=plots_you_want_to_hover)
    tooltip = WGLMakie.ToolTip(f, on_click_callback; plots=pl)
    return DOM.div(f, tooltip)
end

冥王星/伊朱利亚

请注意,只要Julia会话正在运行,Makie的正常交互性就会在例如Pluto中保留WGLMakie。 这给我们带来了设置冥王星/伊朱利亚会议! 在本地,WGLMakie应该只是为Pluto/IJulia开箱即用,但如果您从另一台PC访问笔记本电脑,则必须设置如下内容:

begin
    using Bonito
    some_forwarded_port = 8080
    Page(listen_url="0.0.0.0", listen_port=some_forwarded_port)
end

或者还指定代理URL,如果您有更复杂的代理设置。 有关更高级的设置,请参阅 ?页面 文件及 鲣鱼。configure_server!. 在 headless文档,您还可以阅读有关设置Bonito服务器和端口转发的更多信息。

造型设计

Bonito允许加载任意css,并且 多姆。xxx 包装所有现有的HTML标签。 所以任何CSS文件都可以使用,例如,甚至像https://tailwindcss.com/[顺风]带 资产:

TailwindCSS = Bonito.Asset("/path/to/tailwind.min.css")

鲣鱼还提供 款式 类型,它允许定义整个样式表并将它们分配给任何DOM对象。 这就是鲣鱼如何创建样式化的组件:

Rows(args...) = DOM.div(args..., style=Styles(
    "display" => "grid",
    "grid-template-rows" => "fr",
    "grid-template-columns" => "repeat($(length(args)), fr)",
))

这个样式对象在一个会话中只会被插入一次DOM,后续的使用只会给div一个相同的类。

请注意,鲣鱼已经定义了类似上述的东西 :

using Colors
using Bonito

App() do session::Session
    hue_slider = Slider(0:360)
    color_swatch = DOM.div(class="h-6 w-6 p-2 m-2 rounded shadow")
    onjs(session, hue_slider.value, js"""function (hue){
        $(color_swatch).style.backgroundColor = "hsl(" + hue + ",60%,50%)"
    }""")
    return Row(hue_slider, color_swatch)
end

Bonito还提供了一个styleable卡组件:

using Markdown

App() do session::Session
    # We can now use this wherever we want:
    fig = Figure(size=(300, 300))
    contour(fig[1,1], rand(4,4))
    card = Card(Grid(
        Centered(DOM.h1("Hello"); style=Styles("grid-column" => "1 / 3")),
        StylableSlider(1:100; style=Styles("grid-column" => "1 / 3")),
        DOM.img(src="https://julialang.org/assets/infra/logo.svg"),
        fig; columns="1fr 1fr", justify_items="stretch"
    ))
    # Markdown creates a DOM as well, and you can interpolate
    # arbitrary jsrender'able elements in there:
    return DOM.div(card)
end

希望随着时间的推移,会有很多像上面这样的风格化元素的帮助库,用Bonito+WGLMakie制作华丽的仪表板。

出口

Documenter只是将plots+页面呈现为html,因此如果您想将WGLMakie/Bonito对象内联到您自己的页面中,可以使用如下所示:

using WGLMakie, Bonito, FileIO
WGLMakie.activate!()

open("index.html", "w") do io
    println(io, """
    <html>
        <head>
        </head>
        <body>
    """)
    Page(exportable=true, offline=true)
    # Then, you can just inline plots or whatever you want :)
    # Of course it would make more sense to put this into a single app
    app = App() do
        C(x;kw...) = Card(x; height="fit-content", width="fit-content", kw...)
        figure = (; size=(300, 300))
        f1 = scatter(1:4; figure)
        f2 = mesh(load(assetpath("brain.stl")); figure)
        C(DOM.div(
            Bonito.StylableSlider(1:100),
            Row(C(f1), C(f2))
        ); padding="30px", margin="15px")
    end
    show(io, MIME"text/html"(), app)
    # or anything else from Bonito, or that can be displayed as html:
    println(io, """
        </body>
    </html>
    """)
end