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時間毎に数字を書いて線を引きます。