niszetの日記

細かい情報を載せていくブログ

niszetの各ページへのリンク集

暫定的に。GitHub上に書いた方がよいかも知れないけども。

niszet名義で登録しているもののリスト。

ツイッターアカウント。ツイ廃なので最低限RTはミュート推奨

twitter.com

作ったまま放置のますとどんアカウント。

mstdn.jp

GitHubアカウント。あんまり公開しているリポジトリはない。

github.com

YouTubeチャンネル。動画は少ない。

www.youtube.com

Qiita。記事の投稿はない。

qiita.com

Zenn。こちらも読むだけ。

zenn.dev

Twitch。2022年末時点でスプラ3の配信をしたりしなかったり。

www.twitch.tv

Quartoのdashboardで、value boxをojsで作る場合の注意点

RやPythonと違って少し工夫が必要です。

イントロ

Quarto、皆さん使ってますか?便利な機能が豊富にあって、未だに全ての機能を使いこなせておりませんが、私は色々と手を動かしていくことで学びを得ています。

その中で、駄目な組み合わせや注意しなくてはいけないことがたまにあるため、備忘録として残しておきます。基本的な使い方は公式のマニュアルが充実しているので、このブログに書かれる内容は基本的にニッチな内容です(それはいつもそう)

QuartoではDashboardの機能があり、かなり手軽に作成出来る印象です。ただ、裏にRやPythonを実行出来るようなサーバを準備することが出来ない場合、とりあえずデータを置いておいて、ブラウザ上で表示をいじりたいな…ということがあったりなかったりします。

その際の選択肢として、ojs、Observable JSがあり、これがQuartoの公式でも推されているので使っていこうと思っております。ただ、ほぼPureなJSを触ることになるのでしばらくは苦戦しそうですが。

本題

さて本題。DashboardをQuartoで作る場合、スタイルがうまく当てられずに表示がおかしくなるということでIssueが立ってました。

github.com

Quartoはリポジトリが多いのでどこにどの情報があるのか、追うのが難しくなっていきますね。私はqmdファイルにどうしてそうしたのかをissueのリンクをコメントで貼ったりしています。

コードを見る方が早いと思いますので、実例をここに。


title: "古民家IoT" format: dashboard date: last-modified

date-format: "DD-MMM-YYYY"

Row {height="20%"}

//| include: false
value = 42

::: {.valuebox tile="Humidity" icon="moisture" color="primary"} 湿度(%)

{ojs} value :::

一番良い方法は、インラインで実行するコードの結果を表示するということで、こうなっています。この時、コードの前後に文字があるとダメなので注意です。

値を表示するだけなので、前後に文字(この場合は単位)を付けたい場合、例えばvalue = "42%"とすれば良いわけです。単位のスタイルを変えるといったことはhtmltoolsを使えば行けるかもですが、未調査。

スタイルについては特定の階層構造とタグやクラスなどが関係していて、ブラウザの開発ツールを使ってcssの何が反映されているのかを見ると確認出来ますが…あまり変なことをせず、マニュアルにある範囲でやるのが一番良いかも知れませんね!

ShinyLiveでは、tabsetPanelを使って同じsideBarPanelを指定するとidの重複のエラーが発生する

Shinyでは問題ないことが、ShinyLiveでは問題になることがままあるため注意。

問題はタイトルに書いた通り。具体的にはこういうコードで、ページにtabを入れて切り替えられるようにしたさいに、それぞれ同じsidebarを使いたいということで同じものを指定している。

  tabsetPanel(
    tabPanel(tab_calendar_display,
            sidebarLayout(
              sidebarPanel =  sidebar1,
              mainPanel = mainPanel(
               fluidRow(
                  column(
                    width = 12,
                    calendarOutput("my_calendar")
                  )),
              ),
              position="right")),
    tabPanel(tab_map_display,
            sidebarLayout(
              sidebar1,
              mainPanel(
                h2("EventMap"),
                leafletOutput("mymap")
              ),"right")
    ),
    id = "myTabs"
  )

Shinyでは特に問題なく動作するが、ShinyLiveではエラーとなる(ShinyLiveではエラーがなく表示もされないということが多々あるので、エラーが表示されるだけマシという話はある)

今のところ回避方法は見つかっていないため、同じ構成でも別のsidebarとして作るべきかもしれない。あまり深追いはしていないため、何かわかれば別途投稿します。

Rのleafletパッケージで生成したmapにJavaScriptで制御するための方法

SOは何でも載ってるなぁ…

動的に扱いたいページを作成したいが、ShinyはShinyServerなどを準備できない、公開等もしたくないようなケースに、生成したleafletの地図にJavaScriptで触りたい場合がある。ほとんどないと思うけど、ある。

普通のやり方では出来ないため(elementIdを指定してもダメだった)、調べたら出てきた。

stackoverflow.com

この記事によれば、leafletのオブジェクトをhtmlwidgets::onRender に渡して、下記のようにすれば、JavaScript側からはmapとしてアクセス出来るとのこと。

output$map <- renderLeaflet({
    leaflet() %>%
        addProviderTiles("CartoDB.Positron") %>%
        htmlwidgets::onRender("
            function(el,x) {
                map = this;
            }
        ")
})

実際、

 <script language="javascript" type="text/javascript">
        function OnLinkClick() {
            map.setView([48.45653, 8.90068], 8);
            return false;
        }
  </script>
<a href="javascript:OnLinkClick();">ジャンプ</a>

のようなコードで、リンクをクリックするとRのleafletパッケージで生成した地図に対してsetViewが動作することを確認した。あとはこれに引数として座標を与えられれば任意の座標に飛ぶことが出来る。

ShinyLiveを安定利用するにはまだちょっと早かったようだ

WebRの完成が待たれる。

Shinyアプリを、Shiny Serverなしで動かすことが出来る。そんな素敵な仕組みが開発されています。ShinyLiveという名前で、RやShinyで書かれたコードを変換し、ブラウザ上で動作出来るようにするといったもの(良くわかってませんが…

github.com

今、quartoでサイトを作ってるのですが、shiny的な、インタラクティブに扱いたい物があるものの、Shiny Serverもないしなぁといったところで、朗報だったのですが…

github.com

こちらは既に解決してcloseされていますが、ShinyLiveが依存しているWebRの状況によって、ShinyLiveやサイト自体に変更がなくても動作しなくなることがあることがわかりました。

このあと、動作はしたものの、翌々日にはまた別の理由で動かなくなってしまい、公開できるような状態に持って行くのはまだ難しいなぁ、というところです。

ただ、色々と使い方はわかってきたので、ぼちぼちこちらのブログの再開とともに、ネタを投下していこうと思います。quartoも、沢山知見は溜まりましたしね。

ということで、やっていきましょう。

(R)アルファベットが連続したベクトルが欲しい(小ネタ)

最初躓きますよね?

Rの最初の最初、ベクトルとはどんなものかみたいな話からはじまって、

seq(1,5)
# [1] 1 2 3 4 5

とか、

rep(1,5)
# [1] 1 1 1 1 1

とかで、わーい!ってやっていた時期があると思います。で、seq(1,5)の代わりに1:5でもいけるよ!すげーじゃん!!

で、じゃあアルファベットも・・・って試して、

"a":"z"
#  "a":"z" でエラー:  引数が NA/NaN です 
#  追加情報:  警告メッセージ: 
# 1:  強制変換により NA が生成されました  
# 2:  強制変換により NA が生成されました  

あると思います。

lettersを使う

baseパッケージに、lettersというデータがあるので、これを使います。これが正解。あと、大文字でLETTERSもある。

letters
# [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z"

全てのアルファベットについてチェックしようとかいう場合は、c(letters,LETTERS)でつなげればよろしい。

あと余談ですが、:も関数なので、:(1,5) で 1:5 と同じ結果になりますし、ベクトルなので、要素をインデックスで指定して

letters[c(1,26,26,1)]
# [1] "a" "z" "z" "a"

みたいになりますね。

なんか思いついた場合

C言語とかで文字を扱う場合、文字コードがaからzまで連番だから・・・という考え方で、上記のような連番生成できるんじゃないの?みたいな考えになりますよね。

じゃあそれでやってみるか。とはいえ、文字から数字への変換はNAになってしまうし...というところでハマりがちですが、Rにはraw型があります。ただ、文字からrawへの変換はcharToRawなので注意が必要ですね。

charToRaw("a")
# [1] 61
as.integer(charToRaw("a"))
# [1] 97

raw型は出力が16進数であらわされているのに注意です。10進数にするにはas.integerです。:はraw型を受け付けてくれないので、このように一旦integerにして、それを使い、raw型に戻し、rawからcharへの変換をかけます。

つまり、以下のようになります。

rawToChar(as.raw(`:`(as.integer(charToRaw("a")),as.integer(charToRaw("z")))), multiple = T)
# [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z"

ここで、rawToCharに引数multiple = Tを与えないと、文字がすべて結合されてしまうため注意。

一応、一致を確認してみると、

identical(letters, rawToChar(as.raw(`:`(as.integer(charToRaw("a")),as.integer(charToRaw("z")))), T))
# [1] TRUE

となり、ちゃんと生成できましたね。

めでたしめでたし。