@yanqirenshi/d3.sql 0.1.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.
- package/.babelrc +14 -0
- package/README.md +64 -0
- package/dist/components/D3Sql.js +538 -0
- package/dist/index.js +4 -0
- package/dist/js/SqlModel.js +717 -0
- package/jest.config.cjs +21 -0
- package/package.json +47 -0
- package/src/components/D3Sql.js +356 -0
- package/src/index.js +5 -0
- package/src/js/SqlModel.js +601 -0
- package/tests/SqlModel.test.js +135 -0
- package/tests/d3-dag-stub.js +4 -0
- package/tests/d3-stub.js +19 -0
|
@@ -0,0 +1,717 @@
|
|
|
1
|
+
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
|
|
2
|
+
function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
|
|
3
|
+
function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
|
|
4
|
+
function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
|
|
5
|
+
function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
|
|
6
|
+
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
|
7
|
+
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
|
|
8
|
+
function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
|
|
9
|
+
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
|
|
10
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
11
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
12
|
+
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
|
13
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
|
|
14
|
+
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
15
|
+
function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
|
|
16
|
+
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
|
17
|
+
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
|
|
18
|
+
function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
|
|
19
|
+
function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
|
|
20
|
+
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
|
|
21
|
+
// SqlModel — SQL リネージグラフのデータクラス。
|
|
22
|
+
// 入力({entities, lineageEdges, joinEdges})の正規化・自動レイアウト・
|
|
23
|
+
// 線の幾何計算までを持ち、DOM/React には依存しない(描画は D3Sql が担当)。
|
|
24
|
+
//
|
|
25
|
+
// データモデル(元プロジェクト sql-sitter との会話で決めたルール):
|
|
26
|
+
// - entities: 箱1つ分。kind='table'は物理テーブル、kind='result'はそのSELECT
|
|
27
|
+
// スコープの出力(サブクエリなら内側から Result 1, Result 2, ... と数える。
|
|
28
|
+
// 最終スコープだけ番号を外して太字の「Result」にする)。
|
|
29
|
+
// alias は FROM句の `AS エイリアス` を箱の上に表示するためのキャプション。
|
|
30
|
+
// `whereText`があれば、その箱の直下にWHERE/HAVINGなどの句をそのまま
|
|
31
|
+
// テキストで表示する。`orderByColumns`(列名の配列、ORDER BY内の
|
|
32
|
+
// 並び順)があれば、対応する行の右端に順序番号のboxを付ける。
|
|
33
|
+
// `distinct`があれば右辺に縦帯を付ける(DISTINCTに限らず、UNIONの
|
|
34
|
+
// 合流点など他の意味でも使う)。帯のラベルは既定で「distinct」、
|
|
35
|
+
// `bandLabel`があればそちらを表示する。`columns`を指定しなければ、
|
|
36
|
+
// lineageEdges/joinEdgesで実際に参照されている列から自動導出する。
|
|
37
|
+
// - lineageEdges: 列の値がどこから来たかを示す矢印(常にResult側へ向く)。
|
|
38
|
+
// label は GROUP BYキーなら `G01`/`G02`...(GROUP BY内の並び順)、集約/
|
|
39
|
+
// ウィンドウ関数・式経由ならその内容(例: `MAX`, `LTRIM`, `/1000`)、
|
|
40
|
+
// 単純な値渡しならラベル無し。`from`は単一の{entity,column}のほか、
|
|
41
|
+
// 式が複数列にわたる場合は配列になる(1つのラベルboxから複数本の
|
|
42
|
+
// 斜め線を引く)。リテラル値や引数に列を取らない関数呼び出し(例:
|
|
43
|
+
// `current_date()`)は由来が無いので矢印無し(`from: null`)。
|
|
44
|
+
// - joinEdges: 列同士の一致関係(データの変換ではなく条件)。joinType の
|
|
45
|
+
// 矢印方向は LEFT='b側にだけ矢印', RIGHT='a側にだけ矢印', INNER='矢印
|
|
46
|
+
// 無しの線'。`kind:'in'`は`WHERE col IN (サブクエリ)`(破線の間隔を
|
|
47
|
+
// JOINより細かくして見分ける、label既定は'IN')。
|
|
48
|
+
|
|
49
|
+
import * as d3 from 'd3';
|
|
50
|
+
import { graphStratify, sugiyama, tweakDirection } from 'd3-dag';
|
|
51
|
+
export var ROW_H = 28;
|
|
52
|
+
export var HEADER_H = 34;
|
|
53
|
+
export var BOX_W = 240;
|
|
54
|
+
export var BRANCH_GAP = 11;
|
|
55
|
+
export var LINEAGE_LABEL_GAP = 40;
|
|
56
|
+
// DISTINCTを表す縦長の帯の幅。SELECT DISTINCTのResultの右辺に付け、
|
|
57
|
+
// そのResultへ入る全てのリネージ線をこの帯の右端まで延長する。
|
|
58
|
+
export var DISTINCT_BAND_WIDTH = 24;
|
|
59
|
+
var LAYER_SPACING = 400;
|
|
60
|
+
var SIBLING_GAP = 40;
|
|
61
|
+
export function shuffled(items) {
|
|
62
|
+
var arr = _toConsumableArray(items);
|
|
63
|
+
for (var i = arr.length - 1; i > 0; i--) {
|
|
64
|
+
var j = Math.floor(Math.random() * (i + 1));
|
|
65
|
+
var _ref = [arr[j], arr[i]];
|
|
66
|
+
arr[i] = _ref[0];
|
|
67
|
+
arr[j] = _ref[1];
|
|
68
|
+
}
|
|
69
|
+
return arr;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 実テーブル(kind='table')のヘッダ色。同じ物理テーブル名(title)を使う
|
|
73
|
+
// エンティティは全て同じ色にする。色の割り当てには d3 の scaleOrdinal +
|
|
74
|
+
// schemeTableau10 を使い、どのtitleがどの色になるかはドメインの順序を
|
|
75
|
+
// シャッフルすることでランダムにする。
|
|
76
|
+
export function buildTableColors(entities) {
|
|
77
|
+
var titles = Array.from(new Set(entities.filter(function (e) {
|
|
78
|
+
return e.kind === 'table';
|
|
79
|
+
}).map(function (e) {
|
|
80
|
+
return e.title;
|
|
81
|
+
})));
|
|
82
|
+
var colorScale = d3.scaleOrdinal(d3.schemeTableau10).domain(shuffled(titles));
|
|
83
|
+
var colors = {};
|
|
84
|
+
titles.forEach(function (title) {
|
|
85
|
+
colors[title] = colorScale(title);
|
|
86
|
+
});
|
|
87
|
+
return colors;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// sql-sitterのextract_graph()(実データ)がentityに列一覧を持たない場合は、
|
|
91
|
+
// lineageEdges/joinEdgesで実際に参照されている列を初出順に集めて補う
|
|
92
|
+
// (明示された`columns`があればそれを優先する)。
|
|
93
|
+
export function withDerivedColumns(entities, lineageEdges, joinEdges) {
|
|
94
|
+
var columnsById = {};
|
|
95
|
+
var seenById = {};
|
|
96
|
+
entities.forEach(function (e) {
|
|
97
|
+
columnsById[e.id] = [];
|
|
98
|
+
seenById[e.id] = new Set();
|
|
99
|
+
});
|
|
100
|
+
var addRef = function addRef(ref) {
|
|
101
|
+
if (!ref) return;
|
|
102
|
+
(Array.isArray(ref) ? ref : [ref]).forEach(function (_ref2) {
|
|
103
|
+
var entity = _ref2.entity,
|
|
104
|
+
column = _ref2.column;
|
|
105
|
+
if (!seenById[entity] || seenById[entity].has(column)) return;
|
|
106
|
+
seenById[entity].add(column);
|
|
107
|
+
columnsById[entity].push(column);
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
lineageEdges.forEach(function (edge) {
|
|
111
|
+
addRef(edge.from);
|
|
112
|
+
addRef(edge.to);
|
|
113
|
+
});
|
|
114
|
+
joinEdges.forEach(function (edge) {
|
|
115
|
+
addRef(edge.a);
|
|
116
|
+
addRef(edge.b);
|
|
117
|
+
});
|
|
118
|
+
return entities.map(function (e) {
|
|
119
|
+
return e.columns ? e : _objectSpread(_objectSpread({}, e), {}, {
|
|
120
|
+
columns: columnsById[e.id]
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// sql-sitterはResultスコープを内側から順にResult 1, Result 2, ...と
|
|
126
|
+
// 番号を振り、最終スコープ(一番外側)が常に一番大きい番号になる
|
|
127
|
+
// (番号付きは中間サブクエリの出力、最終スコープだけ番号を外して
|
|
128
|
+
// 太字+黒背景白文字の「Result」にする)。この規則で自動判定し、
|
|
129
|
+
// データ側でいちいち title/headerColor を指定しなくて済むようにする。
|
|
130
|
+
var RESULT_NUMBER_RE = /^Result (\d+)$/;
|
|
131
|
+
export function markFinalResult(entities) {
|
|
132
|
+
var _final = null;
|
|
133
|
+
entities.forEach(function (e) {
|
|
134
|
+
if (e.kind !== 'result') return;
|
|
135
|
+
var m = RESULT_NUMBER_RE.exec(e.title);
|
|
136
|
+
if (!m) return;
|
|
137
|
+
var num = parseInt(m[1], 10);
|
|
138
|
+
if (!_final || num > _final.num) _final = {
|
|
139
|
+
entity: e,
|
|
140
|
+
num: num
|
|
141
|
+
};
|
|
142
|
+
});
|
|
143
|
+
if (!_final) return entities;
|
|
144
|
+
return entities.map(function (e) {
|
|
145
|
+
return e === _final.entity ? _objectSpread(_objectSpread({}, e), {}, {
|
|
146
|
+
title: 'Result',
|
|
147
|
+
headerColor: e.headerColor || '#000000',
|
|
148
|
+
headerTextColor: e.headerTextColor || '#ffffff'
|
|
149
|
+
}) : e;
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
export function entityHeight(entity) {
|
|
153
|
+
return HEADER_H + entity.columns.length * ROW_H;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// JOINで結び付くエンティティ同士をUnion-Findでグループ化する。同じ
|
|
157
|
+
// グループのエンティティは自動レイアウトで同じ列(レイヤー)に配置する
|
|
158
|
+
// (JOINで繋がる相手とは同じ列に並べる)。
|
|
159
|
+
export function groupByJoin(entities, joinEdges) {
|
|
160
|
+
var parent = {};
|
|
161
|
+
entities.forEach(function (e) {
|
|
162
|
+
parent[e.id] = e.id;
|
|
163
|
+
});
|
|
164
|
+
var find = function find(x) {
|
|
165
|
+
while (parent[x] !== x) x = parent[x];
|
|
166
|
+
return x;
|
|
167
|
+
};
|
|
168
|
+
joinEdges.forEach(function (edge) {
|
|
169
|
+
var ra = find(edge.a.entity);
|
|
170
|
+
var rb = find(edge.b.entity);
|
|
171
|
+
if (ra !== rb) parent[ra] = rb;
|
|
172
|
+
});
|
|
173
|
+
var groups = {};
|
|
174
|
+
entities.forEach(function (entity) {
|
|
175
|
+
var g = find(entity.id);
|
|
176
|
+
(groups[g] = groups[g] || []).push(entity);
|
|
177
|
+
});
|
|
178
|
+
return {
|
|
179
|
+
find: find,
|
|
180
|
+
groups: groups
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// entities/lineageEdges/joinEdgesからエンティティ毎の(x,y)を自動計算する。
|
|
185
|
+
// JOINで繋がるエンティティをグループ化して1つのノードとしてd3-dagに渡し、
|
|
186
|
+
// リネージの依存関係(lineageEdges)をグループ間のparentIdsとして
|
|
187
|
+
// d3-dagのsugiyama(層状レイアウト)にレイヤリングさせる。tweakDirection
|
|
188
|
+
// ('RL')で「深い(=物理テーブルに近い)ほど右、浅い(=最終結果)ほど左」に
|
|
189
|
+
// 揃える。グループ内の複数エンティティ(JOINの相手同士)は同じ列内で
|
|
190
|
+
// 縦に並べる。
|
|
191
|
+
export function buildAutoPositions(entities, lineageEdges, joinEdges) {
|
|
192
|
+
if (entities.length === 0) return {};
|
|
193
|
+
var _groupByJoin = groupByJoin(entities, joinEdges),
|
|
194
|
+
find = _groupByJoin.find,
|
|
195
|
+
groups = _groupByJoin.groups;
|
|
196
|
+
var groupParentIds = {};
|
|
197
|
+
Object.keys(groups).forEach(function (g) {
|
|
198
|
+
groupParentIds[g] = new Set();
|
|
199
|
+
});
|
|
200
|
+
lineageEdges.forEach(function (edge) {
|
|
201
|
+
// `from`が無い(リテラル値・列を取らない関数呼び出し)エッジは
|
|
202
|
+
// 由来となる実体が無いのでレイアウトの依存関係には使わない。
|
|
203
|
+
if (!edge.from) return;
|
|
204
|
+
// `from`は単一の{entity,column}か、複数列を1つの式(例: ||連結)で
|
|
205
|
+
// 結合した場合の配列。
|
|
206
|
+
var froms = Array.isArray(edge.from) ? edge.from : [edge.from];
|
|
207
|
+
var gTo = find(edge.to.entity);
|
|
208
|
+
froms.forEach(function (f) {
|
|
209
|
+
var gFrom = find(f.entity);
|
|
210
|
+
if (gFrom !== gTo) groupParentIds[gTo].add(gFrom);
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
var dagData = Object.keys(groups).map(function (g) {
|
|
214
|
+
return {
|
|
215
|
+
id: g,
|
|
216
|
+
parentIds: Array.from(groupParentIds[g])
|
|
217
|
+
};
|
|
218
|
+
});
|
|
219
|
+
var groupHeight = function groupHeight(g) {
|
|
220
|
+
return groups[g].reduce(function (sum, e) {
|
|
221
|
+
return sum + entityHeight(e) + SIBLING_GAP;
|
|
222
|
+
}, -SIBLING_GAP);
|
|
223
|
+
};
|
|
224
|
+
var dag = graphStratify()(dagData);
|
|
225
|
+
var layout = sugiyama().nodeSize(function (node) {
|
|
226
|
+
return [groupHeight(node.data.id), LAYER_SPACING];
|
|
227
|
+
})
|
|
228
|
+
// sugiyamaの既定gapは[1,1]で、同じ列(レイヤー)に並ぶ別グループ同士が
|
|
229
|
+
// ほぼ隙間無く接してしまうため、グループ内で使っているSIBLING_GAPと
|
|
230
|
+
// 同じ幅を明示的に確保する。
|
|
231
|
+
.gap([SIBLING_GAP, 0]).tweaks([tweakDirection('RL')]);
|
|
232
|
+
layout(dag);
|
|
233
|
+
|
|
234
|
+
// X(列・レイヤー)はsugiyamaの結果をそのまま使う。Yはsugiyamaに任せず、
|
|
235
|
+
// 「1つのSQL(サブクエリも含む)ではスコープのメンバー全員を同じ高さに
|
|
236
|
+
// 揃える」というルールで実テーブル側から再帰的に組み立て直す。
|
|
237
|
+
var xById = {};
|
|
238
|
+
var _iterator = _createForOfIteratorHelper(dag.nodes()),
|
|
239
|
+
_step;
|
|
240
|
+
try {
|
|
241
|
+
var _loop = function _loop() {
|
|
242
|
+
var node = _step.value;
|
|
243
|
+
groups[node.data.id].forEach(function (entity) {
|
|
244
|
+
xById[entity.id] = node.x;
|
|
245
|
+
});
|
|
246
|
+
};
|
|
247
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
248
|
+
_loop();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ラベルbox(見積もり幅)がJOIN線や次の列のエンティティと被らないよう、
|
|
252
|
+
// 列の間隔をラベル幅に応じて広げる(ラベルboxの右端とJOIN線/
|
|
253
|
+
// エンティティの間は11pxの余白を確保する)。
|
|
254
|
+
} catch (err) {
|
|
255
|
+
_iterator.e(err);
|
|
256
|
+
} finally {
|
|
257
|
+
_iterator.f();
|
|
258
|
+
}
|
|
259
|
+
var widenedXById = widenColumnsForLabels(xById, lineageEdges, entities);
|
|
260
|
+
return buildTopAlignedPositions(entities, lineageEdges, joinEdges, widenedXById, find, groups);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// ラベル文字列からラベルboxのおおよその幅を見積もる(全角文字は半角の
|
|
264
|
+
// 約2倍幅として計算)。実際の描画幅はSVGの<text>で実測するが、レイアウト
|
|
265
|
+
// (列の間隔)を決める段階ではDOM測定前なので、この見積もりを使う。
|
|
266
|
+
export function estimateLabelWidth(label) {
|
|
267
|
+
if (!label) return 0;
|
|
268
|
+
var width = 8;
|
|
269
|
+
var _iterator2 = _createForOfIteratorHelper(label),
|
|
270
|
+
_step2;
|
|
271
|
+
try {
|
|
272
|
+
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
|
|
273
|
+
var ch = _step2.value;
|
|
274
|
+
var code = ch.codePointAt(0);
|
|
275
|
+
var isWide = code >= 0x1100 && code <= 0x115f || code >= 0x2e80 && code <= 0xa4cf || code >= 0xac00 && code <= 0xd7a3 || code >= 0xf900 && code <= 0xfaff || code >= 0xff00 && code <= 0xff60 || code >= 0xffe0 && code <= 0xffe6;
|
|
276
|
+
width += isWide ? 13 : 7;
|
|
277
|
+
}
|
|
278
|
+
} catch (err) {
|
|
279
|
+
_iterator2.e(err);
|
|
280
|
+
} finally {
|
|
281
|
+
_iterator2.f();
|
|
282
|
+
}
|
|
283
|
+
return width;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// 各列(x)の右側の間隔を、そこを`to`とするラベル付きリネージ線のラベル幅
|
|
287
|
+
// (見積もり)+ 余白ぶん確保できるよう広げる。列の並び順(ランク)は
|
|
288
|
+
// 変えず、間隔だけを必要に応じて広げる。ラベルの左端は`to`のエンティティ
|
|
289
|
+
// 自身の幅(BOX_W)・DISTINCT帯の分だけ`to.x`より右にずれるので、その分も
|
|
290
|
+
// 加味する(これを忘れるとJOIN線/次のエンティティと被る)。
|
|
291
|
+
export function widenColumnsForLabels(xById, lineageEdges, entities) {
|
|
292
|
+
var ranks = _toConsumableArray(new Set(Object.values(xById))).sort(function (a, b) {
|
|
293
|
+
return a - b;
|
|
294
|
+
});
|
|
295
|
+
if (ranks.length === 0) return xById;
|
|
296
|
+
var entityById = {};
|
|
297
|
+
entities.forEach(function (e) {
|
|
298
|
+
entityById[e.id] = e;
|
|
299
|
+
});
|
|
300
|
+
var requiredGapAfter = {};
|
|
301
|
+
lineageEdges.forEach(function (edge) {
|
|
302
|
+
if (!edge.label || !edge.to) return;
|
|
303
|
+
var toX = xById[edge.to.entity];
|
|
304
|
+
var toEntity = entityById[edge.to.entity];
|
|
305
|
+
if (toX === undefined || !toEntity) return;
|
|
306
|
+
var toEdgeOffset = BOX_W + (toEntity.distinct ? BRANCH_GAP + DISTINCT_BAND_WIDTH : 0);
|
|
307
|
+
var needed = toEdgeOffset + LINEAGE_LABEL_GAP + estimateLabelWidth(edge.label) + BRANCH_GAP;
|
|
308
|
+
requiredGapAfter[toX] = Math.max(requiredGapAfter[toX] || 0, needed);
|
|
309
|
+
});
|
|
310
|
+
var remapped = _defineProperty({}, ranks[0], ranks[0]);
|
|
311
|
+
for (var i = 1; i < ranks.length; i++) {
|
|
312
|
+
var prevRank = ranks[i - 1];
|
|
313
|
+
var gap = Math.max(ranks[i] - prevRank, requiredGapAfter[prevRank] || 0);
|
|
314
|
+
remapped[ranks[i]] = remapped[prevRank] + gap;
|
|
315
|
+
}
|
|
316
|
+
var adjusted = {};
|
|
317
|
+
Object.keys(xById).forEach(function (id) {
|
|
318
|
+
adjusted[id] = remapped[xById[id]];
|
|
319
|
+
});
|
|
320
|
+
return adjusted;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// entity.id -> lineageEdgesから求めた「直接の子」(そのエンティティ自身の
|
|
324
|
+
// SQLスコープのFROM句に出てくる参照先)の一覧(重複無し、初出順)。実テーブル
|
|
325
|
+
// は他から値を受け取らないので常に空になる。
|
|
326
|
+
export function buildLineageChildren(entities, lineageEdges) {
|
|
327
|
+
var children = {};
|
|
328
|
+
entities.forEach(function (e) {
|
|
329
|
+
children[e.id] = [];
|
|
330
|
+
});
|
|
331
|
+
var seen = new Set();
|
|
332
|
+
lineageEdges.forEach(function (edge) {
|
|
333
|
+
// `from`が無い(リテラル値・列を取らない関数呼び出し)エッジは
|
|
334
|
+
// 由来となる実体が無いのでスコープの子には含めない。
|
|
335
|
+
if (!edge.from) return;
|
|
336
|
+
var froms = Array.isArray(edge.from) ? edge.from : [edge.from];
|
|
337
|
+
froms.forEach(function (f) {
|
|
338
|
+
if (f.entity === edge.to.entity) return;
|
|
339
|
+
var key = "".concat(edge.to.entity, "|").concat(f.entity);
|
|
340
|
+
if (seen.has(key)) return;
|
|
341
|
+
seen.add(key);
|
|
342
|
+
children[edge.to.entity].push(f.entity);
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
return children;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// JOINの a側を上、b側を下にする表示順。members内で直接JOINしている関係
|
|
349
|
+
// だけを使う簡易的な位相ソート(サイクルが無い前提。無ければ元の順のまま)。
|
|
350
|
+
export function orderByJoinAB(members, joinEdges) {
|
|
351
|
+
var before = {};
|
|
352
|
+
members.forEach(function (m) {
|
|
353
|
+
before[m] = new Set();
|
|
354
|
+
});
|
|
355
|
+
joinEdges.forEach(function (edge) {
|
|
356
|
+
var a = edge.a.entity;
|
|
357
|
+
var b = edge.b.entity;
|
|
358
|
+
if (before[a] !== undefined && before[b] !== undefined) {
|
|
359
|
+
before[b].add(a);
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
var ordered = [];
|
|
363
|
+
var remaining = new Set(members);
|
|
364
|
+
while (remaining.size > 0) {
|
|
365
|
+
var next = _toConsumableArray(remaining).find(function (m) {
|
|
366
|
+
return _toConsumableArray(before[m]).every(function (dep) {
|
|
367
|
+
return !remaining.has(dep);
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
var pick = next !== undefined ? next : _toConsumableArray(remaining)[0];
|
|
371
|
+
ordered.push(pick);
|
|
372
|
+
remaining["delete"](pick);
|
|
373
|
+
}
|
|
374
|
+
return ordered;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// 「1つのSQL(サブクエリも含む)では上端を揃え、JOINがあれば縦に積む」
|
|
378
|
+
// というルールでYを実テーブル側から再帰的に組み立てる。あるエンティティの
|
|
379
|
+
// 「スコープメンバー」=直接の子をJOIN相手も含めて拡張した集合。スコープ
|
|
380
|
+
// メンバーが1つだけなら、その子と同じYに揃える。複数(JOIN)で、そのうち
|
|
381
|
+
// 自分の子を持つ(=さらに深いリネージがある)メンバーが1つだけなら、その
|
|
382
|
+
// メンバーの自然なYはそのままにして、他の実テーブル側はJOINのa/b順で
|
|
383
|
+
// 隙間無く隣接させる(上にも負のYまで拡張して構わない)。複数のメンバーが
|
|
384
|
+
// それぞれ自分の子を持つ場合は、無理に隙間0で隣接させず、reachが大きい方を
|
|
385
|
+
// 優先して自然なYを尊重しつつ重ならないように詰める。
|
|
386
|
+
export function buildTopAlignedPositions(entities, lineageEdges, joinEdges, xById, find, groups) {
|
|
387
|
+
var entityById = {};
|
|
388
|
+
entities.forEach(function (e) {
|
|
389
|
+
entityById[e.id] = e;
|
|
390
|
+
});
|
|
391
|
+
var lineageChildren = buildLineageChildren(entities, lineageEdges);
|
|
392
|
+
var scopeMembersOf = function scopeMembersOf(entityId) {
|
|
393
|
+
var memberIds = new Set();
|
|
394
|
+
lineageChildren[entityId].forEach(function (childId) {
|
|
395
|
+
groups[find(childId)].forEach(function (m) {
|
|
396
|
+
return memberIds.add(m.id);
|
|
397
|
+
});
|
|
398
|
+
});
|
|
399
|
+
return entities.filter(function (e) {
|
|
400
|
+
return memberIds.has(e.id);
|
|
401
|
+
}).map(function (e) {
|
|
402
|
+
return e.id;
|
|
403
|
+
});
|
|
404
|
+
};
|
|
405
|
+
var reachCache = {};
|
|
406
|
+
var _reachOf = function reachOf(entityId) {
|
|
407
|
+
if (entityId in reachCache) return reachCache[entityId];
|
|
408
|
+
reachCache[entityId] = xById[entityId];
|
|
409
|
+
var reach = xById[entityId];
|
|
410
|
+
scopeMembersOf(entityId).forEach(function (childId) {
|
|
411
|
+
reach = Math.max(reach, _reachOf(childId));
|
|
412
|
+
});
|
|
413
|
+
reachCache[entityId] = reach;
|
|
414
|
+
return reach;
|
|
415
|
+
};
|
|
416
|
+
var positions = {};
|
|
417
|
+
var nextY = {};
|
|
418
|
+
var visited = new Set();
|
|
419
|
+
function place(entityId, desiredY) {
|
|
420
|
+
var x = xById[entityId];
|
|
421
|
+
// 列が未使用ならdesiredYをそのまま使う(負の値も許す。JOIN相手の
|
|
422
|
+
// 実テーブル側をa/b順で隣接させるために上へ伸びることがある)。
|
|
423
|
+
var minY = x in nextY ? nextY[x] : -Infinity;
|
|
424
|
+
var y = Math.max(desiredY, minY);
|
|
425
|
+
positions[entityId] = {
|
|
426
|
+
x: x,
|
|
427
|
+
y: y
|
|
428
|
+
};
|
|
429
|
+
nextY[x] = y + entityHeight(entityById[entityId]) + SIBLING_GAP;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// メンバー全体(メンバー自身の既に確定済みの子孫も含む)をdelta分
|
|
433
|
+
// だけずらす。子孫との相対位置(揃え)を保ったまま動かすため。
|
|
434
|
+
function shiftSubtree(id, delta) {
|
|
435
|
+
if (delta === 0) return;
|
|
436
|
+
positions[id] = _objectSpread(_objectSpread({}, positions[id]), {}, {
|
|
437
|
+
y: positions[id].y + delta
|
|
438
|
+
});
|
|
439
|
+
scopeMembersOf(id).forEach(function (childId) {
|
|
440
|
+
return shiftSubtree(childId, delta);
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
function assign(entityId) {
|
|
444
|
+
if (visited.has(entityId)) return;
|
|
445
|
+
visited.add(entityId);
|
|
446
|
+
var members = scopeMembersOf(entityId);
|
|
447
|
+
if (members.length === 0) {
|
|
448
|
+
place(entityId, 0);
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// 制約の強い(その先の列が深い)メンバーから先に確定させる。
|
|
453
|
+
var byReach = _toConsumableArray(members).sort(function (a, b) {
|
|
454
|
+
return _reachOf(b) - _reachOf(a);
|
|
455
|
+
});
|
|
456
|
+
byReach.forEach(assign);
|
|
457
|
+
|
|
458
|
+
// 一番reachが大きい(外部への繋がりが深い)メンバーを基準
|
|
459
|
+
// (アンカー)にして動かさない。他のメンバーはJOINのa/b順で隙間
|
|
460
|
+
// 無く隣接させ、既に確定済みの子孫がいればまとめてずらす(負のY
|
|
461
|
+
// まで拡張して構わない)。このエンティティ自身は、メンバー全員の
|
|
462
|
+
// 中で一番上(Yが最小)になったものに揃える。
|
|
463
|
+
var anchor = byReach[0];
|
|
464
|
+
var ab = orderByJoinAB(members, joinEdges);
|
|
465
|
+
var anchorIndex = ab.indexOf(anchor);
|
|
466
|
+
for (var i = anchorIndex - 1; i >= 0; i--) {
|
|
467
|
+
var below = ab[i + 1];
|
|
468
|
+
var targetY = positions[below].y - entityHeight(entityById[ab[i]]) - SIBLING_GAP;
|
|
469
|
+
shiftSubtree(ab[i], targetY - positions[ab[i]].y);
|
|
470
|
+
}
|
|
471
|
+
for (var _i = anchorIndex + 1; _i < ab.length; _i++) {
|
|
472
|
+
var above = ab[_i - 1];
|
|
473
|
+
var _targetY = positions[above].y + entityHeight(entityById[above]) + SIBLING_GAP;
|
|
474
|
+
shiftSubtree(ab[_i], _targetY - positions[ab[_i]].y);
|
|
475
|
+
}
|
|
476
|
+
var topY = Math.min.apply(Math, _toConsumableArray(members.map(function (id) {
|
|
477
|
+
return positions[id].y;
|
|
478
|
+
})));
|
|
479
|
+
place(entityId, topY);
|
|
480
|
+
}
|
|
481
|
+
_toConsumableArray(entities).sort(function (a, b) {
|
|
482
|
+
return _reachOf(b.id) - _reachOf(a.id);
|
|
483
|
+
}).forEach(function (e) {
|
|
484
|
+
return assign(e.id);
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
// 保険: 再帰的な組み立てで多少の誤差が出ても、列内の重なりだけは必ず
|
|
488
|
+
// 無くす(Y順に並べ、隙間が無ければ後ろのものを必要な分だけ下へずらす)。
|
|
489
|
+
var byColumn = {};
|
|
490
|
+
Object.entries(positions).forEach(function (_ref3) {
|
|
491
|
+
var _ref4 = _slicedToArray(_ref3, 2),
|
|
492
|
+
id = _ref4[0],
|
|
493
|
+
pos = _ref4[1];
|
|
494
|
+
(byColumn[pos.x] = byColumn[pos.x] || []).push(id);
|
|
495
|
+
});
|
|
496
|
+
Object.values(byColumn).forEach(function (ids) {
|
|
497
|
+
ids.sort(function (a, b) {
|
|
498
|
+
return positions[a].y - positions[b].y;
|
|
499
|
+
});
|
|
500
|
+
var minY = -Infinity;
|
|
501
|
+
ids.forEach(function (id) {
|
|
502
|
+
if (positions[id].y < minY) positions[id].y = minY;
|
|
503
|
+
minY = positions[id].y + entityHeight(entityById[id]) + SIBLING_GAP;
|
|
504
|
+
});
|
|
505
|
+
});
|
|
506
|
+
return positions;
|
|
507
|
+
}
|
|
508
|
+
export function buildLayout(entities, positions, tableColors) {
|
|
509
|
+
var boxes = {};
|
|
510
|
+
entities.forEach(function (entity) {
|
|
511
|
+
var h = entityHeight(entity);
|
|
512
|
+
var pos = positions[entity.id];
|
|
513
|
+
boxes[entity.id] = {
|
|
514
|
+
id: entity.id,
|
|
515
|
+
x: pos.x,
|
|
516
|
+
y: pos.y,
|
|
517
|
+
w: BOX_W,
|
|
518
|
+
h: h,
|
|
519
|
+
title: entity.title,
|
|
520
|
+
alias: entity.alias,
|
|
521
|
+
headerColor: entity.headerColor || (entity.kind === 'table' ? tableColors[entity.title] : null),
|
|
522
|
+
headerTextColor: entity.headerTextColor || null,
|
|
523
|
+
distinct: !!entity.distinct,
|
|
524
|
+
bandLabel: entity.bandLabel || 'distinct',
|
|
525
|
+
orderByColumns: entity.orderByColumns || null,
|
|
526
|
+
whereText: entity.whereText || null,
|
|
527
|
+
rows: entity.columns.map(function (col, i) {
|
|
528
|
+
return {
|
|
529
|
+
name: col,
|
|
530
|
+
y: pos.y + HEADER_H + i * ROW_H + ROW_H / 2
|
|
531
|
+
};
|
|
532
|
+
})
|
|
533
|
+
};
|
|
534
|
+
});
|
|
535
|
+
return boxes;
|
|
536
|
+
}
|
|
537
|
+
export function findRowY(box, columnName) {
|
|
538
|
+
var row = box && box.rows.find(function (r) {
|
|
539
|
+
return r.name === columnName;
|
|
540
|
+
});
|
|
541
|
+
return row ? row.y : null;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// JOIN線の幾何。aの左辺から一度左に出てから下へ、bの左辺へ横に入る
|
|
545
|
+
// エルボー型の折れ線。同じ起点(a)から出るJOIN線が複数あるときに、縦の
|
|
546
|
+
// ガイド線が重ならないよう起点ごとに少しずつオフセットする。
|
|
547
|
+
// あわせて「どの列がJOIN線の分岐点(branchX)を持つか」の索引も返す。
|
|
548
|
+
// SELECT側の線はこの分岐点がある列なら、箱の縁ではなく分岐点から始める
|
|
549
|
+
// (「分岐点から線を引く」ルール)。
|
|
550
|
+
export function buildJoinLines(boxes, joinEdges) {
|
|
551
|
+
var branchOffsets = {};
|
|
552
|
+
var branchPointByPort = {};
|
|
553
|
+
var lines = joinEdges.map(function (edge) {
|
|
554
|
+
var a = boxes[edge.a.entity];
|
|
555
|
+
var b = boxes[edge.b.entity];
|
|
556
|
+
var y1 = findRowY(a, edge.a.column);
|
|
557
|
+
var y2 = findRowY(b, edge.b.column);
|
|
558
|
+
if (!a || !b || y1 === null || y2 === null) return null;
|
|
559
|
+
var offsetIndex = branchOffsets[edge.a.entity] || 0;
|
|
560
|
+
branchOffsets[edge.a.entity] = offsetIndex + 1;
|
|
561
|
+
var branchX = a.x - 20 - offsetIndex * 10;
|
|
562
|
+
branchPointByPort["".concat(edge.a.entity, ".").concat(edge.a.column)] = branchX;
|
|
563
|
+
return {
|
|
564
|
+
key: "join:".concat(edge.a.entity, ".").concat(edge.a.column, "<->").concat(edge.b.entity, ".").concat(edge.b.column),
|
|
565
|
+
x1: a.x,
|
|
566
|
+
y1: y1,
|
|
567
|
+
x2: b.x,
|
|
568
|
+
y2: y2,
|
|
569
|
+
branchX: branchX,
|
|
570
|
+
joinType: edge.joinType,
|
|
571
|
+
label: edge.label,
|
|
572
|
+
kind: edge.kind
|
|
573
|
+
};
|
|
574
|
+
}).filter(Boolean);
|
|
575
|
+
return {
|
|
576
|
+
lines: lines,
|
|
577
|
+
branchPointByPort: branchPointByPort
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// リネージ線の幾何。常にsource(右)からResult側(左)へ向く。
|
|
582
|
+
// DISTINCTの帯がある場合は、そこまで線を延長する(distinctは全ての
|
|
583
|
+
// リネージ線が通る帯として表現する。帯とResult本体の間は11pxの余白)。
|
|
584
|
+
// ラベルの左端はbranchXのブレに関わらず、宛先(to)の縁からの固定距離に
|
|
585
|
+
// する(左端位置を揃える)。
|
|
586
|
+
export function buildLineageLines(boxes, lineageEdges, branchPointByPort) {
|
|
587
|
+
return lineageEdges.map(function (edge) {
|
|
588
|
+
var to = boxes[edge.to.entity];
|
|
589
|
+
var y2 = findRowY(to, edge.to.column);
|
|
590
|
+
if (!to || y2 === null) return null;
|
|
591
|
+
var toEdgeX = to.x + to.w + (to.distinct ? BRANCH_GAP + DISTINCT_BAND_WIDTH : 0);
|
|
592
|
+
var labelX = toEdgeX + LINEAGE_LABEL_GAP;
|
|
593
|
+
if (!edge.from) {
|
|
594
|
+
// リテラル値・列を取らない関数呼び出し(由来となる実テーブルが
|
|
595
|
+
// 無い列)は、ラベルboxからResultへの短い矢印だけを描く。
|
|
596
|
+
return {
|
|
597
|
+
key: "literal:".concat(edge.to.entity, ".").concat(edge.to.column),
|
|
598
|
+
x1: labelX,
|
|
599
|
+
y1: y2,
|
|
600
|
+
x2: toEdgeX,
|
|
601
|
+
y2: y2,
|
|
602
|
+
labelX: labelX,
|
|
603
|
+
label: edge.label,
|
|
604
|
+
showPort: false,
|
|
605
|
+
labelInfo: {
|
|
606
|
+
label: edge.label,
|
|
607
|
+
fromTitle: null,
|
|
608
|
+
fromColumn: null,
|
|
609
|
+
toTitle: to.alias || to.title,
|
|
610
|
+
toColumn: edge.to.column,
|
|
611
|
+
sql: edge.sql
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// `from`は単一の{entity,column}か、複数列を1つの式(例: ||連結)で
|
|
617
|
+
// 結合した場合の配列(1つのラベルboxから複数本の斜め線を引く)。
|
|
618
|
+
var froms = Array.isArray(edge.from) ? edge.from : [edge.from];
|
|
619
|
+
var sources = [];
|
|
620
|
+
var _iterator3 = _createForOfIteratorHelper(froms),
|
|
621
|
+
_step3;
|
|
622
|
+
try {
|
|
623
|
+
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
|
|
624
|
+
var f = _step3.value;
|
|
625
|
+
var from = boxes[f.entity];
|
|
626
|
+
var y1 = findRowY(from, f.column);
|
|
627
|
+
if (!from || y1 === null) return null;
|
|
628
|
+
var branchX = branchPointByPort["".concat(f.entity, ".").concat(f.column)];
|
|
629
|
+
// JOIN線の角(branchX)、またはJOIN線が無い場合はエンティティの縁
|
|
630
|
+
// との間を、SELECT側の線の開始点が重なって見えないよう少しだけ
|
|
631
|
+
// 離す。
|
|
632
|
+
var startX = (branchX !== undefined ? branchX : from.x) - BRANCH_GAP;
|
|
633
|
+
sources.push({
|
|
634
|
+
x: startX,
|
|
635
|
+
y: y1,
|
|
636
|
+
title: from.alias || from.title,
|
|
637
|
+
column: f.column
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
} catch (err) {
|
|
641
|
+
_iterator3.e(err);
|
|
642
|
+
} finally {
|
|
643
|
+
_iterator3.f();
|
|
644
|
+
}
|
|
645
|
+
return {
|
|
646
|
+
key: "lineage:".concat(froms.map(function (f) {
|
|
647
|
+
return "".concat(f.entity, ".").concat(f.column);
|
|
648
|
+
}).join('+'), "->").concat(edge.to.entity, ".").concat(edge.to.column),
|
|
649
|
+
x1: sources[0].x,
|
|
650
|
+
y1: sources[0].y,
|
|
651
|
+
x2: toEdgeX,
|
|
652
|
+
y2: y2,
|
|
653
|
+
labelX: labelX,
|
|
654
|
+
label: edge.label,
|
|
655
|
+
sources: sources,
|
|
656
|
+
labelInfo: {
|
|
657
|
+
label: edge.label,
|
|
658
|
+
fromDisplay: sources.map(function (s) {
|
|
659
|
+
return "".concat(s.title, ".").concat(s.column);
|
|
660
|
+
}).join(' , '),
|
|
661
|
+
toTitle: to.alias || to.title,
|
|
662
|
+
toColumn: edge.to.column,
|
|
663
|
+
sql: edge.sql
|
|
664
|
+
}
|
|
665
|
+
};
|
|
666
|
+
}).filter(Boolean);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// SQL リネージグラフのデータクラス本体。入力の正規化(列の自動導出・
|
|
670
|
+
// 最終Resultの判定)からレイアウト(自動配置・箱の寸法)・線の幾何までを
|
|
671
|
+
// 構築時に済ませ、描画側(D3Sql)は結果を SVG に写すだけにする。
|
|
672
|
+
var SqlModel = /*#__PURE__*/function () {
|
|
673
|
+
function SqlModel(data) {
|
|
674
|
+
_classCallCheck(this, SqlModel);
|
|
675
|
+
var raw = data || {};
|
|
676
|
+
this._core = raw;
|
|
677
|
+
this.entities = markFinalResult(withDerivedColumns(raw.entities || [], raw.lineageEdges || [], raw.joinEdges || []));
|
|
678
|
+
this.lineageEdges = raw.lineageEdges || [];
|
|
679
|
+
this.joinEdges = raw.joinEdges || [];
|
|
680
|
+
this.tableColors = buildTableColors(this.entities);
|
|
681
|
+
this.positions = buildAutoPositions(this.entities, this.lineageEdges, this.joinEdges);
|
|
682
|
+
this.boxes = buildLayout(this.entities, this.positions, this.tableColors);
|
|
683
|
+
var join = buildJoinLines(this.boxes, this.joinEdges);
|
|
684
|
+
this.joinLines = join.lines;
|
|
685
|
+
this.lineageLines = buildLineageLines(this.boxes, this.lineageEdges, join.branchPointByPort);
|
|
686
|
+
}
|
|
687
|
+
return _createClass(SqlModel, [{
|
|
688
|
+
key: "boxList",
|
|
689
|
+
value: function boxList() {
|
|
690
|
+
return Object.values(this.boxes);
|
|
691
|
+
}
|
|
692
|
+
// レイアウトは負のYまで伸びることがあるので、初期表示はコンテンツの
|
|
693
|
+
// 左上(負の座標も含む)に余白を付けて揃える(そうしないと、最終Result
|
|
694
|
+
// などの箱が初期ビューポート外になり、パン操作をしないと見えない)。
|
|
695
|
+
}, {
|
|
696
|
+
key: "initialTransform",
|
|
697
|
+
value: function initialTransform() {
|
|
698
|
+
var padding = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 40;
|
|
699
|
+
var boxes = this.boxList();
|
|
700
|
+
if (boxes.length === 0) return {
|
|
701
|
+
x: 0,
|
|
702
|
+
y: 0,
|
|
703
|
+
k: 1
|
|
704
|
+
};
|
|
705
|
+
return {
|
|
706
|
+
x: padding - Math.min.apply(Math, _toConsumableArray(boxes.map(function (b) {
|
|
707
|
+
return b.x;
|
|
708
|
+
}))),
|
|
709
|
+
y: padding - Math.min.apply(Math, _toConsumableArray(boxes.map(function (b) {
|
|
710
|
+
return b.y;
|
|
711
|
+
}))),
|
|
712
|
+
k: 1
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
}]);
|
|
716
|
+
}();
|
|
717
|
+
export { SqlModel as default };
|