タイミングチャート清書サービス のバックアップ(No.7)

更新


設置アドレス

http://dora.bk.tsukuba.ac.jp/~takeuchi/tchart.html

スクリーンイメージ

screenshot.png

概要

このようなテキストを入力すると、

aclk    ~_~_~_~_~_~_~_~_~_~_
awaddr  ==?X=A1==X=A2X=A3==X==?=X=A4X=?
awvalid __~~~~~~~~~~____~~__
awready ____[~~~~]__[~~]____[~~]__
wdata   ====?X=D1X=D2X=?X=D3X=?X=D4==X=?
wvalid  ____~~~~__~~__~~~~__
wready  ____~~~~__~~____~~__
bresp   ====00================
bvalid  ____~~~~__~~____~~__
bready  ~~~~~~~~~~~~~~~~~~1~~

こんなタイミングチャートを生成できるサービスです。

&tchart( aclk ~_~_~_~_~_~_~_~_~_~_ awaddr ==?X=A1==X=A2X=A3==X==?=X=A4X=? awvalid ~~~~~~~~~~~~ awready __[~~~~][~~]__[~~] wdata ====?X=D1X=D2X=?X=D3X=?X=D4==X=? wvalid __~~~~~~~~~~ wready __~~~~~~__~~ bresp ====00================ bvalid __~~~~~~__~~ bready ~~~~~~~~~~~~~~~~~~1~
);

ソースコード編集中に、ほぼリアルタイムで清書結果を確認できるので、試行錯誤しているうちに正しく書けると思います。

清書したタイミングチャートは SVG または PNG 形式で保存できます。

入力内容がサーバーに送られることはなく、完全にブラウザ上だけで画像を生成していますので、守秘義務の問題も発生しません。心配であればリンク先の html を PC に保存して、そこから立ち上げれば、完全にオフラインの状態でも動作します。

設置アドレスはこちらです:
  http://dora.bk.tsukuba.ac.jp/~takeuchi/tchart.html

>>>> 記述文法についてはこちらをご覧下さい。

コメントや質問などがあればこのページの一番下に投稿フォームがありますのでご活用下さい。

不具合

IE では下記リンク先で議論されている問題のため、[Save as PNG] および [Generate Images for Copy] のボタンが正常に働きません。
https://connect.microsoft.com/IE/feedback/details/809823/draw-svg-image-on-canvas-context

代わりに、SVG 画像上で右クリックから [名前を付けて画像を保存] で形式を PNG にすれば保存できます。

同様に右クリックから [コピー] で画像をクリップボードへ入れられるので、 Word などへ貼り付ける用途ではむしろ IE の方が便利かもしれません。

IE 以外でクリップボードへのコピーの仕方

セキュリティ上の制限のため、javascript からクリップボードへ 自動的にコピーすることはできないようになっています。 そこで、手動で対応していただく必要があります。

[Generate Images for Copy] ボタンを押すと SVG ソースと PNG 画像が生成されます

  • SVG ソースをご入り用であれば、SVG ソースにフォーカスが当たっている状況でそのまま Ctrl+C すればコピー可能です
  • PNG 画像がご入り用であれば、PNG 画像上で右クリックから [画像をコピー] します
    • なぜか空の HTML も一緒にコピーされてしまうようで、なおかつこの空の HTML が優先的に貼り付けられるようなので、Word などへ貼り付ける際には [形式を選択して貼り付け]-[ビットマップ(DIB)] などとして下さい
    • Office でのショートカットは Alt+E,S です
    • 上記の制限のため IE では [Generate Images for Copy] で PNG 画像が生成されません
    • 代わりに SVG 画像上の右クリックから [コピー] でコピーできますので、そちらをご利用下さい

謝辞

熊谷正朗さんの "Timing chart formatter by kumagai"
http://www.mech.tohoku-gakuin.ac.jp/rde/contents/library/tchart/indexframe.html

を大いに参考にさせていただきました。

記述文法はほぼ熊谷さんの tchart を踏襲しています。

大変有用なツールをご提供くださったことに感謝いたします。

目次

タイミング記述テキストの文法

# から始まる行はコメント行です

# This line will not appear in the chart
clk    _~_~_~_~_~
signal ___~~~~___

&tchart(

# This line will not appear in the chart clk _~_~_~_~_
signal ~~~~ );

@ から始まる行で設定を変更できます

形式は次の通りです。

@[設定値の名称][スペース][設定値]

設定可能な値としてどんな物があるかについてはこちらです。

@signal_style stroke-linecap="round" stroke-width="2" stroke="green" fill="none"
clk    _~_~_~_~_~
signal ___~~~~___

&tchart( @signal_style stroke-linecap="round" stroke-width="2" stroke="green" fill="none" clk _~_~_~_~_
signal ~~~~ );

% から始まる行で自由な位置に文字列を追加できます

与えるパラメータは、次の通りです。

%[x座標][スペース][y座標][スペース][文字列]
@margin 20
%100 -7 test!
clk    _~_~_~_~_~
signal ___~~~~___

&tchart( @margin 20 %100 -7 test! clk _~_~_~_~_
signal ~~~~ );

空行があれば1行分だけ空白が空きます

clk1   _~_~_~_~_~
clk2   __~~__~~__

signal ___~~_____

&tchart( clk1 _~_~_~_~_
clk2 ~~~~__

signal ~~__ );

その他の文字から始まる行が信号定義になります

フォーマットは

信号名 [空白] タイミング定義

の形です。

タイミング定義の文法と用例

_ と ~ で 0 と 1 を表します。

- がハイインピーダンス状態です。

clk    _~_~_~_~_~_~_~_~_~
data   ___~~~~__~~____~~_
enable ___~~~~~~~~~~_____
output ---~~~~__~~__-----

&tchart( clk _~_~_~_~_~_~_~_~_
data _~~~~~~____~~_ enable ~~~~~~~~~~__ output ---~~~~~~----- );

バス信号や不定値を表すのに = を使えます。

X で値の切り替えを表せます。

X は時間が進みます。イメージ的には X= のように働きます。

clk  _~_~_~_~_~
data =X=X=X=X=X

&tchart( clk _~_~_~_~_
data =X=X=X=X=X );

信号定義に文字列を書き入れることができます。

文字列を書いても時間は進みません。

clk    _~_~_~_~_~_~_~_~_~
enable ___~~~~~~~~~~_____
output ---=D0=X=D1X=D2X=D3X=D4-----

&tchart( clk _~_~_~_~_~_~_~_~_
enable ~~~~~~~~~~__ output ---=D0=X=D1X=D2X=D3X=D4----- );

文字列に特殊文字を含めたい場合には " " でくくります

" を含めたければ "" のように重ねます

スペースも入れられます

"="   -======"A = B"-
"     -======" ""A"" = ""B"" "-
A     -======A-
space -======"A      "-

&tchart( "=" -======"A = B"- " -======" ""A"" = ""B"" "- A -======A- space -======"A "- );

右寄せ、左寄せ

文字列は通常センタリングされますが、_<_ あるいは _>_ から始めることで 左寄せ、右寄せにできます

left   -======"_<_DATA"-
middle -======DATA-
right  -======"_>_DATA"-

&tchart( left -======"_<_DATA"- middle -======DATA- right -======"_>_DATA"- );

? を一文字だけ書くと不定値を表すために色が付きます。

clk    _~_~_~_~_~_~
data   =?==XDATA=====X=?=
valid  ___~~~~~~___

&tchart( clk _~_~_~_~_~_
data =?==XDATA=====X=?= valid ~~~~~~ );

: を入れると空白を入れ、途切れさせることができます。

clk    _~_~_~_:...:~_~_~_~_~
data   ___~~~~:...:~~____~~_

&tchart( clk _~_~_~_:...:~_~_~_~_
data ~~~~:...:~~_~~_ );

不定値部分を表すのに、/ \ * が使えます。

clk        _~_~_~_~_~_~_~
rising     ___==/=/=~~~~~
falling    ~~~==\=\=_____
transition ___=D0=*=D1*=D2*=D3___

&tchart( clk _~_~_~_~_~_~_
rising ___==/=/=~~~~
falling ~~~==\=\=_____ transition =D0=*=D1*=D2*=D3 );

| を入れるとグリッド線を引けます。

[ ] でくくるとハイライトできます。

clk    _~_~_~_~_~_~_~_~_~
data   ___~~~~~~~~|____~~_
enable ___[~~~~~~~~~~]_____
output ---~~~~~~~~__-----

&tchart( clk _~_~_~_~_~_~_~_~_
data ~~~~~~~~|_~~_ enable [~~~~~~~~~~]__ output ---~~~~~~~~__----- );

全ての組み合わせを試してみます

# :-~_=/\X*
0 _~_~_~_~_~_~_[~_~_]~_

1 :::-:~:_:=:/:\:X:*:
2 -:---~-_-=-/-\-X-*-
3 ~:~-~~~_~=~/~\~X~*~
4 _:_-_~___=_/_\_X_*_
5 =:=-=|~=_===/=\=X=*=
6 /:/-/~/_/=///\/X/*/
7 \:\-\~\_\=\/\\\X\*\
8 X:X-X~X_X=X/X\XXX*X
9 *:*-*~*_*=*/*\*X***

&tchart(

# :-~_=/\X* 0 _~_~_~_~_~_~_[~_~_]~_

1 :::-:~:_:=:/:\:X:*: 2 -:---~-_-=-/-\-X-*- 3 ~:~-~~~_~=~/~\~X~*
4 _:_-_~___=_/_\_X_*_ 5 =:=-=|~=_===/=\=X=*= 6 /:/-/~/_/=///\/X/*/ 7 \:\-\~\_\=\/\\\X\*\ 8 X:X-X~X_X=X/X\XXX*X 9 *:*-*~*_*=*/*\*X*** );

不定値の塗り分けをテスト。

@h_line 30
@h_skip 20
@w_transient 5
@w_caption 60
test =/=?\=\=?/=X=?X=*?=*=X=?-=?= =?X

&tchart( @h_line 30 @h_skip 20 @w_transient 5 @w_caption 60 test =/=?\=\=?/=X=?X=*?=*=X=?-=?= =?X );

遷移の傾きをなくすには @w_transient 0 とします

@w_transient 0
clk _~_~_~_~_~_~_~
data ___|~~~~|_______

&tchart( @w_transient 0 clk _~_~_~_~_~_~_
data |~~~~|____ );

svg の特殊文字も正しくエスケープされます

clk _~_~_~_~_~_~_~
data ---|===<D1>=X=<D2>==|---

&tchart( clk _~_~_~_~_~_~_
data ---|===<D1>=X=<D2>==|--- );

生成された svg には変換元のソースコードが埋め込まれます。

下記の CDATA の部分です。ソースコードに CDATA の終了を示す ]]> が現れる場合には、 ]]> にエンコードされます。

LANG:xml
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" 
     width="240pt" height="60pt" viewBox="-10 -10 220 40" version="1.1">
<![CDATA[
clk _~_~_~_~_~_~_~
data ---|===<D1>=X=<D2>==|---
]]>
<g>
<text x="35" y="8.5" text-anchor="end" font-size="10" fill="black" font-family="Helvetica">clk</text>
<path stroke-linecap="round" stroke-width="0.6" stroke="black" fill="none" d="M42,10H52L54,0H64L66,10H76L78,0H88L90,10H100L102,0H112L114,10H124L126,0H136L138,10H148L150,0H160L162,10H172L174,0H184L186,10H196L198,0H208" />
<text x="35" y="28.5" text-anchor="end" font-size="10" fill="black" font-family="Helvetica">data</text>
<text x="101.0" y="28.5" text-anchor="middle" font-size="10" fill="black" font-family="Helvetica">&lt;D1&gt;</text>
<text x="149.0" y="28.5" text-anchor="middle" font-size="10" fill="black" font-family="Helvetica">&lt;D2&gt;</text><path stroke-linecap="round" stroke-width="0.6" stroke="black" fill="none" d="M42,25H52H54H64H66H76L78,20H88H90H100H102H112H114H124L126,30H136H138H148H150H160H162H172L174,25H184H186H196H198H208M76,25L78,30H88H90H100H102H112H114H124L126,20H136H138H148H150H160H162H172L174,25" />
<path d="M77,-10V40" stroke-linecap="round" stroke-width="0.6" stroke="red" fill="none" />
<path d="M173,-10V40" stroke-linecap="round" stroke-width="0.6" stroke="red" fill="none" />
</g>
</svg>

設定値

括弧内が何も指定しないときの既定値です。

scale (= 1.0)

図のサイズを拡大・縮小します。 複数指定した場合、最後の値だけが意味を持ちます。

@scale 1.4
clk     _~_~_~_~_~_~_~_~
data	==?=X=D0X=D1X=D2X=D3X=?===

&tchart( @scale 1.4 clk _~_~_~_~_~_~_~_
data ==?=X=D0X=D1X=D2X=D3X=?=== );

@scale 0.7
clk     _~_~_~_~_~_~_~_~
data	==?=X=D0X=D1X=D2X=D3X=?===

&tchart( @scale 0.7 clk _~_~_~_~_~_~_~_
data ==?=X=D0X=D1X=D2X=D3X=?=== );

margin (= 10)

チャートの周囲の余白の幅を指定します。

w_caption (= 40)

信号名称部分の幅を指定します。

long_signal_name	_~~~~__~~~~______~~___

&tchart( long_signal_name _~~~~~~~~_~~ );

@w_caption 100
long_signal_name	_~~~~__~~~~______~~___

&tchart( @w_caption 100 long_signal_name _~~~~~~~~_~~ );

w_hold (= 10)

信号の単位時間から遷移時間を引いた部分の幅を指定します。 途中で変更すれば異なるクロックドメインを表したりできます。

@w_hold 10
clock	_~_~_~_~_~_~_~_~_~_~_~
@w_hold 22
clock	~_~_~_~_~_~
@w_hold 16
clock	_~_~_~_~_~_~_~_

&tchart( @w_hold 10 clock _~_~_~_~_~_~_~_~_~_~_
@w_hold 22 clock ~_~_~_~_~_
@w_hold 16 clock _~_~_~_~_~_~_~_ );

w_transient (= 2)

信号の遷移時間の幅を指定します。 ゼロを指定すると、遷移のエッジは垂直になります。

@w_transient 0
clock	_~_~_~_~_~_~_~_~_~_~_~
@w_transient 2
clock	_~_~_~_~_~_~_~_~_~_~_~
@w_transient 4
clock	_~_~_~_~_~_~_~_~_~_~_~

&tchart( @w_transient 0 clock _~_~_~_~_~_~_~_~_~_~_
@w_transient 2 clock _~_~_~_~_~_~_~_~_~_~_
@w_transient 4 clock _~_~_~_~_~_~_~_~_~_~_
);

h_line (= 10)

1行の高さを指定します。信号名のフォントサイズもこの値に等しくなります。

@h_line 10
clock	_~_~_~_~_~_~_~_~_~_~_~
@h_line 16
clock	_~_~_~_~_~_~_~_~_~_~_~

&tchart( @h_line 10 clock _~_~_~_~_~_~_~_~_~_~_
@h_line 16 clock _~_~_~_~_~_~_~_~_~_~_
);

h_space (= 10)

行間のスペースを指定します。

clock	_~_~_~_~_~_~_~_~_~_~_~
data1	_~~~~__~~~~______~~___
@h_space 20
data2	_~~~~__~~~~______~~___
data3	_~~~~__~~~~______~~___

&tchart( clock _~_~_~_~_~_~_~_~_~_~_
data1 _~~~~~~~~_~~ @h_space 20 data2 _~~~~~~~~_~~ data3 _~~~~~~~~_~~ );

signal_style

信号線のスタイルを svg の path の属性値の形で与えます。 規定値は次の通りです。

stroke-linecap="round" stroke-width="0.6" stroke="black" fill="none"
data1	_~~~~__~~~~______~~___
@signal_style stroke-linecap="round" stroke-width="2" stroke="green" fill="none"
data2	_~~~~__~~~~______~~___

&tchart( data1 _~~~~~~~~_~~ @signal_style stroke-linecap="round" stroke-width="2" stroke="green" fill="none" data2 _~~~~~~~~_~~ );

grid_style

グリッド線のスタイルを svg の path の属性値の形で与えます。 規定値は次の通りです。

stroke-linecap="round" stroke-width="0.6" stroke="red" fill="none"
@grid_style stroke-linecap="round" stroke-width="0.6" stroke="black" fill="none"
data1	_~~~~__|~~~~______~~___
@grid_style stroke-linecap="round" stroke-width="0.6" stroke="#0CC" fill="none"
data2	_~~~~__~~~~______|~~___

&tchart( @grid_style stroke-linecap="round" stroke-width="0.6" stroke="black" fill="none" data1 _~~~~|~~~~_~~ @grid_style stroke-linecap="round" stroke-width="0.6" stroke="#0CC" fill="none" data2 _~~~~~~~~_|~~ );

highlight_style

ハイライト部分のスタイルを指定します。

規定値は次の通りです。

stroke="none" fill="#ff8"
data1	_~~~~__[~~~~]______~~___
@highlight_style stroke="green" fill="#8f8" stroke-width="1"
data2	_~~~~__~~~~______[~~]___

&tchart( data1 _~~~~[~~~~]_~~ @highlight_style stroke="green" fill="#8f8" stroke-width="1" data2 _~~~~~~~~_[~~] );

notcare_style

不定値部分のスタイルを指定します。

fill="#ccc"
clk     _~_~_~_~_~_~_~_~_~_~_~
data1	====?=*========*=?======
@notcare_style stroke="none" fill="#8f8"
data1	====?=*========*=?======

&tchart( clk _~_~_~_~_~_~_~_~_~_~_
data1 ====?=*========*=?====== @notcare_style stroke="none" fill="#8f8" data1 ====?=*========*=?====== );

caption_font

信号名のフォントを指定します。

規定値は次の通りです。

fill="black" font-family="Helvetica"
clk     _~_~_~_~_~_~_~_~_~_~_~
@caption_font fill="red" font-family="Helvetica"
@signal_style stroke="red" fill="none"
data	_~~~~______~~____~~~~~

&tchart( clk _~_~_~_~_~_~_~_~_~_~_
@caption_font fill="red" font-family="Helvetica" @signal_style stroke="red" fill="none" data _~~~~___~~_~~~~
);

signal_font

信号部分で用いるフォントを指定します。

規定値は次の通りです。

fill="black" font-family="Helvetica"
clk     _~_~_~_~_~_~_~_~
@signal_font fill="red" font-family="Helvetica"
data	==?=X=D0X=D1X=D2X=D3X=?===

&tchart( clk _~_~_~_~_~_~_~_
@signal_font fill="red" font-family="Helvetica" data ==?=X=D0X=D1X=D2X=D3X=?=== );

rotate (= 0)

未実装

ソースコード

こちらでも参照可能です:http://jsbin.com/lezujowaba/edit?html,css,js,output

LANG:html(linenumber)
<!DOCTYPE html>
<html>
<head>
  <script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
  <script src="https://rawgit.com/eligrey/FileSaver.js/master/FileSaver.min.js"></script>
  <script src="https://rawgit.com/eligrey/canvas-toBlob.js/master/canvas-toBlob.js"></script>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>Timing Chart Formatter</title>
</head>
<body>
  <h1>Timing Chart Formatter</h1>
  <textarea id="source" cols="60" rows="10" spellcheck="false">
clock	_~_~_~_~_~_~_~_~_~_~_
data	=?====X=DATA========X=?====
valid	_____~~~~~~~~~~______
ready	_____________[~~]______
  </textarea>
  <div id="result"></div>
  <input type="button" value="Save as SVG" id="as_svg" />
  <input type="button" value="Save as PNG" id="as_png" /> 
  <input type="button" value="Generate Images for Copy" id="for_copy" /> 
  Background: <input id="background" value="white" style="width:60px" />
  <div id="images"></div>
</body>
</html>
LANG:scss(linenumber)
@import "compass";
$line-height: 18px;

textarea {
  font-size: 15px;
  line-height: $line-height;
  padding: 0px 10px;
}

#source {
  @include background-image(
    linear-gradient(left, white 5px, transparent 5px), 
    linear-gradient(right, white 5px, transparent 5px), 
    linear-gradient(white $line-height - 1px, 
      #ddd $line-height - 1px, #ddd $line-height, white $line-height)
  );
  background-size: 100% 100%, 100% 100%, 100% $line-height;
}
LANG:coffeescript(linenumber)
class SvgPath
  constructor: (style)->
    @segments = []
    @style = style

  draw: (x1, y1, x2, y2)->
    @segments.push([x1,y1,x2,y2])

  svg: ->
    for s in @segments
      s[4]= 0
      s[5]= null
      
    for s1 in @segments
      for s2 in @segments
        if s1[2]==s2[0] and s1[3]==s2[1]
          s1[4] += 1
          s2[4] += 1

    for s1 in @segments
      for s2 in @segments
        continue if s2[4]<2 or s1[2]!=s2[0] or s1[3]!=s2[1]
        s1[5]= s2
        s2[4]= -1
        break
      continue if s1[5]
      for s2 in @segments
        continue if s2[4]!=1 or s1[2]!=s2[0] or s1[3]!=s2[1]
        s1[5]= s2
        s2[4]= -1
        break
        
    path = []
    for s1 in @segments
      continue if s1[4]<0
      s = s1
      cx = s[0]
      cy = s[1]
      path.push "M#{cx},#{cy}"
      while s
        if cx == s[2] && cy == s[3]
        else if cx == s[2]
          if path[path.length-1][0]=='V'
            path[path.length-1]= "V#{s[3]}"
          else
            path.push "V#{s[3]}"
        else if cy == s[3]
          if path[path.length-1][0]=='H'
            path[path.length-1]= "H#{s[2]}"
          else
            path.push "H#{s[2]}"
        else 
          path.push "L#{s[2]},#{s[3]}"
        cx = s[2]
        cy = s[3]
        s = s[5]
      
    """<path #{@style} d="#{path.join('')}" />\n"""

class TimeLine
  # : - ~ _ = 
  @transitions = [
    '          ', # :
    '  - 1 4 14', # -
    '  2 ~ / ~/', # ~
    '  3 ` _ _`', # _
    '  23`~/_= ', # =
    '  23`~/_=/', # /
    '  23`~/_=`', # \
    '  23`~/_X ', # X
    '  23`~/_=X', # *
  ]

  @transitionLines =
    ' ' : []
    '~' : [[1,1]]
    '_' : [[0,0]]
    '=' : [[1,1],[0,0]]
    'X' : [[1,0],[0,1]]
    '`' : [[1,0]]
    '/' : [[0,1]]
    '1' : [[1,0.5]]
    '2' : [[0.5,1]]
    '3' : [[0.5,0]]
    '4' : [[0,0.5]]
    '-' : [[0.5,0.5]]

  #                :  -     ~   _   =
  @stateLines = [[],[0.5],[1],[0],[0,1]]

  #
  @codes = ':-~_=/\\X*'
  
  constructor: (config, y)->
    @config = config
    @x = config.w_caption
    @y = y
    @path = new SvgPath(config.signal_style)
    @current = 0
    @crosses = []
    @strings = []
    @grids   = []
    @highlights = []
    
  ys: (s)->
    @y + (1-s) * @config.h_line
  y0: ->
    @y + @config.h_line
  y1: ->
    @y
  yz: ->
    @y + @config.h_line/2.0
  xh: ->
    @x + @config.w_transient/2.0
  xt: ->
    @x + @config.w_transient
  xr: ->
    @x + @config.w_transient + @config.w_hold
  
  parse: (line)->
    while line != ''
      if maches = /^\s+/.exec(line)
        ;
      else if maches = /^\|/.exec(line)
        @grids.push [@xh(), @config.grid_style]
      else if maches = /^\[/.exec(line)
        if @highlights.length==0 or Array.isArray(@highlights[@highlights.length-1])
          @highlights.push @xh()
      else if maches = /^\]/.exec(line)
        if @highlights.length>0 and not Array.isArray(@highlights[@highlights.length-1])
          @highlights[@highlights.length-1] = 
            [@highlights[@highlights.length-1], @xh(), @config.highlight_style]
      else if matches = /^([:\-~_=\/\\X*])/.exec(line)
        @addState(matches[1])
      else if matches = /^"(([^"]|"")*)"/.exec(line)
        @addString(matches[1].replace(/""/g, '"').replace(/[ ]/g, '&nbsp;'))
      else if matches = /([^:\-~_=\/\\X*\|\]\[]+)/.exec(line)
        @addString(matches[1])
      line = line.substr(matches[0].length, line.length-matches[0].length)
    
    @processStrings() + @path.svg()

  addState: (c)->
    s = TimeLine.codes.indexOf(c)
    crosses = @drawTransition(s)
    s = 4 if s > 4
    @drawState(s)
    @crosses.push([@x, crosses]) if crosses!=''
    if (@current == 0 and s != 0) or (@current != 0 and s == 0) or
       (@crosses.length==0 and s == 0)
      @crosses.push([@x, '|'])
    @current = s
    @x = @xr()

  addString: (s)->
    @strings.push [@crosses.length, s.replace(/^\s+|\s+$/g, '')]

  drawTransition: (s)->
    crosses = ''
    transitions = TimeLine.transitions[s].substr(2*@current,2)
    for i in [0..transitions.length-1]
      crosses += @drawTransitionSub(transitions[i])
    crosses
  
  drawTransitionSub: (c)->
    crosses = ''
    for line in TimeLine.transitionLines[c]
      @path.draw(@x,  @ys(line[0]), @xt(), @ys(line[1]))
      crosses += c if line[0] != line[1]
    crosses

  drawState: (s)->
    for line in TimeLine.stateLines[s]
      @path.draw(@xt(), @ys(line), @xr(), @ys(line))
  
  processStrings: ->
    svg = []
    @crosses.push [@x,'|']
    for string in @strings
      y0 = @ys(0)
      y1 = @ys(1)
      yz = @ys(0.5)
      x1 = @crosses[string[0]-1][0]
      x1t = x1 + @config.w_transient
      x1h = x1 + @config.w_transient/2.0
      x1r = x1t + @config.w_hold
      x2 = @crosses[string[0]  ][0]
      x2t = x2 + @config.w_transient
      x2h = x2 + @config.w_transient/2.0
      x2r = x2t + @config.w_hold

      if string[1]=='?'
        path= ["M#{x1t},#{y1}H#{x2}"]
        path.push switch @crosses[string[0]][1]
          when '|'  then ""
          when 'XX' then "L#{x2h},#{yz}"
          when '/'  then "H#{x2t}"
          when '`'  then "L#{x2t},#{y0}"
          when '23' then "H#{x2t}L#{x2},#{yz}L#{x2t},#{y0}"
          when '14' then "L#{x2t},#{yz}"
          when '1'  then "L#{x2t},#{yz}V#{y0}"
          when '2'  then "H#{x2t}L#{x2},#{yz}"
          when '3'  then "V#{yz}L#{x2t},#{y0}"
          when '4'  then "H#{x2t}V#{yz}"
        path.push "L#{x2},#{y0}H#{x1t}"
        path.push switch @crosses[string[0]-1][1]
          when '|'  then ""
          when 'XX' then "L#{x1h},#{yz}"
          when '/'  then "H#{x1}"
          when '`'  then "L#{x1},#{y1}"
          when '23' then "L#{x1},#{yz}"
          when '14' then "H#{x1}L#{x1t},#{yz}L#{x1},#{y1}"
          when '1'  then "V#{yz}L#{x1},#{y1}"
          when '2'  then "H#{x1}V#{yz}"
          when '3'  then "L#{x1},#{yz}V{y1}"
          when '4'  then "H#{x1}L#{xt},#{yz}"
        path.push "Z"
        svg.push """\n<path stroke="none" d="#{path.join('')}" #{@config.notcare_style}/>"""

      sanitized = string[1].replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;')
      if string[1].substr(0,3)=='_<_'
        x = x1t
        anchor = 'start'
        sanitized = sanitized.substring(6)
      else if string[1].substr(0,3)=='_>_'
        x = x2
        anchor = 'end'
        sanitized = sanitized.substring(6)
      else
        x = (x1h+x2h)/2.0
        anchor = 'middle'

      svg.push """<text x="#{x}" y="#{@ys(0)-1.5}" text-anchor="#{anchor}" """ +
               """font-size="#{@config.h_line}" #{@config.signal_font}>#{sanitized}</text>\n"""

    svg.join("")
    
class TimingChart
  @config:
    scale:          1.0
    margin:         10
    w_caption:      40
    w_hold:         10
    w_transient:    2
    h_line:         10
    h_space:        10
    signal_style:   'stroke-linecap="round" stroke-width="0.6" stroke="black" fill="none"'
    grid_style:     'stroke-linecap="round" stroke-width="0.6" stroke="red" fill="none"'
    highlight_style:'stroke="none" fill="#ff8"'
    notcare_style:  'fill="#ccc"'
    rotate:         0
    caption_font:   'fill="black" font-family="Helvetica"'
    signal_font:    'fill="black" font-family="Helvetica"'

  constructor: (config= {})->
    @config= {}
    @setConfig(TimingChart.config)
    @setConfig(config)

  setConfig: (config)->
    @config[k]= v for own k,v of config

  parse: (source)->
    @svg= []
    @grids= []
    @highlights= []
    @y= -1
    @x_max= @config.w_caption
    source= source.replace(/^\n+/, '')
    source= source.replace(/\n+$/, '')
    for line in source.split("\n")
      @parseLine(line)
    @processGrids()
    @processHighlights()
    @formatSVG(source)

  formatSVG: (source)->
    m= @config.margin
    w= (@x_max + 2 * m) * @config.scale * 1.3
    h= (@y     + 2 * m) * @config.scale * 1.3
    @width = w
    @height = h
    @svg = """
    <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" 
      width="#{w}px" height="#{h}px" viewBox="#{-m} #{-m} #{@x_max+2*m} #{@y+2*m}" version="1.1">
    <![CDATA[
    #{source.replace(/\]\]\>/g, ']]&gt;')}
    ]]>
    <g>
    #{@svg.join("\n")}
    </g>
    </svg>
    """

  parseLine: (line)->
    return if line[0] == '#'  #### comment

    if line[0]=='@'           #### configuration
      if !(matches = /^@([^\s]+)[\s]+([^\s].*)$/.exec(line))
        throw new SyntaxError("Illegal Line: #{line}")
      if $.isNumeric(@config[matches[1]])
        @config[matches[1]] = Number(matches[2])
      else
        @config[matches[1]] = matches[2]
      return

    if line[0] == '%'         #### free string
      if !(matches = /^%(-?[\d\.]+)\s+(-?[\d\.]+)\s+?(.*)$/.exec(line))
        throw new SyntaxError("Illegal Line: #{line}")
      @svg.push("""<text x="#{matches[1]}" y="#{matches[2]}" text-anchor="middle" """ +
                """font-size="#{@config.h_line}" #{@config.signal_font}>#{matches[3]}</text>""")
      return
    
    if @y<0
      @y= 0
    else
      @y+= @config.h_space

    line= line.replace(/\s*$/, '')
    return if line==''        #### empty line

    if !(matches = /^([^\s]+)[\s]+([^\s].*)$/.exec(line))
      throw new SyntaxError("Illegal Line: #{line}")
    
    @formatCaption(matches[1])
    @formatTimeline(matches[2])
    @y+= @config.h_line

  formatCaption: (caption)->
    sanitized = caption.replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;')
    @svg.push(
      """<text x="#{@config.w_caption-5}" y="#{@y+@config.h_line-1.5}" text-anchor="end" """+
      """font-size="#{@config.h_line}" #{@config.caption_font}>#{sanitized}</text>"""
    )

  formatTimeline: (line)->
    tline= new TimeLine(@config, @y)
    @svg.push tline.parse(line)
    @x_max= tline.x if tline.x > @x_max
    for g in tline.grids
      @grids.push g
    for h in tline.highlights
      @highlights.push h 
  
  processGrids: ->
    t = -@config.margin/2.0
    b = @y+@config.margin/2.0
    for g in @grids
      @svg.push """<path d="M#{g[0]},#{t}V#{b}" #{g[1]} />"""
    
  processHighlights: ->
    t = -@config.margin/2.0
    b = @y+@config.margin/2.0
    for h in @highlights
      if Array.isArray(h)
        @svg.unshift """<path d="M#{h[0]},#{t}V#{b}H#{h[1]}V#{t}Z" #{h[2]} />"""

lastSource = ''
lastChart = null
update = ->
  source = $('#source').val()
  return if lastSource==source
  lastSource = source
  
  lastChart= new TimingChart()
  svg= lastChart.parse(source)
  $('#result').html(svg)

  $('#images>*').remove()
  
callbackWithCanvas = (f)->
    canvas = document.createElement('canvas')
    canvas.width = lastChart.width
    canvas.height = lastChart.height
    # http://nmi.jp/archives/223
    img = new Image()
    img.onload = ->
      ctx = canvas.getContext("2d")
      ctx.fillStyle = $('#background').val()
      ctx.fillRect(0, 0, canvas.width, canvas.height)
      ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
      f(canvas)
    img.src = "data:image/svg+xml;base64," + btoa(lastChart.svg)

$ ->
  setInterval(update, 100)

  $('#as_svg').on 'click', ->
    update()
    blob = new Blob([lastChart.svg], {type: "image/svg+xml"});
    saveAs(blob, 'timing-chart.svg')

  $('#as_png').on 'click', ->
    update()
    callbackWithCanvas (canvas)->
      canvas.toBlob (blob)->
        saveAs(blob, "timing-chart.png")

  $('#for_copy').on 'click', ->
    $('#images>*').remove()
    update()

    $('#images').append $('<h2>').html('SVG Source')
    
    textarea = $('<textarea cols="60" rows="3">').val(lastChart.svg)
    $('#images').append textarea
    textarea.focus()
    textarea.select()
    
    $('#images').append $('<h2>').html('PNG Image')
    callbackWithCanvas (canvas)->
      $('#images').append $('<img>').attr('src', canvas.toDataURL())

コメント・質問





Counter: 65980 (from 2010/06/03), today: 17, yesterday: 0