nysol-view 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/bin/mdtree.rb ADDED
@@ -0,0 +1,1017 @@
1
+ #!/usr/bin/env ruby
2
+ #-*- coding: utf-8 -*-
3
+
4
+ require 'rubygems'
5
+ require 'nysol/mcmd'
6
+ require 'nysol/viewjs'
7
+ require 'rexml/document'
8
+
9
+ $version=1.0
10
+ $revision="###VERSION###"
11
+
12
+ def help
13
+
14
+ STDERR.puts <<EOF
15
+ ------------------------
16
+ mdtree.rb version #{$version}
17
+ ------------------------
18
+ 概要) PMMLで記述された決定木モデルのHTMLによる視覚化
19
+ 書式) mdtree.rb i= o= [alpha=] [--help]
20
+
21
+ i= : PMMLファイル名
22
+ o= : 出力ファイル名(HTMLファイル)
23
+ alpha= : 枝刈り度を指定する (0 以上の実数で、大きくすると枝が多く刈られる)。
24
+ : 指定しなかった場合、mbonsai で交差検証を指定しなければ、
25
+ : 0.01 が指定されたことになり、交差検証を指定していれば、誤分類率最小のモデルが描画される。
26
+ : このパラメータは mbonsai で構築した決定木のみ有効。
27
+ -bar : ノードを棒グラフ表示にする
28
+ --help : ヘルプの表示
29
+
30
+ 備考)
31
+ 本コマンドのチャート描画にはD3(http://d3js.org/)を用いている。
32
+
33
+ 利用例)
34
+ $ mbonsai c=入院歴 n=来店距離 p=購入パターン d=性別 i=dat1.csv O=outdat
35
+ $ mdtree.rb i=outdat/model.pmml o=model.html
36
+ $ mdtree.rb alpha=0.1 i=outdat/model.pmml o=model2.html
37
+
38
+ Copyright(c) NYSOL 2012- All Rights Reserved.
39
+ EOF
40
+ exit
41
+ end
42
+ def ver()
43
+ $revision ="0" if $revision =~ /VERSION/
44
+ STDERR.puts "version #{$version} revision #{$revision}"
45
+ exit
46
+ end
47
+
48
+ help() if ARGV.size <= 0 or ARGV[0]=="--help"
49
+ ver() if ARGV[0]=="--version"
50
+
51
+
52
+ class Pmml
53
+
54
+ @@condmap = {
55
+ "lessOrEqual"=>"<=","greaterThan"=>">","greaterOrEqual"=>">=",
56
+ "equal"=>"==","notEqual"=>"!=","lessThan"=>"<"
57
+ }
58
+
59
+ def condCKsub(xstr)
60
+ notFlg=false
61
+ cond_str =""
62
+ cond=[]
63
+ if xstr.attributes['booleanOperator'] == "isIn" then
64
+ xstr.elements["Array"].each{|astr|
65
+ astr.to_s.split(" ").each{|astrs|
66
+ cond << REXML::Text.unnormalize(astrs).gsub(/^\"/,"").gsub(/\"$/,"")
67
+ }
68
+ }
69
+ elsif xstr.attributes['booleanOperator'] == "isNotIn" then
70
+ cond_str = "else"
71
+ notFlg=true
72
+ end
73
+
74
+ unless notFlg then
75
+ cond_str = "{#{cond.join(',')}}"
76
+ end
77
+ return cond_str
78
+ end
79
+
80
+ def condCK(xstr)
81
+ cond_str =""
82
+ if xstr.elements["SimplePredicate"] then
83
+ con = xstr.elements["SimplePredicate"]
84
+ if @@condmap[con.attributes['operator']] ==nil then
85
+ raise("UNKNOW FORMAT (#{con.attributes['operator']})")
86
+ end
87
+ cond_str = " #{@@condmap[con.attributes['operator']]} #{con.attributes['value']}"
88
+ elsif xstr.elements["SimpleSetPredicate"] then
89
+ cond_str = condCKsub(xstr.elements["SimpleSetPredicate"])
90
+ elsif xstr.elements["CompoundPredicate"] then
91
+ if xstr.elements["CompoundPredicate"].attributes["booleanOperator"] == "surrogate" then
92
+ xstr.elements["CompoundPredicate"].each_element{|x|
93
+ if x.name["SimplePredicate"] then
94
+ if @@condmap[x.attributes['operator']] ==nil then
95
+ raise("UNKNOW FORMAT (#{x.attributes['operator']})")
96
+ end
97
+ cond_str = " #{@@condmap[x.attributes['operator']]} #{x.attributes['value']}"
98
+ return cond_str
99
+ elsif x.name["SimpleSetPredicate"] then
100
+ cond_str = condCKsub(x)
101
+ return cond_str
102
+ end
103
+ }
104
+ else
105
+ raise("UNKNOW FORMAT (#{xstr.elements['CompoundPredicate'].attributes['booleanOperator']})")
106
+ end
107
+ elsif xstr.elements["Extension/SimplePredicate"] then
108
+ con = xstr.elements["Extension/SimplePredicate"]
109
+ val = []
110
+ if con.attributes['operator'] == "notcontain" then
111
+ cond_str = "else"
112
+ elsif con.attributes['operator'] == "contain" then
113
+ con.elements.each("index"){|idx|
114
+ val << idx.attributes['value']
115
+ }
116
+ cond_str = "#{val.join}"
117
+ end
118
+ end
119
+
120
+ return cond_str
121
+ end
122
+
123
+ def getFld(chd)
124
+ fld = ""
125
+ if chd.elements["CompoundPredicate"] then
126
+ chd.elements["CompoundPredicate"].each_element{|x|
127
+ if x.attributes['field'] then
128
+ fld = x.attributes['field']
129
+ break
130
+ end
131
+ }
132
+ elsif chd.elements["SimplePredicate"] then
133
+ fld =chd.elements["SimplePredicate"].attributes['field']
134
+ elsif chd.elements["SimpleSetPredicate"] then
135
+ fld =chd.elements["SimpleSetPredicate"].attributes['field']
136
+ elsif chd.elements["Extension/SimplePredicate"] then
137
+ fld =chd.elements["Extension/SimplePredicate"].attributes['field']
138
+ end
139
+ return fld
140
+ end
141
+
142
+
143
+ def nodeDiv(xstr)
144
+
145
+ score={}
146
+ scal=0.0
147
+ smax=0.0
148
+ c_p = 0.0
149
+ xstr.elements.each("ScoreDistribution"){|sc|
150
+ score[sc.attributes['value']]=sc.attributes['recordCount']
151
+ scal = scal + sc.attributes['recordCount'].to_f
152
+ smax = sc.attributes['recordCount'].to_f if sc.attributes['recordCount'].to_f > smax
153
+ }
154
+ @scoreMin = scal if @scoreMin == nil or @scoreMin > scal
155
+ @scoreMax = scal if @scoreMax == nil or @scoreMax < scal
156
+
157
+ if xstr.elements["Extension"] then
158
+ if xstr.elements["Extension"].attributes['name'] == "complexity penalty" then
159
+ c_p = xstr.elements["Extension"].attributes['value'].to_f
160
+ end
161
+ end
162
+
163
+ #リーフ処理
164
+ if xstr.elements["Node"] == nil || c_p < @alpha then
165
+ #id = xstr.attributes["id"]
166
+ id = @ncount
167
+ @ncount += 1
168
+ @nodes << {"label"=>"#{condCK(xstr)}","id"=>"#{id}","nodeclass"=>"type-leaf","score"=>score,"scal"=>smax}
169
+ return id
170
+ end
171
+
172
+ #ノード処理
173
+ #id = xstr.attributes["id"]
174
+ id = @ncount
175
+ @ncount += 1
176
+
177
+ fld=""
178
+ xstr.elements.each("Node"){|child|
179
+ toId = nodeDiv(child)
180
+ fld = getFld(child)
181
+ @edges << { "source"=>"#{id}","target"=>"#{toId}","id"=>"#{id}-#{toId}"}
182
+ }
183
+ lbl= "<table><tr><td align='center'>#{condCK(xstr)}<br/></td></tr><tr><td><br/></td></tr><tr><td align='center'>#{fld}<br/></td></tr></table>"
184
+ lbl= "#{condCK(xstr)}@#{fld}"
185
+ @nodes << {"label"=>"#{lbl}","id"=>"#{id}","nodeclass"=>"type-node","score"=>score ,"scal"=>smax}
186
+ return id
187
+ end
188
+
189
+ def getSchem(xstr)
190
+ xstr.elements.each("MiningField"){|sc|
191
+ fld =sc.attributes['name']
192
+ if sc.elements["Extension/alphabetIndex"] then
193
+ idx = []
194
+ sc.elements.each("Extension/alphabetIndex"){|aidx|
195
+ idx[aidx.attributes['index'].to_i] =[] unless idx[aidx.attributes['index'].to_i]
196
+ idx[aidx.attributes['index'].to_i] << aidx.attributes['alphabet']
197
+ }
198
+ @pidx[fld] = idx
199
+ end
200
+ }
201
+ end
202
+
203
+ def setAlpha(xstr)
204
+ al_se1 = al_min = al = -1
205
+ xstr.elements.each("Extension"){|sc|
206
+ if sc.attributes['name'] == "1SE alpha" then
207
+ al_se1 = sc.attributes['value'].to_f
208
+ elsif sc.attributes['name'] == "min alpha" then
209
+ al_min = sc.attributes['value'].to_f
210
+ elsif sc.attributes['name'] == "alpha" then
211
+ al = sc.attributes['value'].to_f
212
+ end
213
+ }
214
+ if @alpha == "min" then
215
+ raise "can not use alpha=min or alpha=1se in this model" if al_min == -1
216
+ @alpha = al_min
217
+ elsif @alpha == "se1" then
218
+ raise "can not use alpha=min or alpha=1se in this model" if al_se1 == -1
219
+ @alpha = al_se1
220
+ elsif @alpha == nil then
221
+ if al==-1 then
222
+ @alpha=al_min
223
+ else
224
+ @alpha=al
225
+ end
226
+ else
227
+ @alpha = @alpha.to_f
228
+ end
229
+ end
230
+
231
+
232
+ def initialize(fn,alpha)
233
+ begin
234
+ @alpha = alpha
235
+ xml = REXML::Document.new(open(fn))
236
+ @timestamp = xml.elements["/PMML/Header/Timestamp"].text
237
+ @dd =[]
238
+ xml.elements.each("/PMML/DataDictionary/DataField"){|dd|
239
+ @dd << dd.attributes["name"]
240
+ }
241
+ @nodes =[]
242
+ @edges =[]
243
+ @pidx ={}
244
+ @scoreMin=nil
245
+ @scoreMax=nil
246
+ @ncount=0
247
+ setAlpha(xml.elements["/PMML/TreeModel"])
248
+
249
+ getSchem(xml.elements["/PMML/TreeModel/MiningSchema"])
250
+ nodeDiv(xml.elements["/PMML/TreeModel/Node"])
251
+ rescue=>msg
252
+ p msg.backtrace
253
+ raise "XML parsing error #{msg}"
254
+ end
255
+
256
+ end
257
+ def nodeList(barFLG=false)
258
+ str=[]
259
+ @nodes.each{|val|
260
+ c_str=[]
261
+ scal =0.0
262
+ val["score"].each{|k,v|
263
+ c_str << "{name:\"#{k}\",csvValue:\"#{v}\",smax:\"#{val['scal']}\"}"
264
+ scal += v.to_f
265
+ }
266
+ size = ( scal - @scoreMin ).to_f / ( @scoreMax - @scoreMin ).to_f + 1.0
267
+ str << "\t#{val['id']}:{label:\"#{val['label']}\",id:\"#{val['id']}\",nodeclass:\"#{val['nodeclass']}\",score:[#{c_str.join(',')}],sizerate:\"#{size}\"}"
268
+ }
269
+ return str.join(",\n")
270
+ end
271
+
272
+
273
+ def edgeList
274
+ str=[]
275
+ @edges.each{|val|
276
+ str << "\t{source:\"#{val['source']}\",target:\"#{val['target']}\",id:\"#{val['id']}\"}"
277
+ }
278
+ return str.join(",\n")
279
+ end
280
+ def legendList
281
+ str=[]
282
+ @nodes[0]["score"].each{|k,v|
283
+ str << "\"#{k}\""
284
+ }
285
+ return str.join(",")
286
+ end
287
+ def indexList
288
+ strA=[]
289
+ @pidx.each{|k,v|
290
+ str=[]
291
+ next unless v
292
+ v.each_index{|i|
293
+ next unless v[i]
294
+ str << "\"#{v[i].join(',')}\""
295
+ }
296
+ strA << "{name:\"#{k}\",val:[#{str.join(',')}]}"
297
+ }
298
+ return strA.join(",\n")
299
+
300
+ end
301
+ def data_max
302
+ return @scoreMax
303
+ end
304
+
305
+
306
+ end
307
+
308
+ # パラメータ処理
309
+ args=MCMD::Margs.new(ARGV,"i=,o=,alpha=,-bar","i=,o=")
310
+ file_i = args.file("i=","r")
311
+ file_o = args.file("o=","w")
312
+ alpha = args.str("alpha=")
313
+ barflg = args.bool("-bar")
314
+
315
+ pm = Pmml.new(file_i,alpha)
316
+ nodedata=pm.nodeList(barflg)
317
+ edgedata=pm.edgeList
318
+ legenddata=pm.legendList
319
+ indexdata=pm.indexList
320
+ d_max = pm.data_max
321
+
322
+
323
+
324
+ bar_node= <<BAROUT
325
+ /*barグラフ挿入*/
326
+ nodeEnter
327
+ .append("g")
328
+ .attr("class","bar")
329
+ .selectAll(".arc").data(function(d)
330
+ {
331
+ return d.score
332
+ })
333
+ .enter()
334
+ .append("rect")
335
+ .attr("class", "arc")
336
+ .attr("x", function(d,i) { return -barSizeMaX/2+i*(barSizeMaX/legands.length)+rPading; })
337
+ .attr("y", function(d) { return -(barSizeMaX/d.smax*d.csvValue-barSizeMaX/2); })
338
+ .attr("width", (barSizeMaX/legands.length-rPading*2))
339
+ .attr("height", function(d) { return barSizeMaX/d.smax*d.csvValue; })
340
+ .style("fill", function(d,i) { return dictColor(i); })
341
+ .on("mouseover", function(d) {
342
+ d3.select("#tooltip")
343
+ .style("left", (d3.event.pageX+10) +"px")
344
+ .style("top", (d3.event.pageY-10) +"px")
345
+ .select("#value")
346
+ .text( d.name + " : " + d.csvValue );
347
+ d3.select("#tooltip").classed("hidden",false);
348
+ })
349
+ .on("mouseout", function() {
350
+ d3.select("#tooltip").classed("hidden", true);
351
+ });
352
+ function calSize(d){
353
+ }
354
+ BAROUT
355
+
356
+ pie_node = <<PIEOUT
357
+ /*Pieグラフ挿入*/
358
+ nodeEnter
359
+ .append("g")
360
+ .attr("class","pie")
361
+ .selectAll(".arc").data(function(d)
362
+ {
363
+ return pie(d.score)
364
+ })
365
+ .enter()
366
+ .append("path")
367
+ .attr("class", "arc")
368
+ .attr("d", d3.svg.arc().outerRadius(radius))
369
+ .style("fill", function(d,i) { return dictColor(i); })
370
+ .on("mouseover", function(d) {
371
+ d3.select("#tooltip")
372
+ .style("left", (d3.event.pageX+10) +"px")
373
+ .style("top", (d3.event.pageY-10) +"px")
374
+ .select("#value")
375
+ .text( d.data.name + " : " + d.data.csvValue );
376
+ d3.select("#tooltip").classed("hidden",false);
377
+ })
378
+ .on("mouseout", function() {
379
+ d3.select("#tooltip").classed("hidden", true);
380
+ });
381
+ function calSize(d){
382
+ }
383
+ PIEOUT
384
+
385
+ graph_dips = pie_node
386
+ graph_dips = bar_node if barflg
387
+
388
+
389
+ outTemplate = <<OUT
390
+ <html lang="ja">
391
+ <head>
392
+ <meta charset="utf-8">
393
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
394
+ <style type="text/css">
395
+ p.title { border-bottom: 1px solid gray;}
396
+ g > .type-node > rect { stroke-dasharray: 10,5; stroke-width: 3px; stroke: #333; fill: white; }
397
+ g > .type-leaf > rect { stroke-width: 3px; stroke: #333; fill: white;}
398
+ .edge path { fill: none; stroke: #333; stroke-width: 1.5px;}
399
+ svg >.legend > rect { stroke-width: 1px; stroke: #333; fill: none}
400
+ svg > .pindex > .pindexL > rect { stroke-width: 1px; stroke: #333; fill: none; }
401
+
402
+ #tooltip {
403
+ position: absolute;
404
+ width: 150px;
405
+ height: auto;
406
+ padding: 10px;
407
+ background-color: white;
408
+ -webkit-border-radius: 10px;
409
+ -moz-border-radius: 10px;
410
+ border-radius: 10px;
411
+ -webkit-box-shadow: 4px 4px 10px rgba(0,0,0,0.4);
412
+ -moz-box-shadow: 4px 4px 10px rgba(0,0,0,0.4);
413
+ box-shadow: 4px 4px 10px rgba(0,0,0,0.4);
414
+ pointer-events: none;
415
+ }
416
+
417
+ #tooltip.hidden {
418
+ display: none;
419
+ }
420
+
421
+ #tooltip p {
422
+ margin: 0;
423
+ font-family: sans-serif;
424
+ font-size: 10px;
425
+ line-height: 14px;
426
+ }
427
+ </style>
428
+
429
+ </head>
430
+ <body>
431
+ <div id="attach">
432
+ <svg class="main-svg" id="svg-canvas" height="100000" width="100000"></svg>
433
+ </div>
434
+ <script>
435
+
436
+ #{ViewJs::dgreejsMin()}
437
+
438
+
439
+
440
+ /*--- dagre-d3-simple.js -----*/
441
+ /*
442
+ * Render javascript edges and nodes to the svg. This method should be more
443
+ * convenient when data was serialized as json and node definitions would be duplicated
444
+ * if edges had direct references to nodes. See state-graph-js.html for example.
445
+ */
446
+ function renderJSObjsToD3(nodeData, edgeData, svgSelector) {
447
+ var nodeRefs = [];
448
+ var edgeRefs = [];
449
+ var idCounter = 0;
450
+
451
+ edgeData.forEach(function(e){
452
+ edgeRefs.push({
453
+ source: nodeData[e.source],
454
+ target: nodeData[e.target],
455
+ label: e.label,
456
+ id: e.id !== undefined ? e.id : (idCounter++ + "|" + e.source + "->" + e.target)
457
+ });
458
+ });
459
+ // ↓これ必要?
460
+ for (var nodeKey in nodeData) {
461
+ if (nodeData.hasOwnProperty(nodeKey)) {
462
+ var u = nodeData[nodeKey];
463
+ if (u.id === undefined) {
464
+ u.id = nodeKey;
465
+ }
466
+ nodeRefs.push(u);
467
+ }
468
+ }
469
+
470
+ renderDagreObjsToD3({nodes: nodeRefs, edges: edgeRefs}, svgSelector);
471
+ }
472
+
473
+ /*
474
+ * Render javscript objects to svg, as produced by dagre.dot.
475
+ */
476
+ function renderDagreObjsToD3(graphData, svgSelector)
477
+ {
478
+ /* 描画用設定値 */
479
+ var radius = 10.0; // 円グラフ最小半径
480
+ var rPading = 2.0; // 円隙間サイズ
481
+ var rectSize = radius*4.0+rPading*2.0; // 円格納枠サイズ
482
+ var barSizeMaX = radius*4.0; // barサイズMAX
483
+ var charH= 18.0; // 文字高さ
484
+ var legendSize = 16.0; // 凡例マーカーサイズ
485
+ var tMaxX=0; // テーブル表示サイズX
486
+ var tMaxY=0; // テーブル表示サイズX
487
+ var tPadding=2.0; // テーブル隙間サイズX
488
+
489
+ var pie = d3.layout.pie()
490
+ .sort(null)
491
+ .value(function(d) { return d.csvValue; });
492
+
493
+ /* ノードデータ,エッジデータ,描画場所*/
494
+ var nodeData = graphData.nodes;
495
+ var edgeData = graphData.edges;
496
+ var svg = d3.select(svgSelector);
497
+
498
+
499
+ // エッジ種類定義
500
+ if (svg.select("#arrowhead").empty()) {
501
+ svg.append('svg:defs').append('svg:marker')
502
+ .attr('id', 'arrowhead')
503
+ .attr('viewBox', '0 0 10 10')
504
+ .attr('refX', 8)
505
+ .attr('refY', 5)
506
+ .attr('markerUnits', 'strokewidth')
507
+ .attr('markerWidth', 8)
508
+ .attr('markerHeight', 5)
509
+ .attr('orient', 'auto')
510
+ .attr('style', 'fill: #333')
511
+ .append('svg:path')
512
+ .attr('d', 'M 0 0 L 10 5 L 0 10 z');
513
+ }
514
+
515
+ /* 描画領域初期化 */
516
+ svg.selectAll("g").remove();
517
+ var svgGroup = svg.append("g");
518
+
519
+ /* -------------------------------
520
+ ノード関係
521
+ ------------------------------- */
522
+ /* ノード領域追加 */
523
+ var nodes = svgGroup
524
+ .selectAll("g .node")
525
+ .data(nodeData);
526
+
527
+ /* ノード 属性追加 class & id */
528
+ var nodeEnter = nodes
529
+ .enter()
530
+ .append("g")
531
+ .attr("class", function (d) {
532
+ if (d.nodeclass) { return "node " + d.nodeclass;}
533
+ else { return "node"; }
534
+ })
535
+ .attr("id", function (d) {
536
+ return "node-" + d.id;
537
+ })
538
+
539
+ /* 枠追加 */
540
+ nodeEnter.append("rect").attr("class", "body");;
541
+
542
+ /* ノードHEADラベル追加 */
543
+ nodeEnter
544
+ .append("text")
545
+ .attr("class", "head")
546
+ .attr("text-anchor", "middle")
547
+ .text(function (d) {
548
+ var sp = d.label.split("@") ;
549
+ return sp[0];
550
+ });
551
+
552
+ /* ノードFOOTラベル追加 */
553
+ nodeEnter
554
+ .append("text")
555
+ .attr("class", "foot")
556
+ .attr("text-anchor", "middle")
557
+ .text(function (d) {
558
+ var sp = d.label.split("@")
559
+ if(sp.length < 2){ return "";}
560
+ else {return sp[1];}
561
+ });
562
+
563
+ /* グラフ挿入 */
564
+ #{graph_dips}
565
+
566
+ /* ノードサイズ設定*/
567
+ var nodeSize = []
568
+ nodes.selectAll("g text").each(function (d) {
569
+ if(nodeSize[d.id]==undefined){
570
+ nodeSize[d.id]=[rectSize,rectSize]
571
+ }
572
+ var bbox = this.getBBox();
573
+ nodeSize[d.id][1]+=charH
574
+ if(nodeSize[d.id][0]<bbox.width){nodeSize[d.id][0]=bbox.width}
575
+ })
576
+ nodes
577
+ .each(function (d,i) {
578
+ d.width = nodeSize[i][0];
579
+ d.height = nodeSize[i][1];
580
+ });
581
+
582
+ /* -------------------------------
583
+ エッジ関係
584
+ ------------------------------- */
585
+ var edges = svgGroup
586
+ .selectAll("g .edge")
587
+ .data(edgeData);
588
+
589
+ var edgeEnter = edges
590
+ .enter()
591
+ .append("g")
592
+ .attr("class", "edge")
593
+ .attr("id", function (d) { return "edge-" + d.id; })
594
+
595
+ edgeEnter
596
+ .append("path")
597
+ .attr("marker-end", "url(#arrowhead)");
598
+
599
+ /* -------------------------------
600
+ 凡例関係
601
+ ------------------------------- */
602
+ var legandX=0; // 凡例表示サイズX
603
+ var legandY=0; // 凡例表示サイズY
604
+
605
+ var legend = svg.append("svg")
606
+ .attr("class", "legend")
607
+ .selectAll("g")
608
+ .data(legands)
609
+ .enter().append("g")
610
+ .attr("class","legandL")
611
+
612
+ legend
613
+ .append("rect")
614
+ .attr("width" , legendSize)
615
+ .attr("height", legendSize)
616
+ .style("fill", function(d, i) { return dictColor(i); });
617
+
618
+ legend.append("text")
619
+ .attr("x", 20)
620
+ .attr("y", 9)
621
+ .attr("dy", ".35em")
622
+ .text(function(d) { return d; });
623
+
624
+ svg.select("svg.legend")
625
+ .append("text")
626
+ .attr("x", 0)
627
+ .attr("y", 9)
628
+ .attr("dy", ".35em")
629
+ .text("Legend")
630
+
631
+ svg.select("svg.legend")
632
+ .each(function() {
633
+ dd=this.getBBox();
634
+ tMaxX = dd.width;
635
+ tMaxY = dd.height;
636
+ })
637
+
638
+ svg.select("svg.legend")
639
+ .selectAll("g.legandL")
640
+ .each(function() {
641
+ dd=this.getBBox();
642
+ if(legandX<dd.width){ legandX = dd.width;}
643
+ legandY += dd.height;
644
+ if(tMaxX<dd.width){ tMaxX = dd.width;}
645
+ })
646
+
647
+ svg.select("svg.legend")
648
+ .append("rect")
649
+ .attr("x", 0)
650
+ .attr("y", 18)
651
+ .attr("width" , legandX + tPadding*2)
652
+ .attr("height", legandY + tPadding*(legands.length+1))
653
+
654
+ tMaxY += (charH+tPadding)*legands.length
655
+
656
+ legend
657
+ .attr("transform", function(d,i) {
658
+ return "translate(0," + (charH+(charH+tPadding)*i+tPadding) + ")";
659
+ }
660
+ );
661
+
662
+ /* -------------------------------
663
+ パターンindex関係
664
+ ------------------------------- */
665
+ var indexX_K =[]; // index表示サイズX_key
666
+ var indexX_V =[]; // index表示サイズX_val
667
+ var indexX_R =[]; // index表示サイズReal
668
+ var indexY_S=[]; // index表示開始位置Y
669
+
670
+ var iposY = tMaxY + (charH+tPadding)
671
+
672
+ var pindex = svg.append("svg")
673
+ .attr("class", "pindex")
674
+ .attr("y", iposY)
675
+ .selectAll("g")
676
+ .data(ptnidxs)
677
+ .enter().append("g")
678
+ .attr("class","pindexL")
679
+ .attr("id",(function(d,i) { return "a"+i;}));
680
+
681
+
682
+ pindex
683
+ .append("text")
684
+ .attr("class","name")
685
+ .text(function(d) {
686
+ return d.name;
687
+ });
688
+
689
+ pindex
690
+ .append("text")
691
+ .attr("class","headerK")
692
+ .text(function(d) { return "index";});
693
+
694
+ pindex
695
+ .append("rect")
696
+ .attr("class","headerK")
697
+
698
+ pindex
699
+ .append("text")
700
+ .attr("class","headerV")
701
+ .text(function(d) { return "alphabet";});
702
+
703
+ pindex
704
+ .append("rect")
705
+ .attr("class","headerV")
706
+
707
+
708
+ pindex
709
+ .selectAll("g.pidx")
710
+ .data(function(d){return d.val})
711
+ .enter()
712
+ .append("text")
713
+ .attr("class","no")
714
+ .text(function(d,j) { return j+1 ;});
715
+
716
+ pindex
717
+ .selectAll("g.pidx")
718
+ .data(function(d){return d.val})
719
+ .enter()
720
+ .append("rect")
721
+ .attr("class","no")
722
+
723
+ pindex
724
+ .selectAll("g.pidx")
725
+ .data(function(d){return d.val})
726
+ .enter()
727
+ .append("text")
728
+ .attr("class","idx")
729
+ .text(function(d,j) { return d ;});
730
+
731
+ pindex
732
+ .selectAll("g.pidx")
733
+ .data(function(d){return d.val})
734
+ .enter()
735
+ .append("rect")
736
+ .attr("class","idx")
737
+
738
+
739
+ //幅チェック
740
+ var indexYT = (charH*2+tPadding);
741
+ for(var i=0 ;i<ptnidxs.length;i++){
742
+ var indexX_KT = 0;
743
+ var indexX_VT = 0;
744
+
745
+ svg.select("svg.pindex")
746
+ .selectAll("#a"+i)
747
+ .selectAll("text.name")
748
+ .each(function() {
749
+ var dd=this.getBBox();
750
+ if(tMaxX<dd.width){ tMaxX = dd.width;}
751
+ indexX_R[i]=dd.width
752
+ })
753
+
754
+ svg.select("svg.pindex")
755
+ .selectAll("#a"+i)
756
+ .selectAll("text.headerK")
757
+ .each(function() {
758
+ var dd=this.getBBox();
759
+ if(indexX_KT<dd.width){ indexX_KT = dd.width;}
760
+ })
761
+
762
+
763
+ svg.select("svg.pindex")
764
+ .selectAll("#a"+i)
765
+ .selectAll("text.headerV")
766
+ .each(function() {
767
+ var dd=this.getBBox();
768
+ if(indexX_VT<dd.width){ indexX_VT = dd.width;}
769
+ })
770
+
771
+
772
+ svg.select("svg.pindex")
773
+ .selectAll("#a"+i)
774
+ .selectAll("text.no")
775
+ .each(function() {
776
+ var dd=this.getBBox();
777
+ if(indexX_KT<dd.width){ indexX_KT = dd.width;}
778
+ })
779
+ svg.select("svg.pindex")
780
+ .selectAll("#a"+i)
781
+ .selectAll("text.idx")
782
+ .each(function() {
783
+ var dd=this.getBBox();
784
+ if(indexX_VT<dd.width){ indexX_VT = dd.width;}
785
+ })
786
+ indexX_K.push(indexX_KT)
787
+ indexX_V.push(indexX_VT)
788
+ if(indexX_R[i]<indexX_VT+indexX_KT+tPadding*2){
789
+ indexX_R[i]=indexX_VT+indexX_KT+tPadding*2
790
+ }
791
+ if(tMaxX<indexX_R[i]){
792
+ tMaxX = indexX_R[i]
793
+ }
794
+ indexY_S.push(indexYT)
795
+ indexYT += (ptnidxs[i].val.length+3)*(charH+tPadding);
796
+ console.log(indexYT)
797
+ }
798
+
799
+ if (ptnidxs.length!=0){
800
+ svg.select("svg.pindex")
801
+ .append("text")
802
+ .attr("y", 9)
803
+ .attr("dy", ".35em")
804
+ .text("Alphabet Index")
805
+ }
806
+
807
+ //グループ内配置
808
+ for(var i=0 ;i<ptnidxs.length;i++){
809
+ arrange = svg.select("svg.pindex").selectAll("#a"+i)
810
+ arrange
811
+ .selectAll("text.headerK")
812
+ .attr("x",function(d,j){ return tPadding })
813
+ .attr("y",function(d,j){ return (charH+tPadding) })
814
+
815
+ arrange
816
+ .selectAll("text.headerV")
817
+ .attr("y",function(d,j){ return (charH+tPadding) })
818
+ .attr("x",function(d,j){ return indexX_K[i]+tPadding*3 })
819
+
820
+ arrange
821
+ .selectAll("text.no")
822
+ .attr("x",function(d,j){ return tPadding })
823
+ .attr("y",function(d,j){ return (j+2)*(charH+tPadding) })
824
+ arrange
825
+ .selectAll("text.idx")
826
+ .attr("y",function(d,j){ return (j+2)*(charH+tPadding) })
827
+ .attr("x",function(d,j){ return indexX_K[i]+tPadding*3 })
828
+
829
+ arrange
830
+ .selectAll("rect.headerK")
831
+ .attr("width",function(d,j){ return indexX_K[i]+tPadding*2 })
832
+ .attr("height",function(d,j){ return charH+tPadding })
833
+ .attr("y",function(d,j){ return tPadding })
834
+
835
+ arrange
836
+ .selectAll("rect.headerV")
837
+ .attr("width",function(d,j){ return indexX_V[i]+tPadding*2 })
838
+ .attr("height",function(d,j){ return charH+tPadding })
839
+ .attr("x",function(d,j){ return indexX_K[i]+tPadding*2 })
840
+ .attr("y",function(d,j){ return tPadding })
841
+
842
+ arrange
843
+ .selectAll("rect.no")
844
+ .attr("width",function(d,j){ return indexX_K[i]+tPadding*2 })
845
+ .attr("height",function(d,j){ return charH+tPadding })
846
+ .attr("y",function(d,j){ return (j+1)*(charH+tPadding)+tPadding })
847
+
848
+ arrange
849
+ .selectAll("rect.idx")
850
+ .attr("width",function(d,j){ return indexX_V[i]+tPadding*2 })
851
+ .attr("height",function(d,j){ return charH+tPadding })
852
+ .attr("x",function(d,j){ return indexX_K[i]+tPadding*2 })
853
+ .attr("y",function(d,j){ return (j+1)*(charH+tPadding)+tPadding })
854
+ }
855
+ console.log(indexY_S)
856
+ //全体配置
857
+ svg.select("svg.pindex")
858
+ .selectAll("g.pindexL")
859
+ .attr("transform", function(d,i) {
860
+ var rtnstr = "translate(0," + indexY_S[i] + ")";
861
+ return rtnstr;
862
+ }
863
+ );
864
+
865
+
866
+ // Add zoom behavior to the SVG canvas
867
+ svgGroup.attr("transform", "translate("+tMaxX+", 5)")
868
+
869
+ svg.call(d3.behavior.zoom().on("zoom", function redraw() {
870
+ dx = tMaxX + d3.event.translate[0]
871
+ dy = 5 + d3.event.translate[1]
872
+ svgGroup.attr("transform",
873
+ "translate(" + dx +"," + dy+ ")" + " scale(" + d3.event.scale + ")");
874
+ }));
875
+
876
+ // Run the actual layout
877
+ dagre.layout()
878
+ .nodes(nodeData)
879
+ .edges(edgeData)
880
+ .run();
881
+
882
+ /* -------------------------------
883
+ ノード表示位置移動
884
+ --------------------------------- */
885
+ nodes
886
+ .attr("transform", function (d) {
887
+ return "translate(" + d.dagre.x + "," + d.dagre.y + ")";
888
+ })
889
+ .selectAll("g.node rect.body")
890
+ .attr("rx", 5)
891
+ .attr("ry", 5)
892
+ .attr("x", function (d) { return -(rectSize/2);})
893
+ .attr("y", function (d) { return -(rectSize/2);})
894
+ .attr("width", function (d) { return rectSize;})
895
+ .attr("height", function (d) { return rectSize; });
896
+
897
+ nodes
898
+ .selectAll("text.foot")
899
+ .attr("transform", function (d) {
900
+ return "translate(" + 0 + "," + (d.height/2) + ")";
901
+ });
902
+
903
+ nodes
904
+ .selectAll("text.head")
905
+ .attr("transform", function (d) {
906
+ return "translate(" + 0 + "," + -( radius*2+rPading*2) + ")";
907
+ });
908
+
909
+ nodes.selectAll("g.pie")
910
+ .attr("transform", function (d) { return "scale("+ d.sizerate +")" ;} );
911
+
912
+
913
+ /* -------------------------------
914
+ エッジ表示位置移動
915
+ --------------------------------- */
916
+ edges
917
+ .selectAll("path")
918
+ .attr("d", function (d) {
919
+ var points = d.dagre.points.slice(0);
920
+ points.unshift(dagre.util.intersectRect(d.source.dagre, points[0]));
921
+
922
+ var preTarget = points[points.length - 2];
923
+ var target = dagre.util.intersectRect(d.target.dagre, points[points.length - 1]);
924
+
925
+ // This shortens the line by a couple pixels so the arrowhead won't overshoot the edge of the target
926
+ var deltaX = preTarget.x - target.x;
927
+ var deltaY = preTarget.y - target.y;
928
+ var m = 2 / Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
929
+ points.push({
930
+ x: target.x + m * deltaX,
931
+ y: target.y + m * deltaY
932
+ }
933
+ );
934
+ return d3.svg.line()
935
+ .x(function (e) {
936
+ return e.x;
937
+ })
938
+ .y(function (e) {
939
+ return e.y;
940
+ })
941
+ .interpolate("bundle")
942
+ .tension(.8)
943
+ (points);
944
+ });
945
+
946
+ edges
947
+ .selectAll("g.label")
948
+ .attr("transform", function (d) {
949
+ var points = d.dagre.points;
950
+ if (points.length > 1) {
951
+ var x = (points[0].x + points[1].x) / 2;
952
+ var y = (points[0].y + points[1].y) / 2;
953
+ return "translate(" + (-d.bbox.width / 2 + x) + "," + (-d.bbox.height / 2 + y) + ")";
954
+ } else {
955
+ return "translate(" + (-d.bbox.width / 2 + points[0].x) + "," + (-d.bbox.height / 2 + points[0].y) + ")";
956
+ }
957
+ });
958
+
959
+
960
+ var tooltip = d3.select("body").append("div")
961
+ .attr("id", "tooltip")
962
+ .attr("class", "hidden")
963
+ .append("p")
964
+ .attr("id", "value")
965
+ .text("0");
966
+ }
967
+
968
+ // 色決定関数
969
+ function dictColor(i){
970
+ var colorSet = [ [70,130,180],[144,238,144],[238,232,170],
971
+ [139,0,0],[0,139,0],[0,0,139]]
972
+ var sur = i % colorSet.length;
973
+ var div = (i-sur) / colorSet.length;
974
+ var r =colorSet[sur][0]+div*20;
975
+ var g =colorSet[sur][1]+div*20;
976
+ var b =colorSet[sur][2]+div*20;
977
+ if(r>255){ r=255;}
978
+ if(g>255){ g=255;}
979
+ if(b>255){ b=255;}
980
+ return "rgb(" + r + ","+ g + "," + b + ")"
981
+ }
982
+
983
+
984
+ var nodes=
985
+ {
986
+ #{nodedata}
987
+ };
988
+
989
+ var edges=[
990
+ #{edgedata}
991
+ ];
992
+
993
+ var legands=[
994
+ #{legenddata}
995
+ ];
996
+
997
+ var ptnidxs=[
998
+ #{indexdata}
999
+ ];
1000
+
1001
+
1002
+ renderJSObjsToD3(nodes, edges, ".main-svg");
1003
+ </script>
1004
+ </body>
1005
+ </html>
1006
+ OUT
1007
+
1008
+
1009
+
1010
+ File.open(file_o,"w"){|fp|
1011
+ fp.puts outTemplate
1012
+ }
1013
+
1014
+
1015
+ # 終了メッセージ
1016
+ MCMD::endLog(args.cmdline)
1017
+