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.
- checksums.yaml +7 -0
- data/bin/m2gv.rb +812 -0
- data/bin/mautocolor.rb +244 -0
- data/bin/mbar.rb +818 -0
- data/bin/mdtree.rb +1017 -0
- data/bin/mgv.rb +809 -0
- data/bin/mnest2tree.rb +159 -0
- data/bin/mpie.rb +733 -0
- data/bin/msankey.rb +644 -0
- data/lib/nysol/viewjs.rb +13490 -0
- metadata +101 -0
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
|
+
|