Relationship Chart
This commit is contained in:
parent
1410666a39
commit
dd2917693a
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACoxUXW/lJhD9KxavNQ5gwMZvaZJVr9J8aO9VW/VtgHFC4+ArjJNdRfnvlX2TzceqUl8sa4A5Z86cmScS4R5JR473+7MHjHkiJclhDQkmNGWasnoneMdNx1TVGN0Kpv4mJQnn+J10pJHOcWeQ1qiRSs8UtXXvqPdoOOOeCSWXnHAzke6JQKg8PgSH1Tj9gWkKYyQd+TNEPz4u2BAqN4yzr9I44CZOGaJbyFxuTs7p78fXu6vrw60Jp+VxFTzpCOuV4FIxKnrRU8kaQ61qkVqjnG0VMtDm8CzEjCnCUE3+7g3fjzlidp2oRF0xKgx5LomHDAtlCxPuvu8XFqtEp0u8XMPrb/dEHjCRTpSvYh7SHbkhHN1Pdg6DP1q/pCT7NO4x5YCrGlfb4o0EZxWrhBaMkZJcgLsNEYvNaXE1LCWCa5lEX1ODvafScKBG1jX1tYJG6L5pRE9Kco4p4vAu60VwaZzGPhcvIhcfcLbHJ2cR7IALxhcYJiQl2UzbDNHDMEY8+4ZuzodcuzQvxydjzCHO4zwVm5jxJsHL+ev7kzkljLm4hnxb/AbTLemIgdZxMGAAtHM9KoY98ww0azxqr9FL4RsvjfRaGua0BMulB8Vtv0RISX5dNNzOzuE0vbFZTTIMK4fipU8nQ8CYP8i4uISj8HWLtBdcU9l6oKBqQ7WudY/cmUbwV5gdpBvMpCN9a61lzlkhDXcgDNS+Z7XXvRGiaUD2tWqk0FqxBpxvGsGlrxt0UPc1awUpyXUa/ezy51bXawOu0/gPurxIRTqimVKtA2uFlrwF06LkWrbaYGNkD0zWzgJXLVjLGSjmTCsccG1AWq/6HyqdxZsQ8TRM+wG+v8NtK13VvziAlvcgP13/kuAeH8d0d3mwcXV5tiMl+TrHZSMUm8UijyHSb3qZ6It5yGF3mxA8+ovR488+wmVcinVCaxRQO2yp1FxQ2WigtteK+rZ1tgEvlG1ISU5Hd4epWBwGIS5T9SPZ1bY4Tu42ZHR5Tgu/v1YeV9vieoDcj+n+wyZ5V9hnASpRc7Z0xuPDYRutHLX3LXrNqZBcUWkMp61RDbVNa43VLSq3KHY15/2ci6/oQ0KX14pfvLhintyiu/tZjFcXvFiUs8Vr28MWW6X9X0vsuST3CNOc8H7d1t0T2cSIaUU+nQ+juIkXYRjChG6MfiKdMVpUtVAvBP/rGmdMqUpJ2Tw/Pz//CwAA//8DAIu0/5cbBgAA
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRTy27cOBD8FYFnUeZbom7GeBcZOFkPEiNZ7I1itzJEZGpAUU4CY/59Qc0EiXPLjSiyuqurmi8kuickPbk9nf56xpgXUpMcNkgwYSgzlMlHwXtue6abjrFWm/Y/UpNwj99JT1rlPfcWqUSDVAHTdJCjpwBoOePAhFalpvu8kP6FuNAAPgePzbx8xLSEOZKefAoR5q+ltwuNn+YVmjRPuI9LdtEXMf/sd/f07e3h8eFwebXgUshNANITNmrBlWZUjGKkirWWDrpDOljth04jc8ZeaCFmTNFNzQJffvaHOUfMvheNkA2jwpJzTcBlVyQPbsHH76eiYrPoruD1Bm/H/oU8YyK9qH+YeSl346dw4+enJxfhZgwxLEdSk1OaT5hywM2ObcJpcjnMsbo22U0BYyY1efhQHSaXxzk9vfLow2X0/R9M/s75Y4hY7e8Kh6MA2SEdBTdUdeCo09JSY6QZkXvbCk5qsptjDnGd16Xax4yf06aS9ORvNy140Xeb/DFk9HlNRfq/pmSN30LezVAARmpySDOsPlc/7easYY1k5fJhzac1V+8RQkKfscz0mNZr+d8owoiNtFtTwpirg8vH6o1bjqQn1nWeO+usc8b7ETXDkQFzhrWABgyCEtCCsgqMsswb5QauwGk+jAUpuku61bZQoN0oPCiKrpNUDdzRzrSGcimUkGi1kfKVrdXDVHjOd0whSGpxBKosd9QqKSlI7VphxrYV4y+WXBPnrPgN+Hz5GpsCA9AhGE6F4poqazntrG7p0HaDHUyH2hfN95giTr8Y9S74NC/zmKvrwlSvrHu/xvK9q215voZIv22R3c3+C6aqRO5CLNt8Tfl8Pp//BwAA//8DAMbyj+ElBAAA
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUT2/cNhDFv4rAsyjzP0XdDLtFDTfNIjbaorcRZxQL0UoLito0MPzdC2q3SNxbb8JAb/j4mzd8ZTMciXXs9nT66UxzXlnN8riXlFCOC8eFflayU6ITthFBBGnDX6xm4yN9Yx3zJkYZA3FNjrhBYXmvh8gRKUghUShrSk/4vLLulcHYIJ3HSM2y/k5pHZeZdeyPccblazkbxiZOy4ZNWiZ6mNcMcyxmfnu4e+S/3h6ePx4uf620FnEzIutYaD2FARTXPiA3AnoOg+h5T7aVUmrdGnuRjXOmNMPUrPjl+/m45Jly7FSjdCO4CuytZggZiuUeVnr+dioudkT3pV7v5f2ze2VnSqxT9b8wL+1u4jTerFs/0ZmmE6SV0k1cjkeYkdXslJYTpTzSjuVMqV/WMReiMXpoWymxBW+sx1ZbpT1qQdgKbzFgAGfcoKK1A/Zt8GYwqKRBR8b0ntVsBzdNkMdlrq7e76aR5sxq9vGpOkyQhyUd36F/uhB9+B9Ai23WMS2sdt6T0VpDcL0ShN5F51QfQfihVyQjCRuMD9ILqYMSQVvnW48gQmQ1+wDxZZyperhnHROSFOqW+KCk46ZF4GB14M5pN5CMwSvJana3zHmct2Vbq4c50+e035d17GeYVrrc9DbFlzFTzFsqEP50JYyHtOAWc/U9AFI0otFCFNGWT1uuPhGOiWKmguM5bdd+/5Eop3bR3ZYSzbk6QH6pfoH1pTCENkoIEABcjANZQYNAAU54JIeO0Cj0aIJBZ4KIzkAvDYKV/VAqrGZU8lbtEY+9i1b2yBVZww2R56GA0Tq6aJUhB+85Vh+nooPYCkOoeaABuQkSeDBac9QWvHKD92r4Ack1LFIUwEjny7LuDhxiS+gkV0ZabkKQvA3W8963fehdSzYWz4+UZpp+APVhjGlZlyFX16xV79B92uby4FR77r6OM/97n9H9Er9QqsqMYZzLfl3H+lazI8G6JTru71X3yg5luarn/dlSoXFWqJo9ZUh5O13Lum2UMPLt7e3tHwAAAP//AwDz0opy9AQAAA==
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUTW/cNhCG/4rAsygPP0SKuhl2i7pu6kW8SIveRuTIFqKVFhS1aWDsfw+o3SBxb70JA70zw2femTc24YFYy26Px19ONKWFlSwNW0iCNBwMB7WXopXQQl1BrYQA/Q8r2fBIX1nLrPZeeEdckSGuA9S8U73nIZATIALIWuec+LKw9o3hUAU6DZ6qeflEcRnmibXsr2EK85dcG4fKj/MaqjiP9DAtCSefm/nz4e6R/3G72z/tLn8ttGRxNQTWMtdYcj1KrqwLXAN2HHvoeEd1I4RQqtH1RTZMieKEY7WEzz/qhzlNlHwrK6kq4NKxc8kCJswtd7jQ/usxd7Ehus/xcgtvn+0bO1FkrSy/w7yku/HjcJPm40gnGo8YF4o3fj4ccAqsZMc4HymmgTYs20vHEdMwT8W12N040JRYyZ6ei92IqZ/j4R2r5wuCh/9B4ESxYy1TUCtjLWmlFDrTSaBgjTdGdh7B9p0k4Qlqp60TFoRyEpyqjW1sQHCelexlnDscf182fk5C1wSw0kjS5KQ2xnkg1VBfW4KAxnSm8S6oxkrdhEahQxSuI+ud8dkgH9C/DhMVD/esZSBIBtUQ76UwXDcBOdbKcWOU6Ul4Z6VgJbubpzRM67wuxcOU6CVu/FjLfsVxoQu52+hfh0Q+rTFD/dvkYrs4h9Wn4ocDBFRQKYAsWtNxTcVHCkMknyjj3cf1mu8/EmnkJrpbY6QpFTtMr8VvuLxmKth4gS4/1XjfUw3UQwA0YAOZYChoGWzQTgejHXijsRM6YC26PkdYySgbrtg8Do6sc0FzKWrHtfLAO+uBKxDBGiUNWvOOY/E0Zh36BjQFxR31gWsnkDutFA+qRitNb63sf0JyNZ+ADDjQ6bKtWwcmhIaCEVxqUXPtnOCNqy3vbNO5zjRU+9zzI8WJxp9AfRh8nJe5T8XVu8U7dB/XKV+cYvPxl2Hi/24zup/9Z4pFnjEOU16w61jPJTsQLmukw3aw2je2y9tV7Le7JV1lapAle04Y03q8hlVTSdDifD6fvwEAAP//AwCWgzK09QQAAA==
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUy27jNhSGX0XgWlR4EyVqlzod1MhkYkzcadEdRR45RGTSoKhMg8DvPqDsIM4MuuhOOOJ/Lt/5yVfk9R5Qh64Ph9+fwacJlSi5JcQIk5hITPiW0Y6RjtQVZZyxRv2DSuRu4QV1qBHGUKMAc5CAhSU17vlgsLWgKKGWsFrknHo3oe4VaVdZeHYGqjB9gzi54FGH/nLehu+5tnaVGcNsqxhGWPspaW9yM1/Wq1v8+Xqzvd+cTk0wZXHlLOqQahtQg2aYN8piQXSP9UB63EPdUko5b0V9kjmfIHo9VpN9eq9vQ/KQTMcqxiuCmULHElmddG651xNsXw65iwXRTY6XS3j57F7RM0TUsfIN5indlRnd1X7qZzfaqzHsds7vVsEPbjdHnXLhEh1iOEBMDhY4vzmv48vnsNvlfOiTHidAJdpC3Duvx9OPGxj0PKaHMMeFzGSf/usM6pCeU0AlWkiO41K2OA+zGh349Iv2ovD9Q7EZdRpC3H9Y0sOJ/fp/oL/T5tF5KNY3qEOEArO8BTwwKrForca65gpLyeUA1KiGUVSiVfDJ+TnMU7H2CXZnah/6u47m0SUwaY55pr9lNtsmBjubVLwvmJKKVJyQLJrTYU7FV7AugkmQh9jG+ZzvJwmTbBF9ciP8gmc1xwg+FRudHos/9PSYcejWUK200loaM0BNYCCWaEkaC9JKsILZxgolrBSKGCl0T4XVNe2HHEElgmyyYvE1N40mQ8+xNUJiATXF7cAs1pTUDeNWkkF8gFvcj1mnTUsEWI4VDBYLRTVWgnNsea0bJoemYcMFp7MhKMnUL0345wT27KUvJ2u/zW7h+XSPlz6ltS1YSTETtMZCKYpbVTe4b9pe9bKF2uTJVsFP4Q3kN4h9mFzKj8id826vx5+PvG/mFqKH8WI7d87EMIUhFWdbFh/29XX2+RUrFot+dx7/uxjjJpgniEU2lnb+YpnH4/H4AwAA//8DAIOrApAMBQAA
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACoxUS2/bOBD+KwLPokJKFCUa2IPhtFgjTWMk7u5ibxQ5jInIpEFRToMi/31BWa6dooe9CSPO43vM/EBO7gEt0PJw+HQEFweUo2inUElKjgnHpNqWdFGSBamLmnAi6vZflCN7B29ogRqmFFUCcAUcMNOkxl1lFNYaBCVUk7JmqaZ8HtDiB5K20HC0Cgo//AVhsN6hBfrbOu1fU29pC9X7URfB97B2Q5ROpWG+rld3+Mtys33YnF4NMKTkwmq0QKJtQBhZ4qoRGjMiOywN6XAHdUspraqW1ac06yIEJ/ti0C+X/tpHB1EtyqKsCoJLgd5zpGWUaeRODrB9O6QpJopuUzyfwtPn4gc6QkCLMj+TeSp3o3p7sx+60fb6JsrwDNEEuYdXH17gKHuUo0PwBwjRwsTNw1N2GYmSghQlLwlBObqXamcdZOvb7KFPgKVqCQNdYQFGYyaoxIJVFdZVLZuSm6YpDcrRHQQH/VXVe6uCH7yJ2Ux59qHPyrto3ejHIVu7CM9BxlPeZ9kPgHK0GbveDrtHkPpt6x/H9M+NfcKyGkMAF7ONjLvsTznski6yVVQKKaTkShmoCRiiieSk0cA1B81K3WgmmOZMEMWZ7CjTsqadSRGUo4cxHsZ45v/71RDbYPd70JcJthPHm15G48N+rcFFa2yS5vxiMlTfT6iyueaqt+DiB5LRAhEKpa5awKakHLNWSyzrSmDOK26AKtGU9ESYsc/jT56IMZpVpm5b3omOEihN0zLSmI5oZSoldN1BUzakaaEVipCuZrTthCIaaM10Qhe8HlX81QrVJNDj6NJ2ZusE+9U6/J2zCyOb4KNXvk+C2R6e3oYI+9+ptvL7gx9sTPjNLO23Ae69S3/n/Z85m5OXPl5ikPYgm1aPgeKmIhxL2jLMaNPiFuoKN3o6FUoZrVCObr16gZAlg0nrJk3Onnp4ypZB7WwEFceQOv8zgTrJ+fm8Mhc+iq+ftisfYHk45HP0j2Mi6VTsbIAPd+XbkJ7v/BCvIM/QnqA358Gu7KTheDpVNgW51i1oTnHJaI2ZEBS3om5w17Sd6HgLtbrYNXsEbQOoONXbhjF1m5n9nS+XIVojVRzS8nzxarLT7M/5ya9DXsn2M/vU/Eq62UpzJUqSZZ9Oh3Ny0P+6m+/v7+//AQAA//8DAKXy/XEoBgAA
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRTTW/UMBD9K5HPceqvOHFu1RbEqkBXZQWIm+OZUKups3Kcfqja/46cXVTgxsWyRn4z7715fiXBPiDpyOXh8O4RQ5pJSZJfS4IJTZmmTO4F7wTrmK44Z7JuxQ9SEn+NL6QjjXKOO4NUokaqgNW0l4OjAGg448BErXJP+3Mm3SuxvgJ89A6raf6KcfZTIB355gNMT3m29ZUbpwWqOI24DXOywWUyn7eba/rxcre/2Z1ezThncOWBdMS0DZrBCiobA1Qx21M7sJ72WLeccylbVZ9gPiSMwY7VDPdv82FKAZPrRCVkxagw5FgSsMlmyr2dcf9yyCxWi65yvVzL67V7JY8YSSfK32ae2l240V88zP3iR7g4ncnO91nlIU4HjMnj6skqcxxt8lMozpM2o8eQSEluvhS70aZhig9/GfXlpH/7H/L3efq7Z3RLQthMS0ikI1y2pCSfrLvzAYvtFekI4yhAtkgHwTVVLVhqa2mo1lIPyJ1pBCcl2Uwh+bBMy1xsQ8KfcRVAOvLejjOeqF9Gd+cTurTErOq7zlnYxQkWl4o3/zmrWCUZy6AlHZZU3CL4iC5h1rePy7nfPxChxQraLDFiSMXOprvig53vsim2ddwaa6zVzg1YMxwYMKtZA6hBIygBDSijQCvDnFa25wpszfshV0hJMK+7WBPGsHdON0AbxoAqaQZqmByo7pu2bxuhB5ND8eZjcTNmnHUtUwiSGhyAKsMtNUpKCrK2GdU0YvjDkvP2OcsGAz6e/srKQAO0CJpToXhNlTGctqZuaCZget1i7TLna4wBxz+M+uRdnOZpSMU5PMVf1t0uIf/3Yg3Skw/0ed3R1eTuMRZ5x9aHHO/zWo/H4/EXAAAA//8DAFmhW4E2BAAA
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACoxU227cNhD9FYGvFRXeRJF6c20HXaSOjeyiLfo2JEe2aplaUJSTwPC/F9TauaJAXwRhSM45c+bMPJEID0h6cnY8Xj5izAupSR63kGBCU6YpkwfBe8F6phvOhTKi/ZvUZHyHn0lPOuU99xapRI1UBdZSJwdPQ0DLGQ9MtKrkhNuF9E8Exibg4+ixmZc/MC3jHElP/hxjmD8WbBgbP81raNI84S4uGaIvZN7vzt/R389uDtc3p1sLLuVxMwbSE2s6tAMIKjsbqGLgKAzMUYet4ZxLaVR7ejbGjCnC1Czh/it+mHPE7HvRCNkwKix5rkmADIWygwUPn4+FxSbRRYnXW3j77Z/IIybSi/pVzFO6N34a3zwsbh2n8Gb7kpoc03zElEfc1LjeV19JcNawRmjBGKnJFfi7MWK1u6iup1IieMMUBkktDoEqy4FaJSUNsoVO6KHrxEBq8g5TxOmbrFejT/MyD7l6Ebn6Dmd/dn4ZwU1YMN7CtCCpyW7ZZ4gBpjni5Sf0az7lOqS1HJ/PMY9xndel2sWMtwlezl/fn68pYczVDeS76jdY7kqLwHgOFiyA9n7AluHAAgPNuoA6aAxKhC4oq4JWlnmtwHEVoOVuKBFSk1+LhvvVe1yWr2w2k0zTxqF66dP5NGLM38lIesI4iiAN0kFwTZUJQKGVlmot9YDc207wV5gDpFvMpCeDcc4x751QlnsQFmQYmAx6sEJ0HahBtp0SWresAx+6TnAVZIce5CCZEaQmN2kOq88/tlpuDbhJ8z/oc5GK9ESztjUenBNacQPWoOJaGW2xs2oApqR3wFsDznEGLfPWCA9cW1AutMMXlS7j7RjxYlyOE3z+Btc0upG/eADDB1A/XH+b4AE/zun+/cnGzfvLA6nJhzWWjVDtikU+jpF+0mWir9Ypj4e7hBAwXM0Bf/YRlnGptgl1VvrWcKQOOkeVFS0tzKnyQoJ2ofO8SHUx+3tMVXEYjLFM1Zdk1/vqLPm7MaPPayr8/tp4XO+rmwnyMKeH7zbJN4X9KEAjJGcFLuDjaRttHHUIBoPmVCjeUmUtp8a2HXWdcdZpg60vil2v+bjm6gOGMaHPW8UvXtwwz+/Q3/8sxqsLXizKWfHa/rTFNmn/1xJ7rskDwrImfNi2df9EdjFi2pAv1tMo7uLVOE3jgn6OYSG9tbYx3JoXgv91jbOWNabt2ufn5+d/AQAA//8DAAb8XS4aBgAA
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSTTW/cOAyG/4qhs+Xoy7LlWzDZRQdpN4M2aIveaInuCHWkgSynLYL57wt5pki7t70JlEi+fF7qhQR4QjKQ29Ppr2cMeSE1yX4LCSY0ZZoy+Sj4INjAdMO5aLtWfyE18ff4kwykU9Zya5BK1EiVYy0d5WSpc2g4446JVpWa8HUhwwsB3zh89habuHzEtPgYyEA++eDi99IbfGPnuLomxRn3YckQbBHzz353T9/eHh4fDpdXCy4lufGODMT0HZoJBJWdcVQxGClMbKQjtj3nXMpetZc0HzKmAHOzuG+v/V3MAbMdRCNkw6gw5FwTBxmK5BEWfPx5Kio2RHclXm/h7Ti8kGdMZBD1L5iXcjd29jc2Pj1BcDeTD345kpqcUjxhyh43HNuE8wzZx1Bdm+xmjyGTmjx8qA4z5Cmmpz8YfbiMvv8fk78De/QBq/0dGQjjKJzskU6Ca6p6BxRaaajWUk/IrekEJzXZxZB9WOO6VPuQ8WvaVJKB/A3zghd9t8kefUab11Skf9bFa/zh8y66EmCkJocU3Wpz9Yqbs4Y1kpXLhzWf1ly9R+cT2oxlpse0Xsv/J0VosSXt1pQw5OoA+Vi9geVYQEBvORgwANraCVuGE3MMNOscaqfRKeE6p4xyWhlmtYKRKwctH6cSKbqLu9W2UJ0w48gQqHFmpMpopCNnnBrGZNd1RqIuiF6xVg9zyQPbM4VOUoOTo8pwoEZJSZ1soRN66jox/Ybk6jhnpZjD58vX2BRo53p0mlOheEuVMZz2pu3o2PWjGXWPrS2a7zEFnH8D9c7bFJc45eq6MNUf6N6voXzvalue7z7QH5tld9F+w1QVy8GHss1Xl8/n8/lfAAAA//8DANrGmTolBAAA
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRUTW/cNhT8KwLPoswvUaJuxjpBF27qRbJIi94ovqeYsEwtSMqJYex/L6jdIvGp6IUgnjiPM/OGeiPBPiMZyO3p9OEFQ06kJtlvJcGEpkxTJo+CD4INTDec9Vwa8Tepib/HVzKQTjnHnUEqUSNVwFo6yslRADSccWCiVaWn/ZbI8EasbwBfvMNmSV8xJr8EMpA/fYDle7nb+sbNywpNXGbch5RtcIXMH/vdPf399nB8OFxOJUwF3HggAzF9h2aygsrOAFXMjtRObKQjtj3nXMpetReYDxljsHOT4Onn/bDkgNkNohGyYVQYcq4J2GwL5dEmPL6eCovNortSr7fyth3eyAtGMoj6XzMv7W7c7G+e07j6GW4ua7bpKW3rZF1e4iupySkuJ4zZ4+bPbUr4PM6vR5uePl7OlG368APdmhF2yxoyGQiXRdBm0Dzb7JdQXTnuZo8hbx9z9CF591+9JKnJw5fqMNs8LfH53Ty+XGze/w+XP1n36ANW+zsyEMZRgOyRToJrqnqw1LbSUK2lnpA70wlOarJbQvZhXdZU7UPGb3GTRAby0c4JL/xuo3v0GV1eY9H5ly65OsQFVpern7PkrGGNZKyA1nxac/UZwUd0GYuIY1yv/b6+hwgtNtBujRFDrg42P1a/2fRYlNvecWussVY7N2HLcGLArGYdoAaNoAR0oIwCrQxzWtmRK7AtH6dSITXBEp1qS+ukJi7aDqhk0lClmaCmBUNBQNejYEbo8Z2P1cNccNb1TCFIanACqgy31CgpKcjWdkJPXSemXyy55oGzYjDgy+XdbQw0QI+gORWKt1QZw2lv2o6OXT+aUffYusL5HmPA+RejPnkXl7RMubompHpn3ec1lH9HtaXluw/0xzaju8U9YazKjK0P5alcx3o+n8//AAAA//8DAGO5D/uCBAAA
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUXW/jKBSG/4rFtXEBY2x81013tFGn02ianV3tHYZDiupAhHFnqyr/fYSTqumM9mLvrGPe8/GcF16RV3tAPbo+HH5/Bp8mVKLklhAjTGAiMOFb2vVU9k1T1awhksl/UIncLbygHrVca6ol4BoEYG5Ig4faamwMSEqoIazhOafaTah/RcpVBp6dhipM3yBOLnjUo7+cN+F7rq1cpccwmyqGEdZ+Ssrr3MyX9eoWf77ebO83p1MTTFlcOYN6JJVVmmuDhw46zC0o3NXSYgOm1YzUumnqk8z5BNGrsZrM03t9E5KHpHtWsboimEl0LJFRSeWWBzXB9uWQu1gQ3eR4uYSXz/4VPUNEPSvfYJ7SXenRXe2nYXajuRrDbuf8bhW8dbs5qpQLl+gQwwFicrDA+c15FV8+h90u50Of1DgBKtEW4t55NZ5+3IBV85gewhwXMpN5+q8zqEdqTgGVaCE5jkvZ4jzManTg0y/ai8L3D8VmVMmGuP+wpIcT+/X/QH+n9KPzUKxvUI8IBWbqDrBlVGDeGYVVU0ssRC0sUC1bRlGJVsEn5+cwT8XaJ9idqX3o7zrqR5dApznmmf4W2WybGMysU/G+YEoqUtWEZNGcDnMqvoJxEXSCPMQ2zud8P0mYYIvokxvhFzyrOUbwqdio9Fj8oabHBUenqZJKKiW0ttAQsMQQJUhrQBgBhjPTGi65EVwSLbgaKDeqoYPNEVQiyCYrFl+LRrJaGoLtQAnmDenwoEWNOz5YKjtqO9V+gFvcj1mndEc4mBpLsAZzSRWWvK6xqRvVMmHbltkLTmdDUJKpX5rwzwnM2UtfTtZ+m93A8+ken/o0pgMjKGacNphLSXEnmxYPbTfIQXTQ6DzZKvgpvIH8BnEIk0v5Eblz3u3V+POR983cQvQwXmznzukYpmBTcbZl8WFfX2efX7Fiseh35/G/izFugn6CWGRjKecvlnk8Ho8/AAAA//8DAEJm1vgMBQAA
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUW2/cNhCF/4rAZ1Hm/aI3w27RhZt6ES/aoG8UZxQL0UoLito0MPa/F9RukLpveRMGOsPDb87wjUzhiKQl96fTL2ec8kJqkoetJJgwlBnK1IG7lvtW60Zo7a11f5OaDE/4jbTEqhh59EglGqQKmKad7CMFQM8ZBya0Kj3D54W0byQMDeB5iNjMy5+YlmGeSEv+GiaYv5azw9DEcV6hSfOIu2nJYYrFzB+7hyf6+/3+8Ly//rXgUsTNAKQlPvQhqgi0c+io6jFQJ31PAcFGwWTUWl5lw5QxTWFsFvjy43yY84Q5tqIRsmFUeHKpCYQciuUuLHj4diouNkSPpV5v5e2zfSNnTKQV9XeY13Z3cRzulrUb8YzjKaQF012cj8cwAanJKc0nTHnADcsZUzcvQy5EY7TBOc7BBau0BSe1kBYkQ3DMavDgg1GmF1HrHjrnreoVCK7AoFKdJTXZwI1jyMM8VTfvD+OAUyY1eX6p9mPI/ZyO79C/XInufgJosU1aIpmWxlpUUsrgTScYgjXRGNHFwGzfCeQRmfbKem4Zl14wL7WxzkJgPpKafAjxdZiw2j2SljCOAqRD2gtuqHIQaNDSU2Ok6ZFHbwUnNXmYpzxM67wu1W7K+Dlt9yUt+TWMC15vep/i65Ax5jUVCJ9MCeM+zbDGXP0IAGcNayRjRbTm05qrjwhDwpix4Dik9dbvfxJhxCZ6WFPCKVf7kF+r38LyujF0kQcffAgmxh41w54BC4ZZQAMGQQmwoLwCozyLRoWOKwiad32pkJpgyVu1RdwEpYzskArBLVUGDHVBO6qwQyV7Jhj07zhWz2PRheiYQpDUYw9UeR6oV1JSkDpYYXprRdF9R3ILC2cFMOD5uqxXBwAOwXAqFNdUec+p89rSzrrOd8ahjsXzE6YJx/+A+jDENC9zn6tb1qp36D6uU3lwqi13X4eJ/rPN6HGOXzBVZcZhmMp+3cZ6qckRw7ImPG7vVftG9mW5qsP2bBnTMO5lTV5ySHk93crWNd4xcblcLv8CAAD//wMAL93JHfQEAAA=
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRUwW7cIBT8FYuzcQBjbHyLNo26Stus2lVb9YZ5zwmKg1eA00bR/nuFd6s0p6oXhJ6Zx7yZwS/Em0ckPbk8HN49oU+RlCS5tSSYUJQpyuSed71gPZOVZqxjrfpBSuJu8Jn0pJXWcquR1qiQSmANHerRUgDUnHFgopG5p7mLpH8hxlWAT85iNcevGKKbPenJN+dh/pnvNq6y07xAFeYJtz4m420m82m7uaEfLnf7293pVMSYwZUD0hNtRmOlBTp02FE5oqFdrUcKCK0VrLZNU59gzicM3kxVhIfX+2FOHpPtRSXqilGhybEkYJLJlAcTcf98yCxWia5yvVzL67Z/IU8YSC/KP2Ke2l3YyV08xmFxE1yc1mTiQ1zX0dg0h2dSkkOYDxiSw1WfyxjxcZie9yY+XJ/O5G189wvtkhA28+IT6QmvO1KSVaBpMsnNvjhz3EwOfVo/puB8dPZfvbI0t1+K3WTSOIfHN358Ocm8/Q+VPxp77zwW2yvSE8ZRQN0hHQVXVHZgqGlqTZWq1Yjc6lZwUpLN7JPzy7zEYusT3oV1JNKTazNFPPG7DPbeJbRpCXnO7yrnahdmWGwqXr3krGJVzVgGLemwpOIzggtoE+Yh9mE59/v6FiKUWEGbJQT0qdiZdF+8N/F+nbyz3GijjVHWjtgwHBkwo1gLqEAhSAEtSC1BSc2skmbgEkzDhzFXSEkwR6dY0yrVoKQdWqp1K6jsFKdDMwramLqRo5YDDtncVx2L2ynjjO2YRKipxhGo1NxQLeuaQt2YVqixbcX4lyTnPHCWBQZ8Or27lYEC6BAUp0LyhkqtOe1009Kh7QY9qA4bmznfYPA4/SXUR2fDHOcxFeeEFG+k+7z4/O8o1rT8dJ7+Wj26mu0DhiJ7bJzPT+Vs6/F4PP4GAAD//wMAZSWeyoIEAAA=
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACoxU227cNhD9FYGvFRXeRJF6c20HXaSOjeyiLfo2Ioc2a5laUJSTwPC/F9LauaJAXwRhSM45c+bMPJEED0h6cnY8Xj5iKjOpSYlbSDChKdOUqQM3vWA9U41lWigj/yY1ie/wM+lJp5zjziKVqJEqz1o6yOCo92g5456JVq054XYm/ROB2Hh8jA6baf4D8xynRHryZ0x++rhiQ2zcOC2+ydOIuzQXSG4l8353/o7+fnZzuL453ZpxXh830ZOeWAjglPN0MGioCgjUSBuoR985waRrW3l6FlPBnGBsZn//Fd9PJWFxvWiEbBgVljzXxEOBlfIAMx4+H1cWm0QXa7zewttv/0QeMZNe1K9intK9cWN88zAPSxz9m+1LanLM0xFzibipcb2vvpLgrGGN0IIxUpMrcHcxYbW7qK7HtURwhin0kloMnirLgVolJfWyhU7o0HUikJq8w5xw/CbrVXR5mqdQqheRq+9w9mfnlwmGEVeMtzDOSGqym/cFkodxSnj5Cd1STrkOeVmPz6dUYlqmZa52qeBthpfz1/fnS86YSnUD5a76Dea7rUXGcbBgAbRzAVuGgXkGmnUetdfolfCdV1Z5rSxzWsHAlYeWD2GNkJr8umq4X5zDef7KZjPJOG4cqpc+nY8RU/lORtITxlF4aZAGwTVVxgOFVlqqtdQBubOd4K8wB8i3WEhPghmGgTk3CGW5A2FB+sCk18EK0XWggmw7JbRuWQfOd53gyssOHcggmRGkJjd58osrP7Zabg24ydM/6MoqFemJZm1rHAyD0IobsAYV18poi51VAZiSbgDeGhgGzqBlzhrhgGsLavBt+KLSZbqNCS/ifBzh8ze4ptGN/MUBGB5A/XD9bYYH/Djl+/cnGzfvLw+kJh+WtG6Earda5GNM9JNeJ/pqGUs83GUEj/5q8vizj3Adl2qbUOOw7bQJlAfDqWKar0bmVA+MgYUOguxITS4md4+5Wh0GMa1T9SXZ9b46y+4uFnRlySu/vzYe1/vqZoQSpvzw3Sb5prAfBWiE5GztjMfH0zbaOGrvDXrNqVC8pcpaTo1tOzp0ZrCDNti6VbHrpRyXUn1AHzO6slX84sUN8/wO3f3PYry64MWinK1e25+22Cbt/1pizzV5QJiXjA/btu6fyC4lzBvyxXIaxV26iuMYZ3RT8jPpbWtFwxjrXhj+572Oi4YJpZ+fn5//BQAA//8DAJsBfCEbBgAA
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSTwW7cOAyGX8XQ2XIkWZYt34LJLjpIuxm0QbvYGy3SHaGOPJDltEUw776QZ4o2vfUiCLRJ/vx+6oUFeCLWs9vT6a9nCmlhJUt+CymhDBeGC/0ou16JXujKilao1v7HSubv6TvrWaudk84Sr8kQ1ygaPtSj44hkpZAoVKNzTfi8sP6Fga+Qnr2jal4+Ulz8HFjPPvmA89fcG3zlpnnFKs4T7cOSILgs5p/97p6/vT08Phwufy205OTKI+uZhRGcdsiHjjquRwLe1XbkSNg6JWrXNPUlzYdEMcBULfjlZ3+cU6DkelWpuhJcWXYuGUKCLHmAhR6/n7KKDdFdjpdbeLv2L+yZIutV+QPmpdyNm/yNm5+eIODN6INfjqxkpzifKCZPG45twmmC5OdQXJvsJk8hsZI9fCgOE6Rxjk+vGH24jL7/g8nfgTv6QMX+jvVMSFJYd8RHJQ3XHQKHprbcmNqMJJ1tlWQl280h+bDO61LsQ6LPcVPJevY3TAtd9N1Gd/SJXFpjlv6vyV7TN592M+aAYCU7xBlXl4qfuKWoRFWL/PFhTac1Fe8JfSSXKM/0GNdr+d9SlFFb0m6NkUIqDpCOxRtYjhuIzkmwYAGMcyM1gkaBAoxokQwaQq2wRW01Gm2FMxoGqREaOYw5knVnd4vLQllXDyCQNwQt12CAD2I72g7G1kLXuFdYi4cp54HrhCasuaURubYSuNV1zbFuoFVmbFs1/oLk6rgUmTfS8+VpbAoMYkdoJFdaNlxbK3lnm5YPbTfYwXTUuKz5nmKg6RdQ77yL8zKPqbguTPEK3fs15OddbMvz1Qf+bbPsbnZfKBbZcvAhb/PV5fP5fP4fAAD//wMAIOQppCUEAAA=
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSTy27bMBBFf0Xg2lT4EiVqFzgtaqRtjMRoi+4ozqgmolAGReWBwP9eUHaRtLtuBGHAy7n3zPCVBPuApCWXh8OHRwxpIiuS/FISTGjKNGVqx5tWsJap0jApeMN+khXx1/hCWlIr57gzSCVqpApYRTvZOwqAhjMOTFQq32l/TaR9JdaXgI/eYTlO3zBOfgykJd99gPEp97a+dMM4QxnHATdhSja4bObrZn1NP19udzfb06kJpywuPZCWGNtbpxzQrsGGqh4tbaTpKSDUTjDpqkqeZD4kjMEO5QT3b/1hTAGTa0UpZMmoMOS4ImCTzZY7O+Hu5ZBdLIiucn21lJff9pU8YiStWP2Bebruwg3+4mHqZj/Axemb7HSfUx7ieMCYPC5MlpjDYJMfQ3HutB48hkRW5Oau2A429WN8+AvU3Sn/5j/i73L3D8/o5oSwHueQSEu44mRFvli39wGLzRVpCeMoQDZIe8E1VQ1YaitpqNZS98idqUXWrMeQfJjHeSo2IeGvuAQgLflohwlP1i+j2/uELs0xp/qh8y5s4wizS8Ubf85KVkrGsmhOhzkVtwg+okuY8+3ifL7vH4nQYhGt5xgxpGJr0774ZKf9AqVx3BprrNXO9Vgx7Bkwq1kNqEEjKAE1KKNAK8OcVrbjCmzFuz5XyIpgHnexbFgHoI2sKtqJilFVy54aXnVUKF2j1KbuMCveOBY3Q9ZZ1zCFIKnBHqgy3FKjpKQgK1sL3de16N8hOU+fswwY8PH0VhYHGqBB0JwKxSuqjOG0MVVNu7rpTKcbrFx2cI0x4PAO1Bfv4jiNfSrOy1P8he52Dvm9F8siPflAn5cZXY3uHmORZ2x9yOt9HuvxeDz+BgAA//8DAO0UJnI2BAAA
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -2223,6 +2223,7 @@ public sealed class RelationshipMapService(IProjectRepository projects, ICharact
|
||||
{
|
||||
CharacterID = x.CharacterID,
|
||||
CharacterName = x.CharacterName,
|
||||
CharacterImportance = x.CharacterImportance,
|
||||
IsFocus = x.CharacterID == focusCharacterId
|
||||
})
|
||||
.ToList();
|
||||
@ -2252,7 +2253,7 @@ public sealed class RelationshipMapService(IProjectRepository projects, ICharact
|
||||
CharacterRelationshipID = x.CharacterRelationshipID,
|
||||
SourceCharacterID = x.CharacterAID,
|
||||
TargetCharacterID = x.CharacterBID,
|
||||
RelationshipLabel = x.CurrentStateName,
|
||||
RelationshipLabel = x.RelationshipTypeName,
|
||||
CategoryName = x.RelationshipCategoryName,
|
||||
CssClass = x.CssClass,
|
||||
Intensity = x.CurrentIntensity
|
||||
|
||||
@ -990,6 +990,7 @@ public sealed class RelationshipMapNodeViewModel
|
||||
{
|
||||
public int CharacterID { get; set; }
|
||||
public string CharacterName { get; set; } = string.Empty;
|
||||
public int? CharacterImportance { get; set; }
|
||||
public bool IsFocus { get; set; }
|
||||
}
|
||||
|
||||
|
||||
@ -70,11 +70,20 @@
|
||||
<a class="btn btn-outline-secondary btn-sm" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Project Start</a>
|
||||
</form>
|
||||
|
||||
<section class="relationship-map-legend" aria-label="Relationship categories">
|
||||
<section class="relationship-map-legend plain-card" aria-label="Relationship categories">
|
||||
<div>
|
||||
<p class="eyebrow">Relationship key</p>
|
||||
<h2>Category colours</h2>
|
||||
</div>
|
||||
<div class="relationship-map-legend-grid">
|
||||
@foreach (var category in Model.Categories)
|
||||
{
|
||||
<span class="relationship-map-legend-item @category.CssClass">@category.CategoryName</span>
|
||||
<span class="relationship-map-legend-item @category.CssClass">
|
||||
<span class="relationship-map-legend-line" aria-hidden="true"></span>
|
||||
<span>@category.CategoryName</span>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="relationship-map-workspace">
|
||||
@ -83,12 +92,17 @@
|
||||
{
|
||||
<div class="empty-state">
|
||||
<h2>No visible relationships at this point</h2>
|
||||
<p class="muted">Try choosing a later book, chapter, or scene, or remove the character focus.</p>
|
||||
<p class="muted">Add initial relationships or relationship events, choose a later story point, or remove the character focus to build the map.</p>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<svg id="relationshipMapSvg" class="relationship-map-svg" role="img" aria-label="Relationship network"></svg>
|
||||
<div class="relationship-map-toolbar" aria-label="Map controls">
|
||||
<button class="btn btn-outline-secondary btn-sm" type="button" data-map-fit>Fit</button>
|
||||
<button class="btn btn-outline-secondary btn-sm" type="button" data-map-reset>Reset view</button>
|
||||
</div>
|
||||
<div id="relationshipMapGraph" class="relationship-map-graph" role="img" aria-label="Relationship network"></div>
|
||||
<p id="relationshipMapError" class="alert alert-warning d-none mt-3">The relationship map renderer could not load. The relationship list below is still available.</p>
|
||||
}
|
||||
</div>
|
||||
|
||||
@ -140,108 +154,158 @@
|
||||
</section>
|
||||
|
||||
@section Scripts {
|
||||
<script src="https://cdn.jsdelivr.net/npm/cytoscape/dist/cytoscape.min.js"></script>
|
||||
<script>
|
||||
(() => {
|
||||
const nodes = @Html.Raw(JsonSerializer.Serialize(Model.Nodes, jsonOptions));
|
||||
const links = @Html.Raw(JsonSerializer.Serialize(Model.Links, jsonOptions));
|
||||
const relationships = @Html.Raw(JsonSerializer.Serialize(Model.Relationships, jsonOptions));
|
||||
const svg = document.getElementById("relationshipMapSvg");
|
||||
if (!svg || !nodes.length) return;
|
||||
|
||||
const width = 960;
|
||||
const height = 620;
|
||||
const centreX = width / 2;
|
||||
const centreY = height / 2;
|
||||
const radius = Math.max(180, Math.min(width, height) / 2 - 90);
|
||||
svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
|
||||
|
||||
const positions = new Map();
|
||||
nodes.forEach((node, index) => {
|
||||
const angle = nodes.length === 1 ? -Math.PI / 2 : (-Math.PI / 2) + (index * 2 * Math.PI / nodes.length);
|
||||
const nodeRadius = node.isFocus ? radius * 0.15 : radius;
|
||||
positions.set(node.characterID, {
|
||||
x: centreX + Math.cos(angle) * nodeRadius,
|
||||
y: centreY + Math.sin(angle) * nodeRadius
|
||||
});
|
||||
});
|
||||
|
||||
const relationshipById = new Map(relationships.map(item => [item.characterRelationshipID, item]));
|
||||
const makeSvg = name => document.createElementNS("http://www.w3.org/2000/svg", name);
|
||||
const linkLayer = makeSvg("g");
|
||||
const nodeLayer = makeSvg("g");
|
||||
svg.append(linkLayer, nodeLayer);
|
||||
const graph = document.getElementById("relationshipMapGraph");
|
||||
const error = document.getElementById("relationshipMapError");
|
||||
if (!graph || !nodes.length) return;
|
||||
|
||||
if (!window.cytoscape) {
|
||||
error?.classList.remove("d-none");
|
||||
return;
|
||||
}
|
||||
|
||||
const focusCharacterId = @Html.Raw(JsonSerializer.Serialize(Model.FocusCharacterID));
|
||||
const fullNetwork = @Html.Raw(JsonSerializer.Serialize(Model.ShowFullNetwork));
|
||||
const connectedToFocus = new Set();
|
||||
if (focusCharacterId && fullNetwork) {
|
||||
links.forEach(link => {
|
||||
const from = positions.get(link.sourceCharacterID);
|
||||
const to = positions.get(link.targetCharacterID);
|
||||
if (!from || !to) return;
|
||||
if (link.sourceCharacterID === focusCharacterId) connectedToFocus.add(link.targetCharacterID);
|
||||
if (link.targetCharacterID === focusCharacterId) connectedToFocus.add(link.sourceCharacterID);
|
||||
});
|
||||
}
|
||||
|
||||
const group = makeSvg("g");
|
||||
group.classList.add("relationship-map-link-group", link.cssClass);
|
||||
group.dataset.relationshipId = link.characterRelationshipID;
|
||||
group.setAttribute("tabindex", "0");
|
||||
group.setAttribute("role", "button");
|
||||
group.setAttribute("aria-label", `${link.relationshipLabel} relationship`);
|
||||
const elements = [
|
||||
...nodes.map(node => ({
|
||||
group: "nodes",
|
||||
data: {
|
||||
id: `c-${node.characterID}`,
|
||||
characterID: node.characterID,
|
||||
label: node.characterName,
|
||||
importance: node.characterImportance || 0,
|
||||
width: node.isFocus ? 172 : node.characterImportance >= 8 ? 158 : node.characterImportance >= 4 ? 144 : 128,
|
||||
height: node.isFocus ? 58 : node.characterImportance >= 8 ? 52 : 46,
|
||||
isFocus: node.isFocus,
|
||||
isNeighbour: connectedToFocus.has(node.characterID)
|
||||
},
|
||||
classes: [
|
||||
node.isFocus ? "is-focus" : "",
|
||||
node.characterImportance >= 8 ? "is-major-character" : "",
|
||||
node.characterImportance >= 4 && node.characterImportance < 8 ? "is-supporting-character" : "",
|
||||
focusCharacterId && fullNetwork && !node.isFocus && !connectedToFocus.has(node.characterID) ? "is-dimmed" : ""
|
||||
].filter(Boolean).join(" ")
|
||||
})),
|
||||
...links.map(link => ({
|
||||
group: "edges",
|
||||
data: {
|
||||
id: `r-${link.characterRelationshipID}`,
|
||||
relationshipID: link.characterRelationshipID,
|
||||
source: `c-${link.sourceCharacterID}`,
|
||||
target: `c-${link.targetCharacterID}`,
|
||||
label: link.relationshipLabel,
|
||||
category: link.categoryName,
|
||||
intensity: link.intensity || 4,
|
||||
tooltip: buildRelationshipTooltip(relationshipById.get(link.characterRelationshipID))
|
||||
},
|
||||
classes: [
|
||||
link.cssClass,
|
||||
focusCharacterId && fullNetwork && link.sourceCharacterID !== focusCharacterId && link.targetCharacterID !== focusCharacterId ? "is-dimmed" : ""
|
||||
].filter(Boolean).join(" ")
|
||||
}))
|
||||
];
|
||||
|
||||
const line = makeSvg("line");
|
||||
line.setAttribute("x1", from.x);
|
||||
line.setAttribute("y1", from.y);
|
||||
line.setAttribute("x2", to.x);
|
||||
line.setAttribute("y2", to.y);
|
||||
line.setAttribute("stroke-width", Math.max(3, Math.min(9, link.intensity || 4)));
|
||||
|
||||
const label = makeSvg("text");
|
||||
label.setAttribute("x", (from.x + to.x) / 2);
|
||||
label.setAttribute("y", (from.y + to.y) / 2 - 8);
|
||||
label.setAttribute("text-anchor", "middle");
|
||||
label.textContent = link.relationshipLabel;
|
||||
|
||||
group.append(line, label);
|
||||
group.addEventListener("click", () => selectRelationship(link.characterRelationshipID));
|
||||
group.addEventListener("keydown", event => {
|
||||
if (event.key === "Enter" || event.key === " ") {
|
||||
event.preventDefault();
|
||||
selectRelationship(link.characterRelationshipID);
|
||||
const cy = cytoscape({
|
||||
container: graph,
|
||||
elements,
|
||||
minZoom: 0.25,
|
||||
maxZoom: 2.5,
|
||||
wheelSensitivity: 0.18,
|
||||
style: relationshipMapStyle(),
|
||||
layout: {
|
||||
name: nodes.length <= 8 ? "circle" : "cose",
|
||||
animate: false,
|
||||
fit: true,
|
||||
padding: 48,
|
||||
nodeRepulsion: 9000,
|
||||
idealEdgeLength: 170,
|
||||
edgeElasticity: 80,
|
||||
numIter: 1200,
|
||||
randomize: false
|
||||
}
|
||||
});
|
||||
linkLayer.append(group);
|
||||
|
||||
cy.ready(() => {
|
||||
cy.fit(undefined, 48);
|
||||
const first = relationships[0];
|
||||
if (first) selectRelationship(first.characterRelationshipID);
|
||||
});
|
||||
|
||||
nodes.forEach(node => {
|
||||
const position = positions.get(node.characterID);
|
||||
const group = makeSvg("g");
|
||||
group.classList.add("relationship-map-node");
|
||||
if (node.isFocus) group.classList.add("is-focus");
|
||||
cy.on("tap", "edge", event => {
|
||||
selectRelationship(event.target.data("relationshipID"));
|
||||
});
|
||||
|
||||
const circle = makeSvg("circle");
|
||||
circle.setAttribute("cx", position.x);
|
||||
circle.setAttribute("cy", position.y);
|
||||
circle.setAttribute("r", node.isFocus ? 34 : 27);
|
||||
cy.on("tap", "node", event => {
|
||||
const characterId = event.target.data("characterID");
|
||||
const characterName = event.target.data("label");
|
||||
document.getElementById("relationshipMapEmptyDetail")?.classList.add("d-none");
|
||||
document.getElementById("relationshipMapDetail")?.classList.remove("d-none");
|
||||
document.querySelector("[data-map-detail-title]").textContent = characterName;
|
||||
document.querySelector("[data-map-detail-category]").textContent = "Character";
|
||||
document.querySelector("[data-map-detail-reciprocal]").textContent = characterId === focusCharacterId ? "Focused" : "Node";
|
||||
document.querySelector("[data-map-detail-type]").textContent = "Character";
|
||||
document.querySelector("[data-map-detail-initial]").textContent = "Select a relationship line to see relationship state.";
|
||||
document.querySelector("[data-map-detail-current]").textContent = "Use Character focus to centre this character.";
|
||||
const history = document.querySelector("[data-map-detail-history]");
|
||||
history.innerHTML = "";
|
||||
const li = document.createElement("li");
|
||||
li.innerHTML = `<a href="/Characters/Details/${characterId}">Open character details</a> or <a href="/RelationshipMap?projectId=@Model.Project.ProjectID&focusCharacterId=${characterId}">focus this character</a>.`;
|
||||
history.append(li);
|
||||
});
|
||||
|
||||
const label = makeSvg("text");
|
||||
label.setAttribute("x", position.x);
|
||||
label.setAttribute("y", position.y + 50);
|
||||
label.setAttribute("text-anchor", "middle");
|
||||
label.textContent = node.characterName;
|
||||
cy.on("mouseover", "node, edge", event => {
|
||||
const label = event.target.isNode() ? event.target.data("label") : event.target.data("tooltip");
|
||||
graph.setAttribute("title", label || "");
|
||||
});
|
||||
|
||||
group.append(circle, label);
|
||||
nodeLayer.append(group);
|
||||
document.querySelector("[data-map-fit]")?.addEventListener("click", () => {
|
||||
cy.resize();
|
||||
cy.fit(undefined, 48);
|
||||
});
|
||||
|
||||
document.querySelector("[data-map-reset]")?.addEventListener("click", () => {
|
||||
cy.layout({
|
||||
name: nodes.length <= 8 ? "circle" : "cose",
|
||||
animate: false,
|
||||
fit: true,
|
||||
padding: 48,
|
||||
randomize: false
|
||||
}).run();
|
||||
});
|
||||
|
||||
window.addEventListener("resize", () => {
|
||||
window.requestAnimationFrame(() => {
|
||||
cy.resize();
|
||||
cy.fit(undefined, 48);
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll("[data-map-select]").forEach(button => {
|
||||
button.addEventListener("click", () => selectRelationship(Number(button.dataset.mapSelect)));
|
||||
});
|
||||
|
||||
if (relationships.length) {
|
||||
selectRelationship(relationships[0].characterRelationshipID);
|
||||
}
|
||||
|
||||
function selectRelationship(id) {
|
||||
const relationship = relationshipById.get(id);
|
||||
if (!relationship) return;
|
||||
|
||||
document.querySelectorAll(".relationship-map-link-group, .relationship-map-list-item").forEach(item => item.classList.remove("is-selected"));
|
||||
document.querySelectorAll(`[data-relationship-id="${id}"], [data-map-select="${id}"]`).forEach(item => item.classList.add("is-selected"));
|
||||
cy.elements().removeClass("is-selected");
|
||||
cy.$id(`r-${id}`).addClass("is-selected");
|
||||
document.querySelectorAll(".relationship-map-list-item").forEach(item => item.classList.remove("is-selected"));
|
||||
document.querySelectorAll(`[data-map-select="${id}"]`).forEach(item => item.classList.add("is-selected"));
|
||||
|
||||
document.getElementById("relationshipMapEmptyDetail")?.classList.add("d-none");
|
||||
document.getElementById("relationshipMapDetail")?.classList.remove("d-none");
|
||||
@ -265,6 +329,128 @@
|
||||
function formatState(name, intensity) {
|
||||
return intensity ? `${name} / Intensity ${intensity}` : name;
|
||||
}
|
||||
|
||||
function buildRelationshipTooltip(relationship) {
|
||||
if (!relationship) return "";
|
||||
return `${relationship.characterAName} - ${relationship.characterBName}: ${relationship.relationshipTypeName}; current ${formatState(relationship.currentStateName, relationship.currentIntensity)}`;
|
||||
}
|
||||
|
||||
function relationshipMapStyle() {
|
||||
const isDark = document.documentElement.dataset.theme === "dark";
|
||||
const nodeFill = isDark ? "#222126" : "#fffdf8";
|
||||
const nodeText = isDark ? "#f7efe4" : "#1f2937";
|
||||
const focusFill = isDark ? "#3b2a16" : "#fff3d8";
|
||||
const majorFill = isDark ? "#332417" : "#fff0d5";
|
||||
const supportingFill = isDark ? "#29252c" : "#fbf6ef";
|
||||
const labelFill = isDark ? "#f7efe4" : "#2f3742";
|
||||
const labelBackground = isDark ? "#17171c" : "#fffdf8";
|
||||
return [
|
||||
{
|
||||
selector: "node",
|
||||
style: {
|
||||
"label": "data(label)",
|
||||
"shape": "round-rectangle",
|
||||
"width": "data(width)",
|
||||
"height": "data(height)",
|
||||
"background-color": nodeFill,
|
||||
"border-color": "#6b7280",
|
||||
"border-width": 2,
|
||||
"color": nodeText,
|
||||
"font-size": 12,
|
||||
"font-weight": 650,
|
||||
"text-wrap": "wrap",
|
||||
"text-max-width": 118,
|
||||
"text-valign": "center",
|
||||
"text-halign": "center",
|
||||
"text-margin-y": 0,
|
||||
"padding": "10px",
|
||||
"shadow-blur": 10,
|
||||
"shadow-color": isDark ? "rgba(0, 0, 0, 0.45)" : "rgba(31, 41, 55, 0.18)",
|
||||
"shadow-opacity": 0.32,
|
||||
"shadow-offset-y": 3,
|
||||
"overlay-opacity": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: "node.is-supporting-character",
|
||||
style: {
|
||||
"background-color": supportingFill,
|
||||
"border-color": isDark ? "#b9a891" : "#b99970",
|
||||
"border-width": 2.5,
|
||||
"font-weight": 700
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: "node.is-major-character",
|
||||
style: {
|
||||
"background-color": majorFill,
|
||||
"border-color": "#c47c2c",
|
||||
"border-width": 3,
|
||||
"font-size": 13,
|
||||
"font-weight": 800,
|
||||
"shadow-opacity": 0.42
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: "node.is-focus",
|
||||
style: {
|
||||
"width": "data(width)",
|
||||
"height": "data(height)",
|
||||
"background-color": focusFill,
|
||||
"border-color": "#c47c2c",
|
||||
"border-width": 4,
|
||||
"font-size": 13,
|
||||
"font-weight": 850,
|
||||
"shadow-opacity": 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: "edge",
|
||||
style: {
|
||||
"label": "data(label)",
|
||||
"curve-style": "bezier",
|
||||
"width": "mapData(intensity, 1, 10, 2, 8)",
|
||||
"line-color": "#7a8796",
|
||||
"target-arrow-shape": "none",
|
||||
"font-size": 11,
|
||||
"font-weight": 700,
|
||||
"color": labelFill,
|
||||
"text-background-color": labelBackground,
|
||||
"text-background-opacity": 0.88,
|
||||
"text-background-padding": 3,
|
||||
"text-rotation": "none",
|
||||
"overlay-opacity": 0
|
||||
}
|
||||
},
|
||||
{ selector: "edge.relationship-category-family", style: { "line-color": "#b86522", "color": "#7c3f12" } },
|
||||
{ selector: "edge.relationship-category-friendship", style: { "line-color": "#2f9e5f", "color": "#14532d" } },
|
||||
{ selector: "edge.relationship-category-romantic", style: { "line-color": "#d9467f", "color": "#831843" } },
|
||||
{ selector: "edge.relationship-category-hostile", style: { "line-color": "#dc3f3f", "color": "#7f1d1d" } },
|
||||
{ selector: "edge.relationship-category-professional", style: { "line-color": "#4f7fd8", "color": "#1e3a8a" } },
|
||||
{ selector: "edge.relationship-category-other", style: { "line-color": "#7a8796", "color": "#374151" } },
|
||||
{
|
||||
selector: ".is-dimmed",
|
||||
style: {
|
||||
"opacity": 0.25
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: "edge.is-selected",
|
||||
style: {
|
||||
"width": 9,
|
||||
"z-index": 20,
|
||||
"text-background-opacity": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: "node:selected",
|
||||
style: {
|
||||
"border-color": "#c47c2c",
|
||||
"border-width": 4
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
}
|
||||
|
||||
@ -1471,13 +1471,46 @@ a:focus-visible {
|
||||
}
|
||||
|
||||
.relationship-map-legend {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.45rem;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(160px, max-content) 1fr;
|
||||
gap: 0.9rem 1.2rem;
|
||||
align-items: center;
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
.relationship-map-legend-item,
|
||||
.relationship-map-legend h2 {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.relationship-map-legend-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.45rem 0.8rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.relationship-map-legend-item {
|
||||
display: inline-flex;
|
||||
gap: 0.45rem;
|
||||
align-items: center;
|
||||
padding: 0.28rem 0.45rem;
|
||||
color: inherit;
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 999px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.relationship-map-legend-line {
|
||||
display: inline-block;
|
||||
width: 2.15rem;
|
||||
height: 0;
|
||||
border-top: 4px solid currentColor;
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.relationship-map-detail-badges .status-pill,
|
||||
.relationship-map-list-item .status-pill {
|
||||
border: 1px solid transparent;
|
||||
@ -1495,60 +1528,23 @@ a:focus-visible {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.relationship-map-svg {
|
||||
display: block;
|
||||
.relationship-map-toolbar {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 0.4rem;
|
||||
margin-bottom: 0.6rem;
|
||||
padding-bottom: 0.45rem;
|
||||
border-bottom: 1px solid rgba(107, 114, 128, 0.12);
|
||||
}
|
||||
|
||||
.relationship-map-graph {
|
||||
width: 100%;
|
||||
min-height: 620px;
|
||||
}
|
||||
|
||||
.relationship-map-link-group {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.relationship-map-link-group line {
|
||||
stroke: #7a8796;
|
||||
stroke-linecap: round;
|
||||
opacity: 0.72;
|
||||
transition: opacity 0.16s ease, stroke-width 0.16s ease;
|
||||
}
|
||||
|
||||
.relationship-map-link-group text {
|
||||
fill: #2f3742;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
paint-order: stroke;
|
||||
stroke: rgba(255, 255, 255, 0.86);
|
||||
stroke-width: 4px;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
|
||||
.relationship-map-link-group:hover line,
|
||||
.relationship-map-link-group:focus line,
|
||||
.relationship-map-link-group.is-selected line {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.relationship-map-link-group.is-selected line {
|
||||
stroke-width: 8;
|
||||
}
|
||||
|
||||
.relationship-map-node circle {
|
||||
fill: #fffdf8;
|
||||
stroke: #6b7280;
|
||||
stroke-width: 2;
|
||||
filter: drop-shadow(0 6px 12px rgba(31, 41, 55, 0.14));
|
||||
}
|
||||
|
||||
.relationship-map-node.is-focus circle {
|
||||
fill: #fff3d8;
|
||||
stroke: #c47c2c;
|
||||
stroke-width: 3;
|
||||
}
|
||||
|
||||
.relationship-map-node text {
|
||||
fill: #1f2937;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 700;
|
||||
border: 1px solid rgba(107, 114, 128, 0.16);
|
||||
border-radius: 0.75rem;
|
||||
background:
|
||||
radial-gradient(circle at 20% 20%, rgba(196, 124, 44, 0.08), transparent 28rem),
|
||||
linear-gradient(180deg, rgba(255, 253, 248, 0.95), rgba(248, 250, 252, 0.86));
|
||||
}
|
||||
|
||||
.relationship-map-details {
|
||||
@ -1613,71 +1609,40 @@ a:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.relationship-category-family,
|
||||
.relationship-category-family line {
|
||||
.relationship-category-family {
|
||||
color: #7c3f12;
|
||||
border-color: #d89a5e;
|
||||
background: #fff1df;
|
||||
stroke: #b86522;
|
||||
}
|
||||
|
||||
.relationship-category-friendship,
|
||||
.relationship-category-friendship line {
|
||||
.relationship-category-friendship {
|
||||
color: #14532d;
|
||||
border-color: #7cc69a;
|
||||
background: #e7f8ed;
|
||||
stroke: #2f9e5f;
|
||||
}
|
||||
|
||||
.relationship-category-romantic,
|
||||
.relationship-category-romantic line {
|
||||
.relationship-category-romantic {
|
||||
color: #831843;
|
||||
border-color: #efa3c1;
|
||||
background: #fde7f0;
|
||||
stroke: #d9467f;
|
||||
}
|
||||
|
||||
.relationship-category-hostile,
|
||||
.relationship-category-hostile line {
|
||||
.relationship-category-hostile {
|
||||
color: #7f1d1d;
|
||||
border-color: #ee8f8f;
|
||||
background: #fee6e6;
|
||||
stroke: #dc3f3f;
|
||||
}
|
||||
|
||||
.relationship-category-professional,
|
||||
.relationship-category-professional line {
|
||||
.relationship-category-professional {
|
||||
color: #1e3a8a;
|
||||
border-color: #93b4ef;
|
||||
background: #e8f0ff;
|
||||
stroke: #4f7fd8;
|
||||
}
|
||||
|
||||
.relationship-category-other,
|
||||
.relationship-category-other line {
|
||||
.relationship-category-other {
|
||||
color: #374151;
|
||||
border-color: #bac2cf;
|
||||
background: #f2f4f7;
|
||||
stroke: #7a8796;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .relationship-map-link-group text {
|
||||
fill: #f7efe4;
|
||||
stroke: rgba(20, 20, 22, 0.92);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .relationship-map-node circle {
|
||||
fill: #222126;
|
||||
stroke: #9ca3af;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .relationship-map-node.is-focus circle {
|
||||
fill: #3b2a16;
|
||||
stroke: #f1c38f;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .relationship-map-node text {
|
||||
fill: #f7efe4;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .relationship-map-list-item {
|
||||
@ -1685,6 +1650,53 @@ a:focus-visible {
|
||||
border-color: rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .relationship-map-graph {
|
||||
border-color: rgba(255, 255, 255, 0.12);
|
||||
background:
|
||||
radial-gradient(circle at 20% 20%, rgba(241, 195, 143, 0.08), transparent 28rem),
|
||||
linear-gradient(180deg, rgba(33, 32, 38, 0.96), rgba(22, 22, 27, 0.92));
|
||||
}
|
||||
|
||||
[data-theme="dark"] .relationship-map-toolbar {
|
||||
border-bottom-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .relationship-category-family {
|
||||
color: #ffd2a5;
|
||||
background: rgba(184, 101, 34, 0.17);
|
||||
border-color: rgba(255, 210, 165, 0.3);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .relationship-category-friendship {
|
||||
color: #a8e8bf;
|
||||
background: rgba(47, 158, 95, 0.16);
|
||||
border-color: rgba(168, 232, 191, 0.3);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .relationship-category-romantic {
|
||||
color: #f8b5cf;
|
||||
background: rgba(217, 70, 127, 0.16);
|
||||
border-color: rgba(248, 181, 207, 0.3);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .relationship-category-hostile {
|
||||
color: #ffb4b4;
|
||||
background: rgba(220, 63, 63, 0.16);
|
||||
border-color: rgba(255, 180, 180, 0.3);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .relationship-category-professional {
|
||||
color: #b7cdf7;
|
||||
background: rgba(79, 127, 216, 0.16);
|
||||
border-color: rgba(183, 205, 247, 0.3);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .relationship-category-other {
|
||||
color: #d0d6df;
|
||||
background: rgba(122, 135, 150, 0.15);
|
||||
border-color: rgba(208, 214, 223, 0.28);
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.timeline-filter-grid {
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user