Excelでダイヤグラム:上り下り

少し間が空きました。
上り下りの対応をしたいと思います。

データの見出しに、「下り【東横線】」と書いていました。
「【」以降が同じならば、同一の路線とみなすことにします。
下り上りでなくても、A線B線でも、内回り外回りでも気にしません。
下り上りで駅名が同じのチェックは入れましょう。

データはこんな感じです。

これを、こんな図に変換します。

データを読むときに、同じ路線なら路線情報を作らないようにします。

Sub 路線情報作成(路線() As 路線情報, sht As Worksheet)
    rw = 1
    idx = 0
    Do While データ先頭(rw, sht)
        s = sht.Cells(rw, 1).Value
        nm = Mid(s, InStr(s, "【"))
        If idx <> 0 Then
            pos = 路線検索(路線, nm)
        Else
            pos = -1
        End If
        
        reg = False
        If pos = -1 Then
            ReDim Preserve 路線(idx)
            路線(idx).路線名 = nm
            路線(idx).下りデータ行 = rw
            路線(idx).上りデータ行 = -1
            pos = idx
            idx = idx + 1
            reg = True
        Else
            If 路線(pos).上りデータ行 <> -1 Then
                MsgBox "3つめのデータ:" & nm
            End If
            路線(pos).上りデータ行 = rw
        End If
        rw = rw + 1
        cnt = 0
        Do While sht.Cells(rw, 1).Value <> ""
            If reg Then
                ReDim Preserve 路線(pos).駅名(cnt)
                路線(pos).駅名(cnt) = sht.Cells(rw, 1).Value
                If cnt > 0 Then
                    ReDim Preserve 路線(pos).駅間(cnt - 1)
                    路線(pos).駅間(cnt - 1) = sht.Cells(rw, 2).Value
                End If
            Else
                If 路線(pos).駅名(路線(pos).駅数 - cnt - 1) <> sht.Cells(rw, 1).Value Then
                    MsgBox "駅名不一致:" & 路線(pos).駅名(路線(pos).駅数 - cnt - 1) & "/" & sht.Cells(rw, 1).Value
                End If
            End If
            cnt = cnt + 1
            rw = rw + 1
        Loop
        If reg Then
            路線(pos).駅数 = cnt
        Else
            If 路線(pos).駅数 <> cnt Then
                MsgBox "駅数不一致:" & nm
            End If
        End If
    Loop
End Sub

Function 路線検索(路線() As 路線情報, nm)
    路線検索 = -1
    For i = 0 To UBound(路線)
        If 路線(i).路線名 = nm Then
            路線検索 = i
            Exit For
        End If
    Next i
End Function

で、上り下りは、y座標が異なることになります。

Sub スジ(路線() As 路線情報, dataSheet As Worksheet, diagSheet As Worksheet)
    dlta = 0.000001
    drw = 1
    For i = 0 To UBound(路線)
        cl = 3
        Do
            brw = 路線(i).下りデータ行
            irw = 1
            Do While irw < 路線(i).駅数 And dataSheet.Cells(brw + irw, cl).Value = ""
                irw = irw + 1
            Loop
            If dataSheet.Cells(brw + irw, cl).Value = "" Then
                Exit Do
            End If
            
            ox = dataSheet.Cells(brw + irw, cl).Value
            tx = ox
            oy = drw + irw
            irw = irw + 1
            With dataSheet
                clr = .Range(.Cells(brw, cl), .Cells(brw, cl)).Font.Color
            End With
            wgt = 0.5
            If dataSheet.Cells(brw, cl) <> "" Then
                wgt = 1.2
            End If
            
            Do While irw <= 路線(i).駅数
                If dataSheet.Cells(brw + irw, cl).Value = "" Then
                    Exit Do
                End If
                dx = dataSheet.Cells(brw + irw, cl).Value
                tx = tx + TimeSerial(0, 路線(i).駅間(irw - 2), 0)
                If dx <> "↓" Then
                    If tx > dx + dlta Then
                        Call 線描画(diagSheet, ox, oy, dx, drw + irw, clr, wgt)
                        ox = dx
                        tx = dx
                        oy = drw + irw
                    ElseIf tx <= dx - TimeSerial(0, 1, 0) - dlta Then
                        Call 線描画(diagSheet, ox, oy, tx, drw + irw, clr, wgt)
                        ox = dx
                        tx = dx
                        oy = drw + irw
                    End If
                End If
                irw = irw + 1
            Loop
            If oy <> drw + irw - 1 Then
                Call 線描画(diagSheet, ox, oy, dx, drw + irw - 1, clr, wgt)
            End If
            cl = cl + 1
        Loop
        If 路線(i).上りデータ行 <> -1 Then
            cl = 3
            Do
                brw = 路線(i).上りデータ行
                irw = 1
                Do While irw < 路線(i).駅数 And dataSheet.Cells(brw + irw, cl).Value = ""
                    irw = irw + 1
                Loop
                If dataSheet.Cells(brw + irw, cl).Value = "" Then
                    Exit Do
                End If
                
                ox = dataSheet.Cells(brw + irw, cl).Value
                tx = ox
                oy = drw + 路線(i).駅数 - irw + 1
                irw = irw + 1
                With dataSheet
                    clr = .Range(.Cells(brw, cl), .Cells(brw, cl)).Font.Color
                End With
                wgt = 0.5
                If dataSheet.Cells(brw, cl) <> "" Then
                    wgt = 1.2
                End If
                
                Do While irw <= 路線(i).駅数
                    If dataSheet.Cells(brw + irw, cl).Value = "" Then
                        Exit Do
                    End If
                    dx = dataSheet.Cells(brw + irw, cl).Value
                    tx = tx + TimeSerial(0, 路線(i).駅間(路線(i).駅数 - irw), 0)
                    If dx <> "↓" Then
                        If tx > dx + dlta Then
                            Call 線描画(diagSheet, ox, oy, dx, drw + 路線(i).駅数 - irw + 1, clr, wgt)
                            ox = dx
                            tx = dx
                            oy = drw + 路線(i).駅数 - irw + 1
                        ElseIf tx <= dx - TimeSerial(0, 1, 0) - dlta Then
                            Call 線描画(diagSheet, ox, oy, tx, drw + 路線(i).駅数 - irw + 1, clr, wgt)
                            ox = dx
                            tx = dx
                            oy = drw + 路線(i).駅数 - irw + 1
                        End If
                    End If
                    irw = irw + 1
                Loop
                If oy <> drw + 路線(i).駅数 - irw + 2 Then
                    Call 線描画(diagSheet, ox, oy, dx, drw + 路線(i).駅数 - irw + 2, clr, wgt)
                End If
                cl = cl + 1
            Loop
        End If
        drw = drw + 路線(i).駅数 + 2
    Next i
End Sub

かなりゴチャゴチャしてきたので、見直しをした方がよさそうですが、今日はここまでにします。

 

Excelでダイヤグラム:リファクタリング

プログラムの動作を変えずに、ソースコードをきれいにすることをリファクタリングと言います。
なんだけど、リファクタリングして動作が変わらないということをどうやって保証するか。

仕事の時はちゃんと悩みますが、今回は出力したダイヤが同じならOKなので、気持ちよくリファクタリングしましょう。

基本は似ているコードの共通化=関数・サブルーチン切り出しです。
今回はここを対応しました。

Sub 線描画(sht As Worksheet, ox, oy, dx, dy, clr, wgt)
    With sht.Shapes.AddConnector(msoConnectorStraight, _
                    X座標(ox, sht), sht.Rows(oy + 1).Top, X座標(dx, sht), sht.Rows(dy + 1).Top)
        .Line.ForeColor.RGB = clr
        .Line.Weight = wgt
    End With
End Sub

呼び出し側でも対応すればOKです。

            Do While irw <= 路線(i).駅数
                If dataSheet.Cells(brw + irw, cl).Value = "" Then
                    Exit Do
                End If
                dx = dataSheet.Cells(brw + irw, cl).Value
                tx = tx + TimeSerial(0, 路線(i).駅間(irw - 2), 0)
                If dx <> "↓" Then
                    If tx > dx + dlta Then
                        Call 線描画(diagSheet, ox, oy, dx, brw + irw, clr, wgt)
                        ox = dx
                        tx = dx
                        oy = brw + irw
                    ElseIf tx <= dx - TimeSerial(0, 1, 0) - dlta Then
                        Call 線描画(diagSheet, ox, oy, tx, brw + irw, clr, wgt)
                        ox = dx
                        tx = dx
                        oy = brw + irw
                    End If
                End If
                irw = irw + 1
            Loop
            If oy <> brw + irw - 1 Then
                Call 線描画(diagSheet, ox, oy, dx, brw + irw - 1, clr, wgt)
            End If

はい、ひとつバグが見つかっていますね。
デルタ値は、違っても気にしないということなので、以下の行で加減が違ってました。

                    ElseIf tx <= dx - TimeSerial(0, 1, 0) - dlta Then

出力結果がちょっと変わりましたが、いい方向に変わってる感じなのでOKです。

もうちょっとリファクタリングしてみたいところはありますが、次は上りの線を引いてみたいと思います。

 

Excelでダイヤグラム:問題修正

ひとつ問題に気が付きました。
駅名が下寄せされていない。

下寄せは、Range(・・).VerticalAlignmentで設定できます。
で、それを修正しようとしてもう一つ問題発見。

            diag.Range(Cells(rw, 1), Cells(rw, 6 * 22 + 1)).Borders(xlEdgeBottom).LineStyle = xlContinuous

ここで、Cellsだとアクティブシートのセルを参照してしまいます。
そんなわけで、2行ほど修正します。

Sub 路線設定(路線() As 路線情報, diag As Worksheet)
    rw = 1
    For i = 0 To UBound(路線)
        路線(i).ダイヤ行 = rw
        diag.Cells(rw, 1).Value = 路線(i).路線名
        rw = rw + 1
        For j = 1 To 路線(i).駅数
            diag.Cells(rw, 1).Value = 路線(i).駅名(j - 1)
            diag.Range(diag.Cells(rw, 1), diag.Cells(rw, 1)).VerticalAlignment = xlBottom
            If j <> 1 Then
                diag.Rows(rw).RowHeight = 路線(i).駅間(j - 2) * 5
            End If
            diag.Range(diag.Cells(rw, 1), diag.Cells(rw, 6 * 22 + 1)).Borders(xlEdgeBottom).LineStyle = xlContinuous
            rw = rw + 1
        Next j
        For hr = 4 To 26
            cl = hr * 6 - 22
            diag.Cells(路線(i).ダイヤ行 + 1, cl).Value = hr
            diag.Cells(路線(i).ダイヤ行 + 1, cl).HorizontalAlignment = xlLeft
            diag.Range(diag.Cells(路線(i).ダイヤ行 + 1, cl), diag.Cells(rw - 1, cl)).Borders(xlEdgeLeft).LineStyle = xlContinuous
        Next
        rw = rw + 1
    Next i
End Sub

次回は全体見直しの想定です。

Excelでダイヤグラム:優等列車の設定

今日は優等列車の設定です。
データの書き方を決めましょう。

先頭行に列車種別を描いています。
字はなんでもいいですが、色をスジに反映しようと思います。
通過のマークを決めます。
紙で時刻表を見てた人はレのような片側矢印がおなじみですが、ここは普通に下向き矢印にしました。

土休日ダイヤですが、渋谷発の最初の急行は、新横浜線直通なんだ。。。
で、それを受けて、日吉始発の各停と急行が。

それは置いといて、VBAの対応です。
通過駅の対応は判断文一つでOKです。

                    If dx <> "↓" Then
                        If tx > dx + dlta Then
                        ・・・・
                    EndIf

線の色と太さを決めます。
太さは小数点以下切り上げのような気がします。

            With dataSheet
                clr = .Range(.Cells(brw, cl), .Cells(brw, cl)).Font.Color
            End With
            wgt = 0.5
            If dataSheet.Cells(brw, cl) <> "" Then
                wgt = 1.2
            End If

色と太さは線を引くところで設定します。
ちょっと独特な書き方になります。

                        With diagSheet.Shapes.AddConnector(msoConnectorStraight, _
                                X座標(ox, diagSheet), diagSheet.Rows(oy + 1).Top, _
                                X座標(dx, diagSheet), diagSheet.Rows(brw + irw + 1).Top)
                            .Line.ForeColor.RGB = clr
                            .Line.Weight = wgt
                        End With

いい感じの出力になりました。

ちょっとソースがごちゃついてきたので、少し見直しもかけたいと思います。

 





 

Excelでダイヤグラム:スジを複数引く

少し間が空いてしまいましたが、再開です。
今日のテーマはスジを複数引くことです。
同時に、途中駅発とか、途中駅終着とかの対応もしましょう。

データはこんな感じです。

既に縦横は変数にしてあったので、対応は限定的でした。

Sub スジ(路線() As 路線情報, dataSheet As Worksheet, diagSheet As Worksheet)
    dlta = 0.000001
    For i = 0 To UBound(路線)
        cl = 3
        Do
            brw = 路線(i).下りデータ行
            irw = 1
            Do While irw < 路線(i).駅数 And dataSheet.Cells(brw + irw, cl).Value = ""
                irw = irw + 1
            Loop
            If dataSheet.Cells(brw + irw, cl).Value = "" Then
                Exit Do
            End If
            
            ox = dataSheet.Cells(brw + irw, cl).Value
            tx = ox
            oy = brw + irw
            irw = irw + 1
            Do While irw <= 路線(i).駅数
                If dataSheet.Cells(brw + irw, cl).Value = "" Then
                    Exit Do
                End If
                dx = dataSheet.Cells(brw + irw, cl).Value
                tx = tx + TimeSerial(0, 路線(i).駅間(irw - 2), 0)
                If tx > dx + dlta Then
                    diagSheet.Shapes.AddConnector msoConnectorStraight, _
                            X座標(ox, diagSheet), diagSheet.Rows(oy + 1).Top, _
                            X座標(dx, diagSheet), diagSheet.Rows(brw + irw + 1).Top
                    ox = dx
                    tx = dx
                    oy = brw + irw
                ElseIf tx <= dx - TimeSerial(0, 1, 0) + dlta Then
                    diagSheet.Shapes.AddConnector msoConnectorStraight, _
                            X座標(ox, diagSheet), diagSheet.Rows(oy + 1).Top, _
                            X座標(tx, diagSheet), diagSheet.Rows(brw + irw + 1).Top
                    ox = dx
                    tx = dx
                    oy = brw + irw
                End If
                irw = irw + 1
            Loop
            If oy <> brw + irw - 1 Then
                diagSheet.Shapes.AddConnector msoConnectorStraight, _
                            X座標(ox, diagSheet), diagSheet.Rows(oy + 1).Top, _
                            X座標(dx, diagSheet), diagSheet.Rows(brw + irw).Top
            End If
            cl = cl + 1
        Loop
    Next i
End Sub

横はclが増えていく感じで、空白の列に達すると終了するようにします。
縦はデータが見つかったら開始行で、データがなくなったら終了行にします。
このへんになると、VBA書いた瞬間は何かと境界条件とかが怪しくなりがちで、
実行結果を見ながらデバッグします。
これで大丈夫そうです。

 

Excelでダイヤグラム:スジを引く

スジを引くところを、路線情報に対応します。
複数路線、上り下り、複数列車の準備をしますが、まだ未確認です。

Sub スジ(路線() As 路線情報, dataSheet As Worksheet, diagSheet As Worksheet)
    dlta = 0.000001
    For i = 0 To UBound(路線)
        cl = 3
        brw = 路線(i).下りデータ行
        ox = dataSheet.Cells(brw + 1, cl).Value
        tx = ox
        oy = brw + 1
        For irw = 2 To 路線(i).駅数
            dx = dataSheet.Cells(brw + irw, cl).Value
            tx = tx + TimeSerial(0, 路線(i).駅間(irw - 2), 0)
            If tx > dx + dlta Then
                diagSheet.Shapes.AddConnector msoConnectorStraight, _
                            X座標(ox, diagSheet), diagSheet.Rows(oy + 1).Top, _
                            X座標(dx, diagSheet), diagSheet.Rows(brw + irw + 1).Top
                ox = dx
                tx = dx
                oy = brw + irw
            ElseIf tx <= dx - TimeSerial(0, 1, 0) + dlta Then
                diagSheet.Shapes.AddConnector msoConnectorStraight, _
                            X座標(ox, diagSheet), diagSheet.Rows(oy + 1).Top, _
                            X座標(tx, diagSheet), diagSheet.Rows(brw + irw + 1).Top
                ox = dx
                tx = dx
                oy = brw + irw
            End If
        Next irw
        If oy <> brw + 路線(i).駅数 Then
            diagSheet.Shapes.AddConnector msoConnectorStraight, _
                            X座標(ox, diagSheet), diagSheet.Rows(oy + 1).Top, _
                            X座標(dx, diagSheet), diagSheet.Rows(brw + 路線(i).駅数 + 1).Top
        End If
    Next i
End Sub

今回のポイントは、デルタ値の導入です。
時刻は1日を1とした数で表示されるので、1時間は1÷24、1分は1÷24÷60になります。
この数字は割り切れないので、誤差が出ます。
この誤差の許容範囲がデルタ値です。
渋谷から中目黒の標準時間が4分なので、渋谷5:00発だと中目黒5:04発になります。
これはその通りなので、ここでスジを分ける必要はありません。
一方、自由が丘だと標準時間で5:12のはずが、自由が丘発の発時刻は5:13です。
実際東急のページで時刻を確認すると自由が丘着は5:11で、少し停車していることがわかります。
これは優等列車退避の場合も適用できるので、ルールを設けます。
最初の版とはルールを少し見直しました。
発時刻が標準時間より早い時は、それに合わせてスジを引きます。
発時刻が標準時間より1分以上遅い時は、きっと停車時間があると考え、標準時間で着いて発時間まで待っているという認識にします。
この判定にデルタ値を許容誤差として追加しています。
最後に終着までのスジを引いてなければ、足します。

これで、路線情報まわりの見直しは完了です。
次は複数のスジを引いたり、途中発着の対応をしたりしたいと思います。

 

 

Excelでダイヤグラム:軸の設定

シートの初期化を整理します。
時間軸は全路線共通ですが、縦の罫線を引く範囲が路線に依存するので、路線の方に移して、残りの列幅設定はシート初期化の方に入れます。

Sub シート初期化(dataSheet As Worksheet)
    Sheets.Add After:=dataSheet
    Cells.Select
    With Selection.Font
        .Name = "Meiryo UI"
        .Size = 8
    End With
    Range("A1").Select
    Columns(1).ColumnWidth = 14
    cl = 2
    For hr = 4 To 25
        For i = 1 To 6
            Columns(cl).ColumnWidth = 5.66
            cl = cl + 1
        Next
    Next
End Sub

列の幅は、CSSのemのような、フォント高さに比例する値のようです。
Excelで列の境界をクリックすると表示されるので、試行錯誤で選びます。
設定は.ColumnWidthでいけます。

次は行の高さです。

Sub 路線設定(路線() As 路線情報, diag As Worksheet)
    rw = 1
    For i = 0 To UBound(路線)
        路線(i).ダイヤ行 = rw
        diag.Cells(rw, 1).Value = 路線(i).路線名
        rw = rw + 1
        For j = 1 To 路線(i).駅数
            diag.Cells(rw, 1).Value = 路線(i).駅名(j - 1)
            If j <> 1 Then
                diag.Rows(rw).RowHeight = 路線(i).駅間(j - 2) * 5
            End If
            diag.Range(Cells(rw, 1), Cells(rw, 6 * 22 + 1)).Borders(xlEdgeBottom).LineStyle = xlContinuous
            rw = rw + 1
        Next j
        For hr = 4 To 26
            cl = hr * 6 - 22
            diag.Cells(路線(i).ダイヤ行 + 1, cl).Value = hr
            diag.Cells(路線(i).ダイヤ行 + 1, cl).HorizontalAlignment = xlLeft
            diag.Range(diag.Cells(路線(i).ダイヤ行 + 1, cl), diag.Cells(rw - 1, cl)).Borders(xlEdgeLeft).LineStyle = xlContinuous
        Next
        rw = rw + 1
    Next i
End Sub

路線が複数の時の検証はまだです。おいおいやります。
RowHeightの単位はポイントのようです。
ここでは5を掛けてますが、好みで設定すればいいと思います。
セルの下線をデフォルト値で引いています。
1時間毎に数字を書いて線を引きます。