Canvas: Dynamic connection direction (#108423)

This commit is contained in:
Adela Almasan 2025-08-21 12:49:42 -05:00 committed by GitHub
parent e49230d3eb
commit 25c13c55c0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 747 additions and 77 deletions

View File

@ -25,7 +25,6 @@
"fiscalYearStartMonth": 0, "fiscalYearStartMonth": 0,
"graphTooltip": 0, "graphTooltip": 0,
"links": [], "links": [],
"liveNow": false,
"panels": [ "panels": [
{ {
"datasource": { "datasource": {
@ -42,7 +41,7 @@
"steps": [ "steps": [
{ {
"color": "green", "color": "green",
"value": null "value": 0
}, },
{ {
"color": "red", "color": "red",
@ -156,11 +155,19 @@
"x": -0.8229007633587786, "x": -0.8229007633587786,
"y": 0.27741935483870966 "y": 0.27741935483870966
}, },
"sourceOriginal": {
"x": 1223.8335877862596,
"y": 300
},
"target": { "target": {
"x": 1.0112359550561798, "x": 1.0112359550561798,
"y": -0.012987012987012988 "y": -0.012987012987012988
}, },
"targetName": "Element 20", "targetName": "Element 20",
"targetOriginal": {
"x": 990,
"y": 474
},
"vertices": [ "vertices": [
{ {
"x": -0.18181818181818182, "x": -0.18181818181818182,
@ -191,11 +198,19 @@
"x": -0.3068702290076336, "x": -0.3068702290076336,
"y": -0.47419354838709676 "y": -0.47419354838709676
}, },
"sourceOriginal": {
"x": 1401.090076335878,
"y": 533
},
"target": { "target": {
"x": 1.0112359550561798, "x": 1.0112359550561798,
"y": -0.4805194805194805 "y": -0.4805194805194805
}, },
"targetName": "Element 20", "targetName": "Element 20",
"targetOriginal": {
"x": 990,
"y": 492
},
"vertices": [ "vertices": [
{ {
"x": 0.415, "x": 0.415,
@ -227,11 +242,19 @@
"x": 0.7903930131004366, "x": 0.7903930131004366,
"y": -0.36935483870967745 "y": -0.36935483870967745
}, },
"sourceOriginal": {
"x": 1778,
"y": 500.5
},
"target": { "target": {
"x": 1.0120481927710843, "x": 1.0120481927710843,
"y": 0.024691358024691357 "y": 0.024691358024691357
}, },
"targetName": "Element 35", "targetName": "Element 35",
"targetOriginal": {
"x": 1523,
"y": 177
},
"vertices": [ "vertices": [
{ {
"x": -0.1607843137254902, "x": -0.1607843137254902,
@ -518,11 +541,19 @@
"x": 0.8876404494382022, "x": 0.8876404494382022,
"y": 0.2597402597402597 "y": 0.2597402597402597
}, },
"sourceOriginal": {
"x": 741,
"y": 356.5
},
"target": { "target": {
"x": -0.9775280898876404, "x": -0.9775280898876404,
"y": -0.012987012987012988 "y": -0.012987012987012988
}, },
"targetName": "Element 19" "targetName": "Element 19",
"targetOriginal": {
"x": 813,
"y": 358
}
} }
], ],
"constraint": { "constraint": {
@ -614,11 +645,19 @@
"x": 0.8876404494382022, "x": 0.8876404494382022,
"y": 0.2597402597402597 "y": 0.2597402597402597
}, },
"sourceOriginal": {
"x": 741,
"y": 239.5
},
"target": { "target": {
"x": -0.9887640449438202, "x": -0.9887640449438202,
"y": -0.06493506493506493 "y": -0.06493506493506493
}, },
"targetName": "Element 18" "targetName": "Element 18",
"targetOriginal": {
"x": 812,
"y": 242
}
} }
], ],
"constraint": { "constraint": {
@ -673,11 +712,19 @@
"x": -0.2247191011235955, "x": -0.2247191011235955,
"y": -0.4805194805194805 "y": -0.4805194805194805
}, },
"sourceOriginal": {
"x": 342,
"y": 395
},
"target": { "target": {
"x": 0.011235955056179775, "x": 0.011235955056179775,
"y": 0.922077922077922 "y": 0.922077922077922
}, },
"targetName": "Element 12" "targetName": "Element 12",
"targetOriginal": {
"x": 343,
"y": 458
}
}, },
{ {
"color": { "color": {
@ -693,11 +740,19 @@
"x": 0.7752808988764045, "x": 0.7752808988764045,
"y": 0.5194805194805194 "y": 0.5194805194805194
}, },
"sourceOriginal": {
"x": 431,
"y": 356.5
},
"target": { "target": {
"x": -1.0833333333333333, "x": -1.0833333333333333,
"y": 0.020618556701030927 "y": 0.020618556701030927
}, },
"targetName": "Element 15" "targetName": "Element 15",
"targetOriginal": {
"x": 526,
"y": 356
}
} }
], ],
"constraint": { "constraint": {
@ -888,11 +943,19 @@
"x": 1, "x": 1,
"y": 0.5 "y": 0.5
}, },
"sourceOriginal": {
"x": 431,
"y": 337.25
},
"target": { "target": {
"x": -1.0112359550561798, "x": -1.0112359550561798,
"y": -0.012987012987012988 "y": -0.012987012987012988
}, },
"targetName": "Element 16", "targetName": "Element 16",
"targetOriginal": {
"x": 562,
"y": 240
},
"vertices": [ "vertices": [
{ {
"x": 0.42748091603053434, "x": 0.42748091603053434,
@ -963,11 +1026,19 @@
"x": 0, "x": 0,
"y": -1 "y": -1
}, },
"sourceOriginal": {
"x": 150,
"y": 223
},
"target": { "target": {
"x": -0.5168539325842697, "x": -0.5168539325842697,
"y": 1 "y": 1
}, },
"targetName": "Element 6", "targetName": "Element 6",
"targetOriginal": {
"x": 296,
"y": 318
},
"vertices": [ "vertices": [
{ {
"x": 0, "x": 0,
@ -996,11 +1067,19 @@
"x": 0, "x": 0,
"y": 1 "y": 1
}, },
"sourceOriginal": {
"x": 150,
"y": 148
},
"target": { "target": {
"x": -1.0112359550561798, "x": -1.0112359550561798,
"y": -0.012987012987012988 "y": -0.012987012987012988
}, },
"targetName": "Element 30", "targetName": "Element 30",
"targetOriginal": {
"x": 563,
"y": 82
},
"vertices": [ "vertices": [
{ {
"x": 0, "x": 0,
@ -1062,11 +1141,19 @@
"x": 1, "x": 1,
"y": 0 "y": 0
}, },
"sourceOriginal": {
"x": 216,
"y": 356.5
},
"target": { "target": {
"x": -1.0112359550561798, "x": -1.0112359550561798,
"y": -0.012987012987012988 "y": -0.012987012987012988
}, },
"targetName": "Element 6" "targetName": "Element 6",
"targetOriginal": {
"x": 252,
"y": 357
}
} }
], ],
"constraint": { "constraint": {
@ -1121,11 +1208,19 @@
"x": 0, "x": 0,
"y": -1 "y": -1
}, },
"sourceOriginal": {
"x": 342,
"y": 223
},
"target": { "target": {
"x": 0.011235955056179775, "x": 0.011235955056179775,
"y": 1.025974025974026 "y": 1.025974025974026
}, },
"targetName": "Element 6" "targetName": "Element 6",
"targetOriginal": {
"x": 343,
"y": 317
}
} }
], ],
"constraint": { "constraint": {
@ -1218,11 +1313,19 @@
"x": -1, "x": -1,
"y": 0 "y": 0
}, },
"sourceOriginal": {
"x": 38,
"y": 493.5
},
"target": { "target": {
"x": -1.0224719101123596, "x": -1.0224719101123596,
"y": 0.013333333333333334 "y": 0.013333333333333334
}, },
"targetName": "Element 9", "targetName": "Element 9",
"targetOriginal": {
"x": 59,
"y": 185
},
"vertices": [ "vertices": [
{ {
"x": -0.9523809523809523, "x": -0.9523809523809523,
@ -1399,11 +1502,19 @@
"x": -1, "x": -1,
"y": 0 "y": 0
}, },
"sourceOriginal": {
"x": 563,
"y": 473.5
},
"target": { "target": {
"x": 1.0224719101123596, "x": 1.0224719101123596,
"y": -0.45454545454545453 "y": -0.45454545454545453
}, },
"targetName": "Element 6", "targetName": "Element 6",
"targetOriginal": {
"x": 433,
"y": 374
},
"vertices": [ "vertices": [
{ {
"x": 0.5692307692307692, "x": 0.5692307692307692,
@ -1468,11 +1579,19 @@
"x": 1, "x": 1,
"y": 0 "y": 0
}, },
"sourceOriginal": {
"x": 989,
"y": 239.5
},
"target": { "target": {
"x": -0.96, "x": -0.96,
"y": 0.4583333333333333 "y": 0.4583333333333333
}, },
"targetName": "Element 2", "targetName": "Element 2",
"targetOriginal": {
"x": 1392,
"y": 487
},
"vertices": [ "vertices": [
{ {
"x": 0.3349875930521092, "x": 0.3349875930521092,
@ -1506,11 +1625,19 @@
"x": 1, "x": 1,
"y": -0.5 "y": -0.5
}, },
"sourceOriginal": {
"x": 989,
"y": 258.75
},
"target": { "target": {
"x": -0.9761904761904762, "x": -0.9761904761904762,
"y": 0.5263157894736842 "y": 0.5263157894736842
}, },
"targetName": "Element 34", "targetName": "Element 34",
"targetOriginal": {
"x": 1138,
"y": 290
},
"vertices": [ "vertices": [
{ {
"x": 0.5033557046979866, "x": 0.5033557046979866,
@ -1612,11 +1739,19 @@
"x": -1, "x": -1,
"y": 0 "y": 0
}, },
"sourceOriginal": {
"x": 811,
"y": 473.5
},
"target": { "target": {
"x": 1.0112359550561798, "x": 1.0112359550561798,
"y": 0.012987012987012988 "y": 0.012987012987012988
}, },
"targetName": "Element 17" "targetName": "Element 17",
"targetOriginal": {
"x": 742,
"y": 473
}
} }
], ],
"constraint": { "constraint": {
@ -1671,11 +1806,19 @@
"x": 1, "x": 1,
"y": -0.5 "y": -0.5
}, },
"sourceOriginal": {
"x": 1025.5,
"y": 382.25
},
"target": { "target": {
"x": -1.0238095238095237, "x": -1.0238095238095237,
"y": 0.10526315789473684 "y": 0.10526315789473684
}, },
"targetName": "Element 33" "targetName": "Element 33",
"targetOriginal": {
"x": 1136,
"y": 384
}
}, },
{ {
"color": { "color": {
@ -1691,11 +1834,19 @@
"x": 1, "x": 1,
"y": 0.5 "y": 0.5
}, },
"sourceOriginal": {
"x": 1025.5,
"y": 333.75
},
"target": { "target": {
"x": -1, "x": -1,
"y": 0.05263157894736842 "y": 0.05263157894736842
}, },
"targetName": "Element 34", "targetName": "Element 34",
"targetOriginal": {
"x": 1137,
"y": 299
},
"vertices": [ "vertices": [
{ {
"x": 0.5246636771300448, "x": 0.5246636771300448,
@ -1721,11 +1872,19 @@
"x": 1, "x": 1,
"y": 0 "y": 0
}, },
"sourceOriginal": {
"x": 1025.5,
"y": 358
},
"target": { "target": {
"x": -0.98, "x": -0.98,
"y": -0.10416666666666667 "y": -0.10416666666666667
}, },
"targetName": "Element 2", "targetName": "Element 2",
"targetOriginal": {
"x": 1391,
"y": 514
},
"vertices": [ "vertices": [
{ {
"x": 0.7564979480164159, "x": 0.7564979480164159,
@ -1792,11 +1951,19 @@
"x": 1, "x": 1,
"y": 0 "y": 0
}, },
"sourceOriginal": {
"x": 742,
"y": 81.5
},
"target": { "target": {
"x": -1.0112359550561798, "x": -1.0112359550561798,
"y": -0.012987012987012988 "y": -0.012987012987012988
}, },
"targetName": "Element 31" "targetName": "Element 31",
"targetOriginal": {
"x": 811,
"y": 82
}
}, },
{ {
"color": { "color": {
@ -1818,11 +1985,19 @@
"x": 0, "x": 0,
"y": 1 "y": 1
}, },
"sourceOriginal": {
"x": 653,
"y": 43
},
"target": { "target": {
"x": -0.029411764705882353, "x": -0.029411764705882353,
"y": 1 "y": 1
}, },
"targetName": "Element 38", "targetName": "Element 38",
"targetOriginal": {
"x": 1673,
"y": 249
},
"vertices": [ "vertices": [
{ {
"x": 0, "x": 0,
@ -1892,11 +2067,19 @@
"x": 1, "x": 1,
"y": 0 "y": 0
}, },
"sourceOriginal": {
"x": 990,
"y": 81.5
},
"target": { "target": {
"x": -0.9939759036144579, "x": -0.9939759036144579,
"y": 0.5061728395061729 "y": 0.5061728395061729
}, },
"targetName": "Element 35", "targetName": "Element 35",
"targetOriginal": {
"x": 1190,
"y": 138
},
"vertices": [ "vertices": [
{ {
"x": 0.48, "x": 0.48,
@ -1961,11 +2144,19 @@
"x": 1, "x": 1,
"y": 0 "y": 0
}, },
"sourceOriginal": {
"x": 1221,
"y": 386
},
"target": { "target": {
"x": 1.0337078651685394, "x": 1.0337078651685394,
"y": 0.45454545454545453 "y": 0.45454545454545453
}, },
"targetName": "Element 20", "targetName": "Element 20",
"targetOriginal": {
"x": 992,
"y": 456
},
"vertices": [ "vertices": [
{ {
"x": -0.05240174672489083, "x": -0.05240174672489083,
@ -2067,11 +2258,19 @@
"x": -1, "x": -1,
"y": 0 "y": 0
}, },
"sourceOriginal": {
"x": 1189,
"y": 179
},
"target": { "target": {
"x": 0.19444444444444445, "x": 0.19444444444444445,
"y": 0.9896907216494846 "y": 0.9896907216494846
}, },
"targetName": "Element 29", "targetName": "Element 29",
"targetOriginal": {
"x": 1011,
"y": 310
},
"vertices": [ "vertices": [
{ {
"x": 1, "x": 1,
@ -2169,11 +2368,19 @@
"x": -1, "x": -1,
"y": 0 "y": 0
}, },
"sourceOriginal": {
"x": 1574,
"y": 308
},
"target": { "target": {
"x": -0.4977168949771689, "x": -0.4977168949771689,
"y": 1.017467248908297 "y": 1.017467248908297
}, },
"targetName": "Element 36", "targetName": "Element 36",
"targetOriginal": {
"x": 1450,
"y": 384
},
"vertices": [ "vertices": [
{ {
"x": 1, "x": 1,
@ -2208,7 +2415,7 @@
}, },
"showAdvancedTypes": true "showAdvancedTypes": true
}, },
"pluginVersion": "11.1.0-pre", "pluginVersion": "12.1.0-pre",
"targets": [ "targets": [
{ {
"datasource": { "datasource": {
@ -2236,7 +2443,8 @@
"mode": "absolute", "mode": "absolute",
"steps": [ "steps": [
{ {
"color": "green" "color": "green",
"value": 0
}, },
{ {
"color": "#6ED0E0", "color": "#6ED0E0",
@ -2260,7 +2468,7 @@
"h": 11, "h": 11,
"w": 15, "w": 15,
"x": 5, "x": 5,
"y": 22 "y": 36
}, },
"id": 4, "id": 4,
"options": { "options": {
@ -2320,11 +2528,19 @@
"x": 1, "x": 1,
"y": 0 "y": 0
}, },
"sourceOriginal": {
"x": 216,
"y": 192
},
"target": { "target": {
"x": -1.02020202020202, "x": -1.02020202020202,
"y": 0.041666666666666664 "y": 0.041666666666666664
}, },
"targetName": "Element 2" "targetName": "Element 2",
"targetOriginal": {
"x": 326,
"y": 191
}
} }
], ],
"constraint": { "constraint": {
@ -2377,11 +2593,19 @@
"x": 1, "x": 1,
"y": 1 "y": 1
}, },
"sourceOriginal": {
"x": 426,
"y": 168
},
"target": { "target": {
"x": -0.9797979797979798, "x": -0.9797979797979798,
"y": 0 "y": 0
}, },
"targetName": "Element 3" "targetName": "Element 3",
"targetOriginal": {
"x": 549,
"y": 93
}
}, },
{ {
"color": { "color": {
@ -2398,11 +2622,19 @@
"x": 1, "x": 1,
"y": -1 "y": -1
}, },
"sourceOriginal": {
"x": 426,
"y": 216
},
"target": { "target": {
"x": -1, "x": -1,
"y": 0.041666666666666664 "y": 0.041666666666666664
}, },
"targetName": "Element 4" "targetName": "Element 4",
"targetOriginal": {
"x": 549,
"y": 297
}
} }
], ],
"constraint": { "constraint": {
@ -2455,11 +2687,19 @@
"x": 1, "x": 1,
"y": 0 "y": 0
}, },
"sourceOriginal": {
"x": 647,
"y": 93
},
"target": { "target": {
"x": -0.9595959595959596, "x": -0.9595959595959596,
"y": -0.041666666666666664 "y": -0.041666666666666664
}, },
"targetName": "Element 11" "targetName": "Element 11",
"targetOriginal": {
"x": 799,
"y": 94
}
} }
], ],
"constraint": { "constraint": {
@ -2512,11 +2752,19 @@
"x": 1, "x": 1,
"y": 0 "y": 0
}, },
"sourceOriginal": {
"x": 648,
"y": 298
},
"target": { "target": {
"x": -0.9797979797979798, "x": -0.9797979797979798,
"y": 0.041666666666666664 "y": 0.041666666666666664
}, },
"targetName": "Element 5" "targetName": "Element 5",
"targetOriginal": {
"x": 798,
"y": 299
}
} }
], ],
"constraint": { "constraint": {
@ -2838,7 +3086,7 @@
}, },
"showAdvancedTypes": false "showAdvancedTypes": false
}, },
"pluginVersion": "11.1.0-pre", "pluginVersion": "12.1.0-pre",
"targets": [ "targets": [
{ {
"csvContent": "gateway, product-details, product-reviews, reviews-ratings, details-checkout\n100, 56, 44, 22, 28", "csvContent": "gateway, product-details, product-reviews, reviews-ratings, details-checkout\n100, 56, 44, 22, 28",
@ -2873,7 +3121,8 @@
"mode": "absolute", "mode": "absolute",
"steps": [ "steps": [
{ {
"color": "green" "color": "green",
"value": 0
}, },
{ {
"color": "blue", "color": "blue",
@ -2897,7 +3146,7 @@
"h": 15, "h": 15,
"w": 15, "w": 15,
"x": 5, "x": 5,
"y": 33 "y": 47
}, },
"id": 2, "id": 2,
"options": { "options": {
@ -2949,6 +3198,10 @@
"field": "server_database2", "field": "server_database2",
"fixed": "white" "fixed": "white"
}, },
"direction": {
"field": "server_database2",
"mode": "field"
},
"path": "straight", "path": "straight",
"size": { "size": {
"fixed": 2, "fixed": 2,
@ -2959,17 +3212,29 @@
"x": -1, "x": -1,
"y": -1 "y": -1
}, },
"sourceOriginal": {
"x": 468,
"y": 125
},
"target": { "target": {
"x": 0.030303030303030304, "x": 0.030303030303030304,
"y": 0 "y": 0
}, },
"targetName": "Element 11" "targetName": "Element 11",
"targetOriginal": {
"x": 285,
"y": 170
}
}, },
{ {
"color": { "color": {
"field": "server_database", "field": "server_database",
"fixed": "white" "fixed": "white"
}, },
"direction": {
"field": "server_database",
"mode": "field"
},
"path": "straight", "path": "straight",
"size": { "size": {
"fixed": 2, "fixed": 2,
@ -2980,17 +3245,29 @@
"x": -1, "x": -1,
"y": -1 "y": -1
}, },
"sourceOriginal": {
"x": 468,
"y": 125
},
"target": { "target": {
"x": 0.09090909090909091, "x": 0.09090909090909091,
"y": 0.16666666666666666 "y": 0.16666666666666666
}, },
"targetName": "Element 12" "targetName": "Element 12",
"targetOriginal": {
"x": 390,
"y": 264
}
}, },
{ {
"color": { "color": {
"field": "server_region", "field": "server_region",
"fixed": "white" "fixed": "white"
}, },
"direction": {
"field": "server_region",
"mode": "field"
},
"path": "straight", "path": "straight",
"size": { "size": {
"fixed": 2, "fixed": 2,
@ -3001,11 +3278,19 @@
"x": 1, "x": 1,
"y": -1 "y": -1
}, },
"sourceOriginal": {
"x": 588.9921875,
"y": 125
},
"target": { "target": {
"x": -0.2727272727272727, "x": -0.2727272727272727,
"y": 0.4166666666666667 "y": 0.4166666666666667
}, },
"targetName": "Element 13" "targetName": "Element 13",
"targetOriginal": {
"x": 658,
"y": 259
}
} }
], ],
"constraint": { "constraint": {
@ -3050,6 +3335,10 @@
"field": "database_server", "field": "database_server",
"fixed": "white" "fixed": "white"
}, },
"direction": {
"field": "database_server",
"mode": "field"
},
"path": "straight", "path": "straight",
"size": { "size": {
"fixed": 2, "fixed": 2,
@ -3060,11 +3349,19 @@
"x": 1, "x": 1,
"y": 1 "y": 1
}, },
"sourceOriginal": {
"x": 328,
"y": 367
},
"target": { "target": {
"x": -0.15151515151515152, "x": -0.15151515151515152,
"y": -0.16666666666666666 "y": -0.16666666666666666
}, },
"targetName": "Element 12" "targetName": "Element 12",
"targetOriginal": {
"x": 386,
"y": 268
}
} }
], ],
"constraint": { "constraint": {
@ -3109,6 +3406,10 @@
"field": "region_server", "field": "region_server",
"fixed": "white" "fixed": "white"
}, },
"direction": {
"field": "region_server",
"mode": "field"
},
"path": "straight", "path": "straight",
"size": { "size": {
"fixed": 2, "fixed": 2,
@ -3119,11 +3420,19 @@
"x": -1, "x": -1,
"y": 1 "y": 1
}, },
"sourceOriginal": {
"x": 699,
"y": 365
},
"target": { "target": {
"x": -0.21212121212121213, "x": -0.21212121212121213,
"y": 0.08333333333333333 "y": 0.08333333333333333
}, },
"targetName": "Element 13" "targetName": "Element 13",
"targetOriginal": {
"x": 659,
"y": 263
}
} }
], ],
"constraint": { "constraint": {
@ -3212,6 +3521,10 @@
"field": "database2_server", "field": "database2_server",
"fixed": "white" "fixed": "white"
}, },
"direction": {
"field": "database2_server",
"mode": "field"
},
"path": "straight", "path": "straight",
"size": { "size": {
"fixed": 2, "fixed": 2,
@ -3222,11 +3535,19 @@
"x": 1, "x": 1,
"y": 1 "y": 1
}, },
"sourceOriginal": {
"x": 172,
"y": 191
},
"target": { "target": {
"x": -0.3333333333333333, "x": -0.3333333333333333,
"y": 0 "y": 0
}, },
"targetName": "Element 11" "targetName": "Element 11",
"targetOriginal": {
"x": 279,
"y": 170
}
} }
], ],
"constraint": { "constraint": {
@ -3605,7 +3926,7 @@
}, },
"showAdvancedTypes": false "showAdvancedTypes": false
}, },
"pluginVersion": "11.1.0-pre", "pluginVersion": "12.1.0-pre",
"targets": [ "targets": [
{ {
"csvContent": "database_server, server_database, server_region, region_server, database2_server, server_database2\n10, 53, 35, 12, 22, 81", "csvContent": "database_server, server_database, server_region, region_server, database2_server, server_database2\n10, 53, 35, 12, 22, 81",
@ -3620,9 +3941,9 @@
"type": "canvas" "type": "canvas"
} }
], ],
"preload": false,
"refresh": "", "refresh": "",
"revision": 1, "schemaVersion": 41,
"schemaVersion": 39,
"tags": ["gdev", "panel-tests", "canvas"], "tags": ["gdev", "panel-tests", "canvas"],
"templating": { "templating": {
"list": [] "list": []
@ -3631,11 +3952,9 @@
"from": "now-6h", "from": "now-6h",
"to": "now" "to": "now"
}, },
"timeRangeUpdatedDuringEditOrView": false,
"timepicker": {}, "timepicker": {},
"timezone": "", "timezone": "",
"title": "Panel Tests - Canvas Connection Examples", "title": "Panel Tests - Canvas Connection Examples",
"uid": "Pu8lwQAVz", "uid": "Pu8lwQAVz",
"version": 6, "version": 15
"weekStart": ""
} }

View File

@ -481,12 +481,19 @@ You can style the selected connection using the following options:
- **Color** - Set the connection color. - **Color** - Set the connection color.
- **Size** - Control the size of the connection by entering a number in the **Value** field. - **Size** - Control the size of the connection by entering a number in the **Value** field.
- **Radius** - Add curve to the connection by entering a value to represent the degree. - **Radius** - Add curve to the connection by entering a value to represent the degree.
- **Arrow Direction** - Control the appearance of the arrow head. Choose from: - **Direction** - Control the appearance of the arrow head. Choose your source from **Fixed** or **Field**. The default value is **Forward** regardless of the source type.
If the direction source is **Fixed**, choose from:
- **Forward** - The arrow head points in the direction in which the connection was drawn. - **Forward** - The arrow head points in the direction in which the connection was drawn.
- **Reverse** - The arrow head points in the opposite direction of which the connection was drawn. - **Reverse** - The arrow head points in the opposite direction of which the connection was drawn.
- **Both** - Adds arrow heads to both ends of the connection. - **Both** - Adds arrow heads to both ends of the connection.
- **None** - Removes the arrow head. - **None** - Removes the arrow head.
If the direction source is **Field**, select a field that contains numeric values:
- **Positive values** - Display forward arrows.
- **Negative values** - Display reverse arrows.
- **Zero** - Display no arrow heads.
- **Line style** - Choose from the following line styles: **Solid**, **Dashed**, and **Dotted**. - **Line style** - Choose from the following line styles: **Solid**, **Dashed**, and **Dotted**.
### Standard options ### Standard options

View File

@ -111,6 +111,26 @@ export enum ResourceDimensionMode {
Mapping = 'mapping', Mapping = 'mapping',
} }
/**
* Links to a resource (image/svg path)
*/
export interface ResourceDimensionConfig extends BaseDimensionConfig {
fixed?: string;
mode: ResourceDimensionMode;
}
export enum ConnectionDirection {
Both = 'both',
Forward = 'forward',
None = 'none',
Reverse = 'reverse',
}
export enum DirectionDimensionMode {
Field = 'field',
Fixed = 'fixed',
}
export interface MapLayerOptions { export interface MapLayerOptions {
/** /**
* Custom options depending on the type * Custom options depending on the type
@ -899,12 +919,9 @@ export interface DataSourceRef {
uid?: string; uid?: string;
} }
/** export interface DirectionDimensionConfig extends BaseDimensionConfig {
* Links to a resource (image/svg path) fixed?: ConnectionDirection;
*/ mode: DirectionDimensionMode;
export interface ResourceDimensionConfig extends BaseDimensionConfig {
fixed?: string;
mode: ResourceDimensionMode;
} }
export interface FrameGeometrySource { export interface FrameGeometrySource {

View File

@ -35,7 +35,7 @@ TextDimensionMode: "fixed" | "field" | "template" @cuetsy(kind="enum")
TextDimensionConfig: { TextDimensionConfig: {
BaseDimensionConfig BaseDimensionConfig
mode: TextDimensionMode mode: TextDimensionMode
fixed?: string fixed?: string
}@cuetsy(kind="interface") }@cuetsy(kind="interface")
ResourceDimensionMode: "fixed" | "field" | "mapping" @cuetsy(kind="enum") ResourceDimensionMode: "fixed" | "field" | "mapping" @cuetsy(kind="enum")
@ -44,6 +44,15 @@ ResourceDimensionMode: "fixed" | "field" | "mapping" @cuetsy(kind="enum")
ResourceDimensionConfig: { ResourceDimensionConfig: {
BaseDimensionConfig BaseDimensionConfig
mode: ResourceDimensionMode mode: ResourceDimensionMode
fixed?: string fixed?: string
}@cuetsy(kind="interface") }@cuetsy(kind="interface")
ConnectionDirection: "forward" | "reverse" | "both" | "none" @cuetsy(kind="enum", memberNames="Forward|Reverse|Both|None")
DirectionDimensionMode: "fixed" | "field" @cuetsy(kind="enum")
DirectionDimensionConfig: {
BaseDimensionConfig
mode: DirectionDimensionMode
fixed?: ConnectionDirection
}@cuetsy(kind="interface")

View File

@ -80,6 +80,7 @@ export enum ConnectionPath {
export interface CanvasConnection { export interface CanvasConnection {
color?: ui.ColorDimensionConfig; color?: ui.ColorDimensionConfig;
direction?: ui.DirectionDimensionConfig;
path: ConnectionPath; path: ConnectionPath;
size?: ui.ScaleDimensionConfig; size?: ui.ScaleDimensionConfig;
source: ConnectionCoordinates; source: ConnectionCoordinates;

View File

@ -2,7 +2,7 @@ import { ComponentType } from 'react';
import { DataLink, RegistryItem, Action } from '@grafana/data'; import { DataLink, RegistryItem, Action } from '@grafana/data';
import { PanelOptionsSupplier } from '@grafana/data/internal'; import { PanelOptionsSupplier } from '@grafana/data/internal';
import { ColorDimensionConfig, ScaleDimensionConfig } from '@grafana/schema'; import { ColorDimensionConfig, ScaleDimensionConfig, DirectionDimensionConfig } from '@grafana/schema';
import { config } from 'app/core/config'; import { config } from 'app/core/config';
import { BackgroundConfig, Constraint, LineConfig, Placement } from 'app/plugins/panel/canvas/panelcfg.gen'; import { BackgroundConfig, Constraint, LineConfig, Placement } from 'app/plugins/panel/canvas/panelcfg.gen';
@ -64,7 +64,7 @@ export interface CanvasConnection {
lineStyle?: LineStyleConfig; lineStyle?: LineStyleConfig;
vertices?: ConnectionCoordinates[]; vertices?: ConnectionCoordinates[];
radius?: ScaleDimensionConfig; radius?: ScaleDimensionConfig;
direction?: ConnectionDirection; direction?: DirectionDimensionConfig;
sourceOriginal?: ConnectionCoordinates; sourceOriginal?: ConnectionCoordinates;
targetOriginal?: ConnectionCoordinates; targetOriginal?: ConnectionCoordinates;
// See https://github.com/anseki/leader-line#options for more examples of more properties // See https://github.com/anseki/leader-line#options for more examples of more properties

View File

@ -14,6 +14,7 @@ import {
ScaleDimensionConfig, ScaleDimensionConfig,
TextDimensionConfig, TextDimensionConfig,
TooltipDisplayMode, TooltipDisplayMode,
DirectionDimensionConfig,
} from '@grafana/schema'; } from '@grafana/schema';
import { Portal } from '@grafana/ui'; import { Portal } from '@grafana/ui';
import { config } from 'app/core/config'; import { config } from 'app/core/config';
@ -24,6 +25,7 @@ import {
getScalarDimensionFromData, getScalarDimensionFromData,
getScaleDimensionFromData, getScaleDimensionFromData,
getTextDimensionFromData, getTextDimensionFromData,
getDirectionDimensionFromData,
} from 'app/features/dimensions/utils'; } from 'app/features/dimensions/utils';
import { CanvasContextMenu } from 'app/plugins/panel/canvas/components/CanvasContextMenu'; import { CanvasContextMenu } from 'app/plugins/panel/canvas/components/CanvasContextMenu';
import { CanvasTooltip } from 'app/plugins/panel/canvas/components/CanvasTooltip'; import { CanvasTooltip } from 'app/plugins/panel/canvas/components/CanvasTooltip';
@ -207,6 +209,7 @@ export class Scene {
getScalar: (scalar: ScalarDimensionConfig) => getScalarDimensionFromData(this.data, scalar), getScalar: (scalar: ScalarDimensionConfig) => getScalarDimensionFromData(this.data, scalar),
getText: (text: TextDimensionConfig) => getTextDimensionFromData(this.data, text), getText: (text: TextDimensionConfig) => getTextDimensionFromData(this.data, text),
getResource: (res: ResourceDimensionConfig) => getResourceDimensionFromData(this.data, res), getResource: (res: ResourceDimensionConfig) => getResourceDimensionFromData(this.data, res),
getDirection: (direction: DirectionDimensionConfig) => getDirectionDimensionFromData(this.data, direction),
getPanelData: () => this.data, getPanelData: () => this.data,
}; };

View File

@ -5,15 +5,24 @@ import {
ScalarDimensionConfig, ScalarDimensionConfig,
ScaleDimensionConfig, ScaleDimensionConfig,
TextDimensionConfig, TextDimensionConfig,
DirectionDimensionConfig,
ConnectionDirection,
} from '@grafana/schema'; } from '@grafana/schema';
import { DimensionSupplier } from './types'; import { DimensionSupplier } from './types';
export interface DimensionContext { export interface DimensionContext {
getColor(color: ColorDimensionConfig): DimensionSupplier<string>; getColor(color: ColorDimensionConfig): DimensionSupplier<string>;
getScale(scale: ScaleDimensionConfig): DimensionSupplier<number>; getScale(scale: ScaleDimensionConfig): DimensionSupplier<number>;
getScalar(scalar: ScalarDimensionConfig): DimensionSupplier<number>; getScalar(scalar: ScalarDimensionConfig): DimensionSupplier<number>;
getText(text: TextDimensionConfig): DimensionSupplier<string>; getText(text: TextDimensionConfig): DimensionSupplier<string>;
getResource(resource: ResourceDimensionConfig): DimensionSupplier<string>; getResource(resource: ResourceDimensionConfig): DimensionSupplier<string>;
getDirection(direction: DirectionDimensionConfig): DimensionSupplier<ConnectionDirection>;
getPanelData(): PanelData | undefined; getPanelData(): PanelData | undefined;
} }

View File

@ -0,0 +1,58 @@
import { DataFrame, Field } from '@grafana/data';
import { ConnectionDirection, DirectionDimensionConfig, DirectionDimensionMode } from '@grafana/schema';
import { DimensionSupplier } from './types';
import { findField, getLastNotNullFieldValue } from './utils';
//---------------------------------------------------------
// Direction dimension
//---------------------------------------------------------
export function getDirectionDimension(
frame: DataFrame | undefined,
config: DirectionDimensionConfig
): DimensionSupplier<ConnectionDirection> {
return getDirectionDimensionForField(findField(frame, config.field), config);
}
export function getDirectionDimensionForField(
field: Field | undefined,
config: DirectionDimensionConfig
): DimensionSupplier<ConnectionDirection> {
const mode = config.mode ?? DirectionDimensionMode.Fixed;
if (mode === DirectionDimensionMode.Fixed || !field) {
const v = config.fixed ?? ConnectionDirection.Forward;
return {
isAssumed: Boolean(config.field?.length) || !config.fixed,
fixed: v,
value: () => v,
get: () => v,
};
}
const getDirectionFromValue = (value: unknown): ConnectionDirection => {
if (value == null) {
return ConnectionDirection.Forward;
}
const numValue = Number(value);
if (isNaN(numValue)) {
return ConnectionDirection.Forward;
}
if (numValue > 0) {
return ConnectionDirection.Forward;
} else if (numValue < 0) {
return ConnectionDirection.Reverse;
} else {
return ConnectionDirection.None;
}
};
return {
field,
get: (index: number): ConnectionDirection => getDirectionFromValue(field.values[index]),
value: () => getDirectionFromValue(getLastNotNullFieldValue(field)),
};
}

View File

@ -0,0 +1,122 @@
import { useCallback } from 'react';
import {
FieldNamePickerConfigSettings,
SelectableValue,
StandardEditorProps,
StandardEditorsRegistryItem,
} from '@grafana/data';
import { t } from '@grafana/i18n';
import { DirectionDimensionMode, DirectionDimensionConfig, ConnectionDirection } from '@grafana/schema';
import { InlineField, InlineFieldRow, RadioButtonGroup, Select } from '@grafana/ui';
import { FieldNamePicker } from '@grafana/ui/internal';
import { DirectionDimensionOptions } from '../types';
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const dummyFieldSettings = {
settings: {},
} as StandardEditorsRegistryItem<string, FieldNamePickerConfigSettings>;
type Props = StandardEditorProps<DirectionDimensionConfig, DirectionDimensionOptions>;
export const DirectionDimensionEditor = ({ value, context, onChange }: Props) => {
const directionOptions = [
{
label: t('dimensions.direction-dimension-editor.label-fixed', 'Fixed'),
value: DirectionDimensionMode.Fixed,
description: t('dimensions.direction-dimension-editor.description-fixed', 'Fixed direction value'),
},
{
label: t('dimensions.direction-dimension-editor.label-field', 'Field'),
value: DirectionDimensionMode.Field,
description: t('dimensions.direction-dimension-editor.description-field', 'Direction based on field value'),
},
];
const fixedDirectionOptions: Array<SelectableValue<ConnectionDirection>> = [
{ value: ConnectionDirection.Forward, label: t('canvas.connection.direction-options.label-forward', 'Forward') },
{ value: ConnectionDirection.Reverse, label: t('canvas.connection.direction-options.label-reverse', 'Reverse') },
{ value: ConnectionDirection.Both, label: t('canvas.connection.direction-options.label-both', 'Both') },
{ value: ConnectionDirection.None, label: t('canvas.connection.direction-options.label-none', 'None') },
];
const labelWidth = 9;
const onModeChange = useCallback(
(mode: DirectionDimensionMode) => {
onChange({
...value,
mode,
});
},
[onChange, value]
);
const onFieldChange = useCallback(
(field?: string) => {
onChange({
...value,
field,
});
},
[onChange, value]
);
const onFixedChange = useCallback(
(selection: SelectableValue<ConnectionDirection>) => {
onChange({
...value,
field: undefined,
fixed: selection.value ?? ConnectionDirection.Forward,
});
},
[onChange, value]
);
const mode = value?.mode ?? DirectionDimensionMode.Fixed;
const selectedDirection = fixedDirectionOptions.find((opt) => opt.value === value?.fixed) || fixedDirectionOptions[0];
return (
<>
<InlineFieldRow>
<InlineField
label={t('dimensions.direction-dimension-editor.label-source', 'Source')}
labelWidth={labelWidth}
grow={true}
>
<RadioButtonGroup value={mode} options={directionOptions} onChange={onModeChange} fullWidth />
</InlineField>
</InlineFieldRow>
{mode === DirectionDimensionMode.Field && (
<InlineFieldRow>
<InlineField
label={t('dimensions.direction-dimension-editor.label-field', 'Field')}
labelWidth={labelWidth}
grow={true}
>
<FieldNamePicker
context={context}
value={value?.field ?? ''}
onChange={onFieldChange}
item={dummyFieldSettings}
/>
</InlineField>
</InlineFieldRow>
)}
{mode === DirectionDimensionMode.Fixed && (
<InlineFieldRow>
<InlineField
label={t('dimensions.direction-dimension-editor.label-direction', 'Direction')}
labelWidth={labelWidth}
grow={true}
>
<Select value={selectedDirection} options={fixedDirectionOptions} onChange={onFixedChange} />
</InlineField>
</InlineFieldRow>
)}
</>
);
};

View File

@ -85,3 +85,5 @@ export enum ResourcePickerSize {
SMALL = 'small', SMALL = 'small',
NORMAL = 'normal', NORMAL = 'normal',
} }
export interface DirectionDimensionOptions {}

View File

@ -6,9 +6,12 @@ import {
TextDimensionConfig, TextDimensionConfig,
ColorDimensionConfig, ColorDimensionConfig,
ScalarDimensionConfig, ScalarDimensionConfig,
DirectionDimensionConfig,
ConnectionDirection,
} from '@grafana/schema'; } from '@grafana/schema';
import { getColorDimension } from './color'; import { getColorDimension } from './color';
import { getDirectionDimension } from './direction';
import { getResourceDimension } from './resource'; import { getResourceDimension } from './resource';
import { getScalarDimension } from './scalar'; import { getScalarDimension } from './scalar';
import { getScaledDimension } from './scale'; import { getScaledDimension } from './scale';
@ -30,6 +33,21 @@ export function getColorDimensionFromData(
return getColorDimension(undefined, cfg, config.theme2); return getColorDimension(undefined, cfg, config.theme2);
} }
export function getDirectionDimensionFromData(
data: PanelData | undefined,
cfg: DirectionDimensionConfig
): DimensionSupplier<ConnectionDirection> {
if (data?.series && cfg.field) {
for (const frame of data.series) {
const d = getDirectionDimension(frame, cfg);
if (!d.isAssumed || data.series.length === 1) {
return d;
}
}
}
return getDirectionDimension(undefined, cfg);
}
export function getScaleDimensionFromData( export function getScaleDimensionFromData(
data: PanelData | undefined, data: PanelData | undefined,
cfg: ScaleDimensionConfig cfg: ScaleDimensionConfig

View File

@ -2,9 +2,9 @@ import { css } from '@emotion/css';
import { useEffect, useMemo, useRef, useState } from 'react'; import { useEffect, useMemo, useRef, useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { DirectionDimensionConfig, DirectionDimensionMode, ConnectionDirection } from '@grafana/schema';
import { useStyles2 } from '@grafana/ui'; import { useStyles2 } from '@grafana/ui';
import { config } from 'app/core/config'; import { config } from 'app/core/config';
import { ConnectionDirection } from 'app/features/canvas/element';
import { Scene } from 'app/features/canvas/runtime/scene'; import { Scene } from 'app/features/canvas/runtime/scene';
import { ConnectionCoordinates } from '../../panelcfg.gen'; import { ConnectionCoordinates } from '../../panelcfg.gen';
@ -47,7 +47,10 @@ export const ConnectionSVG = ({
const EDITOR_HEAD_ID = useMemo(() => `editorHead-${headId}`, [headId]); const EDITOR_HEAD_ID = useMemo(() => `editorHead-${headId}`, [headId]);
const defaultArrowColor = config.theme2.colors.text.primary; const defaultArrowColor = config.theme2.colors.text.primary;
const defaultArrowSize = 2; const defaultArrowSize = 2;
const defaultArrowDirection = ConnectionDirection.Forward; const defaultArrowDirection: DirectionDimensionConfig = {
mode: DirectionDimensionMode.Fixed,
fixed: ConnectionDirection.Forward,
};
const maximumVertices = 10; const maximumVertices = 10;
const [selectedConnection, setSelectedConnection] = useState<ConnectionState | undefined>(undefined); const [selectedConnection, setSelectedConnection] = useState<ConnectionState | undefined>(undefined);
@ -162,7 +165,12 @@ export const ConnectionSVG = ({
const yDist = yEnd - yStart; const yDist = yEnd - yStart;
const { strokeColor, strokeWidth, strokeRadius, arrowDirection, lineStyle, shouldAnimate } = const { strokeColor, strokeWidth, strokeRadius, arrowDirection, lineStyle, shouldAnimate } =
getConnectionStyles(info, scene, defaultArrowSize, defaultArrowDirection); getConnectionStyles(
info,
scene,
defaultArrowSize,
defaultArrowDirection.fixed ?? ConnectionDirection.Forward
);
const isSelected = selectedConnection === v && scene.panel.context.instanceState.selectedConnection; const isSelected = selectedConnection === v && scene.panel.context.instanceState.selectedConnection;

View File

@ -2,9 +2,9 @@ import { css } from '@emotion/css';
import { useEffect, useMemo, useRef, useState } from 'react'; import { useEffect, useMemo, useRef, useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { DirectionDimensionConfig, DirectionDimensionMode, ConnectionDirection } from '@grafana/schema';
import { useStyles2 } from '@grafana/ui'; import { useStyles2 } from '@grafana/ui';
import { config } from 'app/core/config'; import { config } from 'app/core/config';
import { ConnectionDirection } from 'app/features/canvas/element';
import { Scene } from 'app/features/canvas/runtime/scene'; import { Scene } from 'app/features/canvas/runtime/scene';
import { ConnectionCoordinates } from '../../panelcfg.gen'; import { ConnectionCoordinates } from '../../panelcfg.gen';
@ -38,7 +38,10 @@ export const ConnectionSVG = ({ setLineRef, setVertexPathRef, setVertexRef, setC
const EDITOR_HEAD_ID = useMemo(() => `editorHead-${headId}`, [headId]); const EDITOR_HEAD_ID = useMemo(() => `editorHead-${headId}`, [headId]);
const defaultArrowColor = config.theme2.colors.text.primary; const defaultArrowColor = config.theme2.colors.text.primary;
const defaultArrowSize = 2; const defaultArrowSize = 2;
const defaultArrowDirection = ConnectionDirection.Forward; const defaultArrowDirection: DirectionDimensionConfig = {
mode: DirectionDimensionMode.Fixed,
fixed: ConnectionDirection.Forward,
};
const maximumVertices = 10; const maximumVertices = 10;
const [selectedConnection, setSelectedConnection] = useState<ConnectionState | undefined>(undefined); const [selectedConnection, setSelectedConnection] = useState<ConnectionState | undefined>(undefined);
@ -152,7 +155,12 @@ export const ConnectionSVG = ({ setLineRef, setVertexPathRef, setVertexRef, setC
const yDist = yEnd - yStart; const yDist = yEnd - yStart;
const { strokeColor, strokeWidth, strokeRadius, arrowDirection, lineStyle, shouldAnimate } = const { strokeColor, strokeWidth, strokeRadius, arrowDirection, lineStyle, shouldAnimate } =
getConnectionStyles(info, scene, defaultArrowSize, defaultArrowDirection); getConnectionStyles(
info,
scene,
defaultArrowSize,
defaultArrowDirection.fixed ?? ConnectionDirection.Forward
);
const isSelected = selectedConnection === v && scene.panel.context.instanceState.selectedConnection; const isSelected = selectedConnection === v && scene.panel.context.instanceState.selectedConnection;

View File

@ -1,10 +1,11 @@
import { FieldType } from '@grafana/data'; import { FieldType } from '@grafana/data';
import { PanelOptionsSupplier } from '@grafana/data/internal'; import { PanelOptionsSupplier } from '@grafana/data/internal';
import { t } from '@grafana/i18n'; import { t } from '@grafana/i18n';
import { ConnectionDirection } from 'app/features/canvas/element'; import { ConnectionDirection, DirectionDimensionMode } from '@grafana/schema';
import { SVGElements } from 'app/features/canvas/runtime/element'; import { SVGElements } from 'app/features/canvas/runtime/element';
import { BackgroundSizeEditor } from 'app/features/dimensions/editors/BackgroundSizeEditor'; import { BackgroundSizeEditor } from 'app/features/dimensions/editors/BackgroundSizeEditor';
import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimensionEditor'; import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimensionEditor';
import { DirectionDimensionEditor } from 'app/features/dimensions/editors/DirectionDimensionEditor';
import { ResourceDimensionEditor } from 'app/features/dimensions/editors/ResourceDimensionEditor'; import { ResourceDimensionEditor } from 'app/features/dimensions/editors/ResourceDimensionEditor';
import { ScaleDimensionEditor } from 'app/features/dimensions/editors/ScaleDimensionEditor'; import { ScaleDimensionEditor } from 'app/features/dimensions/editors/ScaleDimensionEditor';
@ -181,20 +182,18 @@ export const optionBuilder: OptionSuppliers = {
}, },
addDirection: (builder, context) => { addDirection: (builder, context) => {
const category = [t('canvas.category-arrow-direction', 'Arrow Direction')]; const category = [t('canvas.category-arrow-direction', 'Direction')];
builder.addRadio({ builder.addCustomEditor({
category, category,
id: 'direction',
path: 'direction', path: 'direction',
name: t('canvas.label-direction', 'Direction'), name: t('canvas.label-direction', 'Direction'),
settings: { editor: DirectionDimensionEditor,
options: [ settings: {},
{ value: undefined, label: t('canvas.direction-options.label-forward', 'Forward') }, defaultValue: {
{ value: ConnectionDirection.Reverse, label: t('canvas.direction-options.label-reverse', 'Reverse') }, mode: DirectionDimensionMode.Fixed,
{ value: ConnectionDirection.Both, label: t('canvas.direction-options.label-both', 'Both') }, fixed: ConnectionDirection.Forward,
{ value: ConnectionDirection.None, label: t('canvas.direction-options.label-none', 'None') },
],
}, },
defaultValue: ConnectionDirection.Forward,
}); });
}, },

View File

@ -42,4 +42,57 @@ describe('Canvas migration', () => {
expect(panel.options.root.elements[0].links[0].oneClick).toBe(true); expect(panel.options.root.elements[0].links[0].oneClick).toBe(true);
expect(panel.options.root.elements[0].actions[0].fetch.url).toBe('http://test.com'); expect(panel.options.root.elements[0].actions[0].fetch.url).toBe('http://test.com');
}); });
it('should migrate connection direction from string to object format', () => {
const panel = {
type: 'canvas',
options: {
root: {
elements: [
{
name: 'Element 1',
type: 'rectangle',
connections: [
{
direction: 'forward',
target: 'element2',
},
{
direction: 'reverse',
target: 'element3',
},
{
direction: 'both',
target: 'element4',
},
{
target: 'element5',
},
],
},
],
},
},
pluginVersion: '12.1',
} as unknown as PanelModel;
panel.options = canvasMigrationHandler(panel);
const connectionsElement1 = panel.options.root.elements[0].connections;
expect(connectionsElement1[0].direction).toEqual({
mode: 'fixed',
fixed: 'forward',
});
expect(connectionsElement1[1].direction).toEqual({
mode: 'fixed',
fixed: 'reverse',
});
expect(connectionsElement1[2].direction).toEqual({
mode: 'fixed',
fixed: 'both',
});
});
}); });

View File

@ -76,5 +76,28 @@ export const canvasMigrationHandler = (panel: PanelModel): Partial<Options> => {
} }
} }
// migrate connection direction
if (parseFloat(pluginVersion) <= 12.2) {
const root = panel.options?.root;
if (root?.elements) {
for (const element of root.elements) {
for (const connection of element.connections || []) {
if (connection.direction && typeof connection.direction === 'string') {
// convert old direction to new format
connection.direction = {
mode: 'fixed',
fixed: connection.direction,
};
} else if (!connection.direction) {
connection.direction = {
mode: 'fixed',
fixed: 'forward',
};
}
}
}
}
}
return panel.options; return panel.options;
}; };

View File

@ -97,7 +97,10 @@ export const plugin = new PanelPlugin<Options>(CanvasPanel)
}, },
}, },
}) })
.setMigrationHandler(canvasMigrationHandler) .setMigrationHandler(canvasMigrationHandler, (panel) => {
const pluginVersion = panel?.pluginVersion ?? '';
return parseFloat(pluginVersion) <= 12.2;
})
.setPanelOptions((builder, context) => { .setPanelOptions((builder, context) => {
const state: InstanceState = context.instanceState; const state: InstanceState = context.instanceState;

View File

@ -73,6 +73,7 @@ composableKinds: PanelCfg: {
path: ConnectionPath path: ConnectionPath
color?: ui.ColorDimensionConfig color?: ui.ColorDimensionConfig
size?: ui.ScaleDimensionConfig size?: ui.ScaleDimensionConfig
direction?: ui.DirectionDimensionConfig
vertices?: [...ConnectionCoordinates] vertices?: [...ConnectionCoordinates]
sourceOriginal?: ConnectionCoordinates sourceOriginal?: ConnectionCoordinates
targetOriginal?: ConnectionCoordinates targetOriginal?: ConnectionCoordinates

View File

@ -78,6 +78,7 @@ export enum ConnectionPath {
export interface CanvasConnection { export interface CanvasConnection {
color?: ui.ColorDimensionConfig; color?: ui.ColorDimensionConfig;
direction?: ui.DirectionDimensionConfig;
path: ConnectionPath; path: ConnectionPath;
size?: ui.ScaleDimensionConfig; size?: ui.ScaleDimensionConfig;
source: ConnectionCoordinates; source: ConnectionCoordinates;

View File

@ -1,14 +1,10 @@
import { isNumber, isString } from 'lodash'; import { isNumber, isString } from 'lodash';
import { DataFrame, Field, AppEvents, getFieldDisplayName, PluginState, SelectableValue } from '@grafana/data'; import { DataFrame, Field, AppEvents, getFieldDisplayName, PluginState, SelectableValue } from '@grafana/data';
import { ConnectionDirection } from '@grafana/schema';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
import { hasAlphaPanels, config } from 'app/core/config'; import { hasAlphaPanels, config } from 'app/core/config';
import { import { CanvasConnection, CanvasElementItem, CanvasElementOptions } from 'app/features/canvas/element';
CanvasConnection,
CanvasElementItem,
CanvasElementOptions,
ConnectionDirection,
} from 'app/features/canvas/element';
import { notFoundItem } from 'app/features/canvas/elements/notFound'; import { notFoundItem } from 'app/features/canvas/elements/notFound';
import { advancedElementItems, canvasElementRegistry, defaultElementItems } from 'app/features/canvas/registry'; import { advancedElementItems, canvasElementRegistry, defaultElementItems } from 'app/features/canvas/registry';
import { ElementState } from 'app/features/canvas/runtime/element'; import { ElementState } from 'app/features/canvas/runtime/element';
@ -107,6 +103,7 @@ export function onAddItem(sel: SelectableValue<string>, rootLayer: FrameState |
export function isConnectionSource(element: ElementState) { export function isConnectionSource(element: ElementState) {
return element.options.connections && element.options.connections.length > 0; return element.options.connections && element.options.connections.length > 0;
} }
export function isConnectionTarget(element: ElementState, sceneByName: Map<string, ElementState>) { export function isConnectionTarget(element: ElementState, sceneByName: Map<string, ElementState>) {
const connections = getConnections(sceneByName); const connections = getConnections(sceneByName);
return connections.some((connection) => connection.target === element); return connections.some((connection) => connection.target === element);
@ -347,7 +344,9 @@ export const getConnectionStyles = (
const strokeColor = info.color ? scene.context.getColor(info.color).value() : defaultArrowColor; const strokeColor = info.color ? scene.context.getColor(info.color).value() : defaultArrowColor;
const strokeWidth = info.size ? scene.context.getScale(info.size).get(lastRowIndex) : defaultArrowSize; const strokeWidth = info.size ? scene.context.getScale(info.size).get(lastRowIndex) : defaultArrowSize;
const strokeRadius = info.radius ? scene.context.getScale(info.radius).get(lastRowIndex) : 0; const strokeRadius = info.radius ? scene.context.getScale(info.radius).get(lastRowIndex) : 0;
const arrowDirection = info.direction ? info.direction : defaultArrowDirection; const arrowDirection = info.direction
? scene.context.getDirection(info.direction).get(lastRowIndex)
: defaultArrowDirection;
const lineStyle = getLineStyle(info.lineStyle?.style); const lineStyle = getLineStyle(info.lineStyle?.style);
const shouldAnimate = info.lineStyle?.animate; const shouldAnimate = info.lineStyle?.animate;

View File

@ -3755,7 +3755,7 @@
} }
} }
}, },
"category-arrow-direction": "Arrow Direction", "category-arrow-direction": "Direction",
"category-background": "Background", "category-background": "Background",
"category-border": "Border", "category-border": "Border",
"category-canvas": "Canvas", "category-canvas": "Canvas",
@ -3787,15 +3787,17 @@
"auto": "Auto" "auto": "Auto"
} }
}, },
"connection": {
"direction-options": {
"label-both": "Both",
"label-forward": "Forward",
"label-none": "None",
"label-reverse": "Reverse"
}
},
"description-experimental-types": "Enable selection of experimental element types", "description-experimental-types": "Enable selection of experimental element types",
"description-inline-editing": "Enable editing the panel directly", "description-inline-editing": "Enable editing the panel directly",
"description-pan-zoom": "Enable pan and zoom", "description-pan-zoom": "Enable pan and zoom",
"direction-options": {
"label-both": "Both",
"label-forward": "Forward",
"label-none": "None",
"label-reverse": "Reverse"
},
"drone-front-item": { "drone-front-item": {
"category-drone-front": "Drone Front", "category-drone-front": "Drone Front",
"name-roll-angle": "Roll Angle" "name-roll-angle": "Roll Angle"
@ -6671,6 +6673,14 @@
"label-fixed-color": "Fixed color", "label-fixed-color": "Fixed color",
"noOptionsMessage-no-fields-found": "No fields found" "noOptionsMessage-no-fields-found": "No fields found"
}, },
"direction-dimension-editor": {
"description-field": "Direction based on field value",
"description-fixed": "Fixed direction value",
"label-direction": "Direction",
"label-field": "Field",
"label-fixed": "Fixed",
"label-source": "Source"
},
"file-dropzone-custom-children": { "file-dropzone-custom-children": {
"upload": "Upload" "upload": "Upload"
}, },