🌊

なめらかな曲線をJuliaでSVG出力する

2021/12/29に公開

概要

  • Plots.jlで出力されるグラフ(曲線)は折れ線なのでカクカクしている。
  • BasicBSplineパッケージを使って出力すればBézier曲線で近似されるので嬉しい!

本記事で使うパッケージはJuliaのREPLから以下のコマンドでインストールできます。

]add Plots
add StaticArrays
add BasicBSpline
add https://github.com/hyrodium/BasicBSplineExporter.jl

Plots.jlでの出力

Plots.jlパッケージを使ってグラフを出力してみましょう。

using Plots

plot(sin,-8,8)  # 正弦波を-8から8までプロット
savefig("sin_Plots.svg")  # SVGで保存
savefig("sin_Plots.png")  # PNGで保存

極大値の付近でSVG画像を拡大してみます。


カクカクしていますね。[1]

これは曲線が折れ線によって近似されているためです。以下の画像はSVG画像をInkscapeで開いて確認しているところです。

滑らかなグラフを得るにはどのようにすれば良いでしょうか?
SVGではBézier曲線に対応しているのでファイル形式としては可能のはず…
→ BasicBSplineパッケージを使いましょう!

BasicBSplineExporter.jlでの出力

using BasicBSpline
using BasicBSplineExporter
using StaticArrays

f(t) = SVector(t,sin(t)) # 正弦波のパラメータ表示
t0,t1 = -8,8             # 左端と右端

p = 3                                     # 多項式次数。SVGでは3次までのBézier曲線が使える。
k = KnotVector(t0:t1)+p*KnotVector(t0,t1) # ノット列
P = BSplineSpace{p}(k)                    # B-spline空間の定義

a = fittingcontrolpoints(f,(P,))  # B-spline曲線の制御点の計算
M = BSplineManifold(a,(P,)) # B-spline曲線の定義
save_svg("sin_BasicBSpline.svg", M, xlims=(-10,10), ylims=(-2,2)) # B-spline曲線をSVGで保存
save_png("sin_BasicBSpline.png", M, xlims=(-10,10), ylims=(-2,2)) # B-spline曲線をPNGで保存![](https://storage.googleapis.com/zenn-user-upload/731678b5e5e8-20211229.png)

Inkscapeで確認すると以下のようにBézier曲線で滑らかに曲線が表現されていることが分かります。

上記のように定義したB-spline曲線は全体でC^2級の滑らかさを持つようにノットの位置で繋がれています。各区間では多項式になっているので、曲線は区分多項式として表されていることになります。

滑らかでない場合(Plots.jl)

f(x)=|\sin(x)|のグラフを描いてみましょう。
Plots.jlでは以下のようにすれば画像の出力までできます。

plot(abs∘sin,-8,8)
savefig("abssin_Plots.svg")
savefig("abssin_Plots.png")


折れ線近似なので、導関数の不連続点でも特徴を捉えたグラフになっているように見えます。

しかし先端を拡大してみると、グラフはy軸には当たっておらず、特異点の付近で歪んで見えます。

滑らかでない場合(BasicBSpline.jl)

これまでと同様に、以下を実行すればグラフが出力できます。

f(t) = SVector(t,abs(sin(t)))
t0,t1 = -8,8
p = 3
k = KnotVector(range(t0,t1,length=50))+p*KnotVector(t0,t1)
P = BSplineSpace{p}(k)

a = fittingcontrolpoints(f,(P,))
M = BSplineManifold(a,(P,))
save_svg("abssin_BasicBSpline![](https://storage.googleapis.com/zenn-user-upload/555b66bd3bac-20211229.png).svg", M, xlims=(-10,10), ylims=(-2,2))
save_png("abssin_BasicBSpline.png", M, xlims=(-10,10), ylims=(-2,2))

出力された画像↓

導関数の不連続点での拡大↓

ノットの数(区分多項式の分割の数)を増やしたとしても「滑らかに繋ぐ」という制約があるので近似精度はあまり良くなりません。

B-splineでは、ノットの重複に応じて関数の滑らかさが減るため[2]\sinの根の部分にノットを複数配置すれば良いことになります。

k = KnotVector(range(t0,t1,length=12))+p*KnotVector(t0,t1)
k += p*KnotVector(-2π:π:2π)  # 滑らかさを減らしたい点でのノットを増やす
P = BSplineSpace{p}(k)

a = fittingcontrolpoints(f,(P,))
M = BSplineManifold(a,(P,))
save_svg("abssin_BasicBSpline_modified.svg", M, xlims=(-10,10), ylims=(-2,2))
save_png("abssin_BasicBSpline_modified.png", M, xlims=(-10,10), ylims=(-2,2))

出力された画像↓

拡大しても綺麗!↓

まとめ

  • BasicBSpline等のパッケージを使えば滑らかなグラフが出力できて便利!
  • B-splineではノット列の重複に応じて滑らかさを変更できて便利!
  • とりあえずプロットするのならPlots.jlパッケージが便利!

追記

本記事の英語版をJulia Foremにも投稿しました。

脚注
  1. GRバックエンドを使って今回は検証しています。他のバックエンドだと変わるかも知れません。 ↩︎

  2. 正確には、n点のノットの重複があればその点でC^{p-n}級の滑らかさになります。 ↩︎

GitHubで編集を提案

Discussion