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/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
+