mermaid-cliで出力したsvgファイルにテキストが表示されない場合の対応

問題

ローカルにインストールしたmermaid-cliで出力するsvgファイルでテキストが表示されない問題があった。

  graph TD;
      A-->B;
      B-->C;
      C-->A;

対応

config.jsonを以下のように作成してそれを引き渡すと解決した。

{
  "flowchart": {
    "htmlLabels": false
  }
}
./node_modules/.bin/mmdc -c ~/.config/mermaid/config.json -i a.md && eog a-1.md.svg

参考

mermaid missing text in svg · Issue #112 · mermaid-js/mermaid-cli · GitHub

JetpackComposeで状態変化が画面に反映されない書き方

JetpackComposeに触れる中でViewModelに保持させた状態の変化が画面に即時で反映されない場合があったのでそのコードを記します。

package jp.kawagh.learn_recompose

import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel

data class UiState(val a: Int, var b: Int)

class MyViewModel : ViewModel() {
    var uiState by mutableStateOf(UiState(0, 0))
        private set

    fun incA() {
        // recompose
        uiState = uiState.copy(a = uiState.a + 1)
    }

    fun incB() {
        // not recompose
        uiState.b += 1
    }

}

上記コードのコメントにも書かれていますがincBメソッドを呼んだ場合には(状態の更新は起こるが)画面に状態の更新は反映されません。

状態が画面に即時で反映されて欲しいケースがほとんどだと思うので、incBのような書き方はそもそもコンパイルの段階でエラーで弾けるように data classのメンバの定義は以下のようにvarではなくvalを使おうと思っています。

- data class UiState(val a: Int, var b: Int)
+ data class UiState(val a: Int, val b: Int)

そもそもdata classの定義としてvarを使うことになっていたのはUiStateといったdata classでまとめる前に下のように書いていた名残でした。

    var b by mutableStateOf(0)
        private set

リファクタリングしたつもりで画面と状態更新のズレを生じさせてしまっていました。 UIテストでないと検知出来ない部分で再びハマりそうなので書き残しておきます。

JetpackComposeでアナログ時計の作成

drawCanvasのシンプルなお題としてアナログ時計の作成に取り組みました。

@Composable
fun Clock() {
    val hour = LocalTime.now().hour
    val minute = LocalTime.now().minute
    val shortDegree = minute * 6f
    val longDegree = (hour % 12 + minute / 60) * 30f
    Column {
        Canvas(modifier = Modifier.fillMaxSize()) {
            drawCircle(Color.Gray)
            rotate(longDegree) {
                val longEnd = this.center + Offset(0f, -size.minDimension / 3)
                drawLine(Color.Cyan, start = this.center, end = longEnd, strokeWidth = 10f)
            }
            rotate(shortDegree) {
                val shortEnd = this.center + Offset(0f, -size.minDimension / 2)
                drawLine(Color.Yellow, start = this.center, end = shortEnd, strokeWidth = 5f)
            }
        }
    }
}

CanvasによるdrawScopeではrotateメソッドが使えて描画するパーツを回転させられます。 以下に示すのはrotateの定義で時計のコードには現れていませんが回転の中心を変更することもできます。

inline fun DrawScope.rotate(
    degrees: Float,
    pivot: Offset = center,
    block: DrawScope.() -> Unit
) = withTransform({ rotate(degrees, pivot) }, block)

opencvの型スタブファイル(.pyi)を取得する

関数の定義などをエディタが提供する補完で参照しながらコーディングをしていますが、opencvなどの元々pythonで書かれていないライブラリだったりはその情報が得られず苦労していました。 その折に、以下のissueを見つけました。

このissueが紐づくリポジトリは人気のあるpythonパッケージの型スタブファイルの作成を進めているようです。

下記スクリプトでimportされるopencvディレクトリに.pyiファイルを追加することが出来ます。

CV2_PATH=`python -c 'import cv2, os; print(os.path.dirname(cv2.__file__))'`
URL='https://raw.githubusercontent.com/bschnurr/python-type-stubs/add-opencv/cv2/__init__.pyi'
curl -sSL $URL -o ${CV2_PATH}/__init__.pyi

参考

JetpackComposeで上下左右のドラッグ、スワイプ操作の検知

コード(成功例)

下記コードでドラッグ操作のx軸y軸の距離で大きい方向にテキストを書き換えられます。

@Composable
fun Drag() {
    var state by remember {
        mutableStateOf("-")
    }
    Box(
        modifier = Modifier
            .fillMaxSize()
            .pointerInput(Unit) {
                detectDragGestures(
                    onDrag = { change: PointerInputChange, dragAmount: Offset ->
                        change.consumeAllChanges()
                        val (x, y) = dragAmount
                        if (abs(x) > abs(y)) {
                            state = if (x > 0) ">" else "<"
                        } else {
                            state = if (y > 0) "v" else "^"
                        }
                    },
                )
            }
    ) {
        Column(modifier = Modifier.align(Alignment.Center)) {
            Text(state, fontSize = MaterialTheme.typography.h1.fontSize)
        }
    }

}

コード(失敗例)

こちらは読む必要はないです。

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun NotWorkSwipe() {
    val squareSize = 48.dp
    val swipeableState = rememberSwipeableState("-")
    val sizePx = with(LocalDensity.current) { squareSize.toPx() }
    val horizontalAnchors =
        mapOf(0f to "<", sizePx to ">", 100f to "-") // Maps anchor points (in px) to states
    val verticalAnchors =
        mapOf(0f to "^", sizePx to "v", 100f to "-") // Maps anchor points (in px) to states
    Box(
        modifier = Modifier
            .fillMaxSize()
            .swipeable(
                state = swipeableState,
                anchors = verticalAnchors,
                orientation = Orientation.Vertical
            )
            .swipeable(
                state = swipeableState,
                anchors = horizontalAnchors,
                orientation = Orientation.Horizontal
            )
    ) {
        Column(modifier = Modifier.align(Alignment.Center)) {
            Text(swipeableState.currentValue, fontSize = MaterialTheme.typography.h1.fontSize)
        }
    }
}

swipeableという修飾子をはじめに見つけたのですがこれは水平か鉛直かの方向を指定する必要がありました。 swipeable修飾子を重ねがけしたら出来るのではと思いましたが後ろのswipeable のみ適用される結果となりました。

参考

操作  |  Jetpack Compose  |  Android Developers