食谱
|
该页面正在翻译中。 |
食谱允许您扩展 马基 使用您自己的自定义类型和绘图命令。
|
请注意,如果您是软件包开发人员,则可以在不添加所有配方的情况下添加配方。 |
有两种类型的食谱:
-
_type recipes_定义从用户定义类型到现有绘图类型的简单映射
-
_Full recipes_定义新的自定义绘图函数。
型配方
类型配方大多只是从一种类型或一组输入参数类型的转换,但Makie不知道,到Makie已经可以处理的另一个类型。
这是尝试在Makie中进行转换的顺序逻辑:
-
派遣
convert_arguments(::PlotType,args...) -
如果没有找到匹配的方法,通过
conversion_trait(::PlotType) -
派遣
convert_arguments(::ConversionTrait,args...) -
如果没有找到匹配的方法,请尝试递归地转换每个参数
转换_single_argument直到每种类型不再改变 -
派遣
convert_arguments(::PlotType,converted_args...) -
如果找不到方法,则失败
多个参数转换与 转换/转换
绘制一个 圆 例如,可以通过转换为任何现有绘图类型的点矢量来定义:
Makie.convert_arguments(::Type{<: AbstractPlot}, x::Circle) = (decompose(Point2f, x),)
# or if you picked up MakieCore as a light-weight recipe system dependency
MakieCore.convert_arguments(::Type{<: AbstractPlot}, x::Circle) = (decompose(Point2f, x),)
|
警告 |
为每种绘图类型定义转换可能没有意义,因此可以将转换限制为绘图类型的子集,例如仅用于散点图:
Makie.convert_arguments(P::Type{<:Scatter}, ::MyType) = convert_arguments(P, rand(10))
通过转换特性,可以更轻松地为一组具有相同特性的绘图类型定义行为。 以点为基础的 例如适用于 散点,散点, 线条 等。 预定义的特征是 NoConversion缧, 以点为基础的, CellGrid<:基于网格, VertexGrid<:GridBased, 像样的,像样的, [医]容积式 和 采样/采样. 他们都继承自 [医]转换,有时是间接的。
Makie.convert_arguments(::PointBased, ::MyType) = ...
也可以将多个参数转换在一起。
Makie.convert_arguments(::Type{<:Scatter}, x::MyType, y::MyOtherType) = ...
或者,您可以定义默认绘图类型,以便 情节(x::MyType) 将始终绘制为例如表面图:
plottype(::MyType) = Surface
完整的食谱与 @食谱 宏
完整的食谱分为两部分。 首先是绘图类型名称,例如 我的计划,然后使用 @食谱 宏。
二是至少一个自定义 阴谋! 的方法 我的计划 它使用其他现有的绘图函数创建一个实际的可视化。
我们用一个例子来说明这是如何工作的:
@recipe(MyPlot, x, y, z) do scene
Theme(
plot_color = :red
)
end
这个宏扩展到几件事。 首先是类型定义:
const MyPlot{ArgTypes} = Plot{myplot, ArgTypes}
的类型参数 情节 包含函数 我的计划 而不是例如符号 我的计划. 这样的映射从 我的计划 到 我的计划 更安全、更简单。 自动定义以下签名以使 我的计划 好用:
myplot(args...; kw_args...) = ...
myplot!(args...; kw_args...) = ...
专业的 论证名称 如果你有一个参数列表,就会发出 (x,y,z) 提供给配方宏:
argument_names(::Type{<: MyPlot}) = (:x, :y, :z)
这是可选的,但它将允许使用 plot_object的。x 从调用中获取第一个参数 plot_object=myplot(兰德(10),兰德(10),兰德(10)) 例如。
或者,您可以随时获取 ith参数使用 plot_对象[i],如果你漏掉 (x,y,z),默认版本的 论证名称 将提供 plot_object的。arg1 等。
在正文中给出的主题 @食谱 调用被插入到 默认值 它将主题插入到任何绘制的场景中 我的计划:
function default_theme(scene, ::MyPlot)
Theme(
plot_color => :red
)
end
此外,您可以通过定义来控制绘图使用哪种轴
Makie.args_preferred_axis(::Type{<: MyPlot}, x, y, z) = Makie.LScene
或
Makie.preferred_axis_type(plot::MyPlot) = Makie.LScene # if you need the entire plot object as information
注意Makie默认为 麦琪轴心,轴心 作为优选的轴。
作为定义的第二部分 我的计划,你应该实现的实际绘图 我的计划 专业对象 阴谋!:
function Makie.plot!(myplot::MyPlot)
# normal plotting code, building on any previously defined recipes
# or atomic plotting operations, and adding to the combined `myplot`:
lines!(myplot, rand(10), color = myplot.plot_color)
plot!(myplot, myplot.x, myplot.y, myplot.z)
myplot
end
可以在此处添加特化,具体取决于提供给的参数_types_ 我的计划. 例如,对 我的图(a) 何时 a 是浮点数的3d数组:
const MyVolume = MyPlot{Tuple{<:AbstractArray{<: AbstractFloat, 3}}}
argument_names(::Type{<: MyVolume}) = (:volume,) # again, optional
function plot!(plot::MyVolume)
# plot a volume with a colormap going from fully transparent to plot_color
volume!(plot, plot[:volume], colormap = :transparent => plot[:plot_color])
plot
end
例子:股票图表
假设我们想用经典的开盘/收盘和低/高组合来可视化股票价值。 在这个例子中,我们将创建一个特殊的类型来保存这些信息,以及一个可以绘制这种类型的配方。
首先,我们创建一个结构来保存某一天的股票价值:
using CairoMakie
struct StockValue{T<:Real}
open::T
close::T
high::T
low::T
end
现在我们创建一个新的绘图类型,称为 存货图. 该 做场景 闭包只是一个返回我们默认属性的函数,在这种情况下,它们将股票颜色为红色,股票颜色为绿色。
@recipe(StockChart) do scene
Attributes(
downcolor = :red,
upcolor = :green,
)
end
然后我们进入食谱的肉,这实际上是创建一个情节方法。 我们需要重载一个特定的方法 麦琪阴谋! 作为它的论点,它有一个新的子类型 存货图 情节类型。 该类型的类型参数是一个元组,描述此方法应该为其工作的参数类型。
请注意,我们在 阴谋! 方法,我们可以通过索引到 存货图,由Makie自动转换为可观察值。
这意味着我们必须以动态的方式构造我们的绘图函数,以便每当输入可观察值发生变化时,它就会自我更新。 这可能比您可能从其他绘图包中知道的食谱有点棘手,这些绘图包主要产生静态图。
function Makie.plot!(
sc::StockChart{<:Tuple{AbstractVector{<:Real}, AbstractVector{<:StockValue}}})
# our first argument is an observable of parametric type AbstractVector{<:Real}
times = sc[1]
# our second argument is an observable of parametric type AbstractVector{<:StockValue}}
stockvalues = sc[2]
# we predefine a couple of observables for the linesegments
# and barplots we need to draw
# this is necessary because in Makie we want every recipe to be interactively updateable
# and therefore need to connect the observable machinery to do so
linesegs = Observable(Point2f[])
bar_froms = Observable(Float32[])
bar_tos = Observable(Float32[])
colors = Observable(Bool[])
#这个帮助函数将更新我们的可观察对象
#无论何时 `时代` 或 `股票价值` 改变
函数update_plot(times,stockvalues)
颜色[]
#清除observables里面的向量
空!(linesegs[])
空!(bar_froms[])
空!(bar_tos[])
空!(颜色[])
#然后用我们更新的值重新填充它们
for(t,s)in zip(times,stockvalues)
推!(linesegs[],Point2f(t,s.low))
推!(linesegs[],Point2f(t,s.high))
推!(bar_froms[],s.开放)
推!(bar_tos[],s.关闭)
结束
追加!(颜色[],[x.关闭>x.以股票价值为x打开])
颜色[]=颜色[]
结束
#连接 `更新_plot` 所以它被称为每当 `时代`
#或 `股票价值` 改变
麦琪可观察的。onany(update_plot,times,stockvalues)
#然后用第一个手动调用一次 `时代` 和 `股票价值`
#contents所以我们用正确的值预填充所有observable
update_plot(times[],stockvalues[])
#对于颜色,我们只使用布尔值或0和1的向量,它们是
#根据2元素颜色表着色
#我们用我们的 `[医]下色` 和 `上颜色;上颜色`
#我们给出observable元素类型 `任何` 所以当我们改变时它不会出错
#从符号的颜色,如:红色到不同类型的RGBf(1,0,1)
colormap=可观察{Any}()
地图!(colormap,sc。downcolor,sc。upcolor)做dc,uc
[dc,uc]
结束
在最后一步,我们计划进入我们的 `sc` StockChart对象,这意味着
#我们的新情节是由两个简单的食谱组成的。
#互相顶
行距!(sc,linesegs,color=colors,colormap=colormap)
巴普洛!(sc,times,bar_froms,fillto=bar_tos,color=colors,strokewidth=0,colormap=colormap)
#最后,我们返回新的图表
sc
结束
timestamps = 1:100
# we create some fake stock values in a way that looks pleasing later
startvalue = StockValue(0.0, 0.0, 0.0, 0.0)
stockvalues = foldl(timestamps[2:end], init = [startvalue]) do values, t
open = last(values).close + 0.3 * randn()
close = open + randn()
high = max(open, close) + rand()
low = min(open, close) - rand()
push!(values, StockValue(
open, close, high, low
))
end
# now we can use our new recipe
f = Figure()
stockchart(f[1, 1], timestamps, stockvalues)
# and let's try one where we change our default attributes
stockchart(f[2, 1], timestamps, stockvalues,
downcolor = :purple, upcolor = :orange)
f
作为最后一个例子,让我们假装我们的股票数据是动态进来的,我们想创建一个动画。 如果我们使用observables作为输入参数,然后逐帧更新,这很容易:
timestamps = Observable(collect(1:100))
stocknode = Observable(stockvalues)
fig, ax, sc = stockchart(timestamps, stocknode)
record(fig, "stockchart_animation.mp4", 101:200,
framerate = 30) do t
# push a new timestamp without triggering the observable
push!(timestamps[], t)
# push a new StockValue without triggering the observable
old = last(stocknode[])
open = old.close + 0.3 * randn()
close = open + randn()
high = max(open, close) + rand()
low = min(open, close) - rand()
new = StockValue(open, close, high, low)
push!(stocknode[], new)
# now both timestamps and stocknode are synchronized
# again and we can trigger one of them by assigning it to itself
# to update the whole stockcharts plot for the new frame
stocknode[] = stocknode[]
# let's also update the axis limits because the plot will grow
# to the right
autolimits!(ax)
end
Makie包扩展
有关Makie的包扩展的简单示例,请参阅https://github.com/jkrumbiegel/MakiePkgExtTest。以下文档解释了链接示例中实现的基础知识。
设置您的https://pkgdocs.julialang.org/v1/creating-packages/#Conditional-loading-of-code-in-packages-(Extensions)[包扩展]有 马基 作为依赖,而不是 马基科 或者任何一个Makie的后端。
例如,您必须在主包中定义和导出完整的配方函数:
module SomePackage
export someplot
export someplot!
# functions with no methods
function someplot end
function someplot! end
end # module
然后你的Makie扩展包将添加方法到 某个情节!.
module MakieExtension
使用一些包装
导入SomePackage:someplot,someplot!
麦琪convert_single_argument(v::SomeVector)=v.v
@食谱(SomePlot)做场景
主题()
结束
功能Makie。阴谋!(p::SomePlot)
台词!(p,p[1])
散开!(p,p[1])
返回p
结束
结束#模块
请参阅上面的链接示例以获取更多功能,例如容纳扩展和 需要。jl,或为绘制尚未有方法的函数提供错误提示,或设置您的 项目。汤姆尔.