schemard 0.2.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/.gitignore +11 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.ja.md +93 -0
- data/README.md +56 -0
- data/Rakefile +2 -0
- data/bin/schemard +18 -0
- data/lib/contents/favicon.ico +0 -0
- data/lib/contents/jquery2.min.js +4 -0
- data/lib/contents/normalize.css +427 -0
- data/lib/contents/tableViewer.js +867 -0
- data/lib/locales/messages.yml +37 -0
- data/lib/schemard/controller.rb +160 -0
- data/lib/schemard/metadata.rb +134 -0
- data/lib/schemard/rdoc_parser.rb +66 -0
- data/lib/schemard/relation_generator.rb +54 -0
- data/lib/schemard/schema.rb +79 -0
- data/lib/schemard/schema_parser.rb +92 -0
- data/lib/schemard/utils/localizer.rb +43 -0
- data/lib/schemard/utils/singularizer.rb +56 -0
- data/lib/schemard/utils/struct_assigner.rb +12 -0
- data/lib/schemard/web_server.rb +56 -0
- data/lib/schemard.rb +6 -0
- data/lib/templates/index.html.erb +81 -0
- data/lib/templates/show.html.erb +156 -0
- data/schemard.gemspec +28 -0
- metadata +101 -0
@@ -0,0 +1,867 @@
|
|
1
|
+
|
2
|
+
// テーブル・リレーションを格納するコンテナクラス
|
3
|
+
//
|
4
|
+
class tvFigures {
|
5
|
+
constructor(){
|
6
|
+
this.tables = {};
|
7
|
+
this.relations = {};
|
8
|
+
}
|
9
|
+
addTable(name, tableObject){
|
10
|
+
this.tables[name] = tableObject;
|
11
|
+
}
|
12
|
+
addRelation(name, relationObject){
|
13
|
+
this.relations[name] = relationObject;
|
14
|
+
}
|
15
|
+
getTable(name){
|
16
|
+
return this.tables[name];
|
17
|
+
}
|
18
|
+
static generateRelationName(parentTable, childTable){
|
19
|
+
return "relation-" + parentTable + "-" + childTable;
|
20
|
+
}
|
21
|
+
getRelation(name){
|
22
|
+
return this.relations[name];
|
23
|
+
}
|
24
|
+
getRelationsAs(as, tableName){
|
25
|
+
return Object.values(this.relations).filter((relObj)=>{ return relObj[as].name == tableName });
|
26
|
+
}
|
27
|
+
getRelationsAsChild(childTableName){
|
28
|
+
return this.getRelationsAs("child", childTableName);
|
29
|
+
}
|
30
|
+
getRelationsAsParent(parentTableName){
|
31
|
+
return this.getRelationsAs("parent", parent);
|
32
|
+
}
|
33
|
+
overlayExceptOptions(relation){
|
34
|
+
return { exceptTable: [relation.parent.name, relation.child.name], exceptRelation: [relation.name] };
|
35
|
+
}
|
36
|
+
getOverlayedRelations(){
|
37
|
+
return Object.values(this.relations).filter((relation)=>{
|
38
|
+
return this.isOverlayAboutLines(relation.getFigure().getLines(), this.overlayExceptOptions(relation));
|
39
|
+
})
|
40
|
+
}
|
41
|
+
isNotOverlay(relationFigure, options){
|
42
|
+
let overlay = true;
|
43
|
+
while(overlay){
|
44
|
+
overlay = this.isOverlayAboutLines(relationFigure.getLines(), options);
|
45
|
+
if(!overlay) break;
|
46
|
+
if(!relationFigure.nextOffset()) break;
|
47
|
+
}
|
48
|
+
if(!overlay){
|
49
|
+
relationFigure.fixOffset();
|
50
|
+
}
|
51
|
+
return !overlay;
|
52
|
+
}
|
53
|
+
isOverlayAboutLines(lines, options){
|
54
|
+
const MARGIN = 5; // ギリギリずれている場合に、重なっていると判定するための余白
|
55
|
+
return Object.keys(this.tables)
|
56
|
+
.some((tblName)=>{
|
57
|
+
let { top, bottom, left, right } = this.tables[tblName].position;
|
58
|
+
[ top, bottom, left, right ] = [ top + 1, bottom - 1, left + 1, right - 1 ];
|
59
|
+
|
60
|
+
return lines.some((line)=>{ return line.isOverlay(top, bottom, left, right) })
|
61
|
+
|
62
|
+
}) || Object.keys(this.tables).filter((tblName => !options.exceptTable.includes(tblName)))
|
63
|
+
.some((tblName)=>{
|
64
|
+
let { top, bottom, left, right } = this.tables[tblName].position;
|
65
|
+
[ top, bottom, left, right ] = [ top - MARGIN, bottom + MARGIN, left - MARGIN, right + MARGIN ];
|
66
|
+
|
67
|
+
return lines.some((line)=>{ return line.isOverlay(top, bottom, left, right) })
|
68
|
+
|
69
|
+
}) || Object.keys(this.relations).filter(relName => !options.exceptRelation.includes(relName))
|
70
|
+
.some((relName)=>{
|
71
|
+
let relationLines = this.relations[relName].getFigure().getLines();
|
72
|
+
return lines.some((line)=>{
|
73
|
+
return relationLines.some((relationLine)=>{
|
74
|
+
if(line.isVertical == relationLine.isVertical){
|
75
|
+
// 平行な線
|
76
|
+
return line.fixedPosition == relationLine.fixedPosition
|
77
|
+
&& line.start - MARGIN < relationLine.end && relationLine.start < line.end + MARGIN;
|
78
|
+
}else{
|
79
|
+
// 交差する線
|
80
|
+
return relationLine.start < line.fixedPosition && line.fixedPosition < relationLine.end
|
81
|
+
&& line.start - MARGIN < relationLine.fixedPosition && relationLine.fixedPosition < line.end + MARGIN;
|
82
|
+
}
|
83
|
+
})
|
84
|
+
})
|
85
|
+
})
|
86
|
+
}
|
87
|
+
}
|
88
|
+
|
89
|
+
var figures = new tvFigures();
|
90
|
+
|
91
|
+
// 線 を表すクラス
|
92
|
+
//
|
93
|
+
class tvLine {
|
94
|
+
constructor(direction, fixedPosition){
|
95
|
+
this.isVertical = (direction == "vertical");
|
96
|
+
this.fixedPosition = fixedPosition;
|
97
|
+
}
|
98
|
+
setStartEnd(start, end){
|
99
|
+
this.start = parseFloat(start < end ? start : end);
|
100
|
+
this.end = parseFloat(start < end ? end : start);
|
101
|
+
return this;
|
102
|
+
}
|
103
|
+
getPointOf(startOrEnd){
|
104
|
+
if(this.isVertical){
|
105
|
+
return { left: this.fixedPosition, top: this[startOrEnd] };
|
106
|
+
}else{
|
107
|
+
return { left: this[startOrEnd], top: this.fixedPosition };
|
108
|
+
}
|
109
|
+
}
|
110
|
+
get startPoint(){ return this.getPointOf("start") }
|
111
|
+
get endPoint(){ return this.getPointOf("end") }
|
112
|
+
get centerPoint(){
|
113
|
+
let fixed = this.fixedPosition, center = (this.start + this.end)/2;
|
114
|
+
return this.isVertical ? { left: fixed, top: center } : { left: center, top: fixed };
|
115
|
+
}
|
116
|
+
length(){ return Math.abs(this.start - this.end) }
|
117
|
+
|
118
|
+
pointsOnLine(){
|
119
|
+
if(this.points) return this.points;
|
120
|
+
|
121
|
+
let [fixed, moving] = this.isVertical ? ["left", "top"]: ["top", "left"];
|
122
|
+
|
123
|
+
let center = (this.start + this.end) / 2;
|
124
|
+
this.points = [ { [fixed]: this.fixedPosition, [moving]: center } ];
|
125
|
+
|
126
|
+
// 前後 20px ずつずらしていく。ただし両端 20px は空けておく
|
127
|
+
for(let offset = 20; center - (offset + 20) > this.start; offset += 20){
|
128
|
+
this.points.push({ [fixed]: this.fixedPosition, [moving]: center - offset });
|
129
|
+
this.points.push({ [fixed]: this.fixedPosition, [moving]: center + offset });
|
130
|
+
}
|
131
|
+
return this.points;
|
132
|
+
}
|
133
|
+
isOverlay(top, bottom, left, right){
|
134
|
+
let [fixedMin, fixedMax] = this.isVertical ? [left, right] : [top, bottom];
|
135
|
+
let [startEndMin, startEndMax] = this.isVertical ? [top, bottom] : [left, right];
|
136
|
+
if(this.fixedPosition < fixedMin || fixedMax < this.fixedPosition) return false;
|
137
|
+
if(this.end < startEndMin || startEndMax < this.start) return false;
|
138
|
+
return true;
|
139
|
+
}
|
140
|
+
}
|
141
|
+
class tvVerticalLine extends tvLine {
|
142
|
+
constructor(fixedPosition){
|
143
|
+
super("vertical", fixedPosition)
|
144
|
+
}
|
145
|
+
}
|
146
|
+
class tvHorizontalLine extends tvLine {
|
147
|
+
constructor(fixedPosition){
|
148
|
+
super("horizontal", fixedPosition)
|
149
|
+
}
|
150
|
+
}
|
151
|
+
// テーブルの枠線 を表すクラス(sideプロパティで四辺のいずれか(top,right,bottom,left)を識別する)
|
152
|
+
//
|
153
|
+
class tvTableLine extends tvLine {
|
154
|
+
constructor(tableName, side, fixedPosition){
|
155
|
+
super((side=="top"||side=="bottom")? "horizontal" : "vertical", fixedPosition)
|
156
|
+
this.tableName = tableName;
|
157
|
+
this.side = side;
|
158
|
+
}
|
159
|
+
searchBindPoint(anotherLine){
|
160
|
+
if(!this.points){
|
161
|
+
this.points = this.pointsOnLine();
|
162
|
+
this.boundPoints = new Set();
|
163
|
+
}
|
164
|
+
this.temporaryBound = [];
|
165
|
+
return this.nextBindPoint(anotherLine);
|
166
|
+
}
|
167
|
+
nextBindPoint(anotherLine){
|
168
|
+
let topOrLeft = this.isVertical ? "top" : "left";
|
169
|
+
let centerPosition = this.centerPoint[topOrLeft];
|
170
|
+
// anotherLine が指定されている場合は、検索方向を固定する(中央から端まで)
|
171
|
+
let nextDirection;
|
172
|
+
if(anotherLine){
|
173
|
+
nextDirection = { centerToEnd: centerPosition < anotherLine.centerPoint[topOrLeft] };
|
174
|
+
nextDirection.centerToStart = !nextDirection.centerToEnd;
|
175
|
+
}
|
176
|
+
for(let i=0; i < this.points.length; i++){
|
177
|
+
// 検索方向が指定されている場合
|
178
|
+
if(nextDirection){
|
179
|
+
if(nextDirection.centerToEnd && centerPosition > this.points[i][topOrLeft]) continue;
|
180
|
+
if(nextDirection.centerToStart && centerPosition < this.points[i][topOrLeft]) continue;
|
181
|
+
}
|
182
|
+
if(this.boundPoints.has(i)) continue;
|
183
|
+
if(this.temporaryBound.includes(i)) continue;
|
184
|
+
|
185
|
+
this.temporaryBound.push(i);
|
186
|
+
return this.points[i];
|
187
|
+
}
|
188
|
+
// anotherLineの指定なしで呼び出す
|
189
|
+
if(anotherLine){
|
190
|
+
return this.nextBindPoint();
|
191
|
+
}
|
192
|
+
// それでも見つからない場合は undefined
|
193
|
+
}
|
194
|
+
// temporaryBound を確定する
|
195
|
+
fixBindPoint(){
|
196
|
+
if(this.temporaryBound.length > 0){
|
197
|
+
this.boundPoints.add(this.temporaryBound[this.temporaryBound.length - 1]);
|
198
|
+
}
|
199
|
+
}
|
200
|
+
// boundPoints を解除する
|
201
|
+
unbind(point){
|
202
|
+
for(let i of this.boundPoints){
|
203
|
+
if(this.points[i].left == point.left && this.points[i].top == point.top){
|
204
|
+
this.boundPoints.delete(i);
|
205
|
+
}
|
206
|
+
}
|
207
|
+
}
|
208
|
+
}
|
209
|
+
|
210
|
+
// テーブル を表すクラス
|
211
|
+
//
|
212
|
+
class tvTable {
|
213
|
+
constructor(name, left, top, width, height){
|
214
|
+
this.name = name;
|
215
|
+
this.width = parseFloat(width);
|
216
|
+
this.height = parseFloat(height);
|
217
|
+
this.moveTo(parseFloat(left), parseFloat(top));
|
218
|
+
}
|
219
|
+
moveTo(left, top){
|
220
|
+
this.left = parseFloat(left);
|
221
|
+
this.top = parseFloat(top);
|
222
|
+
this.center = { left: this.left + this.width/2, top: this.top + this.height/2 };
|
223
|
+
this.position = {
|
224
|
+
left: this.left, right: this.left + this.width,
|
225
|
+
top: this.top, bottom: this.top + this.height,
|
226
|
+
};
|
227
|
+
this.sideLines = {}; // clear
|
228
|
+
}
|
229
|
+
getLineOf(side){
|
230
|
+
if(this.sideLines[side]) return this.sideLines[side];
|
231
|
+
|
232
|
+
this.sideLines[side] = new tvTableLine(this.name, side, this.position[side])
|
233
|
+
if(side=="top" || side=="bottom"){
|
234
|
+
this.sideLines[side].setStartEnd(this.position.left, this.position.right);
|
235
|
+
}else{
|
236
|
+
this.sideLines[side].setStartEnd(this.position.top, this.position.bottom);
|
237
|
+
}
|
238
|
+
return this.sideLines[side];
|
239
|
+
}
|
240
|
+
// "top", "bottom", "left", "right" のいずれかを返す
|
241
|
+
getSideOf(line){
|
242
|
+
if(line.isVertical){
|
243
|
+
return line.fixedPosition == this.position.left ? "left" : "right"
|
244
|
+
}else{
|
245
|
+
return line.fixedPosition == this.position.top ? "top" : "bottom"
|
246
|
+
}
|
247
|
+
}
|
248
|
+
getVerticalLines(){
|
249
|
+
return [ this.getLineOf("left"), this.getLineOf("right") ];
|
250
|
+
}
|
251
|
+
getHorizontalLines(){
|
252
|
+
return [ this.getLineOf("top"), this.getLineOf("bottom") ];
|
253
|
+
}
|
254
|
+
// 指定されたテーブル側の辺(1つか2つ)を返す
|
255
|
+
nearlySideLines(otherTable){
|
256
|
+
let otherPosition = otherTable.position;
|
257
|
+
let lines = [];
|
258
|
+
if(this.position.top > otherPosition.bottom) lines.push(this.getLineOf("top"));
|
259
|
+
if(this.position.bottom < otherPosition.top) lines.push(this.getLineOf("bottom"));
|
260
|
+
if(this.position.left > otherPosition.right) lines.push(this.getLineOf("left"));
|
261
|
+
if(this.position.right < otherPosition.left) lines.push(this.getLineOf("right"));
|
262
|
+
return lines;
|
263
|
+
}
|
264
|
+
// 指定されたテーブルの中央の距離のうち、近い方を返す。
|
265
|
+
// ただし、2つのテーブルの垂直位置が少しでも重なる場合は、水平距離は返さない(垂直距離を返す)
|
266
|
+
// また、2つのテーブルの水平位置が少しでも重なる場合は、垂直距離は返さない(水平距離を返す)
|
267
|
+
nearlyCenterPositionDistance(otherTable){
|
268
|
+
let distances = {
|
269
|
+
left: Math.abs(this.center.left - otherTable.center.left),
|
270
|
+
top: Math.abs(this.center.top - otherTable.center.top),
|
271
|
+
};
|
272
|
+
if(this.position.top < otherTable.position.bottom && this.position.bottom > otherTable.position.top)
|
273
|
+
return distances.top;
|
274
|
+
if(this.position.left < otherTable.position.right && this.position.right > otherTable.position.left)
|
275
|
+
return distances.left;
|
276
|
+
else
|
277
|
+
return Object.values(distances).sort((a, b)=> a - b)[0];
|
278
|
+
}
|
279
|
+
static create(name, options){
|
280
|
+
let table = new tvTable(name, options.left, options.top, options.width, options.height);
|
281
|
+
figures.addTable(name, table);
|
282
|
+
return table;
|
283
|
+
}
|
284
|
+
}
|
285
|
+
|
286
|
+
// リレーションを表すクラス
|
287
|
+
//
|
288
|
+
class tvRelation {
|
289
|
+
constructor(name, parentTbl, childTbl, childCardinality){
|
290
|
+
this.name = name;
|
291
|
+
this.parent = parentTbl;
|
292
|
+
this.child = childTbl;
|
293
|
+
this.childCardinality = childCardinality;
|
294
|
+
}
|
295
|
+
unbind(){
|
296
|
+
this.parentSideLine && this.parentSideLine.unbind(this.parentPoint);
|
297
|
+
this.childSideLine && this.childSideLine.unbind(this.childPoint);
|
298
|
+
delete this.relationFigure;
|
299
|
+
}
|
300
|
+
calcLinePosition(){
|
301
|
+
this.unbind();
|
302
|
+
// 選択された辺により折れ線の種類・始点/終点を決定
|
303
|
+
let parentLines = this.parent.nearlySideLines(this.child);
|
304
|
+
let childLines = this.child.nearlySideLines(this.parent);
|
305
|
+
|
306
|
+
let TOO_NEAR_LIMIT = 30;
|
307
|
+
|
308
|
+
if(parentLines.length == 2){
|
309
|
+
|
310
|
+
// 中央位置より優先する辺(方向:垂直/水平)を選択
|
311
|
+
let isPriorVertical = false;
|
312
|
+
if(Math.abs(this.parent.center.left - this.child.center.left)
|
313
|
+
> Math.abs(this.parent.center.top - this.child.center.top)){
|
314
|
+
isPriorVertical = true;
|
315
|
+
}
|
316
|
+
let paLine = parentLines.find(v => v.isVertical === isPriorVertical);
|
317
|
+
let chLine = childLines.find(v => v.isVertical === isPriorVertical);
|
318
|
+
|
319
|
+
/// 平行線が近すぎず、かつ点が見つかれば処理終了
|
320
|
+
if(Math.abs(paLine.fixedPosition - chLine.fixedPosition) > TOO_NEAR_LIMIT){
|
321
|
+
if(this.searchPoint(paLine, chLine)) return true;
|
322
|
+
}
|
323
|
+
// parentLines のもう片方を選択
|
324
|
+
let altPaLine = parentLines.find(v => v.isVertical !== isPriorVertical);
|
325
|
+
let altChLine = childLines.find(v => v.isVertical !== isPriorVertical);
|
326
|
+
/// 平行線が近すぎず、かつ点が見つかれば処理終了
|
327
|
+
if(altChLine && Math.abs(altPaLine.fixedPosition - altChLine.fixedPosition) > TOO_NEAR_LIMIT){
|
328
|
+
if(this.searchPoint(altPaLine, altChLine)) return true;
|
329
|
+
}
|
330
|
+
// それでも点が見つからない場合、交差する組み合わせを試す
|
331
|
+
if(this.searchPoint(paLine, altChLine)) return true;
|
332
|
+
if(this.searchPoint(altPaLine, chLine)) return true;
|
333
|
+
|
334
|
+
}else if(parentLines.length == 1){
|
335
|
+
/// 一組しか線が選択されなかった場合、平行線が近すぎず、かつ点が見つかれば処理終了
|
336
|
+
if(Math.abs(parentLines[0].fixedPosition - childLines[0].fixedPosition) > TOO_NEAR_LIMIT){
|
337
|
+
if(this.searchPoint(parentLines[0], childLines[0])) return true;
|
338
|
+
}
|
339
|
+
|
340
|
+
}else{
|
341
|
+
// テーブル同士が重なっている場合, とりあえずtop同士でつなぐ
|
342
|
+
return this.searchPoint(this.parent.getLineOf("top"), this.child.getLineOf("top"), true);
|
343
|
+
}
|
344
|
+
|
345
|
+
// 選択すべき点がない場合、平行でない辺を選択
|
346
|
+
let paLine = parentLines[0];
|
347
|
+
let chLine = childLines[0];
|
348
|
+
let otherDirection = paLine.isVertical ? "Horizontal": "Vertical";
|
349
|
+
|
350
|
+
// まず長さが短い方の辺を, 平行でない辺に変更
|
351
|
+
if(paLine.length() < chLine.length()){
|
352
|
+
paLine = this.selectOtherSideLineFor("parent", otherDirection);
|
353
|
+
}else{
|
354
|
+
chLine = this.selectOtherSideLineFor("child", otherDirection);
|
355
|
+
}
|
356
|
+
// 点が見つかれば処理終了
|
357
|
+
if(paLine && chLine && this.searchPoint(paLine, chLine)) return true;
|
358
|
+
|
359
|
+
// それでも見つからなければ長さが長い方の辺を変更(長い方の辺は取得できない場合がある)
|
360
|
+
if(paLine.length() >= chLine.length()){
|
361
|
+
paLine = this.selectOtherSideLineFor("parent", otherDirection);
|
362
|
+
}else{
|
363
|
+
chLine = this.selectOtherSideLineFor("child", otherDirection);
|
364
|
+
}
|
365
|
+
// 点が見つかれば処理終了
|
366
|
+
if(paLine && chLine && this.searchPoint(paLine, chLine)) return true;
|
367
|
+
|
368
|
+
// それでも見つからない場合は 0 番目のLineで決定とする
|
369
|
+
return this.searchPoint(parentLines[0], childLines[0], true);
|
370
|
+
}
|
371
|
+
// targetTable の direction (=Vertical or Horizontal) の辺を取得する
|
372
|
+
selectOtherSideLineFor(targetTable, direction){
|
373
|
+
let nonTargetTable = targetTable == "parent" ? "child" : "parent";
|
374
|
+
|
375
|
+
let targetLines = this[targetTable][`get${direction}Lines`]();
|
376
|
+
let nonTargetLines = this[nonTargetTable][`get${direction}Lines`]();
|
377
|
+
|
378
|
+
if(targetLines[0].fixedPosition < nonTargetLines[1].fixedPosition
|
379
|
+
&& targetLines[1].fixedPosition > nonTargetLines[0].fixedPosition){
|
380
|
+
// 重なっている場合, nonTargetLines[0] と nonTargetLines[1] の間にある辺を返す
|
381
|
+
return targetLines.find((line)=>{
|
382
|
+
return nonTargetLines[0].fixedPosition <= line.fixedPosition
|
383
|
+
&& line.fixedPosition <= nonTargetLines[1].fixedPosition;
|
384
|
+
});
|
385
|
+
}else{
|
386
|
+
// 重なってない場合, nonTargetLine[0] に近い方の辺を返す
|
387
|
+
return targetLines[targetLines[0].fixedPosition > nonTargetLines[0].fixedPosition ? 0 : 1];
|
388
|
+
}
|
389
|
+
}
|
390
|
+
// 線を引き、他の何かを重なってないか判定
|
391
|
+
// 重なっていれば、点を選択しなおす(選択すべき点がなければfalseを返す)
|
392
|
+
searchPoint(paLine, chLine, noSearch = false){
|
393
|
+
// 選択された辺により折れ線の種類・始点/終点を決定
|
394
|
+
this.parentSideLine = paLine;
|
395
|
+
this.childSideLine = chLine;
|
396
|
+
this.parentPoint = paLine.searchBindPoint(chLine);
|
397
|
+
this.childPoint = chLine.searchBindPoint(paLine);
|
398
|
+
|
399
|
+
let found = noSearch;
|
400
|
+
let relationFigure = noSearch && this.getFigure();
|
401
|
+
|
402
|
+
//DEBUG if(noSearch) console.log("noSearch!!!!!!!!!!!! " + this.name);
|
403
|
+
|
404
|
+
while(!found){
|
405
|
+
if(!this.parentPoint || !this.childPoint) break;
|
406
|
+
if(found = figures.isNotOverlay(relationFigure = this.getFigure(), figures.overlayExceptOptions(this))) break;
|
407
|
+
|
408
|
+
this.parentPoint = paLine.nextBindPoint(chLine);
|
409
|
+
|
410
|
+
if(!this.parentPoint) break;
|
411
|
+
if(found = figures.isNotOverlay(relationFigure = this.getFigure(), figures.overlayExceptOptions(this))) break;
|
412
|
+
|
413
|
+
this.childPoint = chLine.nextBindPoint(paLine);
|
414
|
+
}
|
415
|
+
if(found){
|
416
|
+
this.parentSideLine.fixBindPoint();
|
417
|
+
this.childSideLine.fixBindPoint();
|
418
|
+
this.fixFigure(relationFigure.fixOffset());
|
419
|
+
}
|
420
|
+
return found;
|
421
|
+
}
|
422
|
+
fixFigure(figure){
|
423
|
+
this.relationFigure = figure;
|
424
|
+
}
|
425
|
+
getFigure(){
|
426
|
+
if(this.relationFigure) return this.relationFigure;
|
427
|
+
|
428
|
+
// Parent, Child が完全に重なっている場合など、this.parentPoint, this.childPointが undefined になっている。
|
429
|
+
// とりあえず中央点にする
|
430
|
+
this.parentPoint = this.parentPoint || this.parentSideLine.centerPoint;
|
431
|
+
this.childPoint = this.childPoint || this.childSideLine.centerPoint
|
432
|
+
|
433
|
+
if(this.parentSideLine.isVertical == this.childSideLine.isVertical){
|
434
|
+
///// └┐, ┌┘, │ のいずれか
|
435
|
+
let prop = this.parentSideLine.isVertical ? "top" : "left";
|
436
|
+
|
437
|
+
if(this.parentPoint[prop] == this.childPoint[prop]){
|
438
|
+
return new tvRelationFigure1(this.parentPoint, this.childPoint)
|
439
|
+
}else{
|
440
|
+
return new tvRelationFigure3(this.parentPoint, this.childPoint, ! this.parentSideLine.isVertical)
|
441
|
+
}
|
442
|
+
}else{
|
443
|
+
///// └, ┘, ┌, ┐ のいずれか
|
444
|
+
// 上にある方を posA、下にある方を posB とする
|
445
|
+
let [posA, posB] = [this.parentPoint, this.childPoint]
|
446
|
+
if(this.parentPoint.top > this.childPoint.top){
|
447
|
+
[posA, posB] = [posB, posA];
|
448
|
+
}
|
449
|
+
let posASideLine = this.parentPoint.top < this.childPoint.top ? this.parentSideLine : this.childSideLine;
|
450
|
+
if(posASideLine.isVertical){
|
451
|
+
// ┌, ┐ のいずれか
|
452
|
+
return new tvRelationFigure2(posA, posB, { top: posA.top, left: posB.left });
|
453
|
+
}else{
|
454
|
+
// └, ┘ のいずれか
|
455
|
+
return new tvRelationFigure2(posA, posB, { top: posB.top, left: posA.left });
|
456
|
+
}
|
457
|
+
}
|
458
|
+
}
|
459
|
+
get parentEdge(){
|
460
|
+
if(!this.parentSideLine || !this.parentPoint) return;
|
461
|
+
return { point: this.parentPoint, side: this.parent.getSideOf(this.parentSideLine) };
|
462
|
+
}
|
463
|
+
get childEdge(){
|
464
|
+
if(!this.childSideLine || !this.childPoint) return;
|
465
|
+
return { point: this.childPoint, side: this.child.getSideOf(this.childSideLine), cardinality: this.childCardinality };
|
466
|
+
}
|
467
|
+
static create(name, parentTbl, childTbl, childCardinality){
|
468
|
+
let relation = new tvRelation(name, parentTbl, childTbl, childCardinality);
|
469
|
+
figures.addRelation(name, relation);
|
470
|
+
return relation;
|
471
|
+
}
|
472
|
+
}
|
473
|
+
|
474
|
+
// 直線図形のリレーション
|
475
|
+
class tvRelationFigure1 {
|
476
|
+
constructor(posA, posB){
|
477
|
+
if(posA.left == posB.left){
|
478
|
+
this.line = new tvLine("vertical", posA.left).setStartEnd(posA.top, posB.top)
|
479
|
+
}else{
|
480
|
+
this.line = new tvLine("horizontal", posA.top).setStartEnd(posA.left, posB.left)
|
481
|
+
}
|
482
|
+
}
|
483
|
+
getLines(){
|
484
|
+
return [ this.line ];
|
485
|
+
}
|
486
|
+
nextOffset(){ return false }
|
487
|
+
fixOffset(){ return this }
|
488
|
+
displayInfo(){
|
489
|
+
return [ this.line.isVertical ?
|
490
|
+
{ top: this.line.start, left: this.line.fixedPosition, width: 10, height: this.line.length(), borders: ["left"] } :
|
491
|
+
{ top: this.line.fixedPosition, left: this.line.start, width: this.line.length(), height: 10, borders: ["top"] }
|
492
|
+
];
|
493
|
+
}
|
494
|
+
}
|
495
|
+
// 折線(角1つ)図形のリレーション
|
496
|
+
class tvRelationFigure2 {
|
497
|
+
constructor(posA, posB, corner){
|
498
|
+
this.lines = [];
|
499
|
+
if(posA.top == corner.top){
|
500
|
+
this.lines.push(new tvHorizontalLine(posA.top).setStartEnd(posA.left, corner.left))
|
501
|
+
this.lines.push(new tvVerticalLine(posB.left).setStartEnd(posB.top, corner.top))
|
502
|
+
}else{
|
503
|
+
this.lines.push(new tvVerticalLine(posA.left).setStartEnd(posA.top, corner.top))
|
504
|
+
this.lines.push(new tvHorizontalLine(posB.top).setStartEnd(posB.left, corner.left))
|
505
|
+
}
|
506
|
+
}
|
507
|
+
getLines(){
|
508
|
+
return this.lines;
|
509
|
+
}
|
510
|
+
nextOffset(){ return false }
|
511
|
+
fixOffset(){ return this }
|
512
|
+
displayInfo(){
|
513
|
+
let [vline, hline] = this.lines[0].isVertical ? this.lines : this.lines.concat().reverse();
|
514
|
+
let borders = [];
|
515
|
+
borders.push(vline.fixedPosition == hline.start ? "left" : "right")
|
516
|
+
borders.push(hline.fixedPosition == vline.start ? "top" : "bottom")
|
517
|
+
return [{
|
518
|
+
top: vline.start, left: hline.start, width: hline.length(), height: vline.length(), borders: borders
|
519
|
+
}];
|
520
|
+
}
|
521
|
+
}
|
522
|
+
// 折線(角2つ)図形のリレーション
|
523
|
+
class tvRelationFigure3 {
|
524
|
+
constructor(posA, posB, isVertical){
|
525
|
+
this.isVertical = isVertical;
|
526
|
+
this.offset = 0;
|
527
|
+
if(isVertical){
|
528
|
+
// 上にある方を start とする
|
529
|
+
[this.start, this.end] = posA.top < posB.top ? [posA, posB] : [posB, posA];
|
530
|
+
}else{
|
531
|
+
// 左にある方を start とする
|
532
|
+
[this.start, this.end] = posA.left < posB.left ? [posA, posB] : [posB, posA];
|
533
|
+
}
|
534
|
+
}
|
535
|
+
get center(){
|
536
|
+
let prop = this.isVertical ? "top" : "left";
|
537
|
+
return parseInt((this.start[prop] + this.end[prop]) / 2) + this.offset;
|
538
|
+
}
|
539
|
+
getLines(){
|
540
|
+
if(this.lines) return this.lines;
|
541
|
+
let lines = [];
|
542
|
+
if(this.isVertical){
|
543
|
+
lines.push(new tvVerticalLine(this.start.left).setStartEnd(this.start.top, this.center));
|
544
|
+
lines.push(new tvHorizontalLine(this.center).setStartEnd(this.start.left, this.end.left));
|
545
|
+
lines.push(new tvVerticalLine(this.end.left).setStartEnd(this.center, this.end.top));
|
546
|
+
}else{
|
547
|
+
lines.push(new tvHorizontalLine(this.start.top).setStartEnd(this.start.left, this.center));
|
548
|
+
lines.push(new tvVerticalLine(this.center).setStartEnd(this.start.top, this.end.top));
|
549
|
+
lines.push(new tvHorizontalLine(this.end.top).setStartEnd(this.center, this.end.left));
|
550
|
+
}
|
551
|
+
return lines;
|
552
|
+
}
|
553
|
+
// 折れ線の位置を 0, 10, -10, 20, -20,, とずらしていく
|
554
|
+
// 終端まで到達した場合は、false を返す
|
555
|
+
nextOffset(){
|
556
|
+
let newOffset = this.offset <= 0 ? Math.abs(this.offset) + 10 : this.offset * -1;
|
557
|
+
|
558
|
+
let prop = this.isVertical ? "top" : "left";
|
559
|
+
let centerLinePosition = parseInt((this.start[prop] + this.end[prop]) / 2) + newOffset;
|
560
|
+
|
561
|
+
if(this.start[prop] < centerLinePosition - 15 && centerLinePosition + 15 < this.end[prop]){
|
562
|
+
this.offset = newOffset;
|
563
|
+
return true;
|
564
|
+
}
|
565
|
+
return false;
|
566
|
+
}
|
567
|
+
fixOffset(){
|
568
|
+
this.lines = this.getLines();
|
569
|
+
return this;
|
570
|
+
}
|
571
|
+
displayInfo(){
|
572
|
+
function toggle(dir){ return { left: "right", right: "left", top: "bottom", bottom: "top" }[dir] }
|
573
|
+
if(this.isVertical){
|
574
|
+
// └┐, ┌┘ のどちらか
|
575
|
+
let [vline, hline, vline2] = this.lines;
|
576
|
+
let borders = [ vline.fixedPosition == hline.start ? "left" : "right", "bottom" ];
|
577
|
+
let base = { left: hline.start, width: hline.length() };
|
578
|
+
return [
|
579
|
+
Object.assign(Object.create(base), { top: vline.start, height: vline.length(), borders: borders }),
|
580
|
+
Object.assign(Object.create(base), { top: vline2.start, height: vline2.length(), borders: [ toggle(borders[0]) ] })
|
581
|
+
];
|
582
|
+
}else{
|
583
|
+
// ┌ ┐
|
584
|
+
// ┘, └ のいずれか
|
585
|
+
let [hline, vline, hline2] = this.lines;
|
586
|
+
let borders = [ hline.fixedPosition == vline.start ? "top" : "bottom", "right" ];
|
587
|
+
let base = { top: vline.start, height: vline.length() };
|
588
|
+
return [
|
589
|
+
Object.assign(Object.create(base), { left: hline.start, width: hline.length(), borders: borders }),
|
590
|
+
Object.assign(Object.create(base), { left: hline2.start, width: hline2.length(), borders: [ toggle(borders[0]) ] })
|
591
|
+
];
|
592
|
+
}
|
593
|
+
}
|
594
|
+
}
|
595
|
+
var Custom = {
|
596
|
+
|
597
|
+
apply: function(){
|
598
|
+
$('.table').bind('moved', Custom.saveTablePosition);
|
599
|
+
$('.table .title').on('click.table_show', Custom.moveToShowPage);
|
600
|
+
$('.editable input').bind('click', Custom.setEditMode);
|
601
|
+
},
|
602
|
+
|
603
|
+
initLayout: function(){
|
604
|
+
$(".table").each(function(){
|
605
|
+
|
606
|
+
if($(this).hasClass("default-position")){
|
607
|
+
$(this).attr("data-pos-left", $(this).offset().left);
|
608
|
+
$(this).attr("data-pos-top", $(this).offset().top);
|
609
|
+
}
|
610
|
+
|
611
|
+
$(this).css({
|
612
|
+
"width": $(this).width() + 20,
|
613
|
+
"left": $(this).attr("data-pos-left") + "px",
|
614
|
+
"top": $(this).attr("data-pos-top") + "px"
|
615
|
+
});
|
616
|
+
|
617
|
+
tvTable.create($(this).attr("data-table-name"), {
|
618
|
+
left: $(this).attr("data-pos-left"), top: $(this).attr("data-pos-top"),
|
619
|
+
width: parseInt($(this).outerWidth(true)), height: parseInt($(this).outerHeight(true))
|
620
|
+
});
|
621
|
+
});
|
622
|
+
$(".table")
|
623
|
+
.filter(function(){ return $(this).hasClass("default-position") })
|
624
|
+
.each(function(){ return $(this).removeClass("default-position") })
|
625
|
+
|
626
|
+
$(".table")
|
627
|
+
.filter(function(){ return $(this).attr("data-relation-to") })
|
628
|
+
.sort((tblA, tblB)=>{
|
629
|
+
return $(tblB).attr("data-relation-to").split(",").length
|
630
|
+
- $(tblA).attr("data-relation-to").split(",").length
|
631
|
+
})
|
632
|
+
.each(function(){ Custom.private.refreshRelationLines($(this)) });
|
633
|
+
|
634
|
+
// 描画順によって重なってしまう場合があるため、重複している線について再描画を試みる
|
635
|
+
figures.getOverlayedRelations().forEach((relation)=>{
|
636
|
+
Custom.private.drawRelationLines(relation.name, relation);
|
637
|
+
});
|
638
|
+
|
639
|
+
$("a[data-link-url]").each(function(){
|
640
|
+
if($(this).attr("href")) return;
|
641
|
+
$(this).attr("href", Custom.URLSuffix($(this).attr("data-link-url")));
|
642
|
+
});
|
643
|
+
},
|
644
|
+
|
645
|
+
saveTablePosition: function(e){
|
646
|
+
var position = $(this).offset().left+","+$(this).offset().top;
|
647
|
+
$.ajax({
|
648
|
+
type: 'POST',
|
649
|
+
url: "/tables/" + $(this).attr("data-table-name"),
|
650
|
+
data: { layout: position }
|
651
|
+
})
|
652
|
+
.done(function( data, textStatus, jqXHR ) {
|
653
|
+
// message.
|
654
|
+
})
|
655
|
+
},
|
656
|
+
|
657
|
+
moveToShowPage: function(e){
|
658
|
+
window.location.href =
|
659
|
+
Custom.URLSuffix("tables/" + $(this).parent().attr("data-table-name"));
|
660
|
+
},
|
661
|
+
|
662
|
+
URLSuffix: function(url){
|
663
|
+
var suffix = /.+\.html/.test(window.location.href)? ".html": "";
|
664
|
+
return url == "/" && suffix == ".html" ? "./..": url + suffix;
|
665
|
+
},
|
666
|
+
|
667
|
+
setEditMode: function(){
|
668
|
+
if($(this).is(':checked')){
|
669
|
+
Custom.dragable(".table");
|
670
|
+
$(".table").addClass("editing");
|
671
|
+
$('.table .title').off('click.table_show');
|
672
|
+
}else{
|
673
|
+
Custom.unDragable(".table");
|
674
|
+
$(".table").removeClass("editing");
|
675
|
+
$('.table .title').on('click.table_show', Custom.moveToShowPage);
|
676
|
+
}
|
677
|
+
},
|
678
|
+
|
679
|
+
unDragable: function(cssSelector){
|
680
|
+
$(document).off('mousedown.draggable', cssSelector);
|
681
|
+
$(document).off('mouseup.draggable');
|
682
|
+
$(document).off('mousemove.draggable');
|
683
|
+
},
|
684
|
+
|
685
|
+
dragable: function(cssSelector){
|
686
|
+
$(document).on('mousedown.draggable', cssSelector, function(e){
|
687
|
+
$(document).data("drag-target", this);
|
688
|
+
$(this).data("dragStartX", e.pageX);
|
689
|
+
$(this).data("dragStartY", e.pageY);
|
690
|
+
$(this).data("startLeft", $(this).offset().left);
|
691
|
+
$(this).data("startTop", $(this).offset().top);
|
692
|
+
$(this).addClass("dragging");
|
693
|
+
});
|
694
|
+
|
695
|
+
$(document).on('mouseup.draggable', function(e){
|
696
|
+
var target = $(document).data("drag-target");
|
697
|
+
if(target){
|
698
|
+
Custom.private.moveTo($($(target).data("drag-target")), e);
|
699
|
+
}
|
700
|
+
$(document).data("drag-target", false);
|
701
|
+
$(target).removeClass("dragging");
|
702
|
+
$(target).trigger("moved");
|
703
|
+
});
|
704
|
+
$(document).on('mousemove.draggable', function(e){
|
705
|
+
if($(document).data("drag-target")){
|
706
|
+
Custom.private.moveTo($($(document).data("drag-target")), e);
|
707
|
+
}
|
708
|
+
});
|
709
|
+
},
|
710
|
+
|
711
|
+
private: {
|
712
|
+
|
713
|
+
moveTo: function($obj, e){
|
714
|
+
var startX = $obj.data("dragStartX");
|
715
|
+
var startY = $obj.data("dragStartY");
|
716
|
+
if(!startX || !startY)
|
717
|
+
return;
|
718
|
+
|
719
|
+
$obj.css({
|
720
|
+
"left": $obj.data("startLeft") + (e.pageX - startX),
|
721
|
+
"top": $obj.data("startTop") + (e.pageY - startY)
|
722
|
+
});
|
723
|
+
figures.getTable($obj.attr("data-table-name")).moveTo(
|
724
|
+
$obj.data("startLeft") + (e.pageX - startX), $obj.data("startTop") + (e.pageY - startY)
|
725
|
+
)
|
726
|
+
// 親テーブルとしての関連を再描画
|
727
|
+
Custom.private.refreshRelationLines($obj);
|
728
|
+
// 子テーブルとしての関連を再描画
|
729
|
+
figures.getRelationsAsChild($obj.attr("data-table-name")).forEach((relObj)=>{
|
730
|
+
Custom.private.refreshRelationLines($(`.table[data-table-name=${relObj.parent.name}]`));
|
731
|
+
})
|
732
|
+
// 親テーブルとして関連しているテーブルを再描画
|
733
|
+
figures.getRelationsAsParent($obj.attr("data-table-name")).forEach((relObj)=>{
|
734
|
+
Custom.private.refreshRelationLines($(`.table[data-table-name=${relObj.child.name}]`));
|
735
|
+
})
|
736
|
+
},
|
737
|
+
|
738
|
+
refreshRelationLines: function($obj){
|
739
|
+
let tableObject = figures.getTable($obj.attr("data-table-name"));
|
740
|
+
// 関連テーブルを取得
|
741
|
+
let relationTableObjects = $obj.attr("data-relation-to").split(",")
|
742
|
+
.filter(v => v).map(rel => figures.getTable(rel));
|
743
|
+
|
744
|
+
let relationCardinalities = $obj.attr("data-relation-cardinality").split(",")
|
745
|
+
.reduce((sum, cardinality)=>{
|
746
|
+
sum[cardinality.split(":")[0]] = cardinality.split(":")[1];
|
747
|
+
return sum;
|
748
|
+
}, {});
|
749
|
+
|
750
|
+
// 関連テーブルを中心の絶対座長(X or Y)の差異が小さい順でソート
|
751
|
+
relationTableObjects.sort((relTblA, relTblB)=>{
|
752
|
+
return relTblA.nearlyCenterPositionDistance(tableObject)
|
753
|
+
- relTblB.nearlyCenterPositionDistance(tableObject)
|
754
|
+
})
|
755
|
+
.forEach((relTable, index)=>{
|
756
|
+
var relName = tvFigures.generateRelationName(tableObject.name, relTable.name);
|
757
|
+
if($(`#${relName}`).length == 0){
|
758
|
+
$("body").append("<div id=" + relName + " class='relation-line' ></div>")
|
759
|
+
}
|
760
|
+
let relation = figures.getRelation(relName) ||
|
761
|
+
tvRelation.create(relName, tableObject, relTable, relationCardinalities[relTable.name]);
|
762
|
+
Custom.private.drawRelationLines(`#${relName}`, relation);
|
763
|
+
})
|
764
|
+
},
|
765
|
+
drawRelationLines: function(relId, relation){
|
766
|
+
relation.calcLinePosition();
|
767
|
+
|
768
|
+
let displayInfo = relation.getFigure().displayInfo();
|
769
|
+
// └┐, ┌┘ のように2つの角をもつ線の場合
|
770
|
+
if(displayInfo.length == 2){
|
771
|
+
if($(relId).children().length == 0){
|
772
|
+
$(relId).append("<div class=\"child-1\"></div><div class=\"child-2\"></div>");
|
773
|
+
}
|
774
|
+
$(relId).css({ top: displayInfo[0].top, left: displayInfo[0].left, border: "none" });
|
775
|
+
$(relId).find(".child-1").css({ top: 0, left: 0, width: displayInfo[0].width, height: displayInfo[0].height });
|
776
|
+
["top", "bottom", "left", "right"].forEach((border)=>{
|
777
|
+
$(relId).find(".child-1").css(`border-${border}`, displayInfo[0].borders.includes(border) ? "solid 1px black" : "none");
|
778
|
+
});
|
779
|
+
$(relId).find(".child-2").css({ width: displayInfo[1].width, height: displayInfo[1].height });
|
780
|
+
$(relId).find(".child-2").css({
|
781
|
+
left: displayInfo[1].left - displayInfo[0].left, top: displayInfo[1].top - displayInfo[0].top
|
782
|
+
});
|
783
|
+
["top", "bottom", "left", "right"].forEach((border)=>{
|
784
|
+
$(relId).find(".child-2").css(`border-${border}`, displayInfo[1].borders.includes(border) ? "solid 1px black" : "none");
|
785
|
+
});
|
786
|
+
|
787
|
+
}else{
|
788
|
+
// ┌, ┘, |, のように1 or 0個の角をもつ線の場合
|
789
|
+
if($(relId).children().length > 0){
|
790
|
+
$(relId).empty();
|
791
|
+
}
|
792
|
+
$(relId).css(displayInfo[0]);
|
793
|
+
["top", "bottom", "left", "right"].forEach((border)=>{
|
794
|
+
$(relId).css(`border-${border}`, displayInfo[0].borders.includes(border) ? "solid 1px black" : "none");
|
795
|
+
})
|
796
|
+
}
|
797
|
+
// 親側の記号を描画
|
798
|
+
Custom.private.drawRelationEdge_1(`${relId}-parent-edge`, relation.parentEdge);
|
799
|
+
// 子側の記号を描画
|
800
|
+
if(relation.childCardinality == "1"){
|
801
|
+
Custom.private.drawRelationEdge_1(`${relId}-child-edge`, relation.childEdge);
|
802
|
+
}else{
|
803
|
+
Custom.private.drawRelationEdge_N(`${relId}-child-edge`, relation.childEdge);
|
804
|
+
}
|
805
|
+
},
|
806
|
+
drawRelationEdge_1: function(edgeId, edge){
|
807
|
+
let { top: offsetTop, left: offsetLeft } = {
|
808
|
+
top: { top: -5, left: -5 }, bottom: { top: 0, left: -5 },
|
809
|
+
left: { top: -5, left: -5 }, right: { top: -5, left: 0 }
|
810
|
+
}[edge.side];
|
811
|
+
|
812
|
+
if($(edgeId).length == 0){
|
813
|
+
$("body").append("<div id='" + edgeId.substring(1) + "' class='relation-edge' ></div>");
|
814
|
+
}
|
815
|
+
$(edgeId).css({ top: edge.point.top + offsetTop, left: edge.point.left + offsetLeft });
|
816
|
+
$(edgeId).css(
|
817
|
+
["left", "right"].includes(edge.side) ? { width: 3, height: 10 } : { width: 10, height: 3 });
|
818
|
+
|
819
|
+
["top", "bottom", "left", "right"].forEach((border)=>{
|
820
|
+
$(edgeId).css(`border-${border}`, edge.side == border ? "solid 1px black" : "none");
|
821
|
+
})
|
822
|
+
},
|
823
|
+
drawRelationEdge_N: function(edgeId, edge){
|
824
|
+
let offsets = {
|
825
|
+
top: [{ left: -6 }, { top: -9, left: 1 }, { left: 7 }],
|
826
|
+
bottom: [{ left: -5, top: -1 }, { top: 7 }, { left: 5, top: -1 }],
|
827
|
+
left: [{ top: -6 }, { left: -8, top: -1 }, { top: 4 }],
|
828
|
+
right: [{ top: -6 }, { left: 8, top: -1 }, { top: 4 }],
|
829
|
+
}[edge.side];
|
830
|
+
|
831
|
+
let points = offsets.map((offset)=>{
|
832
|
+
let point = Object.assign({}, edge.point);
|
833
|
+
if("top" in offset) Object.assign(point, { top: offset.top + edge.point.top });
|
834
|
+
if("left" in offset) Object.assign(point, { left: offset.left + edge.point.left });
|
835
|
+
return point;
|
836
|
+
});
|
837
|
+
|
838
|
+
if($(`${edgeId}-1`).length == 0){
|
839
|
+
$("body").append("<div id='" + edgeId.substring(1) + "-1' class='relation-edge-diagonal' ></div>")
|
840
|
+
$("body").append("<div id='" + edgeId.substring(1) + "-2' class='relation-edge-diagonal' ></div>")
|
841
|
+
}
|
842
|
+
|
843
|
+
let drawDiagonalLine = (rectId, p1, p2)=>{
|
844
|
+
let pos = p1.left < p2.left? p1: p2;
|
845
|
+
let width = Math.abs(p1.left - p2.left);
|
846
|
+
let height = Math.abs(p1.top - p2.top);
|
847
|
+
let isUpToDown = ((p1.top - p2.top) * (p1.left - p2.left) < 0);
|
848
|
+
|
849
|
+
var deg = Math.atan(height / width) / (Math.PI / 180) * (isUpToDown ? -1: 1);
|
850
|
+
|
851
|
+
$(rectId).css({
|
852
|
+
top: pos.top,
|
853
|
+
left: pos.left,
|
854
|
+
width: Math.sqrt((width * width) + (height * height)),
|
855
|
+
height: 1,
|
856
|
+
transform: "rotate(" + deg + "deg)",
|
857
|
+
"transform-origin": "left top"
|
858
|
+
});
|
859
|
+
};
|
860
|
+
drawDiagonalLine(`${edgeId}-1`, points[0], points[1]);
|
861
|
+
drawDiagonalLine(`${edgeId}-2`, points[1], points[2]);
|
862
|
+
},
|
863
|
+
}
|
864
|
+
}
|
865
|
+
|
866
|
+
$(document).ready(Custom.initLayout);
|
867
|
+
$(document).ready(Custom.apply);
|