nysol-view 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/bin/mbar.rb ADDED
@@ -0,0 +1,818 @@
1
+ #!/usr/bin/env ruby
2
+ #-*- coding: utf-8 -*-
3
+
4
+ require "rubygems"
5
+ require "nysol/mcmd"
6
+ require "nysol/viewjs"
7
+
8
+ # ver="1.0" # 初期リリース 2014/5/11
9
+ $cmd = $0.sub(/.*\//,"")
10
+
11
+ $version = 1.0
12
+ $revision = "###VERSION###"
13
+
14
+ def help ()
15
+
16
+ #ヒアドキュメント
17
+ STDERR.puts <<EOF
18
+ ------------------------
19
+ #{$cmd} version #{$version}
20
+ ------------------------
21
+ 概要) CSVデータから棒グラフ(HTML)を作成する
22
+ 1次元グリッド、2次元グリッドのグラフ表示が可能
23
+ マウススクロールで拡大、縮小、マウスドラッグで画像の移動が可能
24
+
25
+ 書式1) #{$cmd} [i=] [o=] [title=] [height=] [width=] [k=] [cc=] f= v= [--help]
26
+ i= : 入力データファイル名(CSV形式)
27
+ o= : 出力ファイル名(HTMLファイル)
28
+ title= : グラフのタイトル文字列を指定する
29
+ height= : 棒グラフ用描画枠の縦幅を指定する(default:250/1つの棒グラフは400)
30
+ width= : 棒グラフ用描画枠の横幅を指定する(default:250/1つの棒グラフは600)
31
+ k= : x軸,y軸に展開する属性項目名
32
+ k=なしの場合は棒グラフを1つ作成する
33
+ 項目を1つ指定した場合は1次元の棒グラフ行列を、
34
+ 項目を2つ指定した場合は2次元の棒グラフ行列を作成する
35
+ (y軸項目,x軸項目の順に指定)
36
+ cc= : 1行に表示する棒グラフの最大数を指定する(default:5)
37
+ 1次元グラフのみで指定可能(k=1つ指定の場合)
38
+ f= : 構成要素項目名を指定する(必須)
39
+ データにnullが含まれる場合は無視する
40
+ v= : 構成量項目(棒グラフの高さを決定する項目)を指定する(必須)
41
+ データにnullが含まれる場合は0として扱う
42
+ 先頭の0は無視する
43
+ 数字以外の場合はエラーとなる
44
+ --help : ヘルプの表示
45
+
46
+ 注意1)コマンドには、f=パラメータやk=パラメータで指定した項目を自動的に並べ替える機能はない
47
+ グラフに表示したい順に、あらかじめ並べ替えておく必要がある。
48
+ 1次元、2次元グラフの場合はデータの先頭の棒グラフの表示順に並べられる
49
+
50
+ 例1) 棒グラフを1つ描画する
51
+ dat1.csvファイルのAgeを構成要素項目に、Populationを構成量項目として棒グラフを1つ描画する
52
+
53
+ dat1.csv
54
+ Age,Population
55
+ 10,310504
56
+ 20,552339
57
+ 30,259034.5555
58
+ 40,0450818
59
+ 50,1231572
60
+ 60,1215966
61
+ 70,641667
62
+
63
+ $ #{$cmd} i=dat1.csv v=Population f=Age o=result1.html
64
+
65
+ 例2) 1次元の棒グラフ行列を描画する
66
+ dat2.csvファイルのAgeを構成要素項目に、Populationを構成量項目として棒グラフを描画する
67
+ k=パラメータにPref項目を指定しているので、
68
+ Pref項目の値をx軸(横方向)に展開した1次元の棒グラフ行列が描画される
69
+ title=パラメータでグラフのタイトルも指定している
70
+
71
+ dat2.csv
72
+ Pref,Age,Population
73
+ 奈良,10,310504
74
+ 奈良,20,552339
75
+ 奈良,30,259034
76
+ 奈良,40,450818
77
+ 奈良,50,1231572
78
+ 奈良,60,1215966
79
+ 奈良,70,641667
80
+ 北海道,10,310504
81
+ 北海道,20,252339
82
+ 北海道,30,859034
83
+ 北海道,40,150818
84
+ 北海道,50,9231572
85
+ 北海道,60,4215966
86
+ 北海道,70,341667
87
+
88
+ $ #{$cmd} i=dat2.csv k=Pref v=Population f=Age o=result2.html
89
+
90
+ 例3) x軸上に表示する棒グラフの最大数を1とする
91
+
92
+ $ #{$cmd} i=dat2.csv k=Pref v=Population f=Age o=result3.html cc=1
93
+
94
+ 例4) 2次元の棒グラフ行列を描画する
95
+ dat3.csvファイルのテーマパーク名を構成要素項目に、
96
+ Numberを構成量項目として棒グラフを描画する
97
+ k=パラメータにGenderとAge項目を指定して、Gender項目の値をx軸(横方向)に、
98
+ Age項目の値をy軸(縦方向)に展開した2次元の棒グラフ行列を描画する
99
+
100
+ dat3.csv
101
+ Gender,Age,テーマパーク名,Number
102
+ 男性,30,デズニ,100
103
+ 男性,30,UFJ,59
104
+ 男性,30,梅屋敷,180
105
+ 男性,40,デズニ,200
106
+ 男性,40,UFJ,3
107
+ 男性,40,梅屋敷,10
108
+ 男性,50,デズニ,110
109
+ 男性,50,UFJ,40
110
+ 女性,30,梅屋敷,100
111
+ 女性,30,デズニ,80
112
+ 女性,30,UFJ,200
113
+ 女性,40,デズニ,90
114
+ 女性,40,UFJ,80
115
+ 女性,40,梅屋敷,120
116
+ 女性,50,デズニ,99
117
+ 女性,50,UFJ,80
118
+ 女性,50,梅屋敷,110
119
+
120
+ $ #{$cmd} i=dat3.csv k=Gender,Age v=Number f=テーマパーク名 o=result3.html title=性別と年代ごとのテーマパーク訪問回
121
+ EOF
122
+
123
+ exit
124
+ end
125
+
126
+ def ver()
127
+ $revision = "0" if $revision =~ /VERSION/
128
+ STDERR.puts "version #{$version} revision #{$revision}"
129
+ exit
130
+ end
131
+
132
+ def checkNull(key)
133
+ if key == nil then # 値の項目がnullの場合0にする
134
+ return "0"
135
+ end
136
+ return key
137
+ end
138
+
139
+ def checkNumeric(key)
140
+ unless key =~ /^-?([0-9]\d*|0)(\.\d+)?$/ # 棒グラフの値の整数チェック
141
+ raise "#{key} is not a numeric"
142
+ end
143
+ end
144
+
145
+ def getStrLength(key, maxLeg)
146
+ if key != nil
147
+ tmpSize = key.split(//).size
148
+ if tmpSize > maxLeg
149
+ return tmpSize
150
+ end
151
+ end
152
+ return maxLeg
153
+ end
154
+
155
+ # (dataStr, countKey, maxValue, maxLeg, maxValueSize, minValue) = makeZeroDemData(iFile, legendKey, barValue)
156
+ # iFile(inputファイル)
157
+ # legendKey(キーの項目) (例:年代)
158
+ # barValue(値の項目) (例:人口)
159
+ def makeZeroDemData(iFile, legendKey, barValue)
160
+ dataStr = "var data = [\n{"
161
+ countKey = 0
162
+ maxValue = 0
163
+ minValue = 0
164
+ maxLeg = 0
165
+ maxValueSize = 0
166
+ if iFile then
167
+ MCMD::Mcsvin.new("i=#{iFile}"){|csv|
168
+ csv.each{|flds| # 行処理
169
+ csv.names.each {|header| # 一行の項目分ループ
170
+ if header == "#{legendKey}" then # 棒グラフのキー項目
171
+ maxLeg = getStrLength(flds[header], maxLeg)
172
+ end
173
+ if header == barValue # header読み込み(headerがvalueの時)
174
+ flds[header] = checkNull(flds[header])
175
+ checkNumeric(flds[header])
176
+ if flds[header].length > maxValueSize
177
+ maxValueSize = flds[header].length
178
+ end
179
+ if flds[header] =~ /\d+\.\d+/ then
180
+ if flds[header].to_f > maxValue.to_f then #構成量項目の最大値を取得
181
+ maxValue = flds[barValue]
182
+ end
183
+ if flds[header].to_f < minValue.to_f then #構成量項目の最小値を取得
184
+ minValue = flds[barValue].to_f
185
+ end
186
+ else
187
+ if flds[header].to_i > maxValue.to_i then #構成量項目の最大値を取得
188
+ maxValue = flds[barValue]
189
+ end
190
+ if flds[header].to_i < minValue.to_i then #構成量項目の最小値を取得
191
+ minValue = flds[barValue].to_i
192
+ end
193
+ end
194
+ dataStr << "\"_#{flds[legendKey]}\":\"#{flds[barValue]}\","
195
+ end
196
+ }
197
+ countKey +=1 # 行数カウント(0次元の場合はヘッダを除いたデータの行数)
198
+ }
199
+ dataStr = dataStr.chop
200
+ dataStr << "}\n];\n"
201
+ }
202
+ end
203
+ return dataStr, countKey, maxValue, maxLeg, maxValueSize, minValue
204
+ end
205
+
206
+ # (dataStr, xcount, keycount, maxValue, maxLeg, maxValueSize, minValue) = makeOneDemData(iFile, primKey, legendKey, barValue)
207
+ # iFile(inputファイル)
208
+ # primKey(主キー) (例:Pref)
209
+ # legendKey(キーの項目) (例:年代)
210
+ # barValue(値の項目) (例:人口)
211
+ def makeOneDemData(iFile, primKey, legendKey, barValue)
212
+ dataStr = "var data = ["
213
+ xcount = {} # hash
214
+ keycount = {}
215
+ maxValue = 0
216
+ minValue = 0
217
+ maxLeg = 0
218
+ maxValueSize = 0
219
+ if iFile then
220
+ MCMD::Mcsvin.new("i=#{iFile} k=#{primKey} -q"){|csv|
221
+ csv.each{|flds,top,bot| # 行処理
222
+ if top == true then # 先頭行の場合
223
+ dataStr << "\n{"
224
+ end
225
+ csv.names.each {|header| # 一行の項目分ループ
226
+ unless xcount.has_key?("#{flds[primKey]}")
227
+ xcount[flds[primKey]] = 1 # x軸の項目ハッシュ(カウント用)
228
+ end
229
+ if header == "#{primKey}" && xcount[flds[primKey]] == 1 # 1回目に出たときだけ作る
230
+ dataStr << "\"#{header}\":\"#{flds[header]}\","
231
+ xcount[flds[primKey]] +=1
232
+ end
233
+ if header == "#{legendKey}" then # 棒グラフのキー項目
234
+ maxLeg = getStrLength(flds[header], maxLeg)
235
+ keycount[flds[legendKey]] = 1 # 棒グラフのキー項目ハッシュ(カウント用)
236
+ end
237
+ if header == "#{barValue}" then
238
+ flds[header] = checkNull(flds[header])
239
+ checkNumeric(flds[header])
240
+ if flds[header].length > maxValueSize
241
+ maxValueSize = flds[header].length
242
+ end
243
+ if flds[header] =~ /\d+\.\d+/ then
244
+ if flds[header].to_f > maxValue.to_f then #構成量項目の最大値を取得
245
+ maxValue = flds[barValue]
246
+ end
247
+ if flds[header].to_f < minValue.to_f then #構成量項目の最小値を取得
248
+ minValue = flds[barValue].to_f
249
+ end
250
+ else
251
+ if flds[header].to_i > maxValue.to_i then #構成量項目の最大値を取得
252
+ maxValue = flds[barValue]
253
+ end
254
+ if flds[header].to_i < minValue.to_i then #構成量項目の最小値を取得
255
+ minValue = flds[barValue].to_i
256
+ end
257
+ end
258
+ dataStr << "\"_#{flds[legendKey]}\":\"#{flds[barValue]}\"," # key項目:value項目値
259
+ end
260
+ }
261
+ if bot == true then # primKeyの同じ項目の終わり
262
+ dataStr = dataStr.chop
263
+ dataStr << "},"
264
+ end
265
+ }
266
+ dataStr = dataStr.chop
267
+ dataStr << "\n];\n"
268
+ }
269
+ end
270
+ return dataStr, xcount, keycount, maxValue, maxLeg, maxValueSize, minValue
271
+ end
272
+
273
+ # (dataStr, xcount, ycount, keycount, maxValue, maxLeg, maxValueSize, minValue) = makeTwoDemData(iFile, yBar, xBar, legendKey, barValue)
274
+ # iFile(inputファイル)
275
+ # yBar(keyの値:行の項目)
276
+ # xBar(keyの値:列の項目)
277
+ # legendKey(キーの項目)
278
+ # barValue(値の項目)
279
+ def makeTwoDemData(iFile, yBar, xBar, legendKey, barValue)
280
+ dataStr = "var data = ["
281
+ xcount = {}
282
+ ycount = {}
283
+ maxValue = 0
284
+ minValue = 0
285
+ maxLeg = 0
286
+ maxValueSize = 0
287
+ xycount = Hash.new{|h,k| h[k]=Hash.new(&h.default_proc)} # hashネスト宣言
288
+ keycount = {}
289
+ if iFile then
290
+ MCMD::Mcsvin.new("i=#{iFile} k=#{yBar},#{xBar} -q"){|csv|
291
+ csv.each{|flds,top,bot|
292
+ if top == true then # 先頭
293
+ dataStr << "\n{"
294
+ end
295
+ csv.names.each {|header| # 一行の項目分ループ
296
+ xcount[flds[xBar]] = 1 # x軸の項目ハッシュ(カウント用)
297
+ ycount[flds[yBar]] = 1 # y軸の項目ハッシュ(カウント用)
298
+ unless xycount[flds[xBar]].has_key?("#{flds[yBar]}")
299
+ xycount[flds[xBar]][flds[yBar]] = 1 # y軸の項目ハッシュ(カウント用)
300
+ end
301
+ if header == "#{xBar}" && xycount[flds[xBar]][flds[yBar]] == 1 then
302
+ dataStr << "\"#{xBar}\":\"#{flds[xBar]}\",\"#{yBar}\":\"#{flds[yBar]}\","
303
+ xycount[flds[xBar]][flds[yBar]] +=1
304
+ end
305
+ if header == "#{legendKey}" then # 棒グラフのキー項目
306
+ maxLeg = getStrLength(flds[header], maxLeg)
307
+ keycount[flds[legendKey]] = 1 # 棒グラフのキー項目ハッシュ
308
+ end
309
+ if header == "#{barValue}" then # 棒グラフの値項目
310
+ flds[header] = checkNull(flds[header])
311
+ checkNumeric(flds[header])
312
+ if flds[header].length > maxValueSize
313
+ maxValueSize = flds[header].length
314
+ end
315
+ if flds[header] =~ /\d+\.\d+/ then
316
+ if flds[header].to_f > maxValue.to_f then #構成量項目の最大値を取得
317
+ maxValue = flds[barValue]
318
+ end
319
+ if flds[header].to_f < minValue.to_f then #構成量項目の最小値を取得
320
+ minValue = flds[barValue].to_f
321
+ end
322
+ else
323
+ if flds[header].to_i > maxValue.to_i then #構成量項目の最大値を取得
324
+ maxValue = flds[barValue]
325
+ end
326
+ if flds[header].to_i < minValue.to_i then #構成量項目の最小値を取得
327
+ minValue = flds[barValue].to_i
328
+ end
329
+ end
330
+ dataStr << "\"_#{flds[legendKey]}\":\"#{flds[barValue]}\"," # key項目:value項目値
331
+ end
332
+ }
333
+ if bot == true then
334
+ dataStr = dataStr.chop
335
+ dataStr << "},"
336
+ end
337
+ }
338
+ dataStr = dataStr.chop
339
+ dataStr << "\n];\n"
340
+ }
341
+ end
342
+ return dataStr, xcount, ycount, keycount, maxValue, maxLeg, maxValueSize, minValue
343
+ end
344
+
345
+ help() if ARGV.size <= 0 or ARGV[0] == "--help"
346
+ ver() if ARGV[0] == "--version"
347
+
348
+ # ===================================================================
349
+ # パラメータ処理 f=,v=は必須
350
+ args = MCMD::Margs.new(ARGV,"i=,o=,title=,cc=,height=,width=,k=,f=,v=,--help","f=,v=")
351
+ input_args = ARGV.join(" ")
352
+ command = $cmd
353
+
354
+ # mcmdのメッセージは警告とエラーのみ
355
+ ENV["KG_VerboseLevel"] = "2" unless args.bool("-mcmdenv")
356
+
357
+ iFile = args.file("i=","r") # inputファイル名を取得(readable)
358
+ oFile = args.file("o=","w") # outputファイル名を取得(writable)
359
+
360
+ title = args.str("title=") # タイトル取得
361
+ svgHeight = args.int("height=") # 棒グラフ用SVGの縦幅
362
+ svgWidth = args.int("width=") # 棒グラフ用SVGの横幅
363
+
364
+ keyFld = args.field("k=", iFile) # key項目値取得
365
+ key1 = key2 = nil
366
+ if keyFld then
367
+ key1 = keyFld["names"][0] # 行キー
368
+ key2 = keyFld["names"][1] # 列キー
369
+ end
370
+ xMax = args.int("cc=") # x軸に並べる棒グラフの数取得
371
+ legendKey = args.str("f=") # 棒グラフの構成要素項目
372
+ barValue = args.str("v=") # 棒グラフの値のキー
373
+
374
+ args.field("v=", iFile) if args.keyValue["v="] # 項目値をヘッダからチェック
375
+ args.field("f=", iFile) if args.keyValue["f="] # 凡例項目をヘッダからチェック
376
+
377
+ if xMax then
378
+ if keyFld == nil or key2 then
379
+ raise "cc= takes only k=A"
380
+ end
381
+ if xMax < 1 then
382
+ raise "cc= takes more than 1"
383
+ end
384
+ end
385
+
386
+ # ===================================================================
387
+ # デフォルト値
388
+ # デフォルトは0次元グラフの設定値
389
+ #0次元
390
+ if key1 == nil and key2 == nil then
391
+ svgHeight = 400 unless svgHeight
392
+ svgWidth = 600 unless svgWidth
393
+ else
394
+ svgHeight = 250 unless svgHeight
395
+ svgWidth = 250 unless svgWidth
396
+ end
397
+
398
+ # キャンパスのマージン
399
+ outerMarginL = 30
400
+ outerMarginR = 30
401
+ outerMarginT = 30
402
+ outerMarginB = 30
403
+
404
+ maxValue = 0
405
+ xMax = 5 unless xMax
406
+
407
+ # ============
408
+ # INPUTファイルの読み込み
409
+ xcount = {}
410
+ ycount = {}
411
+ keycount = {}
412
+ xNum = 1
413
+ yNum = 1
414
+ keyNum = 0
415
+ dataStr = ""
416
+ maxValue=0
417
+ minValue=0
418
+ maxValueSize=0
419
+ maxLeg=0
420
+ # 2次元グラフ処理
421
+ if key1 != nil and key2 != nil then
422
+ (dataStr, xcount, ycount, keycount, maxValue, maxLeg, maxValueSize, minValue) = makeTwoDemData(iFile, key1, key2, legendKey, barValue)
423
+ xNum = xcount.length # x軸の棒グラフ数
424
+ yNum = ycount.length # y軸の棒グラフ数
425
+ keyNum = keycount.length # 凡例用キー数
426
+ # 1次元グラフ処理
427
+ elsif key1 != nil and key2 == nil then
428
+ (dataStr, xcount, countKey, maxValue, maxLeg, maxValueSize, minValue) = makeOneDemData(iFile, key1, legendKey, barValue)
429
+ xNum = xcount.length #主キーの数
430
+ keyNum = countKey.length #keyの数
431
+ if xNum > xMax
432
+ yNum = xNum / xMax
433
+ xNum = xMax
434
+ end
435
+ # 0次元グラフ処理
436
+ elsif key1 == nil and key2 == nil then
437
+ (dataStr, countKey, maxValue, maxLeg, maxValueSize, minValue) = makeZeroDemData(iFile, legendKey, barValue)
438
+ keyNum = countKey
439
+ end
440
+ xCampusSize = xNum * svgWidth + outerMarginL + outerMarginR
441
+ yCampusSize = yNum * svgHeight + outerMarginT + outerMarginB
442
+ maxValueCount = maxValue.length
443
+ maxLength = maxValueCount + maxValueCount / 3
444
+ if maxLength < maxValueSize
445
+ maxLength = maxValueSize
446
+ end
447
+ # 棒グラフ用SVGのマージン
448
+ # 左は最大桁数+カンマの数*10(単位)+10(マージン)
449
+ barMarginL = maxLength * 10 + 10
450
+ barMarginR = 10
451
+ barMarginT = 20
452
+ #barMarginB = 20
453
+ # 下は最大桁数*10(単位)+30(マージン)
454
+ barMarginB = maxLeg * 10 + 30
455
+ # ============
456
+ # 文字列作成
457
+ colorStyle = ""
458
+ campusStr = ""
459
+ outLineStr = ""
460
+ barStr = ""
461
+ graphTitle = ""
462
+ #legendStr = ""
463
+ svgStr = ""
464
+ xZeroBar = ""
465
+ #2次元グラフの場合
466
+ if key1 != nil and key2 != nil then
467
+ colorStyle = <<EOT
468
+ var tmpKeys = d3.keys(data[0]).filter(function(key) { if(key !== "#{key1}" && key !== "#{key2}") { return key; }});
469
+ EOT
470
+ #0,1次元グラフの場合
471
+ else
472
+ colorStyle = <<EOT
473
+ var tmpKeys = d3.keys(data[0]).filter(function(key) { if(key !== "#{key1}") { return key; }});
474
+ EOT
475
+ end
476
+
477
+ # ============
478
+ # TITLE用文字列作成
479
+ titleMargin =20
480
+ tmpX = (xCampusSize - outerMarginR) / 2
481
+ titleStr = ""
482
+ if title then
483
+ titleStr = <<EOT
484
+ var title = d3.select("svg").append("text")
485
+ .attr("x", (#{tmpX}))
486
+ .attr("y", "#{titleMargin}")
487
+ .attr("text-anchor", "middle")
488
+ .style("font-size", "13pt")
489
+ .text("#{title}");
490
+ EOT
491
+ end
492
+
493
+ # 2次元
494
+ if key1 != nil and key2 != nil then
495
+ xdomain = xcount.keys
496
+ ydomain = ycount.keys
497
+
498
+ outLineStr = <<EOT
499
+ var out_x = d3.scale.ordinal()
500
+ .domain(#{xdomain})
501
+ .rangeBands([0, out_axis_width]);
502
+
503
+ var out_x_axis = d3.svg.axis()
504
+ .scale(out_x) //スケールの設定
505
+ .orient("bottom");
506
+
507
+ var out_xaxis = d3.select("svg")
508
+ .append("g")
509
+ .attr("class", "axis")
510
+ .attr("transform", "translate(" + outer_margin.left + "," + (out_axis_height + outer_margin.top) + ")")
511
+ .call(out_x_axis);
512
+
513
+ var out_y = d3.scale.ordinal()
514
+ .domain(#{ydomain})
515
+ .rangeBands([0, out_axis_height]);
516
+
517
+ var out_y_axis = d3.svg.axis()
518
+ .scale(out_y)
519
+ .orient("left");
520
+
521
+ var out_yaxis = d3.select("svg")
522
+ .append("g")
523
+ .attr("class", "axis")
524
+ .attr("transform", "translate(" + outer_margin.left + "," + outer_margin.top + ")") // x方向,y方向
525
+ .call(out_y_axis);
526
+ EOT
527
+ else
528
+ outLineStr = ""
529
+ end
530
+
531
+ svgStr = <<EOT
532
+ // 描画領域を作成(svgをデータ行分つくる)
533
+ var svg = d3.select("svg").selectAll(".bar")
534
+ .data(data)
535
+ .enter().append("svg")
536
+ .attr("width", svgWidth)
537
+ .attr("height", svgHeight)
538
+ .attr("x", function(d,i) {
539
+ if (i < #{xNum}) { return svgWidth*i + outer_margin.left ; }
540
+ else { return svgWidth*(i % #{xNum}) + outer_margin.left; } }) // width+0, width+1,...width+xNum, width+0..
541
+ .attr("y", function(d,i) { return outer_margin.top + svgHeight * parseInt(i/#{xNum}); }) // i/3を整数値で取得
542
+ .attr("class", "bar")
543
+ .append("g")
544
+ .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
545
+ EOT
546
+ # 1次元
547
+ if key1 != nil and key2 == nil then
548
+ titleY = barMarginT / 2
549
+ graphTitle = <<EOT
550
+ svg.append("text")
551
+ .attr("class", "text")
552
+ .style("text-anchor", "middle")
553
+ .style("font-size", "10px")
554
+ .attr("transform", "translate(0, -#{titleY})")
555
+ .text( function(d) {return d.#{key1}});
556
+ EOT
557
+ end
558
+
559
+ comStr = <<EOT
560
+ var command = d3.select("body").append("div")
561
+ .attr("x", "430")
562
+ .attr("y", "20")
563
+ .attr("text-anchor", "left")
564
+ .style("font-size", "14px")
565
+ .text("#{command} #{input_args}");
566
+ EOT
567
+
568
+ if minValue < 0 then
569
+ xZeroBar = <<EOT
570
+ svg.append("g")
571
+ .attr("class", "x0 axis")
572
+ .append("line")
573
+ .attr("x1", 0)
574
+ .attr("y1", y(0))
575
+ .attr("x2", inWidth )
576
+ .attr("y2", y(0));
577
+ EOT
578
+ end
579
+
580
+ # ============
581
+ # 描画
582
+ html = STDOUT
583
+ if oFile != nil then
584
+ html = open(oFile,"w")
585
+ end
586
+
587
+ html.puts <<HEOF
588
+ <!DOCTYPE html>
589
+ <html>
590
+ <head>
591
+ <meta charset="utf-8">
592
+ <style>
593
+
594
+ body {
595
+ font: 10px sans-serif;
596
+ }
597
+
598
+ svg {
599
+ padding: 10px 0 0 10px;
600
+ }
601
+
602
+ .arc {
603
+ stroke: #fff;
604
+ }
605
+
606
+ #tooltip {
607
+ position: absolute;
608
+ width: 150px;
609
+ height: auto;
610
+ padding: 10px;
611
+ background-color: white;
612
+ -webkit-border-radius: 10px;
613
+ -moz-border-radius: 10px;
614
+ border-radius: 5px;
615
+ -webkit-box-shadow: 4px 4px 10px rgba(0,0,0,0.4);
616
+ -moz-box-shadow: 4px 4px 10px rgba(0,0,0,0.4);
617
+ box-shadow: 4px 4px 10px rgba(0,0,0,0.4);
618
+ pointer-events: none;
619
+ }
620
+
621
+ #tooltip.hidden {
622
+ display: none;
623
+ }
624
+
625
+ #tooltip p {
626
+ margin: 0;
627
+ font-family: sans-serif;
628
+ font-size: 10px;
629
+ line-height: 14px;
630
+ }
631
+
632
+ .axis text {
633
+ font: 11px
634
+ font-family: sans-serif;
635
+ }
636
+
637
+ .axis path,
638
+ .axis line {
639
+ fill: none;
640
+ stroke: #000;
641
+ shape-rendering: crispEdges;
642
+ }
643
+ </style>
644
+ </head>
645
+ <body>
646
+ <script>
647
+ #{ViewJs::d3jsMin()}
648
+
649
+ #{dataStr}
650
+ // キャンパスサイズ
651
+ var out_w = #{xCampusSize};
652
+ var out_h = #{yCampusSize};
653
+
654
+ // キャンパスのマージン
655
+ var outer_margin = {top: #{outerMarginT}, right: #{outerMarginR}, bottom: #{outerMarginB}, left: #{outerMarginL}};
656
+
657
+ // 凡例の位置
658
+
659
+ // 外側のx軸サイズ
660
+ var out_axis_width = out_w - outer_margin.left - outer_margin.right;
661
+ var out_axis_height = out_h - outer_margin.top - outer_margin.bottom;
662
+
663
+ // 棒グラフ表示用SVGのマージン
664
+ var margin = {top: #{barMarginT}, right: #{barMarginR}, bottom: #{barMarginB}, left: #{barMarginL}};
665
+
666
+ var svgWidth = #{svgWidth};
667
+ var svgHeight = #{svgHeight};
668
+
669
+ var vbox_x = 0;
670
+ var vbox_y = 0;
671
+ var vbox_default_width = vbox_width = out_w;
672
+ var vbox_default_height = vbox_height = out_h;
673
+
674
+ var outline = d3.select("body").append("svg")
675
+ .attr("width", out_w )
676
+ .attr("height", out_h)
677
+ .attr("viewBox", "" + vbox_x + " " + vbox_y + " " + vbox_width + " " + vbox_height);
678
+
679
+ var drag = d3.behavior.drag().on("drag", function(d) {
680
+ vbox_x -= d3.event.dx;
681
+ vbox_y -= d3.event.dy;
682
+ return outline.attr("translate", "" + vbox_x + " " + vbox_y);
683
+ });
684
+ outline.call(drag);
685
+ zoom = d3.behavior.zoom().on("zoom", function(d) {
686
+ var befere_vbox_width, before_vbox_height, d_x, d_y;
687
+ befere_vbox_width = vbox_width;
688
+ before_vbox_height = vbox_height;
689
+ vbox_width = vbox_default_width * d3.event.scale;
690
+ vbox_height = vbox_default_height * d3.event.scale;
691
+ d_x = (befere_vbox_width - vbox_width) / 2;
692
+ d_y = (before_vbox_height - vbox_height) / 2;
693
+ vbox_x += d_x;
694
+ vbox_y += d_y;
695
+ return outline.attr("viewBox", "" + vbox_x + " " + vbox_y + " " + vbox_width + " " + vbox_height);
696
+ });
697
+ outline.call(zoom);
698
+
699
+ #{titleStr}
700
+
701
+ //outlineのX軸
702
+ //outlineのY軸
703
+ #{outLineStr}
704
+
705
+ // make colorlist
706
+ var color1 = d3.scale.category10();
707
+ var color2 = d3.scale.category20b();
708
+ var color3 = d3.scale.category20();
709
+ var color4 = d3.scale.category20c();
710
+ var cl = color1.range();
711
+ var colorList = cl.concat(color1.range());
712
+ colorList = colorList.concat(color2.range());
713
+ colorList = colorList.concat(color3.range());
714
+ colorList = colorList.concat(color4.range());
715
+
716
+ #{colorStyle}
717
+ var dataNames = tmpKeys.map(function(d) {return d.substr(1);});
718
+
719
+ // set color
720
+ var color = d3.scale.ordinal()
721
+ .range(colorList)
722
+ .domain(dataNames);
723
+
724
+ // レンジオブジェクト
725
+ // 棒グラフの横幅を0からwidthの間に、棒グラフの横幅を10%づつバディングして並べる
726
+ var inWidth = svgWidth - (margin.left + margin.right);
727
+ var x = d3.scale.ordinal()
728
+ .rangeRoundBands([0, inWidth], .1);
729
+
730
+ // 棒グラフの縦幅を定義
731
+ var inHeight = svgHeight - margin.top - margin.bottom;
732
+ var y = d3.scale.linear()
733
+ .range([inHeight, 0]);
734
+
735
+ // 棒グラフのx軸のオブジェクト
736
+ var xAxis = d3.svg.axis()
737
+ .scale(x)
738
+ .orient("bottom");
739
+
740
+ // 棒グラフのy軸のオブジェクト
741
+ var yAxis = d3.svg.axis()
742
+ .scale(y)
743
+ .orient("left");
744
+
745
+ data.forEach(function(d) {
746
+ d.datasets = color.domain().map(function(d1) {
747
+ return {name: d1, value: +d["_" + d1]};
748
+ });
749
+ });
750
+
751
+ // 棒グラフのx軸のドメインの設定
752
+ x.domain(dataNames);
753
+
754
+ // 棒グラフのy軸のドメインの最小値、最大値の設定(0からdataのvalの最大値)
755
+ y.domain([#{minValue}, #{maxValue}]);
756
+
757
+ // 個々のsvg領域の描画
758
+ #{svgStr}
759
+ #{graphTitle}
760
+
761
+ // 棒グラフのx軸
762
+ svg.append("g")
763
+ .attr("class", "x axis")
764
+ .attr("transform", "translate(0," + inHeight + ")")
765
+ .call(xAxis)
766
+ .selectAll("text")
767
+ .style("text-anchor", "end")
768
+ .style("font-size", "10px")
769
+ .attr("dx", "-.8em")
770
+ .attr("dy", ".15em")
771
+ .attr("transform", function(d) {
772
+ return "rotate(-90) translate(0,-10)"
773
+ });
774
+
775
+ // マイナス対応
776
+ #{xZeroBar}
777
+
778
+ // 棒グラフのy軸
779
+ svg.append("g")
780
+ .attr("class", "y axis")
781
+ .call(yAxis);
782
+
783
+ svg.selectAll("rect")
784
+ .data(function(d) { return d.datasets; })
785
+ .enter().append("rect")
786
+ .attr("width", x.rangeBand())
787
+ .attr("x", function(d) { return x(d.name); })
788
+ .attr("y", function(d) {return y(Math.max(0, d.value)); })
789
+ .attr("height", function(d) { return Math.abs(y(d.value) - y(0)); })
790
+ .style("fill", "steelblue")
791
+ .on("mouseover", function(d) {
792
+ d3.select("#tooltip")
793
+ .style("left", (d3.event.pageX+10) +"px")
794
+ .style("top", (d3.event.pageY-10) +"px")
795
+ .select("#value")
796
+ .text( d.name + " : " + d.value );
797
+ d3.select("#tooltip").classed("hidden",false);
798
+ })
799
+ .on("mouseout", function() {
800
+ d3.select("#tooltip").classed("hidden", true);
801
+ });
802
+
803
+ var tooltip = d3.select("body").append("div")
804
+ .attr("id", "tooltip")
805
+ .attr("class", "hidden")
806
+ .append("p")
807
+ .attr("id", "value")
808
+ .text("0");
809
+
810
+ #{comStr}
811
+ </script>
812
+ </body>
813
+ </html>
814
+
815
+ HEOF
816
+
817
+ # 終了メッセージ
818
+ MCMD::endLog(args.cmdline)