nysol-view 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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)