@yrest/cli 0.11.0 → 0.11.1
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/README.md +1 -1
- package/assets/svg/0..1.svg +3 -0
- package/assets/svg/0..n.svg +3 -0
- package/assets/svg/1..1.svg +3 -0
- package/assets/svg/1..n.svg +3 -0
- package/assets/svg/n.svg +3 -0
- package/dist/cli/index.js +141 -67
- package/dist/cli/index.mjs +141 -67
- package/dist/index.js +141 -67
- package/dist/index.mjs +141 -67
- package/package.json +1 -1
package/dist/cli/index.mjs
CHANGED
|
@@ -1196,9 +1196,15 @@ function generateERData(storage) {
|
|
|
1196
1196
|
const relations = storage.getRelations();
|
|
1197
1197
|
const schema = storage.getSchema();
|
|
1198
1198
|
const pivotTables = /* @__PURE__ */ new Set();
|
|
1199
|
+
const pivotFKs = /* @__PURE__ */ new Map();
|
|
1199
1200
|
for (const fields of Object.values(relations)) {
|
|
1200
1201
|
for (const def of Object.values(fields)) {
|
|
1201
|
-
if (def.type === "many2many")
|
|
1202
|
+
if (def.type === "many2many") {
|
|
1203
|
+
pivotTables.add(def.through);
|
|
1204
|
+
if (!pivotFKs.has(def.through)) pivotFKs.set(def.through, /* @__PURE__ */ new Set());
|
|
1205
|
+
pivotFKs.get(def.through).add(def.foreignKey);
|
|
1206
|
+
pivotFKs.get(def.through).add(def.otherKey);
|
|
1207
|
+
}
|
|
1202
1208
|
}
|
|
1203
1209
|
}
|
|
1204
1210
|
const fkFields = /* @__PURE__ */ new Map();
|
|
@@ -1234,6 +1240,9 @@ function generateERData(storage) {
|
|
|
1234
1240
|
for (const [source, fields] of Object.entries(relations)) {
|
|
1235
1241
|
for (const [key, def] of Object.entries(fields)) {
|
|
1236
1242
|
if (def.type === "many2many") {
|
|
1243
|
+
const throughRels = relations[def.through] ?? {};
|
|
1244
|
+
const fkRel = throughRels[def.foreignKey];
|
|
1245
|
+
const otherRel = throughRels[def.otherKey];
|
|
1237
1246
|
erRelations.push({
|
|
1238
1247
|
source,
|
|
1239
1248
|
target: def.target,
|
|
@@ -1241,9 +1250,14 @@ function generateERData(storage) {
|
|
|
1241
1250
|
label: def.through,
|
|
1242
1251
|
through: def.through,
|
|
1243
1252
|
foreignKey: def.foreignKey,
|
|
1244
|
-
otherKey: def.otherKey
|
|
1253
|
+
otherKey: def.otherKey,
|
|
1254
|
+
fkCarDirect: fkRel?.carDirect,
|
|
1255
|
+
fkCarInverse: fkRel?.carInverse,
|
|
1256
|
+
otherCarDirect: otherRel?.carDirect,
|
|
1257
|
+
otherCarInverse: otherRel?.carInverse
|
|
1245
1258
|
});
|
|
1246
1259
|
} else {
|
|
1260
|
+
if (pivotFKs.get(source)?.has(key)) continue;
|
|
1247
1261
|
erRelations.push({
|
|
1248
1262
|
source,
|
|
1249
1263
|
target: def.target,
|
|
@@ -1269,7 +1283,15 @@ function erDiagramSection(data) {
|
|
|
1269
1283
|
|
|
1270
1284
|
<div class="card" style="padding:0;overflow:hidden;position:relative;">
|
|
1271
1285
|
<canvas id="er-canvas" style="width:100%;display:block;cursor:grab;"></canvas>
|
|
1272
|
-
<
|
|
1286
|
+
<div style="position:absolute;top:10px;right:14px;display:flex;gap:6px;align-items:center;">
|
|
1287
|
+
<select id="er-scale" title="Download quality" style="background:#161b22;border:1px solid #30363d;color:#7d8590;padding:3px 6px;border-radius:6px;font-size:11px;cursor:pointer;">
|
|
1288
|
+
<option value="1">1\xD7</option>
|
|
1289
|
+
<option value="2">2\xD7</option>
|
|
1290
|
+
<option value="3" selected>3\xD7</option>
|
|
1291
|
+
<option value="4">4\xD7</option>
|
|
1292
|
+
</select>
|
|
1293
|
+
<button id="er-dl" title="Download PNG" style="background:#161b22;border:1px solid #30363d;color:#7d8590;padding:4px 10px;border-radius:6px;font-size:11px;cursor:pointer;line-height:1.4;">\u2193 PNG</button>
|
|
1294
|
+
</div>
|
|
1273
1295
|
</div>
|
|
1274
1296
|
|
|
1275
1297
|
<script>${/*js*/
|
|
@@ -1279,7 +1301,7 @@ function erDiagramSection(data) {
|
|
|
1279
1301
|
var D=${json};
|
|
1280
1302
|
|
|
1281
1303
|
// \u2500\u2500 Constants \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1282
|
-
var NW=178,HH=34,FH=22,FP=10,ROW_GAP=44,COL_GAP=100,BR=
|
|
1304
|
+
var NW=178,HH=34,FH=22,FP=10,ROW_GAP=44,COL_GAP=100,BR=12,BO=16,LGH=84,MIN_GAP=10;
|
|
1283
1305
|
|
|
1284
1306
|
var C={bg:'#0d1117',nb:'#161b22',nh:'#1c2128',pb:'#0d1117',ph:'#161b22',
|
|
1285
1307
|
bo:'#30363d',bs:'#58a6ff',tx:'#e6edf3',mu:'#7d8590',
|
|
@@ -1319,6 +1341,30 @@ function erDiagramSection(data) {
|
|
|
1319
1341
|
}
|
|
1320
1342
|
});
|
|
1321
1343
|
|
|
1344
|
+
// \u2500\u2500 Dynamic node width \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1345
|
+
function computeNodeWidths(){
|
|
1346
|
+
nodes.forEach(function(n){
|
|
1347
|
+
var e=n.e;
|
|
1348
|
+
ctx.font='600 12px -apple-system,BlinkMacSystemFont,sans-serif';
|
|
1349
|
+
var hdrW=FP+ctx.measureText(e.name).width+FP;
|
|
1350
|
+
if(e.isPivot){
|
|
1351
|
+
ctx.font='9px monospace';
|
|
1352
|
+
hdrW+=ctx.measureText('pivot').width+FP;
|
|
1353
|
+
}
|
|
1354
|
+
var maxRow=hdrW;
|
|
1355
|
+
e.fields.forEach(function(f){
|
|
1356
|
+
var leftPad=FP+(f.pk||f.fk?16:0);
|
|
1357
|
+
var rightPad=(f.required&&!f.pk?14:0)+FP;
|
|
1358
|
+
ctx.font=(f.required&&!f.pk?'600 ':'')+'11px monospace';
|
|
1359
|
+
var nw=ctx.measureText(f.name).width;
|
|
1360
|
+
ctx.font='10px monospace';
|
|
1361
|
+
var tw=ctx.measureText(f.type).width;
|
|
1362
|
+
maxRow=Math.max(maxRow,leftPad+nw+MIN_GAP+tw+rightPad);
|
|
1363
|
+
});
|
|
1364
|
+
n.w=Math.max(NW,Math.ceil(maxRow));
|
|
1365
|
+
});
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1322
1368
|
// \u2500\u2500 Layered layout (simplified Sugiyama) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1323
1369
|
var groups=[];
|
|
1324
1370
|
|
|
@@ -1357,7 +1403,8 @@ function erDiagramSection(data) {
|
|
|
1357
1403
|
median(adj[b].map(function(x){return np[x]!=null?np[x]:0;}));
|
|
1358
1404
|
});
|
|
1359
1405
|
}
|
|
1360
|
-
var
|
|
1406
|
+
var maxNW=nodes.reduce(function(m,n){return Math.max(m,n.w);},NW);
|
|
1407
|
+
var colW=maxNW+COL_GAP;
|
|
1361
1408
|
var totalW=(maxL+1)*colW-COL_GAP;
|
|
1362
1409
|
var startX=Math.max(20,(W-totalW)/2);
|
|
1363
1410
|
for(var l=0;l<=maxL;l++){
|
|
@@ -1405,37 +1452,37 @@ function erDiagramSection(data) {
|
|
|
1405
1452
|
|
|
1406
1453
|
var badges=[];
|
|
1407
1454
|
|
|
1408
|
-
|
|
1409
|
-
|
|
1455
|
+
// \u2500\u2500 SVG cardinality badges via Path2D \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1456
|
+
// SVG orientation: LEFT side touches the node, RIGHT side connects to the line.
|
|
1457
|
+
// Rotation = Math.atan2(uy,ux) aligns local +x with the outward (away-from-node) direction.
|
|
1458
|
+
var P2D={
|
|
1459
|
+
'1..1':new Path2D('M75 40C75 59.33 59.33 75 40 75C20.67 75 5 59.33 5 40C5 20.67 20.67 5 40 5C59.33 5 75 20.67 75 40ZM41 26C41 24.3431 39.6569 23 38 23C36.3431 23 35 24.3431 35 26V37H26V26C26 24.3431 24.6569 23 23 23C21.3431 23 20 24.3431 20 26V37H15C13.3431 37 12 38.3431 12 40C12 41.6569 13.3431 43 15 43H20V54C20 55.6569 21.3431 57 23 57C24.6569 57 26 55.6569 26 54V43H35V54C35 55.6569 36.3431 57 38 57C39.6569 57 41 55.6569 41 54V43H66C67.6569 43 69 41.6569 69 40C69 38.3431 67.6569 37 66 37H41V26Z'),
|
|
1460
|
+
'1..n':new Path2D('M40 75C59.33 75 75 59.33 75 40C75 20.67 59.33 5 40 5C20.67 5 5 20.67 5 40C5 59.33 20.67 75 40 75ZM44.9862 42.9025L18.6801 58.0904C17.2452 58.9188 15.4105 58.4272 14.582 56.9923C13.7536 55.5574 14.2452 53.7227 15.6801 52.8942L32.9862 42.9025H17C15.3431 42.9025 14 41.5594 14 39.9025C14 38.2457 15.3431 36.9025 17 36.9025H32.9862L15.5981 26.5986C14.1632 25.7702 13.6716 23.9354 14.5 22.5005C15.3284 21.0656 17.1632 20.574 18.5981 21.4024L44.9862 36.9025H46V26C46 24.3431 47.3431 23 49 23C50.6569 23 52 24.3431 52 26V36.9025H68C69.6569 36.9025 71 38.2457 71 39.9025C71 41.5594 69.6569 42.9025 68 42.9025H52V53C52 54.6569 50.6569 56 49 56C47.3431 56 46 54.6569 46 53V42.9025H44.9862Z'),
|
|
1461
|
+
'0..1':new Path2D('M75 40C75 59.33 59.33 75 40 75C20.67 75 5 59.33 5 40C5 20.67 20.67 5 40 5C59.33 5 75 20.67 75 40ZM23 57C24.6569 57 26 55.6569 26 54V43H33.3781C34.7102 48.1757 39.4085 52 45 52C50.5915 52 55.2898 48.1757 56.6219 43H66C67.6569 43 69 41.6569 69 40C69 38.3431 67.6569 37 66 37H56.6219C55.2898 31.8243 50.5915 28 45 28C39.4085 28 34.7102 31.8243 33.3781 37H26V26C26 24.3431 24.6569 23 23 23C21.3431 23 20 24.3431 20 26V37H15C13.3431 37 12 38.3431 12 40C12 41.6569 13.3431 43 15 43H20V54C20 55.6569 21.3431 57 23 57ZM51 40C51 41.0929 50.7078 42.1175 50.1973 43C49.1599 44.7934 47.2208 46 45 46C42.7792 46 40.8401 44.7934 39.8027 43C39.2922 42.1175 39 41.0929 39 40C39 38.9071 39.2922 37.8825 39.8027 37C40.8401 35.2066 42.7792 34 45 34C47.2208 34 49.1599 35.2066 50.1973 37C50.7078 37.8825 51 38.9071 51 40Z'),
|
|
1462
|
+
'0..n':new Path2D('M40 75C59.33 75 75 59.33 75 40C75 20.67 59.33 5 40 5C20.67 5 5 20.67 5 40C5 59.33 20.67 75 40 75ZM17 42.9025C15.3431 42.9025 14 41.5594 14 39.9025C14 38.2457 15.3431 36.9025 17 36.9025H32.9862L15.5981 26.5986C14.1632 25.7702 13.6716 23.9354 14.5 22.5005C15.3284 21.0656 17.1632 20.574 18.5981 21.4024L45.4091 36.8818L45.4036 36.9025H45.445L45.4091 36.8818C46.7819 31.7662 51.4511 28 57 28C62.579 28 67.2687 31.8072 68.6129 36.9652C69.9758 37.2481 71 38.4557 71 39.9025C71 41.331 70.0017 42.5262 68.6647 42.8286C67.3931 48.091 62.6531 52 57 52C51.3734 52 46.6513 48.1276 45.3534 42.9025H44.9862L18.6801 58.0904C17.2452 58.9188 15.4105 58.4272 14.582 56.9923C13.7536 55.5574 14.2452 53.7227 15.6801 52.8942L32.9862 42.9025H17ZM62.1397 36.9025C61.0891 35.163 59.1804 34 57 34C54.8196 34 52.9109 35.163 51.8603 36.9025C51.3142 37.8067 51 38.8667 51 40C51 41.0529 51.2712 42.0424 51.7475 42.9025C52.7704 44.7496 54.7392 46 57 46C59.2608 46 61.2296 44.7496 62.2525 42.9025C62.7288 42.0424 63 41.0529 63 40C63 38.8667 62.6858 37.8067 62.1397 36.9025Z'),
|
|
1463
|
+
'n':new Path2D('M40 75C59.33 75 75 59.33 75 40C75 20.67 59.33 5 40 5C20.67 5 5 20.67 5 40C5 59.33 20.67 75 40 75ZM17 36.9025H33.0677L15.5981 26.5986C14.1632 25.7702 13.6716 23.9354 14.5 22.5005C15.3284 21.0656 17.1632 20.574 18.5981 21.4024L44.9862 36.9025H68C69.6569 36.9025 71 38.2457 71 39.9025C71 41.5594 69.6569 42.9025 68 42.9025H44.9862L18.6801 58.0904C17.2452 58.9188 15.4105 58.4272 14.582 56.9923C13.7536 55.5574 14.2452 53.7227 15.6801 52.8942L32.9862 42.9025H17C15.3431 42.9025 14 41.5594 14 39.9025C14 38.2457 15.3431 36.9025 17 36.9025Z')
|
|
1464
|
+
};
|
|
1465
|
+
|
|
1466
|
+
function pushBadge(x,y,ux,uy,car,col){
|
|
1467
|
+
badges.push({x:x,y:y,ux:ux,uy:uy,car:car,col:col});
|
|
1410
1468
|
}
|
|
1411
1469
|
|
|
1412
|
-
// Badge: solid colored circle; crow's foot (many) or double-bar (one) in dark ink inside.
|
|
1413
|
-
// ux,uy points outward from the node \u2014 badge is offset BO px into the gap.
|
|
1414
|
-
// Crow's foot tines open toward the node (-x in rotated frame), tip toward line (+x).
|
|
1415
1470
|
function drawBadge(b){
|
|
1416
|
-
var
|
|
1417
|
-
ctx.
|
|
1418
|
-
ctx.
|
|
1419
|
-
ctx.
|
|
1420
|
-
ctx.
|
|
1421
|
-
ctx.
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
ctx.moveTo(-5,4);ctx.lineTo(5,0); // lower tine \u2192 tip
|
|
1425
|
-
ctx.moveTo(-5,0);ctx.lineTo(5,0); // center line
|
|
1426
|
-
ctx.moveTo(-5,-4);ctx.lineTo(-5,4); // base bar (near node)
|
|
1427
|
-
}else{
|
|
1428
|
-
ctx.moveTo(-3,-5);ctx.lineTo(-3,5); // near bar
|
|
1429
|
-
ctx.moveTo(2,-5);ctx.lineTo(2,5); // far bar
|
|
1430
|
-
}
|
|
1431
|
-
ctx.stroke();
|
|
1471
|
+
var p=P2D[b.car]||P2D['n'],sc=BR/40;
|
|
1472
|
+
ctx.save();
|
|
1473
|
+
ctx.translate(b.x+b.ux*BO,b.y+b.uy*BO);
|
|
1474
|
+
ctx.rotate(Math.atan2(b.uy,b.ux));
|
|
1475
|
+
ctx.scale(sc,sc);
|
|
1476
|
+
ctx.translate(-40,-40);
|
|
1477
|
+
ctx.fillStyle=b.col;
|
|
1478
|
+
ctx.fill(p,'evenodd');
|
|
1432
1479
|
ctx.restore();
|
|
1433
1480
|
}
|
|
1434
1481
|
|
|
1435
1482
|
function ecol(t){return t==='one2one'?C.o2o:t==='many2many'?C.m2m:C.m2o;}
|
|
1436
1483
|
|
|
1437
1484
|
// offset: pixels to shift perpendicular to the edge direction (for parallel edges)
|
|
1438
|
-
function drawEdgeLine(a,b,col,dashed,label,
|
|
1485
|
+
function drawEdgeLine(a,b,col,dashed,label,carA,carB,off){
|
|
1439
1486
|
var p1=edgePt(a,b.x+b.w/2,b.y+b.h/2);
|
|
1440
1487
|
var p2=edgePt(b,a.x+a.w/2,a.y+a.h/2);
|
|
1441
1488
|
var dx=p2.x-p1.x,dy=p2.y-p1.y,d=Math.sqrt(dx*dx+dy*dy)||1;
|
|
@@ -1446,13 +1493,19 @@ function erDiagramSection(data) {
|
|
|
1446
1493
|
ctx.setLineDash(dashed?[5,4]:[]);
|
|
1447
1494
|
ctx.beginPath();ctx.moveTo(q1x,q1y);ctx.lineTo(q2x,q2y);ctx.stroke();
|
|
1448
1495
|
ctx.setLineDash([]);
|
|
1449
|
-
pushBadge(q1x,q1y,ux,uy,
|
|
1450
|
-
pushBadge(q2x,q2y,-ux,-uy,
|
|
1496
|
+
pushBadge(q1x,q1y,ux,uy,carA,col);
|
|
1497
|
+
pushBadge(q2x,q2y,-ux,-uy,carB,col);
|
|
1451
1498
|
if(label){
|
|
1452
1499
|
var mx=(q1x+q2x)/2,my=(q1y+q2y)/2;
|
|
1500
|
+
var la=Math.atan2(uy,ux);
|
|
1501
|
+
if(la>Math.PI/2||la<-Math.PI/2)la+=Math.PI; // keep text readable
|
|
1502
|
+
var ls=off>0?11:-11; // alternate above/below for parallel edges
|
|
1503
|
+
ctx.save();
|
|
1504
|
+
ctx.translate(mx,my);
|
|
1505
|
+
ctx.rotate(la);
|
|
1453
1506
|
ctx.fillStyle=C.mu;ctx.font='10px monospace';ctx.textAlign='center';ctx.textBaseline='middle';
|
|
1454
|
-
ctx.fillText(label,
|
|
1455
|
-
ctx.
|
|
1507
|
+
ctx.fillText(label,0,ls);
|
|
1508
|
+
ctx.restore();
|
|
1456
1509
|
}
|
|
1457
1510
|
}
|
|
1458
1511
|
|
|
@@ -1468,14 +1521,14 @@ function erDiagramSection(data) {
|
|
|
1468
1521
|
var col=ecol(r.type);
|
|
1469
1522
|
if(r.type==='many2many'&&r.through&&nIdx[r.through]!=null){
|
|
1470
1523
|
var sn=nodes[nIdx[r.source]],pn=nodes[nIdx[r.through]],tn=nodes[nIdx[r.target]];
|
|
1471
|
-
if(sn&&pn)edges.push({a:sn,b:pn,col:col,dashed:true,label:r.foreignKey||'',
|
|
1472
|
-
if(tn&&pn)edges.push({a:tn,b:pn,col:col,dashed:true,label:r.otherKey||'',
|
|
1524
|
+
if(sn&&pn)edges.push({a:sn,b:pn,col:col,dashed:true,label:r.foreignKey||'',carA:r.fkCarDirect||'1..1',carB:r.fkCarInverse||'0..n',off:0});
|
|
1525
|
+
if(tn&&pn)edges.push({a:tn,b:pn,col:col,dashed:true,label:r.otherKey||'',carA:r.otherCarDirect||'1..1',carB:r.otherCarInverse||'0..n',off:0});
|
|
1473
1526
|
}else{
|
|
1474
1527
|
var sn=nodes[nIdx[r.source]],tn=nodes[nIdx[r.target]];
|
|
1475
1528
|
if(sn&&tn&&sn!==tn){
|
|
1476
|
-
var
|
|
1477
|
-
var
|
|
1478
|
-
edges.push({a:sn,b:tn,col:col,dashed:false,label:r.label,
|
|
1529
|
+
var carA=r.carInverse||(r.type!=='one2one'?'1..n':'1..1');
|
|
1530
|
+
var carB=r.carDirect||(r.type==='many2many'?'1..n':'1..1');
|
|
1531
|
+
edges.push({a:sn,b:tn,col:col,dashed:false,label:r.label,carA:carA,carB:carB,off:0});
|
|
1479
1532
|
}
|
|
1480
1533
|
}
|
|
1481
1534
|
});
|
|
@@ -1534,12 +1587,24 @@ function erDiagramSection(data) {
|
|
|
1534
1587
|
|
|
1535
1588
|
function drawLegend(){
|
|
1536
1589
|
var cvW=cv.width/dpr,cvH=cv.height/dpr;
|
|
1537
|
-
var
|
|
1590
|
+
var yL=cvH-LGH,y1=yL+13,y2=yL+35,y3=yL+62;
|
|
1538
1591
|
|
|
1539
1592
|
ctx.fillStyle='#161b22';
|
|
1540
|
-
ctx.fillRect(0,
|
|
1593
|
+
ctx.fillRect(0,yL,cvW,LGH);
|
|
1541
1594
|
ctx.strokeStyle='#30363d';ctx.lineWidth=1;
|
|
1542
|
-
ctx.beginPath();ctx.moveTo(0,
|
|
1595
|
+
ctx.beginPath();ctx.moveTo(0,yL);ctx.lineTo(cvW,yL);ctx.stroke();
|
|
1596
|
+
|
|
1597
|
+
function legendBadge(cx,cy,car,col,angle){
|
|
1598
|
+
var p=P2D[car]||P2D['n'],sc=8/40;
|
|
1599
|
+
ctx.save();
|
|
1600
|
+
ctx.translate(cx,cy);
|
|
1601
|
+
ctx.rotate(angle);
|
|
1602
|
+
ctx.scale(sc,sc);
|
|
1603
|
+
ctx.translate(-40,-40);
|
|
1604
|
+
ctx.fillStyle=col;
|
|
1605
|
+
ctx.fill(p,'evenodd');
|
|
1606
|
+
ctx.restore();
|
|
1607
|
+
}
|
|
1543
1608
|
|
|
1544
1609
|
// Row 1: relation line types
|
|
1545
1610
|
var types=[
|
|
@@ -1558,42 +1623,43 @@ function erDiagramSection(data) {
|
|
|
1558
1623
|
x+=18+ctx.measureText(it.l).width+22;
|
|
1559
1624
|
});
|
|
1560
1625
|
|
|
1561
|
-
// Row 2:
|
|
1562
|
-
function badge2(cx,cy,many,col){
|
|
1563
|
-
ctx.fillStyle=col;ctx.strokeStyle=col;ctx.lineWidth=1;
|
|
1564
|
-
ctx.beginPath();ctx.arc(cx,cy,6,0,Math.PI*2);ctx.fill();
|
|
1565
|
-
ctx.save();ctx.translate(cx,cy);
|
|
1566
|
-
ctx.strokeStyle=C.bg;ctx.lineWidth=1.2;ctx.lineCap='round';
|
|
1567
|
-
ctx.beginPath();
|
|
1568
|
-
if(many){
|
|
1569
|
-
ctx.moveTo(-4,-3);ctx.lineTo(4,0);ctx.moveTo(-4,3);ctx.lineTo(4,0);
|
|
1570
|
-
ctx.moveTo(-4,0);ctx.lineTo(4,0);ctx.moveTo(-4,-3);ctx.lineTo(-4,3);
|
|
1571
|
-
}else{
|
|
1572
|
-
ctx.moveTo(-2,-3);ctx.lineTo(-2,3);ctx.moveTo(2,-3);ctx.lineTo(2,3);
|
|
1573
|
-
}
|
|
1574
|
-
ctx.stroke();ctx.restore();
|
|
1575
|
-
}
|
|
1576
|
-
|
|
1577
|
-
ctx.font='10px monospace';ctx.textAlign='left';ctx.textBaseline='middle';
|
|
1626
|
+
// Row 2: relation type examples (pair of badges + line)
|
|
1578
1627
|
x=14;
|
|
1579
1628
|
var pairs=[
|
|
1580
|
-
{
|
|
1581
|
-
{
|
|
1582
|
-
{
|
|
1629
|
+
{carA:'1..n',carB:'1..1',col:C.m2o,l:'many2one (N:1)'},
|
|
1630
|
+
{carA:'1..1',carB:'1..1',col:C.o2o,l:'one2one (1:1)'},
|
|
1631
|
+
{carA:'1..1',carB:'0..n',col:C.m2m,l:'pivot edge (1:0..N)'}
|
|
1583
1632
|
];
|
|
1584
1633
|
pairs.forEach(function(p){
|
|
1585
1634
|
ctx.strokeStyle=p.col+'55';ctx.lineWidth=1.2;ctx.setLineDash(p.col===C.m2m?[4,3]:[]);
|
|
1586
1635
|
ctx.beginPath();ctx.moveTo(x+8,y2);ctx.lineTo(x+56,y2);ctx.stroke();
|
|
1587
1636
|
ctx.setLineDash([]);
|
|
1588
|
-
|
|
1589
|
-
|
|
1637
|
+
legendBadge(x+8,y2,p.carA,p.col,0);
|
|
1638
|
+
legendBadge(x+56,y2,p.carB,p.col,Math.PI);
|
|
1590
1639
|
ctx.fillStyle=C.mu;ctx.font='10px monospace';ctx.textAlign='left';ctx.textBaseline='middle';
|
|
1591
1640
|
ctx.fillText(p.l,x+66,y2);
|
|
1592
1641
|
x+=66+ctx.measureText(p.l).width+24;
|
|
1593
1642
|
});
|
|
1594
1643
|
|
|
1644
|
+
// Row 3: individual symbol meanings
|
|
1645
|
+
var symbols=[
|
|
1646
|
+
{car:'1..1',l:'exactly one'},
|
|
1647
|
+
{car:'0..1',l:'zero or one'},
|
|
1648
|
+
{car:'1..n',l:'one or more'},
|
|
1649
|
+
{car:'0..n',l:'zero or more'},
|
|
1650
|
+
{car:'n', l:'many (pivot)'}
|
|
1651
|
+
];
|
|
1652
|
+
x=14;
|
|
1653
|
+
ctx.font='10px monospace';ctx.textAlign='left';ctx.textBaseline='middle';
|
|
1654
|
+
symbols.forEach(function(s){
|
|
1655
|
+
legendBadge(x+8,y3,s.car,C.mu,0);
|
|
1656
|
+
ctx.fillStyle=C.mu;
|
|
1657
|
+
ctx.fillText(s.l,x+20,y3);
|
|
1658
|
+
x+=20+ctx.measureText(s.l).width+18;
|
|
1659
|
+
});
|
|
1660
|
+
|
|
1595
1661
|
ctx.fillStyle='#3d444d';ctx.font='10px monospace';ctx.textAlign='right';ctx.textBaseline='middle';
|
|
1596
|
-
ctx.fillText('drag \xB7 scroll to zoom \xB7 dbl-click to reset',cvW-14,
|
|
1662
|
+
ctx.fillText('drag \xB7 scroll to zoom \xB7 dbl-click to reset',cvW-14,y1);
|
|
1597
1663
|
ctx.textBaseline='alphabetic';
|
|
1598
1664
|
}
|
|
1599
1665
|
|
|
@@ -1605,7 +1671,7 @@ function erDiagramSection(data) {
|
|
|
1605
1671
|
ctx.save();ctx.translate(pan.x,pan.y);ctx.scale(zm,zm);
|
|
1606
1672
|
badges=[];
|
|
1607
1673
|
var edges=collectEdges();
|
|
1608
|
-
edges.forEach(function(e){drawEdgeLine(e.a,e.b,e.col,e.dashed,e.label,e.
|
|
1674
|
+
edges.forEach(function(e){drawEdgeLine(e.a,e.b,e.col,e.dashed,e.label,e.carA,e.carB,e.off);});
|
|
1609
1675
|
nodes.forEach(function(n){drawNode(n);});
|
|
1610
1676
|
badges.forEach(function(b){drawBadge(b);});
|
|
1611
1677
|
ctx.restore();
|
|
@@ -1651,14 +1717,21 @@ function erDiagramSection(data) {
|
|
|
1651
1717
|
});
|
|
1652
1718
|
|
|
1653
1719
|
document.getElementById('er-dl').addEventListener('click',function(){
|
|
1654
|
-
var SCALE=3;
|
|
1655
|
-
var
|
|
1720
|
+
var SCALE=parseInt(document.getElementById('er-scale').value,10)||3;
|
|
1721
|
+
var pad=36;
|
|
1722
|
+
var bx0=1e9,by0=1e9,bx1=-1e9,by1=-1e9;
|
|
1723
|
+
nodes.forEach(function(n){
|
|
1724
|
+
bx0=Math.min(bx0,n.x);by0=Math.min(by0,n.y);
|
|
1725
|
+
bx1=Math.max(bx1,n.x+n.w);by1=Math.max(by1,n.y+n.h);
|
|
1726
|
+
});
|
|
1727
|
+
var cw=bx1-bx0+pad*2,ch=by1-by0+pad*2+LGH;
|
|
1656
1728
|
var oc=document.createElement('canvas');
|
|
1657
1729
|
oc.width=Math.round(cw*SCALE);oc.height=Math.round(ch*SCALE);
|
|
1658
|
-
var sav={cv:cv,ctx:ctx,dpr:dpr};
|
|
1730
|
+
var sav={cv:cv,ctx:ctx,dpr:dpr,px:pan.x,py:pan.y,zm:zm};
|
|
1659
1731
|
cv=oc;ctx=oc.getContext('2d');dpr=SCALE;
|
|
1732
|
+
pan.x=pad-bx0;pan.y=pad-by0;zm=1;
|
|
1660
1733
|
draw();
|
|
1661
|
-
cv=sav.cv;ctx=sav.ctx;dpr=sav.dpr;
|
|
1734
|
+
cv=sav.cv;ctx=sav.ctx;dpr=sav.dpr;pan.x=sav.px;pan.y=sav.py;zm=sav.zm;
|
|
1662
1735
|
var a=document.createElement('a');
|
|
1663
1736
|
a.download='er-diagram.png';a.href=oc.toDataURL('image/png');a.click();
|
|
1664
1737
|
});
|
|
@@ -1666,6 +1739,7 @@ function erDiagramSection(data) {
|
|
|
1666
1739
|
// \u2500\u2500 Init \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1667
1740
|
requestAnimationFrame(function(){
|
|
1668
1741
|
resize();
|
|
1742
|
+
computeNodeWidths();
|
|
1669
1743
|
var r=cv.getBoundingClientRect();
|
|
1670
1744
|
computeLayout(r.width,r.height);
|
|
1671
1745
|
var h=requiredH();
|