こんなの。
JavaScriptで大きさを変えられるようにしてみたもの。
1から10の間で、1ごとに線を引くと1と2の間が空きすぎて、1から2の間に補助線入れるとそれをどこまで入れればいいか悩んで・・・これをいい具合に自動で計算する方法を考えてみました。
となる。
つまり割り算してから対数を取ればよい。
だから。
普通の人は桁線から下に数え、800の線を間違えて900と認識するか、用心深い人は500線から上に数えて「600、700、800、あれ?」となる。
ただし、ラベルを書く位置を計算する場合は別途検討する。
これも分かりづらいが、見る人が見れば分かる。
上側の目盛の青いところが20線、下側の目盛の赤いところが50線。 数字は上側の目盛の最小間隔を1としたときの下側の目盛の最小間隔。 5をちょっと超えたところまで互い違いの目盛ができることが分かる。
より、最小間隔の比が5.61倍以上ならば問題は起きない。
これは上段が飽和相当温位 \(\makeunit{K}\)、 下段が気温 \(\makeunit{\degC}\) なのだが、300Kより上がなにやら不穏な空気をかもし出している。 310Kまで5線が引かれ、その上は20線になっている。 結果、300/305/310/320/340/360/380/400となり、10ずつ数えても20ずつ数えても数が合わない。
305Kが引けたので、多少狭くなっても400Kまでは10Kごとに線を引く。
Math.sqrt(Math.log(x))
)を渡している。
10.5の線に注目しながら縮めていくと、この線が消えると同時に11・13・15・17・19の線も消える。消える直前は我慢して19の線を引いていた状態である。
ソース → logscale.js
このページの冒頭にある、マウスで大きさ変えられるヤツが実際に動作しているサンプルです。
ソース 開く
%!
% i <logscale-proc> -
% use parent's dictionary as it is.
/logscale-proc {
dup v3 mul 1000 div % i val
dup v1 ge 1 index v2 le and {
exch % val i
1 index func y1 sub wpd mul exch % val pos i
p % -
} {
pop pop
} ifelse
} bind def
% written base step <logscale-sub> wirtten decade
% use parent's dictionary as it is.
% call:
% written: must be write at least one line.
% base: base value (10/100/1000)
% step: line step (1/2/5/10/20/50/100)
% return:
% written: true if at least one line is written
% decade: true if the last line of the decade is wirtten
/logscale-sub {
/step exch def
/base exch def
/j i base idiv base mul def
/i i step idiv step mul def
false exch % initial written return value
{ % use written on call here
i step add step j base add {
logscale-proc
pop true % written
} for
/j j base add def
/i j def
} if
{
i 1000 ge { true exit } if
v3 j base add dup step sub calcdelta msp lt { false exit } if
i step add step j base add {
logscale-proc
pop true % written
} for
/j j base add def
/i j def
} loop
} bind def
/logscale-nosparse-sequence <<
400 [ 500 600 700 800 1000 ]
300 [ 400 500 600 800 1000 ]
200 [ 300 500 700 1000 ]
>> def
% draw one decade.
% use parent's dictionary as it is.
/logscale-decade {
1 {
false
10 1 logscale-sub { pop exit } if
10 2 logscale-sub { pop exit } if
10 5 logscale-sub { pop exit } if
100 10 logscale-sub { pop exit } if
100 20 logscale-sub { pop exit } if
100 50 logscale-sub { pop exit } if
false 1000 100 logscale-sub { pop pop exit } if pop
% written is on stack
v3 1000 750 calcdelta msp ge or {
nosparse {
logscale-nosparse-sequence i get {
logscale-proc
} forall
} {
i 100 add 100 500 { logscale-proc } for
use750 { 750 logscale-proc } if
1000 logscale-proc
} ifelse
exit
} if
v3 1000 500 calcdelta msp ge {
use250 {
[ 250 500 1000 ]
} {
[ 200 500 1000 ]
} ifelse { logscale-proc } forall
exit
} if
v3 300 100 calcdelta msp ge {
[ 300 1000 ] { logscale-proc } forall
exit
} if
1000 logscale-proc
exit
} repeat
} bind def
% start end width minspacing proc <logscale> -
% start end width minspacing proc dict <logscale> -
% proc: val pos lineidx <proc> -
% func: val <func> val
% return value in propotion to the scale
% default { log }
% dict: nosparse(bool), use750(bool), use250(bool), calcdelta(proc)
% calcdelta: v3 larger smaller <calcdelta> distance
% return actual distance distance between v3*larger/1000 and v3*smaller/1000
% default: log(larger / smaller) * widthperdecade
/logscale {
1 dict begin
/nosparse false def
/use750 false def
/use250 false def
dup type /dicttype eq { { def } forall } if
% /calcdelta defined, use calcdelta.
% /calcdelta not defined and func defined, use func.
% both are not defined, use starndard log calcdelta
currentdict /calcdelta known {
currentdict /func known not {
/logscale-no-func trap
} if
} {
currentdict /func known {
/calcdelta {
2 index mul 1000 div func exch
2 index mul 1000 div func exch
sub wpd mul exch pop
} bind def
} {
/func { log } bind def
/calcdelta { % v3 larger smaller <calcdelta> dist
div log wpd mul exch pop
} bind def
} ifelse
} ifelse
[ /p /msp /w /v2 /v1 ] { exch def } forall
/y1 v1 func def
/wpd w v2 func y1 sub div def % width per decade
/k 1 def
/m 10 v1 log ceiling exp def
/i v1 k m mul div 1000 mul ceiling cvi 1 sub def
{
/v3 k m mul def
logscale-decade
v3 v2 ge { exit } if
/k k 10 mul def
/i 100 def
} loop
end
} bind def
PostScript
start end width minspc proc logscale-start end width minspc proc optdict logscale-
JavaScript
logscale(start, end, width, minspc, proc, opt = {});
値 start から end までの対数目盛を描画する。 実際に描画をするのは proc で指定したプロシージャで、 logscale 自体は描画処理を行わない。 width は start から end までの幅、 minspc は目盛線の最低間隔。 単位は proc と辻褄が合っていれば問わない。
戻り値はない。
PostScriptの optdict は辞書、JavaScriptの opt はオブジェクトで、以下のオプションを指定できる。
600以降で抜け落ちる100線がある場合、
false
を指定すると600から900までの100線の描画処理を行わない。
true
を指定すると600以降についても描画処理を行い
proc
の呼び出しを行う。
呼び出しの対象とならない100線が存在することに注意。
デフォルトは
false
。
true
にすると、450の位置に線が引けない場合に、150の位置に線が引けるならば750の位置に線を引く(150-200が引けるならば、その5倍の750-1000も引ける)。
750なので100線ではなく50線として描画され、人間が見たときに700や800ではなく750であるという判断材料になる。
デフォルトは
false
。
true
にすると、150の位置に線が引けない場合に、100-200-500-1000ではなく、100-250-500-1000と分割する。
use750
と同様、50線となるので、200ではなく250であるという判断材料になる。
デフォルトは
false
。
軸変換関数。 プロシージャを指定する。 引数として start と end の間の値 x が渡されるので、それを軸上の距離に比例する値に変換して返す。 実際に目盛を打つ点( proc の pos に渡す値)は
になる。
通常は
{ log }
。
完全な対数ではないが、対数っぽい変化をするような量がある場合に、それを
func
に指定すると、対数目盛っぽく線を間引いて描画できる。
軸差分関数。 プロシージャを指定する。 引数として基準値 v3 、大きいほうの値に対する積 larger 、小さいほうの値に対する積 smaller の3つを取る。 \(v3\cdot larger / 1000\) と \(v3\cdot smaller / 1000\) に対する目盛の軸上の距離を返す。 通常は
になっている。
calcdelta
を指定した場合はfuncも必ず指定しなければならない。
なお、
wpd
(width per decade)という変数が定義されており、
と定義されているので、以下のように書ける。
{ % v3 larger smaller
2 index mul 1000 div func exch
2 index mul 1000 div func exch
sub wpd mul exch pop
} bind def
このプロシージャは、2回
func
を呼び出すよりも効率のよい軸差分を求める方法がある場合に意味がある。
実際、
func
が
{ log }
の場合、
となり、\(\log\)の計算を1度で済ますことができる上に、
v3
が不要になる。
proc には3つの引数が渡される。 最初の引数は val で、その線における値(数値型)。 ラベルを描画するときはこの値を文字列に変換して表示すればよい。 ふたつ目は pos で、 start を0、 end を width とした場合の位置を示す(数値型)。 この位置に目盛線や目盛ラベルを表示する。 3つ目は lineindex で、100から1000までの整数値である。 線種を決定するのに使用する。 なお、 proc の呼び出し順は値の小さい順とは限らない。
たとえば、
0.8 125 8.5 0.1 { 3 array astore == } logscale
とすれば、8.5の長さの中に最低間隔0.1で、開始値0.8、終了値125で対数目盛を引くとしたら、どの位置にどの値でどんな線を引けばいいかを教えてくれる。 この8.5とか0.1という数字は長さを示すが、単位は好きに決めてよい。 この例ではレターサイズの横幅をイメージしているので、8.5インチに0.1インチ(約2.5mm)間隔で目盛を打つ感じである。
実際に
/drawline {
1 dict begin
/i exch def /pos exch def pop
i 500 mod 0 eq { 0 setgray } {
i 100 mod 0 eq { 0.3 setgray } {
i 50 mod 0 eq { 0.6 setgray } {
0.8 setgray
} ifelse } ifelse } ifelse
pos 72 mul 0 moveto 0 72 rlineto stroke
end
} bind def
0.8 125 8.5 0.1 { drawline } logscale
とすれば、Ghostscriptのデフォルトページサイズは横8.5インチなので、用紙の下端に所望の目盛が現れる。
pos 72 mul
の部分でインチをポイントに変換している。
あるいは、第2パラメータは無視してしまって、第1パラメータから自分で描画位置を計算してもよい。
グラフ用紙として描画した例を以下に示す。
各線はこうなっている。
| 種類 | 線種 | 太さ[pt] | 明るさ |
|---|---|---|---|
| 1線 | 点線 | 極細 | 0.5 |
| 5線 | 破線 | 0.2 | 0.5 |
| 10線 | 実線 | 0.2 | 0.5 |
| 50線 | 破線 | 0.4 | 0 |
| 100線 | 実線 | 0.4 | 0 |
| 桁線 | 実線 | 0.8 | 0 |
線の太さは0.2ポイント単位にしてあるので、360dpiのプリンタで再現可能である。 このグラフ用紙では5線より細かい線は出てこないが、1線は画面上での見やすさを考えて極細にしてある。 360dpiのプリンタでは極細と0.2ポイントの区別は付かないが、線種で区別が付く。 元々目盛線は明るめに描画しているが、明るさが0ではないものはそれよりも明るく(うすく)描画しているという意味で、この値が1だと真っ白になって見えなくなる。
Copyright (C) 2019-2023 akamoz.jp
$Id: index.htm,v 1.20 2023/10/09 23:20:32 you Exp $