From 1c37f55ba5cc0c4f758fa9c7064b79154c2f4fe6 Mon Sep 17 00:00:00 2001 From: marjoleinpnnl <87781337+marjoleinpnnl@users.noreply.github.com> Date: Wed, 1 Nov 2023 16:39:53 -0700 Subject: [PATCH] Petri graph layout clean new branch (#391) * Multi-graph special handling for 'all'. More robust simple spring-force graph. * set layout in graph * any changes? * all moved nodes stay stuck * change variable name * unfix by double clicking * add layout input as argument in function rather than just the signal boolean * Fixing node-color-scale reference. * Graph layout passed in as dictionary-of-points (networkx convention) * Removed 'layoutdata' flag * retaining input x/y more clearly * whitespace cleanup * Improved names for x/y coordinate values. * Example notebook * Added tests for graph layouts. * Future work around graph labeling. --------- Co-authored-by: Joseph Cottam --- notebook/visual examples/Graphs.ipynb | 2545 ++++------------- notebook/visual examples/Scatterplot.ipynb | 2064 +++++++++++++ src/pyciemss/visuals/graphs.py | 98 +- .../visuals/schemas/multigraph.vg.json | 10 +- .../visuals/schemas/spring_graph.vg.json | 422 +-- test/test_visuals/test_plots.py | 75 + 6 files changed, 3089 insertions(+), 2125 deletions(-) create mode 100644 notebook/visual examples/Scatterplot.ipynb diff --git a/notebook/visual examples/Graphs.ipynb b/notebook/visual examples/Graphs.ipynb index 4c0526097..7edd35cd6 100644 --- a/notebook/visual examples/Graphs.ipynb +++ b/notebook/visual examples/Graphs.ipynb @@ -19,7 +19,7 @@ "outputs": [], "source": [ "import networkx as nx\n", - "from pyciemss.visuals import plots" + "from pyciemss.visuals import plots, vega\n" ] }, { @@ -40,7 +40,7 @@ "outputs": [ { "data": { - "image/png": "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", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAX0AAAEsCAYAAAAvq2MDAABKd0lEQVR4AWJkGAWjITAaAqMhMBoCIwaMFvojJqpHPToaAqMhgAUoMDAwzGdgYPjAwMAQiCbvwMDA8ACKQVL5DAwME0EMLHgCVKyAgYEBmQ0VJooyYGBgALlnAwMDQwIDA0M8AwODI1E6SVA0WuiTEFijSkdDYDQEhl0IgArpfqivQIU+qMAFcUEF/n5ooXuAgYGhgYGBoZ6BgQFXmQkqpEH6FjAwMIDUg9ggM0A0sfg/AwNDI9QukF4QBtlLrH6i1OHyAFGaRxWNhsBoCIyGwBAPgfsMDAwXGRgYQK3sCwwMDAFQ/5xHEgO17kEFPqgVDirQQa1vUIUA0qfPwMCwEdpCB2kFVSIgNSA2shmgwhzUmwDpW8jAwACqHEB2giqcQmjLHtSTAPUsQGpB+pFb+iB3geRB4iDzYWpgvYr3DAwMoEoCpD8RpAhaSYHEQFyQOMieD6OFPig4RvFoCIyGwEgMAVCBCCqEQS18EBtUqCpCh3NAhTKo0IUV0KAWtz1SSxzUKv8IHcoBFcIgeVAYgswB8WFqQWKgCgNUSIPUgPTB2CC1IPtBlQioQgENMx1EaumD9IHKaFCBvx5auYAKb1CvAuQ+WAUDs0uAgYEB5AdQoQ+qwECVDshvIDeA7AbhDSADQQKjeDQERkNgNARGWgiACk5QgQoqLEGtblAhia1ABhXioAITVgiDwglUeIN6AKCCF8QHqQHRoIIcxAYV4iAMEgPxQTRIDqQPlx3Icsj2gfTD3AgyB9S6BxXuoPIbXQ5mBmiYCuQfUCUBUgPig3okOMenQAaP4tEQGA2B0RAYriEAKkRBQyLo/gMVkqDWPqiAhrXCQYUmciEM0gMrXEHiID5IDYgG6QOx5RkYGEDmgMRAfJB9oIoFWR9ILbIdyHIgc2GVDEg/yByQehCNTw7ZDJB6UK8ARIPcA66kQDUFyJBRPBoCoyEwGgIjKQRAhSFoOAU0FAIq6EF+BxWOoIIWNCQCG38HjYODegSgFj1IDjQUAyqEkQtXkF6QGIgGmQFig4ZcQHpAwzWggh3UygbZCbILVNkYMjAwgMbzQWqQzQQNJ4EKdZBakH2gMhrEB7FBYqB5BNBQz0PoGD7ILpi9IBrmLtDwDqg3AHI/iA1q9YOGoxxABoIUjuLREBgNgdEQGEkhACoEBZEmYEF+B7XGQQUycgHNDx3HBxWcoMIWpA6kD6QONkwDEkMufEFskFkgcdBEL6igBg0jgQp8UCEPKuxBcqAKAVQ5wAp9kB0g9SBzQfKggh5URoPMAg3pgOYYQOIgfSDzQBUTyC6QGKiyAdGwQh+kHjSkAzIfJA6qJEB6LoAMBAmM4tEQGA2B0RAYDYGBB6ACHlSY43IJIXlc+uDio4U+PChGGaMhMBoCoyEw/MFooT/843jUh6MhMBoCoyEAB6OFPjwoRhmjITAaAqMhMPzBaKE//ON41IejITAaAqMhAAejhT48KEYZoyEwGgKjITD8wWihP/zjeNSHoyEwGgKjIQAHo4U+PChGGaMhMBoCoyEw/MFooT/843jUh6MhMBoCtAsB0Lp50Bk7oI1VtLOFiiaPFvpUDMxRo0ZDYDQERlwIgHa+go5HABX8+DZVDZqAGS30B01UjDpkNARGQ2AIhgCohQ86mwfkdFAFAKIHNR4t9Ad19Iw6bjQERkNgEIcA6CwbEAYdiAY6VA12quYgdjLuq78GtaNHHTcaAqMhMBoClIaAQvkWUEFN0JgHnT6gA9GwqQMdaAZq3YMOPQNhUOEPorGpHTRioy39QRMVow4ZDYHREKBnCChUbCaqgH7Q4Qs7wRLZeaAJXNCpmaDhHZA4iA9ig8b3QfxBi0cL/UEbNaMOGw2B0RAYxCEAOiIZNHkLomHOBE3kgsRANExs0NGjhf6gi5JRB42GwGgIDIEQAJ3HD7psBdTahzkXNKEL4oOGeWBig44eLfQHXZSMOmg0BEZDYDQEaAdGC33ahe2oyaMhMBoCoyEw6MBooT/oomTUQaMhMBoCoyFAOzBa6NMubEdNHg2B0RAYDYFBB0YL/UEXJaMOGg2B0RAYDQHagdFCn3ZhO2ryaAiMhsDwDQHQunx9JO8dRGIPauZooT+oo2fUcaSEgELFdgUGxr/2DP8ZFRj+MxgwMPwXSLVQeaYpISDFyMB4gIHx/4f//5gvBFmJDpkMSor/R9XSNQRAG7ZAO3JBG7JAFoMqAdBSTZAYiD9o8WihP2ijZtRhxISAQtVWA4a//+MZGBgC/jMwgDbGoGhLsVC5oC0hYIAi+J/hAyMj44b/jP83BFlIbESRG+WMhgBxAFTogwp5EA3SAaKR+SCxQYlHC/1BGS2jjiIUAgoVmx0Y/jPW/2cAt7hwKsda6KOqfsDAwNQQZCm2EFV4lDcaAngBqJDvZ2AA78wFKQTxQeUpqOAH8QctBjly0Dpu1GGjIYAeAgoN+wUYvn/t/8/AQNQZJ0QU+jArDjAyMxUGmonBuusw8VF6mIbAuuMviDpwLchSAtuBa6BCHjSUA0svoF4mqMAH7cod1CE2WugP6ugZdRxyCECHctZjG8ZBVofMJqHQZ2AAD/v8Lwy0lBz0GRfZj6Ns8kJg3fHnRB24FmQpCSrg0S0BiYEKeRANkgON6YOOYAAV/qNn74BCZBSPhgAlIaBQsTXh////oO40KHPhNEqYi+2Km47EGX1ZAQllUZ4PcoLc3//8+af4+uOvB28//5b+9P2PK07NUAlGBsYFgZbiiVDuKDUaAtgAqLBHL/TvMzAwCGJTPJjERlv6gyk2Rt2CNQSgBf58rJIMDAxCXKyPWwN1DztoiClxsjJb4FIHEv//n+HRx+9/Lp6+/UHsy/e/5iAxbHi04McWKqNiSABU6K9nYADfnAUSBl2gAqoEBn0vcbTQB0XXKB60IQAa0vn/9z9o7BWjhc/IwPCx0ltjf5qtMmh1DqhbTZI/Pnz9veDI9XfOv/8wyGLTyMjIUBhoIQG6JAOb9KjYaAgMSTBa6A/JaBsZjoZO2p7HNoYPGsbZX+bwg4+D1YSS0Pj/n+Hj0evvD73+9MsXmzmMDP8dAy0liRr7xaZ/VGw0BAYbGC30B1uMjLoHHgIK5VvX/2f4D7qDFC4GYpgpCJxdlmbFxsLEqAviUwF/uPXsy8Grj776YzHrASMHu2GgoeCgnpzD4u5RodEQwApGC32swTIqONAhAFqH//8/I2hYB8UpoBb+6VoXPiZGRjkUCSpwjlx7vxlbi/8/w//GYEtJ0HgtFWwZNWI0BAYWjBb6Axv+o7bjCAHQpdXoG69AY/g3WjyfsrMwaeHQRqnwh93n3zz/8vOvJopBoKWcnOyKo619lFAZ5QxRMFroD9GIG87OxtXKX5xkvtFWTQTbEAzVguPP3/8nNp9+hbECaLS1T7UgHm4GgRYYwA5eGxJnOo0W+sMtCQ4D/yhUbFnw/z8D6DwduG9AyzLP1bnxMjCAMxlcnBaMM3c+rn385kcwmtkPgiwlQMvy0IRHuSM4BEC7wkEXo4N25oJWj4EKf9Du3UE9/zNa6I/gFDtYvS5fvuU9euG+OsNypamCUDg93Pz/P8ORDSdf2qDbNbqSBz1ERjQfVMiDjmAA0bBCHjbvA6MHZQCNFvqDMlpGrqNwDe3ca/d6RMrk7YcPHxguXADlSQYGBQUFMCYlVPdefLsbfffu6BAPKSE47NWCWvigAh9EDynPjhb6Qyq6hr9jFSo2N/z/z1iP7FMNCZ7dOwrsXZHF8LFBBf6CBQsYPDw8GDg4OBh27NjB4ODgwKChoYFPG4rc4zffN5y58wltuej/gzjOYUHRO8oZGiHwv54fY3UYNpczNn4EDdmgS8Fa8zAaXX7Q8kcL/UEbNSPTYdjG8zuDdXeGm8q5ExsiDx48YABhUEEP0nPgAGRvFYwPEiOEf//5f2HLmVegnb7ISkfH9ZFDY4iz/zfwQxIGAX8wNnwEHbmArgo0ng8SB9EwOVDLH5RmQGP8MLFBR48W+oMuSka2gxQqNh/4/5/RHjkUKB3PX7hwIYO+vj6DgQEoPyKbjJd9cP2JlyjuAKkOspQYzTOggBjFoFU7oPFDUG8QRINCBHQ+1EMGBoZB3fofTcCgqBrFgyYEsBX6h8scd8oKcRHd0kf2zMaNGxn+///PEBAAypvIMgTZH9afeAnK2CgKRwt9lOAY6RxQKwLUqgedrglKK6Alm4N+jH+00B/pyXaQ+R9boX+hzu2CABcrKIMR7dofP34w7Ny5k9wCH2zP+hMvwTQyMVroI4fGKHsogtFCfyjG2jB2M7ZCf3Ou9UJdaQGUdfuEgmDmzJkM8vLy4MlcQmpxyF9cf+IlaN01ivRooY8SHKOcIQhGC/0hGGnD2cnYCn1Sd+KClmqChnVASzVhYUXqmP7f//+Pbjr5yhqmH0aPFvqwkBilhyoYLfSHaswNU3djW7KZYqO0pcZH04dYL4OWbIIwsnoBAQEGEEYWw8f++uPPzl0X3qLPI1wMspQgaZgJnx2jcqMhMBBgtNAfiFAftRNnCGC7JUuIi/XRuTo3qp+qidMRDAwMlx5+3nL3+Te0iub/wiBLSeQleviMGJUbDYFBCUYL/UEZLSPXUQoV2xX+//8LWg2BEghXGtx383CwEL1BC0Uz6ZyP60+8BB0FAVp3DdfNyPA/cfTSdHhwjHQGaLUO8pwPaOXOkAiT0UJ/SETTyHKkQvmWC/8ZGJAzFEO2o8rKUnd1upy9g2Noh4GR4b9ioKXkg5EVG6O+xQFAG7NAyzVha/RBDQTQnbmFONQPGuHRQn/QRMWoQ2AhoFC5peD/P4Z+GB9GX29yP8nJxoLzMnOYOgrpj1vPvHr0689/tFu5Ro9goDBch5t2UKEP2oQFomF+A+3wBYmBaJjYoKNHC/1BFyWjDoLejfvgPwMDP3JoeOtIHpkaY4Rx+iWyGkrZrz783Hj0xgeMM/tHh3YoDdlhpx9U2IMKeBAN8xxovgfEB9EwsUFHjxb6gy5KRh0ECgGF8q0T/jP8zwexkXF3iN7cUBPZZGQxarF//vl7YNvpNwYMjODxWiRj/z8MspQEdd+RxEaZQz0E/u+uIO7ANdcObAeugQp39EIfm9igC6bRQn/QRcmog0AhgKu1D5Kj5FgGkH5s+P///1e2nn3N9/vPf4xVQqPn6GMLsaEv9n93BVHDMIyuHaDCHN3DIDH0Qh90BAOocQCi0dUPGv5ooT9oomLUIeghoFC5LeD/v3+gyTF0qQ+Lk8wPUuvqRFALf8+Ft8KY4/gMDAz/GTYGWUmQfHAPuoNH+cMuBNALfdBqnvMMDAyJDAwMRFUmAxUio4X+QIX8qL1EhQC2o5ZhGnOcVFeUuKl5MqCN/cPkiaE/fP2zZf+ltzaYQzog3f8fMnJwGIxeiA4Ki1GMBkCFPqhBAlq9AyrwBaGnay5AUzfouKOF/qCLklEHIQPoMM8B9CWcMDWgjVuToozO2aiIgMZdUSZ+YWqw0aBlmcdvfuT//P0PxiXoUPUfGZmZHALNxECZGio0So2GwNAHo4X+0I/DYe8DQgU/KAAYGRgepNgpXk6xUWQQ5eU0ZWJkkACJI+GPf/7+P/XozY/Xd59/Nfjy468Wkhw6c7TARw+RUf6wAaOF/rCJyuHtEXDB/+PLBvQLVnD5WoiLjUGIi52h0FHz6a9f/14zMILPPselHEn8/0NGZuaA0RY+UpCMMocVGC30h1V0Dn/PYDuQDZ+v+wNM8Emjyv1n2MjIyZ4wOoaPGiyjvOEFRgv94RWfI8I3ClVbDRj+/ZtATKufuEL//0NGJqaCQHNx0Lb6ERGGo54cuWC00B+5cT/kfa5QsRm0gqIBX+GPv9D//5CRgaFh9BC1IZ8URj1AAhgt9EkIrFGlgzMEQCdzMjD+DWD4B1477YB8fAOWQv/if4b/G5iYmTeMjtsPzvgcdRVtwWihT9vwHTV9AEJg8cFHR55/+gm+9UpVlLeQ4f9/0LLLB6MnZA5AZIxaOejAaKE/6KJk1EGUhsC6488PMDAw2oPMGT1CARQKo3g0BBBgtNBHhMUoa5iEwLrjL0CXsIDOQGFgZGYyHB3GGSYRO+oNqoDRQp8qwThqyGAKgXXHX/yHuWf0InNYSIzSoyEAAaOFPiQcRslhFAKjhf4wisxRr1AdjBb6VA/SUQMHOgRGC/2BjoFR+wczGC30B3PsjLqN5BBYf+qVwf+//0BH3ILORR69/ITkEBzVMNzBaKE/3GN4hPlv/fHnDv8ZGKE3Io3eazvCon/Uu0SA0UKfiEAaVTJ0QmC00B86cTXq0oEBo4X+wIT7qK00CoHRQp9GATtq7LABo4X+sInKUY+AQmDt8ecNjAyM9SD2f4b/jcGWkqB7TEHcUTwaAqOAgYFhtNAHhcIoHjYhMFroD5uoHPUIjcBooU+jgB01dmBCYLTQH5hwH7V16IDRQn/oxNWoS4kIgXXHXkxgYGTIByllZGQoDLSQmABij+LREBgNAQgYLfQh4TBKDpMQGD1sbZhE5Kg3aAZGC32aBe2owQMRAqOF/kCE+qidQwmMFvpDKbZG3UowBEYLfYJBNKpghIPRQn+EJ4Dh5n2UY5UZ/iuOXpwy3GJ41D+UgtFCn9IQHNU/qEJg9LC1QRUdo44ZhGC00B+EkTLqJPJDYLTQJz/sRnWODDBa6I+MeB4xvhwt9EdMVI96lEwwWuiTGXCj2gZfCKCeu8NwMchSwmDwuXLURaMhMLBgtNAf2PAftZ2KIYBa6I8eq0zFoB01ahiB0UJ/GEXmSPfKaKE/0lPAqP+JAaOFPjGhNKpmSITA+pMvA/7/+78e7Nj/DBuDrCQCwOxRYjQERkMADkYLfXhQjDKGegiMHrY21GNw1P30AKOFPj1CedQOuoTAaKFPl2AetWSIg9FCf4hH4KjzESEwWugjwmKUNRoCuMBooY8rZEbFh1wIrDv+fAEDA2M8yOGMDP8TAy0lF4DYo3g0BEZDAAFGC31EWIyyhngIjB62NsQjcNT5dAGjhT5dgnnUEnqEwGihT49QHrVjqIPRQn+ox+Co++EhMFrow4NilDEaAjjBaKGPM2hGJYZaCKw79uI9AyODAMjdjBzsgoGGgh9A7FE8GgKjIYAAo4U+IixGWUM8BEYPWxviETjqfLqA0UKfLsE8agk9QmC00KdHKI/aMdTBaKE/1GNw1P3gEFh//r3A/x8/34M5DAwMQZYSo2kbFhij9GgIIIHRjIEUGKPMoQtGD1sbunE36nL6gtFCn77hPWobjUJgtNCnUcCOGjvswGihP+yidGR6aLTQH5nxPupr0sFooU96mI3qGIQhsP7484T/DIzzwU4bPVYZHAyjxGgIYAOjhT62UBkVG3IhMHrY2pCLslEHDxAYLfQHKOBHraVuCIwW+tQNz1HThi8YLfSHb9yOKJ+NFvojKrpHPUsBGC30KQi8Ua30DwHIevzf/oyM/wz+/2cwYGBgcAC5guvbk2/uh5y4QOyf7KJX2H++PsPAxHSB4T/DRsaGDw9A4qN4NARGAQPDaKE/mgqGRAisO/4q/j/jvwDG/wxY773l+v7kh/tBJw7snmG8wMDEOIGx/sNC7PKjoqMhMHLAaKE/cuJ6SPoUetl5PwMDgwI+D+Av9GE6GR8wMDE2jBb+sPAYpUciGC30R2KsDwE/g4Zx/v38OR9Xyx7dC8QV+jBdjAcYGBkTR4d9YOExSo8kMFroj6TYHiJ+XX/qlcH/P//2w45JJsbZXN+fMrgfdCRGKVQN4wcGRkZHxoYPF6ACo9RoCIwIMFroj4hoHjqeBA/n/P0/H3+B//+hqgT3JSkRjq98nCwKLMyMPz58/e3w++9/BqZ/fxiEfz9ew3BpxR+GI/2WDP//y+P1PSMTqMW/AK+aUcnREBhGYLTQH0aROdS9QqiFz87CeNReV/gzNzuzBQMD5LIUAn7+wPD+wQmGZeGCDK9vmONUy8gUyNjwYQNO+VGJ0RAYRmC00B9GkTmUvbL++HOF//8Zz+No4X+01xHaLsTD6kFkYY8ZFM8uLGCY7eCIveU/OtSDGWCjIsMVjBb6wzVmh5i/1h17ASrwQevuUVzOxsJ4xctY7AMjI4MNigQ5nH9/LjP0qjMyfH2jg6md8QEDI6MhY8OH0SsWMQNnVGQYgdFCf4AiE7Q65f/3P/ozT9z8d/PV58cPOjxH7AYi5N20yNEBLfD/MzIy6CKLU8j+wLDA5wLDg8PgTV0oZjEyTGRs+FSAIjbKGQ2BYQZGC306RCi4gP/x25+R4b/D////A5CHMOadvHPh8vMP8BYuIwPDAQYGxg0MjEwbR0JFAA6b7z/vI4cJNEouBpiLM1G5wIcYja/Fz8ikOLqUExJMo+TwBKOFPg3jFXTG+z9Gxnx8a83RC31k5zAyMDxgYGJoeNDuM2x3kuJq5XsYiWziZGP2Qw4PqrJ/ftrF0CFnzvD/Pz+KuYwMCxkbPiWgiI1yRkNgGIHRQp8GkQmalGRgYKr/z/CfYOGBr9CHOQ1S+DMVPmj3GlYrTHC18hXEORcaKvLFw/xPM3pv00qGw73hGOaPtvYxgmRUYPiA0UKfynGJcpkHEWYTU+jDjGEEDftwciU+aHAcFpONOMLqY4C5+GWqTNzCAg4X/f/fI4YmIX7M1j5TIWPDhwm4tI2Kj4bAUAajhT4VY2/98ZfziWndQ638yMDw/8Kc43eeXnv10fr/fwb8m4igmhgZGC4wMDMmPmjzHvI7Sdcde7GBgZHBH+o1MCXKx7bZRkvQF8yhB7GzZgvD8ck+qFYxXmBs/GiIKjbKGw2B4QFGC30qxCN0mAJ0bAB8QhbTWMguUkVJLlZudmYJBgbwscDIyh58+fnn1rpzT99M2nNT/s3X39bIkmjsD4zMjI5DveBfd+zFe/QJXBd94bW8nCzBaP7Fyf3x4wfDxo0bGW7cuMGgoKDAEB4ezsDBgeOwTWymfP+wlqFTHsM+xsZPo3kDW3iNig15MJqwqRCFa0+8WI97svb/Q2tNofNi/GygJYICxFr3+N23HUkLTwnefvkV107SD4yMzIZDdYUPaN7jPwPjffTwCLQQBy1dxXuiJrKeDRs2MEhISDBYWFgwnDhxguHFixcMAQFYT19mwAE+MDQKfMTYtMXIBDqX5wAOPaPCoyEwZMFooU9h1K079mICAyNDPjZjZEU41pqo8BsTOhYYm16Y2O1XX9a49R10/c/AgLrKhAF8GcIFBk5ux6E4xr/+xKv8////oY6b/2f4EGgpTnTFCAqjzs5Ohvz8fHDrHtTqnzhxIkN5eTlIini8wOcAxrp9xtFxfeIDcFTlUAKjhT4FsQU+HOzf//VYjPhorsq/VUqYIwqLHMlC77/+3O/Se0j07bdfGDtJQZO7Dzq9A0k2lIYawK34/yzyDIz/FRgZ/iswMP4XAN9y9Z9BgIERY1gL7hJ+LhYGJz1hOJ8YRmNjI0N9fT1cKTofLoGPsSV/IcOZBairhRgZGhkbPjXg0zYqNxoCQxGMFvoUxNq64y9AwxMYQxGkjksT44Q///5fNm/Zw4i14Gf87/igw5duQxHrjr22B7mZkemvAcN/RoF/jP8NGEEFOuSiE4zwAKklBovwsTLYagkRoxSuBr2QR+fDFeJjYFu6OVro4wuxUbkhDEYLfTIjD9emIn1FnuVK4tyRZBqLVxuoxW/UvMcIfagHtIv3QacPKYfJ47QHMin9R5+B6a8A439G8MT0f4b/oPkIkB4YDWJThP/++/+GmYlRBNmQb39/vYu2liWp1Ecv5NH5yObjZI8W+jiDZlRi+IHRQp+MOIUUjJhHB4jwsh2w1RakWsGIzWmnH7xbGTrjOMaGIkZGxsQHHd4Ez4WHHF/MyA9rpYPmG/6DhmAobKWjuJWR8eO3n3/u//z79+ejD1+/g+TOPnoLHquHHTmhKynAkGSuApKC49ffvjOkOZHWUQBN5CooKDAYGBiAV/BcuHCBISIiAm4mUYzR4R2igmlU0fAAo4U+GfG4/sSLgv//GUD3tqLo9jUVO8HCzAg66x1FnMqcD679B26ir+oBrd/vj7B2BB3iBrKPkfEfuPKBt9L/MxigL48EqSMH//777+bvv/+/f/z588urLz//PP/wjeXZp+88Lz99E3n19ZcMMWaqiPAyZNuooyi9++YzQ4kPakWAogAL58OHDwwrVqxg4OTkZPj+/Tt45Q5oNQ8WpbiFplnuZnh1zRVFwejlKijBMcoZPmC00CcjLrFtKoKu1MFY702G8QS13H359bJz/wGMkyfbvA0YOFlZCOrHqwDaSgepuf32E3jnL6yVfufNF8Xvv/9grCICqSUVO0j+vedvbq6Eri9Al+kHI7coCQvt0U0gmf+RoUHgPQOkt4PQzMgEOmZ5yG+AQ3holDUaAhAwWuhDwoFoEjy08+Pne3QNvqZiu1mYGVFbi+iKsPBBywxBrVVSW6ce/YcYbrz8jGJipJECg5kcyjA5ijyI8+///yc/fv97A2ulf/3xm/HGq0/8X37+5rz/7itq0xukgUzsKvoWXGAGSLwGVxxWQh/AwzsCrH8UGaHLT7dwZzL8ZmRHscFV5jUDjwzGIiUUNVTlfH6xmaFXHWMH8OjmLKqG8qhhgwiMFvokRgb282L+Pwy0kCDqGAV063bs2AHeUJSQQPBsNhStE3fdZujfdwtFDDROHmGoCC5sYa30W68+cX7+8Zudmq10Ra4fT1S4v77R5P3+RZ37yx8htt+MGjxf+TmY/nNyMv8luuI4y+7G8IhVC8UP6myPGLSMTFHEaMrBdgwDI8NGxoZPATS1d9Tw0RAYIDBa6JMY8NhW7ShLcm3Rk+dFO7+FsMGgowMOHDgA3lhEaqF/4dHHNwHTjuBv1hN2AlYVsFa6rdDHj8Jsv/7r83/h5Gb+w47cSseqkTTBh49YtT+fZXfFaNb7moqdZGFmxLUTmTRb8Kn+9+cKQ5OIDAPDf3AvBK50dDwfHhSjjOEHRgt9EuMU2w5cC3WBnZKC7O6kGAUa1lmwYAGDg4MD+PgAUgv9E/feMkTMOkGKlWC1sFa6CPufnzaC77/DWukgSUHWPwYgmkr4INgcRoYLDP8ZPjAwMl5g+P//AwMLxwVGxwbwkA9Ift3xFyA2fJ6AkZHhowHfi7UKmvpJIHma4tOzpjLsqo1h+P0Dbj/YPkYmwdFrE8EhMUoMQzBa6JMYqeuOPwfdbAXenATT6qgrtFCAmxV1RydMEge9cOFCBnt7iDGg1j6phf6n778Z9Bp3YZhuJPDlpjDrz++wVroi9w8WSfYfPLzMf0VYmP4TtbIGw1BMgYcMoAteGBhABfYFBgbGD6ATQ0HKGF07SNokhtxzEv339KD5t80GrAw/+BksCtcy8IjRbmL856fNDIfbfRn+/XvC8OTsA4ZHpyF38I5eogKKxlE8jMFooU9i5O7Yd2bpX1YelIFoZxOFL+wc7JBCgwjzQAeDgVr6oFb+gwcPGMgp9EHWFE+c/4ad6S83iM3B/J8TRCfLPmWQ5vwJYpKP/zNcZGAEtc6hrXQmhgcM/xgeMLAwPGB07AAdiEa+2Wg6QRPj7N/fbrH7tkae+/8HeKX0+x/j14uqRdwmijQYwfr/7wrDobb/DL+/IlZA/f5xgeHuAQaGtw8CR69LRIukUe6wAqOFPonR+b+B/wDD//+QJjpMb/aptQyi6kS3SmGTtyDtoMIftHoHtLnIw8MDJEQ83lNJvFqEyo8MoDP5IXxYqxxMk9pKhxhBOfl/d2UBA8N/jH0PN34IMzAapzKoS6KOvlBkI7YCH2FgI6Nrx+h5O4jwGGUNQzBa6JMYqf8b+DYw/Ee9+IMhaftKBjkrjF2yxBhNdkv/83MGhpOTMK2AtdL/MzxgYATfsQttpTN9YHRsA6/swdQ08CL/d1eAKh7UypSBgeHuL+Ff8vqujCzS+qwUu/L/36MMh9r5UFr4MEP/M1xkdOug5pwGzORRejQEBhUYLfRJjI7/DXwNDP8ZEMc6gvT7Td7JYBRH0kQuSBsIg85/Bx0dQHIr/9ubRwzHeuVAZiDhj4yuHagrUZAkBzPz//4GAYY/P0BDR9ib9ZLGDAxqXv8YWLmYyPDHR4b3D3YwnJ3ljrFSB2LYRwYWJofBXClCnDlKjoYA5WC00CcxDP83CBQw/P+HOhQhprWbIes4yRuzSLQaVfmry8sZLi1DP9jtIKNrB/j4BVTFQ4O3eelEL3uhV1t4Wf5iT5f///1hEFBlYVB1Y2AQQK/vsPrxIcPHh8cZLiyWYfj9FfecCwuT4WiBjzX8RgWHIcCeuYahR6nlpf8NAgYM//+dxzCv4eNFBgYGfQxxWgmcnX2A4f091AKekWEio0tHAa2spLW5CuVb9mvyfnVYYXyZgY/lD27r/vxkYOAQeM8gZy3IwMLBwCCi/oiBS+Q+w/+/LAw/P31m+P72B8PDo18ZXt+wZmBkwHeC20cGRsYERpf2DbgtG5UZDYHhBUYLfTLi838D/wOM6/ViN2xkUHZEueSbDKOJ1PL/CcOeah6MoYoh3GJVqNxS8P8f5BA7Ld6vDOtNL/1gZ/pLyzN4Rod0iExto8qGFxgt9MmIz/8NfBMY/qNdkcgp/Iih/B5oPBqEyTCVePDi2vH3Es82CaLpeMjo2oGvVYumfPBwFSq2K/z//xfUewLPR3hJvDkwTecGai+Gus7dyMDCkYC8SYy6xo+aNhoCgxeMFvpkxM3/BgEHhv//9mNoDV+8nEHTD32cHUMZJQJvP/5g4D3TwcD2H20t/n+GhYxuHaQd4EOJQ6ioV6F8y/n/DJBrFDlZ/t287nAMcX7Pw9MXGASkDRj4pahhI2iXcMNALU2lhgdGzRgNAUrBaKFPZghiXa8P2pla/ewmAysXTc6N+ffn1583F7ayiH04he7qjwwsHApDseWqULG54f9/RvhqqHWmF28a8X+GFPrv7l9guLYdsoySX5qBQVyDgUFIkYGBhQ3d/4T4GxkYGCaMFvaEgmlUfiSA0UKfzFjGOaHLJXKZofS2AAMjkyyZRuPWtr+FgeHvVwz5z8xCM/mcyjIwJAa5gELVVoP/f/+DhnXALkUZ1vn76yPDifmfGf7/hezSZWS8w/D/P+SGFSn9ywxK1rDdtG8YGBhAa/hBw2qoG89A5/0wsx8YipUhOEBGidEQoAEYLfQpCNT/DXwLGP4zYJ65I2d1hiFhMwcDEwvGCZJkW7evmYHh3zcM7b8Z2Bl2cSd+/M3C5RBoJjZoN19hOBx0TyO+YZ3LGw8yfHwK2azFyAgqzNcx/P+fCDZHVPkAg7o7bMx/dBctOFBGidEQIA6MFvrEhRNWVf8bBAQYGP6DjmXAXKoJavEXXv5G8VDPnx8MDGdnMTCAduBiccVhzhCGN8yg04EZPjCyMDkOlYIfeViHkZHx41qTCy+wDuuA/MzIlMjA8D8BfvyFlucFBiFFyLAPI2Pg6JJLUCCN4tEQIA6MFvrEhRNOVeBhHkjBDxpeQFPH+IHBu2cbg2mKNQMDA+mXrDw8/Ifh/j4WBlDBj2YyiPvt5eNXu1R62f//Z4DbzcjwPzHQUpLgBekg/QOF0Yd14uSeH2xSuwtp1WMM60AuNPlfzw+60hC8uofBKOYJAxcfZNhnCC9THajwH7V3ZIPRQp8K8f+/QSCA4f+/9TiNAi3n9J94hEHNU4/QkM/7b79//Hj/moPt0R4G4a/XcRrJ8OomA8OtvQzvBIxuHrJcITGUCn7k1TqCrH8unLc/AWm1g3yLMazDCFqGKsDw/999kDQDaKjHOhNRybl2jKZhcMCMEqMhQBwYzTDEhRNBVeBlnAz/NzD8/w8vkLBqAg37aPueZVByUGAQVHzAwC3K8OTVW+Pzd5/qivx7xaDDcPsfL8sf/OfL/Pn5lOHEXGmY+b9ZBT7ucDz04g8TB2TVCwMDw2Bt8SuUb53wn+F/PsjtoGGdAxanP8tz/4C02l9cP8lwZz9i5RMjE+iY4w3gsIUtkWXluMBgngSpJEYPSQMF4ygeDQGSwGihT1Jw4VcMHepZwPD/P+YYPy6tNlm4ZPCLf3t7hOHcSvh5MkOh4Feo2Ozw/z8jfH8D5rDOXAZ4pYl0Ty3KIXf80gcZdP0hQ0FDeG8C/sgdlR0NAdqB0UKfymELmdz9B9qxi7mqB5tdxBb6H58xMPz9xcAgBBrtgBr0989NhjOLJGDX/WEv+BkXBFqKQ1a9QLUNBKXQsF+A4ftX0CYssAcwhnXOLDnJ8OMTpJUPGsJhYFSAXVmIskpKSu8gg5INpNBnYBhduTMQkTlq55AGo4U+jaLvf4OAAgMDuPDHfx4PgUL/GyMfw89Xd68I3lwNWf6p5fWCQUhBAu7sIVLwkzOsA/MjykY45JU7DAyOoxuuYKE0So+GAHFgtNAnLpzIVgUZ8vkXwMDAGIB12AdLof/pDwvDhS9CL9kkzMWfsygzcH97+MTtkCtk3BvkEuPIUgZOwW4QE4zRCn6Q2H6bLUc+8KjBh38YGQauxY8+rFOk/OhInuIjiNvAq3VQhnUWMjZ8QjlO4n8933+Qn8DYIuUjAwsbZN6EhUGR2tc3gu0YJUZDYBiD0UKfjpELaf0zODAw/AMPcTAwMDowWGeCZl0vMPxn+HDlMw932TXVkmufwdfeMrR7G3zkYGUBF3DOx/yP8H26DikoGRkWMthmHWD4xzAf7vxBWvCjD+vIcP08ecTqNGQYB+R41GGdhwwMjAawYR2QNLjShB1lzcT8hMEqHV75MY6u3AEF0SgeDQGSwGihT1Jw0V6xQsXmA///M4LHrA1khA7EmyiBd55KvNx7wfJ8JmTVCuiMH0ZGRQbbjACUgp+B4SPD5fUvGD4+h6/iQW/xM/xnuMDIye4YaCj4gfa+YWBAH9a5YHecgZ/1D7giY8BcrePI2PABdG0i3Gkoy2GRV+4wMAzpC2PgHhxljIYAncFooU/nACdknULF1oT///+DW/CMDAwf+wJMIAUkAwOD/x6Dm0x/vkEKdEaGRsaGTw3/91YkDNaCX6FyW8D/f4j9C+0ad05GyryAtPJ/fHrCcHYpL9JqnYmMDZ8wLoBBWbmDfPzCEL8whlA6GJUfDQFagdFCn1YhS4G5ChVbHvz/D9nB66UlfcRVTRI8rKNxb9oRzVsTwGwGBsYHjI0fFUHW/N9fZcDw5x+ohQyrIDBa/DeVc45cU82D6mVgoHWLHzSs8//7V9CGKvAuWoxhnZPzLjD8/gHpuTAyYgzrgPwFwigX0cubHWGQNYH6gbGQ0bV9AkjNKB4NgdEQIB6MFvrEhxXdVCKfS8POwnSzw8cI0rpnYGAI3KnxkeH/P0jhzsiUyNjwAXzkwmAr+BXKt67/z/A/ABRooE1YKMM6944cZHh2CTyEBZJnYGTCGNYBi4Pqpnr+8wwM/yGVw+jKHViwjNKjIUA2GC30yQ462mmEtpLfw2xIsVC5oC0hAC74zC7mH5B+vh08zs/AwHiAsfGjI0wdloKfgeHR6SMMj05DW8cMDFha/FQ/qI0awzpwPyGv3EFe6cTCITh6ZDIslEbp0RAgHowW+sSHFV1VKlRsWfAfemyztADXkRIHLXDBjbF8k5HJkLHhA/xI5YEu+KEVFnxYR5P325Ht5ufAbgcHIOqwzkXQCibk1TpgNVAC5fgFZtabDJapsB7PR0bXDvCwEVTpKDUaAqMhQCQYLfSJDCh6K4PeGwsqPMFW17jpPhHmYgcvV0Rfvomxrn1/gwDD7x8HGBgZEMdBoLX4MQ5q+0+do5mRh3VYmBienLU9wQtfrYM5rINSYYE9ikT8bxBIYPj/DzypzcDBd5LBJAYyCTy6cgcplEaZoyFAGhgt9EkLL7qqJm75Juh0NSZB9Nby/wEo+NGHdWbrX7/gKvoWPCzFgLlaB7z6CF+A4ly5M3r8Ar5gG5UbBXjBaKGPN3gGVhK5EEVfvumzz/wC66/3kAIVunwT3bVYC/4Pzw4yXNkAn0TFaPGD6hAyzuRHH9axFPp0cLkR0mTt8dk3Gf7+hgzPMDJeZGz4CHE7uqOR+CjHL6g4nmSQ0IS09JkYEhmdO8AT2EjKR5mjITAaAkSA0UKfiEAaSCWkLt9EdyvWgh/thE5qFPwK5Vv2/2cA7TZmYMAY1rm58wDD67vQyWdQrYI6D4HuZhj/fz3/fQaG/5Ddy/rBNxl4xSGVxuiZO7AgGqVHQ4BkMFrokxxk9NWgULml4P8/hn6QrejLNwN2aT1h/PcHPM7PgLR8E6QWGUML/gkMjEj3+aIV/F+5FJ7ss9n0lZwz+ZHdCLIXZVjn88ubDBfXwgpr0JETBId1QGaATyv9/w++gokBaeXO6PELoBAaxaMhQB4YLfTJCze66QINmzB8//rgPwPkSkRil29ic+D/XRUL8BX82I9mxn/9InTC+TwDAwN4NQ01hnVAbkdZuYN6/MJDRtcOSOsfpHAUj4bAaAiQBEYLfZKCa2AU41q+yfbr40fvfaaQjVogp6Et3wQJoWOMgv/3jwsM55Yp4j2Tn5FhQqCFRCG6WSA+8rAOJ8u/myesT0nAV+sgD+tAzsh3QF5eCtKPC/9vEChg+P8P3MNh4BE5wmAQBlv2uZHRtQO86QuX3lHx0RAYDQHcYLTQxx02g0YG2poma/kmNk9gFPxoJ3Rib/FjHs2MPqyzzvTiTSP+z5ChHIxhHaZCxoYPRB+b8L+BD3QRDfhaRQbkM3dGV+5gi9JRsdEQIBqMFvpEB9XAKkRevqkqxncwy0oNvAJH8P35mw4nwyEFLciJjEyKjA0fHoCY+PD/3ZUFDAz/IS1pkEIsBf8R8yWXcZ3JD62I4MM6XhJvDkzTuYGYrEVdrXOQseEjQg5kHwGMsnIH+fgFRsZARpf2DQS0j0qPhsBoCOAAo4U+joAZbMLoyzfbvA0YYGftE7N8E5t/ME7oRCv4QXrQj2aGXcaCPqxz3eEYouK5vPEgw8en4EqJATKsAzojn2BFBLIPhv/X879nYPgPnidgMIp5wsDFB5mwZmEyZHRsg+9AhqkfpUdDYDQEiAOjhT5x4TQoVBG5fPMDY+NHQWIdjFHw//v3hOHqxq/4zuR//fXHg/4D1xW+//4LtgZlWOfd/QsM17Yj1uAzkjasAzIQfNnM/3/w4azRlTugUBnFoyFAHTBa6FMnHOliCvIYOjMj45Mef2NI65eBgYHY5ZvYHIpR8BNxGcvTj98Yph65yeAo/BIxrAO++nD+Z4b/fyHuYmQkeVgH5D6cK3f+M1xkdOtAVCggxaN4NARGQ4AkMFrokxRcA6uYhOWbFxgbPxqS4tr/uytAY+6gsXLYaiCMM/kvadUfvCsXDRm2YWBgeP3l+48ExgUcbP9/QqyiwrAOyCCU4xf4pQ8y6PpD7PzPsJDRrQPl/lyQ+lE8GgKjIUA8GC30iQ+rQaES+fpBYW72kzWuuuCjCcDLN/ebMyCdtY/zjHpcHsFyQidGwb9WspKBST8RbgT/39cMNj/WMLC9vYE+rAM/6x+umEjG/wa+BQzQE0YZpPQOMijZQAr90ZU7RIbgqLLREMANRgt93GEzKGWgq2bg492knL5JjIewFvyPTl9GPpMfveDn/Pfpn8WplNcCHy6Ig+1gZNjI2PCJ7LX0OFfujB6/AA7eUWI0BCgBo4U+JaE3QHoVyrds+M/A4A+yHnn5Jpaz9olavgkyBxljKfgZrt5++EX75VYemLqHmtkM5+Qhy+hBYqy/PzHYnI5lEPh84yMDA6MC+qmfIDXE4v/IF6dYpHxkYGGDDDmxMCgyOnaQtAqIWDtH1Y2GwEgBo4X+EIxphYrNDv//M+4HOR10+iY1lm+CzELG//dXKDD8ZtiAfCb/6ZtPGUxfb4Qrey3rzXBSq+PPb0Z2FpAgqODXujOpSDlpCmL9P0iCBPy/QcCA4f8/0Pp/BvByT+tMSIEPOqfNtWM0vZIQlqNKR0MAGxjNRNhCZQiI4Vq+qfhk5UmDK7XgcX4GBkaSlm+iext0UNuXn79P8zD/VYHJvXhw+6PEk93wgvgjv9bfw6aLmX+z8EKUUHgZy/8GgQCG///Wgw1DPXPnIKNrB2iyGSw1SoyGwGgIkAdGC33ywm3AdSlUbE34//8/+FYpai7fRPYYaLWQ4J+PF5cYXZbT4v2KkPryhoHhwio4/wOfJsMRs6X/f7PwQNITqOBn/F8YaClJ8pn3KCt3kI9fYGSYyOjSUQC3dJQxGgKjIUAWgGRSsrSOahrIEAAVyLhO3zS43nRQ8eES6IoXxgeMjR8VyXErbKUQH8sfhtUml/+q83xlhpvz5eUnhgtr+WD89wIGTw9aruL5/x9yGihInJHAZSzrz78X+P/zpz3jf0aD/wz/HRj+MwgwMDIYiLw7xWB7KoaBgZXzCwOXEA8DvxTIuLkMj8+WUDJXADJkFI+GwEgHo4X+EE4BsEIZ5AUedpYLzZ4G4I1L1Fi+iTxvADK/Ru3B8RS5J5YgNhy/vMHAcHsfnEvsmfzrjz93YGBgiv/P8B/rmnt4oQ83GYWxgIGRaSFjw4cDKKKjnNEQGA0BosBooU9UMA1ORSQs3yRpCSW0F3H+PwMD+Nx6Ga6fJ49YnYbOE6CFxevbPxhu7uaAiWI/oRNyJj+osP/PwFjPAL1hC6YHnZZ8tYfB4lwWujAan/EAAyNj42jhjxYso9zRECAARgt9AgE02KWRl29KC3AdKXHQAp87D16+ecxHBjw0IqTIwCCqeoKBiUUDdIjZVyaBB7u4EsAFOgMDwwFGBsYH/xkYDzAy/D0YaCn5ALkHwcjI+PGC3XEG+Bn5L66fZGBiMmcQQ5yvxkDMmfwM/8//Z2Akapewxp3JDJp3JhMb9KCWP+jY5g/EahhVNxoCIxmMFvpDPPaRh2FgyzdlmF7wK/8+zyD15y5W36EV+ihqfv7+e2ft5Ucqpx+9BYu3a9w5GSnzAtLK//HpCcPZpbwM///zM6g5M6AU/GgndGJr8YMNJIIgsdAHLeZ8wAA6crnhw+jpm0SE76iSkQ1GC/1hEP8K5Vsu/Gdg0LcQ/MjQrvPkgyL7e8iRxDj8hq/Qh2l59+0nw8VHz+5OktmoDBNjODnvAsPvH+B5AwZGxocM5skzGVjY2uDyaAX/GyHLz0dM53P+Z2QCr+OHq0NlfBTlYzukJs39l/v9FTV2ZgYtlr9fGRg4BD4zfH7Gy/D9PQPD1Q03GW7tkABXNqh6kXiMHxgYGUFHT4wW/EihMsocDQF0MFroo4fIEOQ7N63OipJ+PjVJ7hlRriem0IcZJPL3CYPRj10M3He3HWR4dgm6IgjUuGYCFbAHME7ohBb8HzgV+Y+YLmb4zQpf4AMzEkyzsjA8NlMVPCvGzwaqRGBDTWA5HMQDhrv7LjCsTTFm+PZWFrua0YIfe7iMio6GAAKMFvqIsBiSLMiRCaD1+v9BhSdRfiCl0AcZyPL/5z/jS2XfpZ5v5wbxGUBr5hs+wdfMoxf8v/6z3N7JlaT0h5kLscQTrBFCKEtwMWjJ8vxmYWZkhYiQRH5guLvvAMOSIEfsLX9wwW9IzO1hJNk6qng0BIYJGC30h3BEQgv8/aDJWVze+M3A+p/1z/c/DILKrAxSRgwMwioXGLiEQMo//P7zn+fH778Pbr/4xnPh/kc3QS42JpAELmx0uYJB/tn6iwwMjKALzlEmTpEL/sOcIQxvmCFH6iObxc7874eBsiCHlBB8sQ+yNGnsr2/3M0w1VcHe6mck+Whp0iwfVT0aAkMXjBb6QzTuCBX4X1lE/rxXCmMRk5JmYGPBW5bDQ2DnlRcMT9/8+inIwc4OF0RjiLw/U2/n5dOEJgzm/t9TGXCH1WjZZTZbTrAAEiH8/Q6DjaYAA5OENpIohcx/fy4z9KozMnx9o4NhEhk3dmGYMSowGgLDEIwW+kMwUkFn4jD8/nGegRGyjh7ZC5/+sDCcFw5gMNU3+MPFxoxvAhVZGwr72+OLDMef8TB8+g8/VBNFnpGZyTDQTAxjwhS0w5bhx49H/xkYoQfxQLSx/P3+10vlBzOzuAZEgJrkn5/XGFrFpTGHesDDPKBTRlF6JNS0etSs0RAYimC00B+CsfZ/dwXohE2Mw8d+MvO8PCQUJe6qT9apCygh8e/zC4bjl57/fMUsha3V/4CRg90w0FAQpUBde/x5AyNk8xWKWd4K736zSWiSM36PYg5OzucXmxh61f0w5BkZGhkbPjVgiI8KjIbACAajhf4Qi3zksXMUp7NwX2Gwr/rPwMikiyJOCefHB4ZLJ44x3OUwxjTlP8PEICsJ+GQuSMG6Yy/eMzAyoCwX1eJ6xqCuR9SeLJAR5OO1yTsZLq9xRzVgtLWPGh6jvFHAwDBa6A+hVAAe1vnz8z6WiduPDA71txlYOEyo7Z3/Hx4z7L32+eVnJmHIrVhIFjAy/HcMtJQEn4Gz/vjzhP8MjOBTP5GUMPhr/vvKxC8JWfWDLEFt9u9vJxhaJS0wjGVkIvvaRgyzRgVGQ2AYgNFCfwhF4v/dFaChCtDZNaiutihcy8AjFowqSD3ev3//r2089UqagQFxgibE9P8LgywlwYemrTv2AnThCvg2L4gcA4MoH9tmGy1BXxif5jS21j6FVzfS3M2jFoyGAJ3BaKFP5wCnxLr/uyruY0zecoudZLAshByTQInhBPTeevZl49VHX1EKdZAWRob/iqDzerAN7TjqCm8R4GbxAakjBv/48YNh48aNDCBaQECAwd3dnYGDg4Tlnc8ubmGYZYdmH2UXyRDj7lE1oyEwlMBooT9EYuv/7grQxC1oAhfVxY5NJxiYWTGHNVBVUcz7/5/h0dazLxl//2FA2Q37n+F/IxMDw4L/DIzwy9qhln0MtBB/zwA9qRMqhpdauHAhg76+PoOBgQHDjh07wAW+gwPI23i1IUs+YGgUEMRYycPIBFrFM3q3LnJIjbJHLBgt9IdI1P/fUzGB4T8D4iZykLt5JHYzWOS7gpj0wPdefl1+8f6XSBS7/jNcYGT8X/ifAXJnL0yOnYXxqJeJmDWMTwz94MEDBgUFyIkMFy5cYADxAwICiNGKUNOtcpTh62tUexkhR0YgFI2yRkNg5ILRQn+IxP3/3ZXnGRjQjlrQi1rOIKaLWggT4R9QYXrgwAGGhATwcDwROiBKfv75e2DbmTcYTW9GRoaq//8ZEAevMTAwSAmxXzBXEyD6aAiIDQzgoZ2DBw8y3L9/nwFU4EtISMCkiKNXRl1guL4V1d7RpZvEhd2oqhEBRgv9IRLN/3dX/MdwqnPrZVKXaJ44cYIBhEFj5qQW+iD7t5x5+Rh9iIeB4f9CBgbGeJA8DCuIcy40VORDEYPJ4aNB4/kvXrxgALX0GRkZGfz9MaYR8GlnYNiSv5DhzAJUe0cLffxhNio7osBooT8EohvHeP5HBpd2flKcD2rhgwpTDQ0NcMFPTqF/8uaHo8/e/0QZPmFk/L/z/39GlDXy5BT6Hz58YABVRjA/NTY2MtTXYy5WgsljpUcLfazBMio4GgIwMFrow0JiENM4Cv2DDC7tiKOOSXA/qPAnZ3gHZMX5+58WPnj5HaUl/f///52MjKiFPjnDOx0dHQwZGRnggh/U2l+wYAFDRUUFyFri8ejwDvFhNapyRILRQn8IRDvWQl9U8wKDfhzq2DWRfqGk0H/6/ueBUzc/oI3rYw7vkDORC+qFgCojQUFBhvfv3zNEREQwkDymPzqRS2QqGFU2UsFooT8EYn4wFfoPXn3bef7eZ5ShnP+MDDMZ/zOkowTlf4YPgZbiHxkYGORRxInggFr5JBf2EHMfMjQI8GPsWGZkAp2vj3FAHETLKDkaAiMLjBb6QyC+sRb6DAyDZ3iH4X8jIwMj6BwelDkGUjdnURwVb27tZ5hi6ohiDiPjR8aGjyjnAaHIj3JGQ2CEgdFCfwhEOOTs/H/nUZ3K+IHBpY2swoyS4Z2Ttz5cePbuJ8qwEiMjQ+H/fwwODIwMKEttlCQ49usr8KMWwqieoC5vZ80WhuOTUXfkjh7DQN0wHgVDHowW+kMkCrEu2XRqus7AxKpJTy+sP/EStLMVsoMKajHo4DXQzluMA9f+M3zwsxC7yszIiLLaB6qNutS/P1cYmkRksAztjB64Rt2QHjVtiIPRQn+IROD/XRUXGBgZ9FGcqx2+gUHSgMQtqygmkMTBuTmLg10QZND/Hz9BFQLKEI+MCNtOUxVBlDkAkFqqY2yHrYEsYWQSZGz4gHLuP0h4FI+GwEgFo4X+EIn5wXAMA45D1y4GWUqAh3vWHXsxgYER7agIBgYGDyORzZxszLQ7bfPr2wMM3UpoK4oYQAeHL2Rs+ETatuMhkh5GnTkaAuSC0UKf3JCjsz7s4/oMDAxWpTsZuIRo3pL+////4w0nXvGiX5ICGs8PtJCYAAqO9cefK/xnYAStkkFp7bMxM370MhF9zMjIiHmXLUgjJRg0rNOtxsfw/a0chjGjB61hBMmowCgYLfSHUBr4v7sCNHyCugSSS+wEg1UhzU/ZxNHKZ4AdrQwLRlxXJvJwMF9z0RfmZWRkRDmlE6aPLPr/v8cMU8w+M7y9rYWhf/ToBYwgGRUYDQEQGC30QaEwRDDOqxINEzcyCKuhrJyhppd+//l/dsvpV8rorXzQmTuwS1SQ7Vt3/AWotY86/8DAwMDGwnjZ3VD0GwszI+Xn///5eZ2hT+sPw7c3mNdDMjJeZGz4CB5yQnbXKHs0BEYBaNRzNBSGVAhgbe0zMH5gsK04zMDOR/Vx8////1/Zdvb1/19//mMUruitfFhA4hrmAcv/Z/hgrSV4QIyfDbSUE2UYCCxPmPjI8OjYDoZ5Xu4YK3VAehkZPzIwMDowNnwAVTwgkVE8GgKjIYAERlv6SIExFJj/91QGMPz/vx7DraycjxhsK79ScwknaBz/0LX3L999/o159y6Wi9GR3bT+1CuD/3/R9xYgVLCxMh1x0Bb8ys3BAhqaIqbw/8jw/sEJhqXh/AxvboD0IAxDZo3uvkUOjVH2aAhggNFCHyNIBr/A/90VGxgYUDdCQVxNvRb/v3//r28/9/oPthY+AwPDRUYOdodAQ0G8SyHXn3wZ8P/f/wWYd+tCXAslH6hKcV2WEuL4ysPBosHGAmqpQ2W+v+dneHPzBsP1LdwMx6boMjD8R9kfAFUFoSAt/ALGhg8g+yBio+RoCIyGAAYYLfQxgmTwC/zf3yDA8PvHAYx1+zCnKzquYFB2syTn3BsGBoaPrz7+2n/02nvQDltsO34/MjIzOQSaiRE1fAJt8R8gUPDDXA6mZV7uOGB6Pg9zCSZYFgsBKfBHh3SwBM2o0GgIoIPRQh89RIYIH7qEE3dh+p/hAYNJ8gUGQRVDIgv/j19//Dlx4Op77l+//9ngCgZGJsbAQHNxUE8DlxIM8fXn3wv8//5zAfoxDRgKoQIkFfqMjAcZGBgDRjdgQQNvlBoNAQJgtNAnEECDWZpgwQ9zPLfkLgZl5zcMPGJsDGw8igwsnF/+/P3P8evPv/c/fv/7cP3JV65X73/aYa7OgRkAoRkZ/icGWkqSPXwCGe75N4GBgRF12SnEeDhJVKHPyPiQgYERNJxDUgUEt2SUMRoCIxSMFvpDPOIJDvVg8d9TVrUDp9i9iB8+YWAgaUgHi5UoQuuPP0/4z8CQwMDAiPUSGLyFPqRlv2B07B4lSEc5oyFANBgt9IkOqsGtEOsxDTicTFqh//8gIwdHAKFJWxxW4RUGLe1kYGBw+M/AAKqAFGCVAEqhDy7k/z9gYGACDWUdYGz4ANqghtfcUcnREBgNAdxgtNDHHTZDTgY63AM6EgFrCxrmIeIK/f8PGZmYCkgdv4fZMUqPhsBoCAxOMFroD854ochV4EtX/jMkMDAygE7gxFgDj7fQ/8+wkZHx/wZKxu4pcvyo5tEQGA0BmoLRQp+mwTvwhoMrAMjwCWgIBeQge0Sh//8hAwPDA4b/jBcYmRkPMLCxHaDFMA7I0lE8GgKjITA4wGihPzjiYdQVoyEwGgKjIUAXMFro0yWYRy0ZDYHREBgNgcEBRgv9wREPo64YDYHREBgNAbqA0UKfLsE8asloCIyGwGgIDA4wWugPjngYBaMhMBoCoyFAFzBa6NMlmEctGQ2B0RAYDYHBAUYL/cERD6OuGA2B0RAYDQG6gNFCny7BPGrJaAiMhsBoCAwOMFroD454GHXFaAiMhsBoCNAFjBb6dAnmUUtGQ2A0BEZDYHCA0UJ/cMTDqCtGQ2A0BEZDgC5gtNCnSzCPWjIaAqMhMBoCgwOMFvqDIx5GXTEaAqMhMBoCdAGjhT5dgnnUktEQGA2B0RAYHGC00B8c8TDqitEQGA2B0RCgCxgt9OkSzKOWjIbAaAiMhsDgAKOF/uCIh1FXjIbAaAiMhgBdwGihT5dgHrVkNARGQ2A0BAYHGC30B0c8jLpiNARGQ2A0BOgCRgt9ugTzqCWjITAaAqMhMDjAaKE/OOJh1BWjITAaAqMhQBcwWujTJZhHLRkNgdEQGA2BwQFGC/3BEQ+jrhgNgdEQGA0BuoDRQp8uwTxqyWgIjIbAaAgMDjBa6A+OeBh1xWgIjIbAaAjQBYwW+nQJ5lFLRkNgNARGweAAo4X+4IiHUVeMhsBoCIyGAF3AaKFPl2AetWQ0BEZDYDQEBgcYLfQHRzyMumI0BEZDYDQE6AJGC326BPOoJaMhMBoCoyEwOMBooT844mHUFaMhMBoCoyFAFzBa6NMlmEctGQ2B0RAYDYHBAUYL/cERD6OuGA2B0RAYDQG6gNFCny7BPGrJaAiMhsBoCAwOMFroD454GHXFaAiMhsBoCNAFjBb6dAnmUUtGQ2A0BEZDYHCA0UJ/cMTDqCtGQ2A0BEZDgC5gtNCnSzCPWjIaAqMhMBoCgwOMFvqDIx5GXTEaAqMhMBoCdAGjhT5dgnnUktEQGA2B0RAYHGC00B8c8TDqitEQGA2B0RCgCxgt9OkSzKOWjIbAaAiMhsDgAKOF/uCIh1FXjIbAaAiMhgBdwGihT5dgHrVkNARGQ2A0BAYHGC30B0c8jLpiNARGQ2A0BOgCRgt9ugTzqCWjITAaAqMhMDjAaKE/OOJh1BWjITAaAqMhQBcwWuiPgtEQGA2B0RAYQWC00B9BkT3q1dEQGA2BUTBa6I+mgdEQGA2B0RAYQWC00B9BkT3q1dEQGA2BUTBa6I+mgdEQGA2B0RAYQWC00B9BkT3q1dEQGA2BUTBa6I+mgdEQGA2B0RAYQWC00B9BkT3q1dEQGA2BUTBa6I+mgdEQGA2B0RAYQWC00B9BkT3q1dEQGA2BUTBa6I+mgdEQGA2B0RAYQWC00B9BkT3q1dEQGA2BUTBa6I+mgdEQGA2B0RAYQWC00B9BkT3q1dEQGA2BUTBa6I+mgdEQGA2B0RAYQWC00B9BkT3q1dEQGA2BUTBa6I+mgdEQGA2B0RAYQWC00B9BkT3q1dEQGA2BUTBa6I+mgdEQGA2B0RAYQWC00B9BkT3q1dEQGA2BUTBa6I+mgdEQGA2B0RAAbASFAAA1EzaGg7TkeAAAAABJRU5ErkJggg==", "text/plain": [ "" ] @@ -95,26 +95,63 @@ "data": [ { "name": "node-data", + "on": [ + { + "modify": "node.datum", + "trigger": "fix.length == 2", + "values": "{interactionX: fix[0], interactionY: fix[1]}" + }, + { + "modify": "reset", + "trigger": "reset", + "values": "{interactionX: null, interactionY: null}" + } + ], "values": [ { + "fx": null, + "fy": null, + "group": "U", "id": 0, - "label": "n0" + "inputX": -0.1482166732511968, + "inputY": 0.06905348785594277, + "label": 0 }, { + "fx": null, + "fy": null, + "group": "X", "id": 1, - "label": "n1" + "inputX": -1, + "inputY": 0.4564093903149109, + "label": 1 }, { + "fx": null, + "fy": null, + "group": "W", "id": 2, - "label": "n2" + "inputX": 0.5833384369100308, + "inputY": 0.4592981281583665, + "label": 2 }, { + "fx": null, + "fy": null, + "group": "Z", "id": 3, - "label": "n3" + "inputX": 0.034337072693475655, + "inputY": -0.7451349045498585, + "label": 3 }, { + "fx": null, + "fy": null, + "group": "W", "id": 4, - "label": "n4" + "inputX": 0.5305411636476903, + "inputY": -0.2396261017793615, + "label": 4 } ] }, @@ -122,39 +159,50 @@ "name": "link-data", "values": [ { + "group": "T", "source": 0, "target": 1 }, { + "group": "V", "source": 0, "target": 2 }, { + "group": "V", "source": 0, "target": 3 }, { + "group": "U", "source": 0, "target": 4 }, { - "source": 1, + "group": "W", + "source": 2, "target": 4 }, { - "source": 2, + "group": "Y", + "source": 3, "target": 4 } ] } ], "description": "A node-link diagram with force-directed layout.", - "height": 300, + "height": 500, "legends": [ { "stroke": "color", "symbolType": "stroke", "title": "Group" + }, + { + "stroke": "colorlink", + "symbolType": "stroke", + "title": "Link Group" } ], "marks": [ @@ -173,6 +221,12 @@ "cursor": { "value": "pointer" }, + "fx": { + "signal": "datum.interactionX == null ? scale('xscale', datum.inputX) : datum.interactionX" + }, + "fy": { + "signal": "datum.interactionY == null ? scale('yscale', datum.inputY) : datum.interactionY" + }, "size": { "signal": "2 * nodeRadius * nodeRadius" } @@ -182,18 +236,6 @@ "data": "node-data" }, "name": "nodes", - "on": [ - { - "modify": "node", - "trigger": "fix", - "values": "fix === true ? {fx: node.x, fy: node.y} : {fx: fix[0], fy: fix[1]}" - }, - { - "modify": "node", - "trigger": "!fix", - "values": "{fx: null, fy: null}" - } - ], "transform": [ { "forces": [ @@ -231,9 +273,7 @@ "signal": "restart" }, "signal": "force", - "static": { - "signal": "static" - }, + "static": true, "type": "force" } ], @@ -253,7 +293,7 @@ "value": "black" }, "fontSize": { - "value": 15 + "value": 10 }, "text": { "field": "datum.label" @@ -275,9 +315,22 @@ "name": "labels", "transform": [ { - "as": "y", - "expr": "datum.y", - "type": "formula" + "anchor": [ + "top", + "bottom", + "right", + "left" + ], + "avoidMarks": [ + "nodes" + ], + "offset": [ + 1 + ], + "size": { + "signal": "[width + 60, height]" + }, + "type": "label" } ], "type": "text", @@ -287,7 +340,8 @@ "encode": { "update": { "stroke": { - "value": "#ccc" + "field": "group", + "scale": "colorlink" }, "strokeWidth": { "value": 0.5 @@ -312,6 +366,69 @@ } ], "type": "path" + }, + { + "encode": { + "enter": { + "fill": { + "field": "group", + "scale": "colorlink" + }, + "shape": { + "value": "triangle-right" + }, + "size": { + "value": 40 + }, + "stroke": { + "field": "group", + "scale": "colorlink" + } + }, + "hover": { + "opacity": { + "value": 1 + } + }, + "update": { + "x": { + "field": "target.x" + }, + "y": { + "field": "target.y" + } + } + }, + "from": { + "data": "link-data" + }, + "name": "arrows", + "transform": [ + { + "as": "tan", + "expr": "atan2((datum.datum.target.y-datum.datum.source.y),(datum.datum.target.x-datum.datum.source.x))", + "type": "formula" + }, + { + "as": "angle", + "expr": "datum.tan*180/PI", + "type": "formula" + }, + { + "as": "y", + "expr": "datum.datum.target.y - nodeRadius*sin(datum.tan)", + "type": "formula" + }, + { + "as": "x", + "expr": "datum.datum.target.x - nodeRadius*cos(datum.tan)", + "type": "formula" + } + ], + "type": "symbol", + "zindex": { + "value": 40 + } } ], "padding": 0, @@ -326,6 +443,43 @@ "scheme": "category20c" }, "type": "ordinal" + }, + { + "domain": { + "data": "link-data", + "field": "group" + }, + "name": "colorlink", + "range": { + "scheme": "category20c" + }, + "type": "ordinal" + }, + { + "domain": { + "data": "node-data", + "field": "inputX" + }, + "name": "xscale", + "range": [ + 10, + { + "signal": "width - 10" + } + ] + }, + { + "domain": { + "data": "node-data", + "field": "inputY" + }, + "name": "yscale", + "range": [ + 10, + { + "signal": "height - 10" + } + ] } ], "signals": [ @@ -349,13 +503,6 @@ "name": "linkDistance", "value": 80 }, - { - "bind": { - "input": "checkbox" - }, - "name": "static", - "value": true - }, { "description": "State variable for active node fix status.", "name": "fix", @@ -376,6 +523,17 @@ ], "value": false }, + { + "description": "Unfix node", + "name": "reset", + "on": [ + { + "events": "symbol:dblclick", + "update": "item().datum" + } + ], + "value": null + }, { "description": "Graph node most recently interacted with.", "name": "node", @@ -396,12 +554,18 @@ "signal": "fix" }, "update": "fix && fix.length" + }, + { + "events": { + "signal": "reset" + }, + "update": "true" } ], "value": false } ], - "width": 300 + "width": 500 } }, "metadata": {}, @@ -410,8 +574,22 @@ ], "source": [ "g = nx.generators.barabasi_albert_graph(5, 3)\n", + "def rand_group():\n", + " possible = \"TUVWXYZ\"\n", + " return random.sample(possible, 1)[0]\n", + "\n", + "node_properties = {n: {\"group\": rand_group()} for n in g.nodes()}\n", + "\n", + "pos = nx.fruchterman_reingold_layout(g)\n", + "\n", + "edge_attributions = {e: {\"group\": rand_group()}\n", + " for e in g.edges()}\n", + "\n", + "nx.set_node_attributes(g, node_properties)\n", + "nx.set_edge_attributes(g, edge_attributions)\n", "nx.set_node_attributes(g, {k:f\"n{i}\" for i, k in enumerate(g.nodes)}, \"label\")\n", - "schema = plots.spring_force_graph(g, node_labels=\"label\")\n", + "\n", + "schema = plots.spring_force_graph(g, node_labels=\"label\", layout=pos)\n", "plots.save_schema(schema, \"_schema.json\")\n", "plots.ipy_display(schema, format=\"interactive\")" ] @@ -419,7 +597,7 @@ { "cell_type": "code", "execution_count": 6, - "id": "67ab6bf5", + "id": "29942999", "metadata": {}, "outputs": [ { @@ -433,1971 +611,460 @@ "data": { "application/vnd.vega.v5+json": { "$schema": "https://vega.github.io/schema/vega/v5.json", - "axes": [ - { - "labelOverlap": true, - "orient": "bottom", - "scale": "x", - "tickCount": { - "signal": "ceil(width/3)" - }, - "title": { - "signal": "x_name" - } - }, - { - "labelOverlap": true, - "orient": "left", - "scale": "y", - "tickCount": { - "signal": "ceil(height/3)" - }, - "title": { - "signal": "y_name" - } - } - ], "data": [ { - "name": "points", - "values": [ - { - "test4": 1.4985307211641574, - "test5": 2.154607120895615 - }, - { - "test4": 0.18609256309747202, - "test5": 2.3105514262657434 - }, - { - "test4": 0.47365861217910066, - "test5": 2.6389708988828726 - }, - { - "test4": 0.6529136273654698, - "test5": 1.8746561195115532 - }, - { - "test4": 1.0258656914380422, - "test5": 2.3491502520810226 - }, - { - "test4": 0.13522530872952987, - "test5": 0.49398692445857395 - }, - { - "test4": 1.4751072740157576, - "test5": 1.6431532503705286 - }, - { - "test4": 2.2566940412156447, - "test5": 1.3195861907995494 - }, - { - "test4": 1.8196267913668573, - "test5": 1.1753115113703947 - }, - { - "test4": 1.1598924368134358, - "test5": 0.47374993866559056 - }, - { - "test4": 2.152795947691504, - "test5": 1.9353053773207733 - }, - { - "test4": 2.0077608975753667, - "test5": 1.8668277621406988 - }, - { - "test4": 1.2696228845606785, - "test5": 2.4119750574743666 - }, - { - "test4": 1.5552730789901883, - "test5": 1.3823309742853707 - }, - { - "test4": 2.709014544885976, - "test5": 1.0035561976902507 - }, - { - "test4": 1.4859239549423227, - "test5": 2.853648281882421 - }, - { - "test4": 1.8797520124101714, - "test5": 0.07172509031605623 - }, - { - "test4": 0.0529950581500499, - "test5": 2.8849215687763756 - }, - { - "test4": 2.799792617436673, - "test5": 2.647288912100909 - }, - { - "test4": 2.504093490837053, - "test5": 2.5890733832651955 - }, - { - "test4": 0.5305829920906653, - "test5": 0.5705606580044874 - }, - { - "test4": 1.9588182814646262, - "test5": 0.18310103319131665 - }, - { - "test4": 1.1272266850222323, - "test5": 2.889369324890685 - }, - { - "test4": 1.3644342002964345, - "test5": 0.37746594810276013 - }, + "name": "node-data", + "on": [ { - "test4": 1.224295775676109, - "test5": 1.0141927872142495 + "modify": "node.datum", + "trigger": "fix.length == 2", + "values": "{interactionX: fix[0], interactionY: fix[1]}" }, { - "test4": 1.2181870783617077, - "test5": 1.3313868743709856 - }, + "modify": "reset", + "trigger": "reset", + "values": "{interactionX: null, interactionY: null}" + } + ], + "values": [ { - "test4": 0.5467254294411026, - "test5": 0.7905630441613349 + "group": "U", + "id": 0, + "label": 0 }, { - "test4": 0.46550432319129564, - "test5": 2.60211174598442 + "group": "T", + "id": 1, + "label": 1 }, { - "test4": 2.2528919924217035, - "test5": 0.4581321569558453 + "group": "Z", + "id": 2, + "label": 2 }, { - "test4": 1.456860633335322, - "test5": 2.274943935477351 + "group": "U", + "id": 3, + "label": 3 }, { - "test4": 1.3055831319399918, - "test5": 1.84444708551169 - }, + "group": "W", + "id": 4, + "label": 4 + } + ] + }, + { + "name": "link-data", + "values": [ { - "test4": 0.5679788670259793, - "test5": 1.1630667246746462 + "group": "Y", + "source": 0, + "target": 1 }, { - "test4": 2.5740719917519033, - "test5": 0.10185120756517496 + "group": "T", + "source": 0, + "target": 2 }, { - "test4": 0.7301914277196087, - "test5": 1.3786499822600482 + "group": "T", + "source": 0, + "target": 3 }, { - "test4": 1.9109344303280946, - "test5": 2.943297512416426 + "group": "W", + "source": 0, + "target": 4 }, { - "test4": 2.25540894512815, - "test5": 2.212723565914129 + "group": "X", + "source": 1, + "target": 4 }, { - "test4": 2.3352075983638336, - "test5": 0.783406207286168 + "group": "T", + "source": 2, + "target": 4 + } + ] + } + ], + "description": "A node-link diagram with force-directed layout.", + "height": 500, + "legends": [ + { + "stroke": "color", + "symbolType": "stroke", + "title": "Group" + }, + { + "stroke": "colorlink", + "symbolType": "stroke", + "title": "Link Group" + } + ], + "marks": [ + { + "encode": { + "enter": { + "fill": { + "field": "group", + "scale": "color" + }, + "stroke": { + "value": "white" + } }, + "update": { + "cursor": { + "value": "pointer" + }, + "fx": { + "signal": "datum.interactionX == null ? scale('xscale', datum.inputX) : datum.interactionX" + }, + "fy": { + "signal": "datum.interactionY == null ? scale('yscale', datum.inputY) : datum.interactionY" + }, + "size": { + "signal": "2 * nodeRadius * nodeRadius" + } + } + }, + "from": { + "data": "node-data" + }, + "name": "nodes", + "transform": [ { - "test4": 0.02283136835837163, - "test5": 0.18181898145991882 - }, - { - "test4": 0.02475758544677964, - "test5": 2.2003336934933166 - }, - { - "test4": 2.327738139987348, - "test5": 1.4159487983246937 - }, - { - "test4": 2.2383654646991924, - "test5": 1.212455454199069 - }, - { - "test4": 1.4924640059318905, - "test5": 2.5506066992594487 - }, - { - "test4": 1.698916644404029, - "test5": 1.2745598107925749 - }, - { - "test4": 1.3939399413961238, - "test5": 2.814773718806941 - }, - { - "test4": 1.1893867039570174, - "test5": 1.061832360000596 - }, - { - "test4": 0.7120596752879254, - "test5": 2.2821828781848867 - }, - { - "test4": 1.656245113274614, - "test5": 2.919580489671201 - }, - { - "test4": 2.3013108123643486, - "test5": 2.7428716031493012 - }, - { - "test4": 2.0477961132953, - "test5": 1.3012967933635218 - }, - { - "test4": 1.779260771043534, - "test5": 0.7453085761290418 - }, - { - "test4": 1.5713491811490585, - "test5": 1.1339159944917174 - }, - { - "test4": 2.148226191158722, - "test5": 0.2866846606620249 - }, - { - "test4": 2.4079038863512157, - "test5": 2.8526670686670874 - }, - { - "test4": 1.1265835177179668, - "test5": 0.6042723263631427 - }, - { - "test4": 2.8894290018953184, - "test5": 2.232964421668804 - }, - { - "test4": 0.4261770042864017, - "test5": 2.614597186336121 - }, - { - "test4": 1.9974289628417854, - "test5": 1.995591378169098 - }, - { - "test4": 0.8363946407444185, - "test5": 0.3664487313636553 - }, - { - "test4": 0.7360817660361261, - "test5": 0.08553153557376991 - }, - { - "test4": 2.8332770256710953, - "test5": 2.449405333195559 - }, - { - "test4": 2.084704834237357, - "test5": 2.3506502735763632 - }, - { - "test4": 1.2228548082550597, - "test5": 0.5938350538368318 - }, - { - "test4": 0.1963360913772535, - "test5": 2.7691410954572495 - }, - { - "test4": 1.9578881564309953, - "test5": 0.13676158086018042 - }, - { - "test4": 1.0343131983677738, - "test5": 1.0882257445349262 - }, - { - "test4": 1.5796951658307956, - "test5": 0.8327815391775865 - }, - { - "test4": 0.21720319258520926, - "test5": 1.3195551914172206 - }, - { - "test4": 2.8236483893153292, - "test5": 1.5395262617027394 - }, - { - "test4": 1.798060810555087, - "test5": 0.020931837549403864 - }, - { - "test4": 2.396296032427898, - "test5": 1.1930258856302352 - }, - { - "test4": 1.4233328943912555, - "test5": 1.351803617437887 - }, - { - "test4": 2.2397704096501783, - "test5": 2.9929655425738595 - }, - { - "test4": 2.5754868086614873, - "test5": 2.8824192610679757 - }, - { - "test4": 1.246658001754116, - "test5": 0.5743531555783497 - }, - { - "test4": 0.9700506985169077, - "test5": 0.05005928237258972 - }, - { - "test4": 1.9072980088949423, - "test5": 0.6224974086439526 - }, - { - "test4": 2.295107597888712, - "test5": 0.08047138751003646 - }, - { - "test4": 1.5378463652613958, - "test5": 2.126388105070153 - }, - { - "test4": 1.2524981960146357, - "test5": 2.1941420922425476 - }, - { - "test4": 0.570420306441215, - "test5": 1.039056253169505 - }, - { - "test4": 0.8298752388413594, - "test5": 1.0421367688193477 - }, - { - "test4": 0.9273114340249197, - "test5": 0.4162711053077539 - }, - { - "test4": 1.6566035678780215, - "test5": 2.3041243121435 - }, - { - "test4": 2.972974740688411, - "test5": 0.09131923703460687 - }, - { - "test4": 0.47973366861308564, - "test5": 2.9145794048449583 - }, - { - "test4": 1.7028140362780348, - "test5": 0.8732078555783949 - }, - { - "test4": 0.14184154388103598, - "test5": 1.4349753541838812 - }, - { - "test4": 1.8523605259580773, - "test5": 0.9412051066833718 - }, - { - "test4": 2.0331406123411186, - "test5": 0.8940637158429265 - }, - { - "test4": 1.272302136072152, - "test5": 0.3367990685785932 - }, - { - "test4": 2.830511701546474, - "test5": 0.6094527448958251 - }, - { - "test4": 2.5605583662839733, - "test5": 2.0510964757723245 - }, - { - "test4": 2.0960847244251997, - "test5": 2.4839298036892785 - }, - { - "test4": 1.407905427566821, - "test5": 1.4025349022394389 - }, - { - "test4": 1.7512156811245276, - "test5": 0.14200898693980002 - }, - { - "test4": 1.8430702570666848, - "test5": 0.6992728216108067 - }, - { - "test4": 2.0966538476742502, - "test5": 2.0519601860923427 - }, - { - "test4": 2.8237054684976557, - "test5": 0.4064742462852521 - }, - { - "test4": 1.2357399178375341, - "test5": 1.8528981769588575 - }, - { - "test4": 1.7453568506662316, - "test5": 2.719417044089297 - } - ] - }, - { - "name": "heatmap_bins", - "source": "points", - "transform": [ - { - "field": { - "signal": "x_name" - }, - "signal": "x_extent", - "type": "extent" - }, - { - "as": [ - "x_start", - "x_end" - ], - "extent": { - "signal": "x_extent" - }, - "field": { - "signal": "x_name" - }, - "maxbins": { - "signal": "max_x_bins" - }, - "signal": "x_bins", - "type": "bin" - }, - { - "field": { - "signal": "y_name" - }, - "signal": "y_extent", - "type": "extent" - }, - { - "as": [ - "y_start", - "y_end" - ], - "extent": { - "signal": "y_extent" - }, - "field": { - "signal": "y_name" - }, - "maxbins": { - "signal": "max_y_bins" - }, - "signal": "y_bins", - "type": "bin" - }, - { - "as": [ - "__count" - ], - "groupby": [ - "x_start", - "x_end", - "y_start", - "y_end" - ], - "ops": [ - "count" - ], - "type": "aggregate" - } - ] - } - ], - "height": 200, - "legends": [ - { - "fill": "color", - "gradientLength": { - "signal": "height - 16" - }, - "title": "Count of Records" - } - ], - "marks": [ - { - "encode": { - "update": { - "fill": { - "field": "__count", - "scale": "color" - }, - "x": { - "field": "x_end", - "scale": "x" - }, - "x2": { - "field": "x_start", - "scale": "x" - }, - "y": { - "field": "y_end", - "scale": "y" - }, - "y2": { - "field": "y_start", - "scale": "y" - } - } - }, - "from": { - "data": "heatmap_bins" - }, - "name": "marks", - "style": [ - "rect" - ], - "type": "rect" - }, - { - "encode": { - "enter": { - "fill": { - "value": "black" - }, - "fillOpacity": { - "value": 1 - } - }, - "update": { - "size": { - "value": 10 - }, - "x": { - "field": { - "signal": "x_name" - }, - "scale": "x" - }, - "y": { - "field": { - "signal": "y_name" - }, - "scale": "y" - } - } - }, - "from": { - "data": "points" - }, - "type": "symbol" - } - ], - "scales": [ - { - "bins": { - "signal": "x_bins" - }, - "domain": { - "signal": "[x_bins.start, x_bins.stop]" - }, - "name": "x", - "range": [ - 0, - { - "signal": "width" - } - ], - "type": "linear", - "zero": false - }, - { - "bins": { - "signal": "y_bins" - }, - "domain": { - "signal": "[y_bins.start, y_bins.stop]" - }, - "name": "y", - "range": [ - { - "signal": "height" - }, - 0 - ], - "type": "linear", - "zero": false - }, - { - "domain": { - "data": "heatmap_bins", - "field": "__count" - }, - "interpolate": "hcl", - "name": "color", - "range": "heatmap", - "type": "linear", - "zero": true - } - ], - "signals": [ - { - "bind": { - "input": "range", - "max": 100, - "min": 1, - "step": 1 - }, - "description": "Max number X bins", - "name": "max_x_bins", - "value": 4 - }, - { - "bind": { - "input": "range", - "max": 100, - "min": 1, - "step": 1 - }, - "description": "Max number Y bins", - "name": "max_y_bins", - "value": 4 - }, - { - "name": "x_name", - "value": "test4" - }, - { - "name": "y_name", - "value": "test5" - } - ], - "width": 300 - } - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import pandas as pd\n", - "import numpy as np\n", - "\n", - "df = pd.DataFrame(3*np.random.random((100, 2)), columns=['test4', 'test5'])\n", - "schema = plots.heatmap_scatter(df, max_x_bins=4, max_y_bins=4)\n", - "plots.save_schema(schema, \"_schema.json\")\n", - "plots.ipy_display(schema, format=\"interactive\")" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "6e083016", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n" - ] - }, - { - "data": { - "application/vnd.vega.v5+json": { - "$schema": "https://vega.github.io/schema/vega/v5.json", - "axes": [ - { - "labelOverlap": true, - "orient": "bottom", - "scale": "x", - "tickCount": { - "signal": "ceil(width/3)" - }, - "title": "gamma" - }, - { - "labelOverlap": true, - "orient": "left", - "scale": "y", - "tickCount": { - "signal": "ceil(height/3)" - }, - "title": "alpha" - } - ], - "data": [ - { - "name": "points", - "values": [ - { - "alpha": 5.9784883316857185, - "gamma": 8.945626575409701 - }, - { - "alpha": 1.97190570168565, - "gamma": 5.603352372388732 - }, - { - "alpha": 8.091437097306919, - "gamma": 3.766757411797653 - }, - { - "alpha": 6.719283797935544, - "gamma": 6.592948316435251 - }, - { - "alpha": 0.8302807742935314, - "gamma": 6.974357899271521 - }, - { - "alpha": 8.97712332766224, - "gamma": 5.630352422593903 - }, - { - "alpha": 0.8980978433162434, - "gamma": 1.9296292855161856 - }, - { - "alpha": 1.114907196144591, - "gamma": 6.077597682137238 - }, - { - "alpha": 9.547709481680823, - "gamma": 5.146462909515672 - }, - { - "alpha": 1.9988505366594433, - "gamma": 5.698685596568787 - }, - { - "alpha": 2.0175887787394418, - "gamma": 2.096583695160273 - }, - { - "alpha": 0.11087500439810105, - "gamma": 1.0573980617461343 - }, - { - "alpha": 1.6543172193979983, - "gamma": 1.2306383886016992 - }, - { - "alpha": 8.22860414224558, - "gamma": 3.0502711540460425 - }, - { - "alpha": 1.0321781510987482, - "gamma": 1.6396565189340295 - }, - { - "alpha": 4.49595306492898, - "gamma": 8.283032157180251 - }, - { - "alpha": 0.2743003894776863, - "gamma": 9.86366496864936 - }, - { - "alpha": 1.8146260626588373, - "gamma": 9.709411574895036 - }, - { - "alpha": 6.963656671726587, - "gamma": 2.6868158714961443 - }, - { - "alpha": 6.536821218336284, - "gamma": 2.197035257435166 - }, - { - "alpha": 1.5149135921322043, - "gamma": 8.436156651617777 - }, - { - "alpha": 0.2785972085521615, - "gamma": 2.2325938777671714 - }, - { - "alpha": 3.3181276403154145, - "gamma": 8.717358568443666 - }, - { - "alpha": 2.474106358507134, - "gamma": 0.386641491579266 - }, - { - "alpha": 5.296282935101208, - "gamma": 3.1728881483746827 - }, - { - "alpha": 5.718592403580539, - "gamma": 3.566812591038677 - }, - { - "alpha": 3.689487911148607, - "gamma": 1.9628702800775388 - }, - { - "alpha": 2.9217203324791994, - "gamma": 6.839246117153723 - }, - { - "alpha": 2.8443453227487425, - "gamma": 7.505494523642388 - }, - { - "alpha": 2.2950335516947016, - "gamma": 4.796755286344181 - }, - { - "alpha": 3.096256778889787, - "gamma": 2.4210371241552298 - }, - { - "alpha": 6.793706575831284, - "gamma": 5.565714011997791 - }, - { - "alpha": 1.0881512415680417, - "gamma": 9.184776428199731 - }, - { - "alpha": 5.575685899560202, - "gamma": 9.229475957746583 - }, - { - "alpha": 0.7429916874172227, - "gamma": 1.4436889581099366 - }, - { - "alpha": 4.014391203442362, - "gamma": 5.348156464650822 - }, - { - "alpha": 9.930139858319244, - "gamma": 2.251169532724686 - }, - { - "alpha": 2.3775880339844138, - "gamma": 2.499144372252969 - }, - { - "alpha": 8.716506264004936, - "gamma": 3.466037761395526 - }, - { - "alpha": 9.56649351937782, - "gamma": 3.095125195602871 - }, - { - "alpha": 5.194171921508484, - "gamma": 8.49882338800778 - }, - { - "alpha": 5.433498150345617, - "gamma": 4.9870936458408615 - }, - { - "alpha": 9.044828467632017, - "gamma": 4.887782135547382 - }, - { - "alpha": 9.80080449102157, - "gamma": 6.133047017336271 - }, - { - "alpha": 6.893436106345878, - "gamma": 9.412341151775985 - }, - { - "alpha": 0.4915038369941327, - "gamma": 5.915008409057517 - }, - { - "alpha": 6.45628844732773, - "gamma": 6.032006215570812 - }, - { - "alpha": 8.730086941424268, - "gamma": 3.215246885129157 - }, - { - "alpha": 6.339664301750898, - "gamma": 9.326154995310219 - }, - { - "alpha": 1.186880390424453, - "gamma": 5.05998632702063 - }, - { - "alpha": 3.543481197691667, - "gamma": 1.2121177958635643 - }, - { - "alpha": 3.8032987466926604, - "gamma": 7.998051073304431 - }, - { - "alpha": 0.16156623215720645, - "gamma": 9.936193111653665 - }, - { - "alpha": 8.088889375699225, - "gamma": 4.390966189990508 - }, - { - "alpha": 0.8684176232504204, - "gamma": 2.7592914315896175 - }, - { - "alpha": 9.371691990461535, - "gamma": 3.5105549898062294 - }, - { - "alpha": 7.135681016473187, - "gamma": 0.16381541400300348 - }, - { - "alpha": 6.198539755106342, - "gamma": 8.667924340029126 - }, - { - "alpha": 7.204821246007685, - "gamma": 3.9624958931966745 - }, - { - "alpha": 0.542091357290958, - "gamma": 3.62803282290856 - }, - { - "alpha": 4.502193386808278, - "gamma": 4.741881575265121 - }, - { - "alpha": 6.3946016136889305, - "gamma": 5.689904025947912 - }, - { - "alpha": 9.206447906015686, - "gamma": 2.878361477635888 - }, - { - "alpha": 3.0701403484232657, - "gamma": 0.012621606291097764 - }, - { - "alpha": 9.179441021534426, - "gamma": 2.49402239024802 - }, - { - "alpha": 4.324923826776729, - "gamma": 4.216521090519461 - }, - { - "alpha": 5.429449136208006, - "gamma": 7.9352614686759715 - }, - { - "alpha": 0.11309549462388935, - "gamma": 2.4031999090392606 - }, - { - "alpha": 4.8729059893101105, - "gamma": 1.8194287226187922 - }, - { - "alpha": 2.7229181599246646, - "gamma": 6.82608287194432 - }, - { - "alpha": 2.120110573972467, - "gamma": 4.486804597037244 - }, - { - "alpha": 6.462215975367914, - "gamma": 9.664411381388742 - }, - { - "alpha": 5.944719068984467, - "gamma": 0.8059835924413405 - }, - { - "alpha": 6.421942274914542, - "gamma": 7.021311096530556 - }, - { - "alpha": 2.399593095457152, - "gamma": 3.1551458751475514 - }, - { - "alpha": 1.6094802807049036, - "gamma": 1.7974988018399929 - }, - { - "alpha": 0.7735513764777624, - "gamma": 0.3605064383241785 - }, - { - "alpha": 9.968517986892211, - "gamma": 5.6595050426192675 - }, - { - "alpha": 5.057528429110262, - "gamma": 2.680764515429328 - }, - { - "alpha": 9.61679099480759, - "gamma": 6.284721433093141 - }, - { - "alpha": 9.287305878916953, - "gamma": 2.8203088845495374 - }, - { - "alpha": 1.0716279774521809, - "gamma": 0.6176054154997013 - }, - { - "alpha": 3.998391037373048, - "gamma": 6.168454220210502 - }, - { - "alpha": 5.518414970533564, - "gamma": 2.0090544995198902 - }, - { - "alpha": 4.878017262401734, - "gamma": 8.680525562900204 - }, - { - "alpha": 7.887914662167519, - "gamma": 6.687600297706644 - }, - { - "alpha": 5.955583903704154, - "gamma": 9.522290412961071 - }, - { - "alpha": 7.318022175779435, - "gamma": 1.3274612866805335 - }, - { - "alpha": 2.3629151886593283, - "gamma": 7.351405954116991 - }, - { - "alpha": 2.2418618806852186, - "gamma": 6.6030199401732155 - }, - { - "alpha": 8.01875971303388, - "gamma": 6.433771120559827 - }, - { - "alpha": 5.585783359608416, - "gamma": 5.966928279523178 - }, - { - "alpha": 8.970646831230432, - "gamma": 7.487108631296909 - }, - { - "alpha": 1.7203414856913113, - "gamma": 7.496808099473677 - }, - { - "alpha": 3.7615505683990236, - "gamma": 4.665213854056351 - }, - { - "alpha": 4.890100371737165, - "gamma": 6.713397497814646 - }, - { - "alpha": 1.5132750750905566, - "gamma": 0.4791103262244045 - }, - { - "alpha": 4.5830335131339535, - "gamma": 1.7743642674865034 - }, - { - "alpha": 7.280682169822569, - "gamma": 2.3118861486970363 - }, - { - "alpha": 8.517101335669354, - "gamma": 0.6752709207759078 - } - ] - }, - { - "name": "mesh", - "values": [ - { - "__count": 0, - "x_end": 0.5555555555555556, - "x_start": -0.5555555555555556, - "y_end": 0.5555555555555556, - "y_start": -0.5555555555555556 - }, - { - "__count": 1.234567901234568, - "x_end": 1.6666666666666667, - "x_start": 0.5555555555555556, - "y_end": 0.5555555555555556, - "y_start": -0.5555555555555556 - }, - { - "__count": 4.938271604938272, - "x_end": 2.7777777777777777, - "x_start": 1.6666666666666667, - "y_end": 0.5555555555555556, - "y_start": -0.5555555555555556 - }, - { - "__count": 11.111111111111112, - "x_end": 3.8888888888888893, - "x_start": 2.7777777777777777, - "y_end": 0.5555555555555556, - "y_start": -0.5555555555555556 - }, - { - "__count": 19.75308641975309, - "x_end": 5, - "x_start": 3.8888888888888893, - "y_end": 0.5555555555555556, - "y_start": -0.5555555555555556 - }, - { - "__count": 30.864197530864196, - "x_end": 6.111111111111111, - "x_start": 5, - "y_end": 0.5555555555555556, - "y_start": -0.5555555555555556 - }, - { - "__count": 44.44444444444445, - "x_end": 7.222222222222222, - "x_start": 6.111111111111112, - "y_end": 0.5555555555555556, - "y_start": -0.5555555555555556 - }, - { - "__count": 60.49382716049384, - "x_end": 8.333333333333334, - "x_start": 7.222222222222223, - "y_end": 0.5555555555555556, - "y_start": -0.5555555555555556 - }, - { - "__count": 79.01234567901236, - "x_end": 9.444444444444445, - "x_start": 8.333333333333334, - "y_end": 0.5555555555555556, - "y_start": -0.5555555555555556 - }, - { - "__count": 100, - "x_end": 10.555555555555555, - "x_start": 9.444444444444445, - "y_end": 0.5555555555555556, - "y_start": -0.5555555555555556 - }, - { - "__count": 1.234567901234568, - "x_end": 0.5555555555555556, - "x_start": -0.5555555555555556, - "y_end": 1.6666666666666667, - "y_start": 0.5555555555555556 - }, - { - "__count": 2.469135802469136, - "x_end": 1.6666666666666667, - "x_start": 0.5555555555555556, - "y_end": 1.6666666666666667, - "y_start": 0.5555555555555556 - }, - { - "__count": 6.17283950617284, - "x_end": 2.7777777777777777, - "x_start": 1.6666666666666667, - "y_end": 1.6666666666666667, - "y_start": 0.5555555555555556 - }, - { - "__count": 12.34567901234568, - "x_end": 3.8888888888888893, - "x_start": 2.7777777777777777, - "y_end": 1.6666666666666667, - "y_start": 0.5555555555555556 - }, - { - "__count": 20.98765432098766, - "x_end": 5, - "x_start": 3.8888888888888893, - "y_end": 1.6666666666666667, - "y_start": 0.5555555555555556 - }, - { - "__count": 32.098765432098766, - "x_end": 6.111111111111111, - "x_start": 5, - "y_end": 1.6666666666666667, - "y_start": 0.5555555555555556 - }, - { - "__count": 45.67901234567902, - "x_end": 7.222222222222222, - "x_start": 6.111111111111112, - "y_end": 1.6666666666666667, - "y_start": 0.5555555555555556 - }, - { - "__count": 61.728395061728406, - "x_end": 8.333333333333334, - "x_start": 7.222222222222223, - "y_end": 1.6666666666666667, - "y_start": 0.5555555555555556 - }, - { - "__count": 80.24691358024693, - "x_end": 9.444444444444445, - "x_start": 8.333333333333334, - "y_end": 1.6666666666666667, - "y_start": 0.5555555555555556 - }, - { - "__count": 101.23456790123457, - "x_end": 10.555555555555555, - "x_start": 9.444444444444445, - "y_end": 1.6666666666666667, - "y_start": 0.5555555555555556 - }, - { - "__count": 4.938271604938272, - "x_end": 0.5555555555555556, - "x_start": -0.5555555555555556, - "y_end": 2.7777777777777777, - "y_start": 1.6666666666666667 - }, - { - "__count": 6.17283950617284, - "x_end": 1.6666666666666667, - "x_start": 0.5555555555555556, - "y_end": 2.7777777777777777, - "y_start": 1.6666666666666667 - }, - { - "__count": 9.876543209876544, - "x_end": 2.7777777777777777, - "x_start": 1.6666666666666667, - "y_end": 2.7777777777777777, - "y_start": 1.6666666666666667 - }, - { - "__count": 16.049382716049386, - "x_end": 3.8888888888888893, - "x_start": 2.7777777777777777, - "y_end": 2.7777777777777777, - "y_start": 1.6666666666666667 - }, - { - "__count": 24.69135802469136, - "x_end": 5, - "x_start": 3.8888888888888893, - "y_end": 2.7777777777777777, - "y_start": 1.6666666666666667 - }, - { - "__count": 35.80246913580247, - "x_end": 6.111111111111111, - "x_start": 5, - "y_end": 2.7777777777777777, - "y_start": 1.6666666666666667 - }, - { - "__count": 49.38271604938272, - "x_end": 7.222222222222222, - "x_start": 6.111111111111112, - "y_end": 2.7777777777777777, - "y_start": 1.6666666666666667 - }, - { - "__count": 65.4320987654321, - "x_end": 8.333333333333334, - "x_start": 7.222222222222223, - "y_end": 2.7777777777777777, - "y_start": 1.6666666666666667 - }, - { - "__count": 83.95061728395063, - "x_end": 9.444444444444445, - "x_start": 8.333333333333334, - "y_end": 2.7777777777777777, - "y_start": 1.6666666666666667 - }, - { - "__count": 104.93827160493828, - "x_end": 10.555555555555555, - "x_start": 9.444444444444445, - "y_end": 2.7777777777777777, - "y_start": 1.6666666666666667 - }, - { - "__count": 11.111111111111112, - "x_end": 0.5555555555555556, - "x_start": -0.5555555555555556, - "y_end": 3.8888888888888893, - "y_start": 2.7777777777777777 - }, - { - "__count": 12.34567901234568, - "x_end": 1.6666666666666667, - "x_start": 0.5555555555555556, - "y_end": 3.8888888888888893, - "y_start": 2.7777777777777777 - }, - { - "__count": 16.049382716049386, - "x_end": 2.7777777777777777, - "x_start": 1.6666666666666667, - "y_end": 3.8888888888888893, - "y_start": 2.7777777777777777 - }, - { - "__count": 22.222222222222225, - "x_end": 3.8888888888888893, - "x_start": 2.7777777777777777, - "y_end": 3.8888888888888893, - "y_start": 2.7777777777777777 - }, - { - "__count": 30.864197530864203, - "x_end": 5, - "x_start": 3.8888888888888893, - "y_end": 3.8888888888888893, - "y_start": 2.7777777777777777 - }, - { - "__count": 41.97530864197531, - "x_end": 6.111111111111111, - "x_start": 5, - "y_end": 3.8888888888888893, - "y_start": 2.7777777777777777 - }, - { - "__count": 55.555555555555564, - "x_end": 7.222222222222222, - "x_start": 6.111111111111112, - "y_end": 3.8888888888888893, - "y_start": 2.7777777777777777 - }, - { - "__count": 71.60493827160495, - "x_end": 8.333333333333334, - "x_start": 7.222222222222223, - "y_end": 3.8888888888888893, - "y_start": 2.7777777777777777 - }, - { - "__count": 90.12345679012347, - "x_end": 9.444444444444445, - "x_start": 8.333333333333334, - "y_end": 3.8888888888888893, - "y_start": 2.7777777777777777 - }, - { - "__count": 111.11111111111111, - "x_end": 10.555555555555555, - "x_start": 9.444444444444445, - "y_end": 3.8888888888888893, - "y_start": 2.7777777777777777 - }, - { - "__count": 19.75308641975309, - "x_end": 0.5555555555555556, - "x_start": -0.5555555555555556, - "y_end": 5, - "y_start": 3.8888888888888893 - }, - { - "__count": 20.98765432098766, - "x_end": 1.6666666666666667, - "x_start": 0.5555555555555556, - "y_end": 5, - "y_start": 3.8888888888888893 - }, - { - "__count": 24.69135802469136, - "x_end": 2.7777777777777777, - "x_start": 1.6666666666666667, - "y_end": 5, - "y_start": 3.8888888888888893 - }, - { - "__count": 30.864197530864203, - "x_end": 3.8888888888888893, - "x_start": 2.7777777777777777, - "y_end": 5, - "y_start": 3.8888888888888893 - }, - { - "__count": 39.50617283950618, - "x_end": 5, - "x_start": 3.8888888888888893, - "y_end": 5, - "y_start": 3.8888888888888893 - }, - { - "__count": 50.617283950617285, - "x_end": 6.111111111111111, - "x_start": 5, - "y_end": 5, - "y_start": 3.8888888888888893 - }, - { - "__count": 64.19753086419755, - "x_end": 7.222222222222222, - "x_start": 6.111111111111112, - "y_end": 5, - "y_start": 3.8888888888888893 - }, - { - "__count": 80.24691358024693, - "x_end": 8.333333333333334, - "x_start": 7.222222222222223, - "y_end": 5, - "y_start": 3.8888888888888893 - }, - { - "__count": 98.76543209876544, - "x_end": 9.444444444444445, - "x_start": 8.333333333333334, - "y_end": 5, - "y_start": 3.8888888888888893 - }, - { - "__count": 119.75308641975309, - "x_end": 10.555555555555555, - "x_start": 9.444444444444445, - "y_end": 5, - "y_start": 3.8888888888888893 - }, - { - "__count": 30.864197530864196, - "x_end": 0.5555555555555556, - "x_start": -0.5555555555555556, - "y_end": 6.111111111111111, - "y_start": 5 - }, - { - "__count": 32.098765432098766, - "x_end": 1.6666666666666667, - "x_start": 0.5555555555555556, - "y_end": 6.111111111111111, - "y_start": 5 - }, - { - "__count": 35.80246913580247, - "x_end": 2.7777777777777777, - "x_start": 1.6666666666666667, - "y_end": 6.111111111111111, - "y_start": 5 - }, - { - "__count": 41.97530864197531, - "x_end": 3.8888888888888893, - "x_start": 2.7777777777777777, - "y_end": 6.111111111111111, - "y_start": 5 - }, - { - "__count": 50.617283950617285, - "x_end": 5, - "x_start": 3.8888888888888893, - "y_end": 6.111111111111111, - "y_start": 5 - }, - { - "__count": 61.72839506172839, - "x_end": 6.111111111111111, - "x_start": 5, - "y_end": 6.111111111111111, - "y_start": 5 - }, - { - "__count": 75.30864197530865, - "x_end": 7.222222222222222, - "x_start": 6.111111111111112, - "y_end": 6.111111111111111, - "y_start": 5 - }, - { - "__count": 91.35802469135803, - "x_end": 8.333333333333334, - "x_start": 7.222222222222223, - "y_end": 6.111111111111111, - "y_start": 5 - }, - { - "__count": 109.87654320987656, - "x_end": 9.444444444444445, - "x_start": 8.333333333333334, - "y_end": 6.111111111111111, - "y_start": 5 - }, - { - "__count": 130.8641975308642, - "x_end": 10.555555555555555, - "x_start": 9.444444444444445, - "y_end": 6.111111111111111, - "y_start": 5 - }, - { - "__count": 44.44444444444445, - "x_end": 0.5555555555555556, - "x_start": -0.5555555555555556, - "y_end": 7.222222222222222, - "y_start": 6.111111111111112 - }, - { - "__count": 45.67901234567902, - "x_end": 1.6666666666666667, - "x_start": 0.5555555555555556, - "y_end": 7.222222222222222, - "y_start": 6.111111111111112 - }, - { - "__count": 49.38271604938272, - "x_end": 2.7777777777777777, - "x_start": 1.6666666666666667, - "y_end": 7.222222222222222, - "y_start": 6.111111111111112 - }, - { - "__count": 55.555555555555564, - "x_end": 3.8888888888888893, - "x_start": 2.7777777777777777, - "y_end": 7.222222222222222, - "y_start": 6.111111111111112 - }, - { - "__count": 64.19753086419755, - "x_end": 5, - "x_start": 3.8888888888888893, - "y_end": 7.222222222222222, - "y_start": 6.111111111111112 - }, - { - "__count": 75.30864197530865, - "x_end": 6.111111111111111, - "x_start": 5, - "y_end": 7.222222222222222, - "y_start": 6.111111111111112 - }, - { - "__count": 88.8888888888889, - "x_end": 7.222222222222222, - "x_start": 6.111111111111112, - "y_end": 7.222222222222222, - "y_start": 6.111111111111112 - }, - { - "__count": 104.93827160493828, - "x_end": 8.333333333333334, - "x_start": 7.222222222222223, - "y_end": 7.222222222222222, - "y_start": 6.111111111111112 - }, - { - "__count": 123.45679012345681, - "x_end": 9.444444444444445, - "x_start": 8.333333333333334, - "y_end": 7.222222222222222, - "y_start": 6.111111111111112 - }, - { - "__count": 144.44444444444446, - "x_end": 10.555555555555555, - "x_start": 9.444444444444445, - "y_end": 7.222222222222222, - "y_start": 6.111111111111112 - }, - { - "__count": 60.49382716049384, - "x_end": 0.5555555555555556, - "x_start": -0.5555555555555556, - "y_end": 8.333333333333334, - "y_start": 7.222222222222223 - }, - { - "__count": 61.728395061728406, - "x_end": 1.6666666666666667, - "x_start": 0.5555555555555556, - "y_end": 8.333333333333334, - "y_start": 7.222222222222223 - }, - { - "__count": 65.4320987654321, - "x_end": 2.7777777777777777, - "x_start": 1.6666666666666667, - "y_end": 8.333333333333334, - "y_start": 7.222222222222223 - }, - { - "__count": 71.60493827160495, - "x_end": 3.8888888888888893, - "x_start": 2.7777777777777777, - "y_end": 8.333333333333334, - "y_start": 7.222222222222223 - }, - { - "__count": 80.24691358024693, - "x_end": 5, - "x_start": 3.8888888888888893, - "y_end": 8.333333333333334, - "y_start": 7.222222222222223 - }, - { - "__count": 91.35802469135803, - "x_end": 6.111111111111111, - "x_start": 5, - "y_end": 8.333333333333334, - "y_start": 7.222222222222223 - }, - { - "__count": 104.93827160493828, - "x_end": 7.222222222222222, - "x_start": 6.111111111111112, - "y_end": 8.333333333333334, - "y_start": 7.222222222222223 - }, - { - "__count": 120.98765432098767, - "x_end": 8.333333333333334, - "x_start": 7.222222222222223, - "y_end": 8.333333333333334, - "y_start": 7.222222222222223 - }, - { - "__count": 139.50617283950618, - "x_end": 9.444444444444445, - "x_start": 8.333333333333334, - "y_end": 8.333333333333334, - "y_start": 7.222222222222223 - }, - { - "__count": 160.49382716049382, - "x_end": 10.555555555555555, - "x_start": 9.444444444444445, - "y_end": 8.333333333333334, - "y_start": 7.222222222222223 - }, - { - "__count": 79.01234567901236, - "x_end": 0.5555555555555556, - "x_start": -0.5555555555555556, - "y_end": 9.444444444444445, - "y_start": 8.333333333333334 - }, - { - "__count": 80.24691358024693, - "x_end": 1.6666666666666667, - "x_start": 0.5555555555555556, - "y_end": 9.444444444444445, - "y_start": 8.333333333333334 - }, - { - "__count": 83.95061728395063, - "x_end": 2.7777777777777777, - "x_start": 1.6666666666666667, - "y_end": 9.444444444444445, - "y_start": 8.333333333333334 - }, - { - "__count": 90.12345679012347, - "x_end": 3.8888888888888893, - "x_start": 2.7777777777777777, - "y_end": 9.444444444444445, - "y_start": 8.333333333333334 - }, - { - "__count": 98.76543209876544, - "x_end": 5, - "x_start": 3.8888888888888893, - "y_end": 9.444444444444445, - "y_start": 8.333333333333334 - }, - { - "__count": 109.87654320987656, - "x_end": 6.111111111111111, - "x_start": 5, - "y_end": 9.444444444444445, - "y_start": 8.333333333333334 - }, - { - "__count": 123.45679012345681, - "x_end": 7.222222222222222, - "x_start": 6.111111111111112, - "y_end": 9.444444444444445, - "y_start": 8.333333333333334 - }, - { - "__count": 139.50617283950618, - "x_end": 8.333333333333334, - "x_start": 7.222222222222223, - "y_end": 9.444444444444445, - "y_start": 8.333333333333334 - }, - { - "__count": 158.0246913580247, - "x_end": 9.444444444444445, - "x_start": 8.333333333333334, - "y_end": 9.444444444444445, - "y_start": 8.333333333333334 - }, - { - "__count": 179.01234567901236, - "x_end": 10.555555555555555, - "x_start": 9.444444444444445, - "y_end": 9.444444444444445, - "y_start": 8.333333333333334 - }, - { - "__count": 100, - "x_end": 0.5555555555555556, - "x_start": -0.5555555555555556, - "y_end": 10.555555555555555, - "y_start": 9.444444444444445 - }, - { - "__count": 101.23456790123457, - "x_end": 1.6666666666666667, - "x_start": 0.5555555555555556, - "y_end": 10.555555555555555, - "y_start": 9.444444444444445 - }, - { - "__count": 104.93827160493828, - "x_end": 2.7777777777777777, - "x_start": 1.6666666666666667, - "y_end": 10.555555555555555, - "y_start": 9.444444444444445 - }, - { - "__count": 111.11111111111111, - "x_end": 3.8888888888888893, - "x_start": 2.7777777777777777, - "y_end": 10.555555555555555, - "y_start": 9.444444444444445 - }, - { - "__count": 119.75308641975309, - "x_end": 5, - "x_start": 3.8888888888888893, - "y_end": 10.555555555555555, - "y_start": 9.444444444444445 - }, - { - "__count": 130.8641975308642, - "x_end": 6.111111111111111, - "x_start": 5, - "y_end": 10.555555555555555, - "y_start": 9.444444444444445 - }, - { - "__count": 144.44444444444446, - "x_end": 7.222222222222222, - "x_start": 6.111111111111112, - "y_end": 10.555555555555555, - "y_start": 9.444444444444445 - }, - { - "__count": 160.49382716049382, - "x_end": 8.333333333333334, - "x_start": 7.222222222222223, - "y_end": 10.555555555555555, - "y_start": 9.444444444444445 - }, - { - "__count": 179.01234567901236, - "x_end": 9.444444444444445, - "x_start": 8.333333333333334, - "y_end": 10.555555555555555, - "y_start": 9.444444444444445 - }, - { - "__count": 200, - "x_end": 10.555555555555555, - "x_start": 9.444444444444445, - "y_end": 10.555555555555555, - "y_start": 9.444444444444445 + "forces": [ + { + "force": "center", + "x": { + "signal": "cx" + }, + "y": { + "signal": "cy" + } + }, + { + "force": "collide", + "radius": { + "signal": "nodeRadius" + } + }, + { + "force": "nbody", + "strength": { + "signal": "nodeCharge" + } + }, + { + "distance": { + "signal": "linkDistance" + }, + "force": "link", + "links": "link-data" + } + ], + "iterations": 300, + "restart": { + "signal": "restart" + }, + "signal": "force", + "static": true, + "type": "force" } - ] - } - ], - "height": 200, - "legends": [ - { - "fill": "color", - "gradientLength": { - "signal": "height - 16" - }, - "title": "Histogram Values" - } - ], - "marks": [ + ], + "type": "symbol", + "zindex": 1 + }, { "encode": { - "update": { + "enter": { + "align": { + "value": "center" + }, + "baseline": { + "value": "middle" + }, "fill": { - "field": "__count", - "scale": "color" + "value": "black" }, - "x": { - "field": "x_end", - "scale": "x" + "fontSize": { + "value": 10 }, - "x2": { - "field": "x_start", - "scale": "x" + "text": { + "field": "datum.label" + } + }, + "update": { + "x": { + "field": "x" }, "y": { - "field": "y_end", - "scale": "y" + "field": "y" + } + } + }, + "from": { + "data": "nodes" + }, + "interactive": false, + "name": "labels", + "transform": [ + { + "anchor": [ + "top", + "bottom", + "right", + "left" + ], + "avoidMarks": [ + "nodes" + ], + "offset": [ + 1 + ], + "size": { + "signal": "[width + 60, height]" }, - "y2": { - "field": "y_start", - "scale": "y" + "type": "label" + } + ], + "type": "text", + "zindex": 2 + }, + { + "encode": { + "update": { + "stroke": { + "field": "group", + "scale": "colorlink" + }, + "strokeWidth": { + "value": 0.5 } } }, "from": { - "data": "mesh" + "data": "link-data" }, - "name": "marks", - "style": [ - "rect" + "interactive": false, + "transform": [ + { + "require": { + "signal": "force" + }, + "shape": "line", + "sourceX": "datum.source.x", + "sourceY": "datum.source.y", + "targetX": "datum.target.x", + "targetY": "datum.target.y", + "type": "linkpath" + } ], - "type": "rect" + "type": "path" }, { "encode": { "enter": { "fill": { - "value": "black" + "field": "group", + "scale": "colorlink" + }, + "shape": { + "value": "triangle-right" + }, + "size": { + "value": 40 }, - "fillOpacity": { + "stroke": { + "field": "group", + "scale": "colorlink" + } + }, + "hover": { + "opacity": { "value": 1 } }, "update": { - "size": { - "value": 10 - }, "x": { - "field": "gamma", - "scale": "x" + "field": "target.x" }, "y": { - "field": "alpha", - "scale": "y" + "field": "target.y" } } }, "from": { - "data": "points" + "data": "link-data" }, - "type": "symbol" + "name": "arrows", + "transform": [ + { + "as": "tan", + "expr": "atan2((datum.datum.target.y-datum.datum.source.y),(datum.datum.target.x-datum.datum.source.x))", + "type": "formula" + }, + { + "as": "angle", + "expr": "datum.tan*180/PI", + "type": "formula" + }, + { + "as": "y", + "expr": "datum.datum.target.y - nodeRadius*sin(datum.tan)", + "type": "formula" + }, + { + "as": "x", + "expr": "datum.datum.target.x - nodeRadius*cos(datum.tan)", + "type": "formula" + } + ], + "type": "symbol", + "zindex": { + "value": 40 + } } ], + "padding": 0, "scales": [ { "domain": { - "fields": [ - { - "data": "points", - "field": "gamma" - }, - { - "data": "mesh", - "field": "x_start" - }, - { - "data": "mesh", - "field": "y_end" - } - ] + "data": "node-data", + "field": "group" + }, + "name": "color", + "range": { + "scheme": "category20c" + }, + "type": "ordinal" + }, + { + "domain": { + "data": "link-data", + "field": "group" + }, + "name": "colorlink", + "range": { + "scheme": "category20c" + }, + "type": "ordinal" + }, + { + "domain": { + "data": "node-data", + "field": "inputX" }, - "name": "x", + "name": "xscale", "range": [ - 0, + 10, { - "signal": "width" + "signal": "width - 10" } - ], - "type": "linear" + ] }, { "domain": { - "fields": [ - { - "data": "points", - "field": "alpha" - }, - { - "data": "mesh", - "field": "y_start" - }, - { - "data": "mesh", - "field": "y_end" - } - ] + "data": "node-data", + "field": "inputY" }, - "name": "y", + "name": "yscale", "range": [ + 10, + { + "signal": "height - 10" + } + ] + } + ], + "signals": [ + { + "name": "cx", + "update": "width / 2" + }, + { + "name": "cy", + "update": "height / 2" + }, + { + "name": "nodeRadius", + "value": 15 + }, + { + "name": "nodeCharge", + "value": -80 + }, + { + "name": "linkDistance", + "value": 80 + }, + { + "description": "State variable for active node fix status.", + "name": "fix", + "on": [ + { + "events": "symbol:mouseout[!event.buttons], window:mouseup", + "update": "false" + }, { - "signal": "height" + "events": "symbol:mouseover", + "update": "fix || true" }, - 0 + { + "events": "[symbol:mousedown, window:mouseup] > window:mousemove!", + "force": true, + "update": "xy()" + } ], - "type": "linear" + "value": false }, { - "domain": { - "data": "mesh", - "field": "__count" - }, - "interpolate": "hcl", - "name": "color", - "range": "heatmap", - "type": "linear", - "zero": true + "description": "Unfix node", + "name": "reset", + "on": [ + { + "events": "symbol:dblclick", + "update": "item().datum" + } + ], + "value": null + }, + { + "description": "Graph node most recently interacted with.", + "name": "node", + "on": [ + { + "events": "symbol:mouseover", + "update": "fix === true ? item() : node" + } + ], + "value": null + }, + { + "description": "Flag to restart Force simulation upon data changes.", + "name": "restart", + "on": [ + { + "events": { + "signal": "fix" + }, + "update": "fix && fix.length" + }, + { + "events": { + "signal": "reset" + }, + "update": "true" + } + ], + "value": false } ], - "width": 300 + "width": 500 } }, "metadata": {}, @@ -2405,26 +1072,22 @@ } ], "source": [ - "import numpy as np\n", - "import pandas as pd\n", - "from pyciemss.visuals import plots, vega\n", - "\n", - "def create_fake_data():\n", - " nx, ny = (10, 10)\n", - " x = np.linspace(0, 10, nx)\n", - " y, a = np.linspace(0, 10, ny, retstep = True)\n", + "g = nx.generators.barabasi_albert_graph(5, 3)\n", + "def rand_group():\n", + " possible = \"TUVWXYZ\"\n", + " return random.sample(possible, 1)[0]\n", "\n", - " # create mesh data\n", - " xv, yv = np.meshgrid(x, y)\n", - " zz = xv**2 + yv**2\n", + "node_properties = {n: {\"group\": rand_group()}\n", + " for n in g.nodes()}\n", "\n", - " # create scatter plot\n", - " df = pd.DataFrame(10*np.random.random((100, 2)), columns=['alpha', 'gamma'])\n", - " return (xv, yv, zz), df\n", + "edge_attributions = {e: {\"group\": rand_group()}\n", + " for e in g.edges()}\n", "\n", - "mesh_data, scatter_data = create_fake_data()\n", + "nx.set_node_attributes(g, node_properties)\n", + "nx.set_edge_attributes(g, edge_attributions)\n", + "nx.set_node_attributes(g, {k:f\"n{i}\" for i, k in enumerate(g.nodes)}, \"label\")\n", "\n", - "schema = plots.heatmap_scatter(scatter_data, mesh_data)\n", + "schema = plots.spring_force_graph(g, node_labels=\"label\")\n", "plots.save_schema(schema, \"_schema.json\")\n", "plots.ipy_display(schema, format=\"interactive\")" ] @@ -2432,7 +1095,7 @@ { "cell_type": "code", "execution_count": null, - "id": "9d3ad843", + "id": "dec992e7", "metadata": {}, "outputs": [], "source": [] diff --git a/notebook/visual examples/Scatterplot.ipynb b/notebook/visual examples/Scatterplot.ipynb new file mode 100644 index 000000000..3cfaa649b --- /dev/null +++ b/notebook/visual examples/Scatterplot.ipynb @@ -0,0 +1,2064 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "a1299176", + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "5c1729f7-1b0d-4913-8048-01624c506cc5", + "metadata": {}, + "outputs": [], + "source": [ + "import networkx as nx\n", + "from pyciemss.visuals import plots\n", + "\n", + "import pandas as pd\n", + "import numpy as np" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "67ab6bf5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + }, + { + "data": { + "application/vnd.vega.v5+json": { + "$schema": "https://vega.github.io/schema/vega/v5.json", + "axes": [ + { + "labelOverlap": true, + "orient": "bottom", + "scale": "x", + "tickCount": { + "signal": "ceil(width/3)" + }, + "title": { + "signal": "x_name" + } + }, + { + "labelOverlap": true, + "orient": "left", + "scale": "y", + "tickCount": { + "signal": "ceil(height/3)" + }, + "title": { + "signal": "y_name" + } + } + ], + "data": [ + { + "name": "points", + "values": [ + { + "test4": 1.4985307211641574, + "test5": 2.154607120895615 + }, + { + "test4": 0.18609256309747202, + "test5": 2.3105514262657434 + }, + { + "test4": 0.47365861217910066, + "test5": 2.6389708988828726 + }, + { + "test4": 0.6529136273654698, + "test5": 1.8746561195115532 + }, + { + "test4": 1.0258656914380422, + "test5": 2.3491502520810226 + }, + { + "test4": 0.13522530872952987, + "test5": 0.49398692445857395 + }, + { + "test4": 1.4751072740157576, + "test5": 1.6431532503705286 + }, + { + "test4": 2.2566940412156447, + "test5": 1.3195861907995494 + }, + { + "test4": 1.8196267913668573, + "test5": 1.1753115113703947 + }, + { + "test4": 1.1598924368134358, + "test5": 0.47374993866559056 + }, + { + "test4": 2.152795947691504, + "test5": 1.9353053773207733 + }, + { + "test4": 2.0077608975753667, + "test5": 1.8668277621406988 + }, + { + "test4": 1.2696228845606785, + "test5": 2.4119750574743666 + }, + { + "test4": 1.5552730789901883, + "test5": 1.3823309742853707 + }, + { + "test4": 2.709014544885976, + "test5": 1.0035561976902507 + }, + { + "test4": 1.4859239549423227, + "test5": 2.853648281882421 + }, + { + "test4": 1.8797520124101714, + "test5": 0.07172509031605623 + }, + { + "test4": 0.0529950581500499, + "test5": 2.8849215687763756 + }, + { + "test4": 2.799792617436673, + "test5": 2.647288912100909 + }, + { + "test4": 2.504093490837053, + "test5": 2.5890733832651955 + }, + { + "test4": 0.5305829920906653, + "test5": 0.5705606580044874 + }, + { + "test4": 1.9588182814646262, + "test5": 0.18310103319131665 + }, + { + "test4": 1.1272266850222323, + "test5": 2.889369324890685 + }, + { + "test4": 1.3644342002964345, + "test5": 0.37746594810276013 + }, + { + "test4": 1.224295775676109, + "test5": 1.0141927872142495 + }, + { + "test4": 1.2181870783617077, + "test5": 1.3313868743709856 + }, + { + "test4": 0.5467254294411026, + "test5": 0.7905630441613349 + }, + { + "test4": 0.46550432319129564, + "test5": 2.60211174598442 + }, + { + "test4": 2.2528919924217035, + "test5": 0.4581321569558453 + }, + { + "test4": 1.456860633335322, + "test5": 2.274943935477351 + }, + { + "test4": 1.3055831319399918, + "test5": 1.84444708551169 + }, + { + "test4": 0.5679788670259793, + "test5": 1.1630667246746462 + }, + { + "test4": 2.5740719917519033, + "test5": 0.10185120756517496 + }, + { + "test4": 0.7301914277196087, + "test5": 1.3786499822600482 + }, + { + "test4": 1.9109344303280946, + "test5": 2.943297512416426 + }, + { + "test4": 2.25540894512815, + "test5": 2.212723565914129 + }, + { + "test4": 2.3352075983638336, + "test5": 0.783406207286168 + }, + { + "test4": 0.02283136835837163, + "test5": 0.18181898145991882 + }, + { + "test4": 0.02475758544677964, + "test5": 2.2003336934933166 + }, + { + "test4": 2.327738139987348, + "test5": 1.4159487983246937 + }, + { + "test4": 2.2383654646991924, + "test5": 1.212455454199069 + }, + { + "test4": 1.4924640059318905, + "test5": 2.5506066992594487 + }, + { + "test4": 1.698916644404029, + "test5": 1.2745598107925749 + }, + { + "test4": 1.3939399413961238, + "test5": 2.814773718806941 + }, + { + "test4": 1.1893867039570174, + "test5": 1.061832360000596 + }, + { + "test4": 0.7120596752879254, + "test5": 2.2821828781848867 + }, + { + "test4": 1.656245113274614, + "test5": 2.919580489671201 + }, + { + "test4": 2.3013108123643486, + "test5": 2.7428716031493012 + }, + { + "test4": 2.0477961132953, + "test5": 1.3012967933635218 + }, + { + "test4": 1.779260771043534, + "test5": 0.7453085761290418 + }, + { + "test4": 1.5713491811490585, + "test5": 1.1339159944917174 + }, + { + "test4": 2.148226191158722, + "test5": 0.2866846606620249 + }, + { + "test4": 2.4079038863512157, + "test5": 2.8526670686670874 + }, + { + "test4": 1.1265835177179668, + "test5": 0.6042723263631427 + }, + { + "test4": 2.8894290018953184, + "test5": 2.232964421668804 + }, + { + "test4": 0.4261770042864017, + "test5": 2.614597186336121 + }, + { + "test4": 1.9974289628417854, + "test5": 1.995591378169098 + }, + { + "test4": 0.8363946407444185, + "test5": 0.3664487313636553 + }, + { + "test4": 0.7360817660361261, + "test5": 0.08553153557376991 + }, + { + "test4": 2.8332770256710953, + "test5": 2.449405333195559 + }, + { + "test4": 2.084704834237357, + "test5": 2.3506502735763632 + }, + { + "test4": 1.2228548082550597, + "test5": 0.5938350538368318 + }, + { + "test4": 0.1963360913772535, + "test5": 2.7691410954572495 + }, + { + "test4": 1.9578881564309953, + "test5": 0.13676158086018042 + }, + { + "test4": 1.0343131983677738, + "test5": 1.0882257445349262 + }, + { + "test4": 1.5796951658307956, + "test5": 0.8327815391775865 + }, + { + "test4": 0.21720319258520926, + "test5": 1.3195551914172206 + }, + { + "test4": 2.8236483893153292, + "test5": 1.5395262617027394 + }, + { + "test4": 1.798060810555087, + "test5": 0.020931837549403864 + }, + { + "test4": 2.396296032427898, + "test5": 1.1930258856302352 + }, + { + "test4": 1.4233328943912555, + "test5": 1.351803617437887 + }, + { + "test4": 2.2397704096501783, + "test5": 2.9929655425738595 + }, + { + "test4": 2.5754868086614873, + "test5": 2.8824192610679757 + }, + { + "test4": 1.246658001754116, + "test5": 0.5743531555783497 + }, + { + "test4": 0.9700506985169077, + "test5": 0.05005928237258972 + }, + { + "test4": 1.9072980088949423, + "test5": 0.6224974086439526 + }, + { + "test4": 2.295107597888712, + "test5": 0.08047138751003646 + }, + { + "test4": 1.5378463652613958, + "test5": 2.126388105070153 + }, + { + "test4": 1.2524981960146357, + "test5": 2.1941420922425476 + }, + { + "test4": 0.570420306441215, + "test5": 1.039056253169505 + }, + { + "test4": 0.8298752388413594, + "test5": 1.0421367688193477 + }, + { + "test4": 0.9273114340249197, + "test5": 0.4162711053077539 + }, + { + "test4": 1.6566035678780215, + "test5": 2.3041243121435 + }, + { + "test4": 2.972974740688411, + "test5": 0.09131923703460687 + }, + { + "test4": 0.47973366861308564, + "test5": 2.9145794048449583 + }, + { + "test4": 1.7028140362780348, + "test5": 0.8732078555783949 + }, + { + "test4": 0.14184154388103598, + "test5": 1.4349753541838812 + }, + { + "test4": 1.8523605259580773, + "test5": 0.9412051066833718 + }, + { + "test4": 2.0331406123411186, + "test5": 0.8940637158429265 + }, + { + "test4": 1.272302136072152, + "test5": 0.3367990685785932 + }, + { + "test4": 2.830511701546474, + "test5": 0.6094527448958251 + }, + { + "test4": 2.5605583662839733, + "test5": 2.0510964757723245 + }, + { + "test4": 2.0960847244251997, + "test5": 2.4839298036892785 + }, + { + "test4": 1.407905427566821, + "test5": 1.4025349022394389 + }, + { + "test4": 1.7512156811245276, + "test5": 0.14200898693980002 + }, + { + "test4": 1.8430702570666848, + "test5": 0.6992728216108067 + }, + { + "test4": 2.0966538476742502, + "test5": 2.0519601860923427 + }, + { + "test4": 2.8237054684976557, + "test5": 0.4064742462852521 + }, + { + "test4": 1.2357399178375341, + "test5": 1.8528981769588575 + }, + { + "test4": 1.7453568506662316, + "test5": 2.719417044089297 + } + ] + }, + { + "name": "heatmap_bins", + "source": "points", + "transform": [ + { + "field": { + "signal": "x_name" + }, + "signal": "x_extent", + "type": "extent" + }, + { + "as": [ + "x_start", + "x_end" + ], + "extent": { + "signal": "x_extent" + }, + "field": { + "signal": "x_name" + }, + "maxbins": { + "signal": "max_x_bins" + }, + "signal": "x_bins", + "type": "bin" + }, + { + "field": { + "signal": "y_name" + }, + "signal": "y_extent", + "type": "extent" + }, + { + "as": [ + "y_start", + "y_end" + ], + "extent": { + "signal": "y_extent" + }, + "field": { + "signal": "y_name" + }, + "maxbins": { + "signal": "max_y_bins" + }, + "signal": "y_bins", + "type": "bin" + }, + { + "as": [ + "__count" + ], + "groupby": [ + "x_start", + "x_end", + "y_start", + "y_end" + ], + "ops": [ + "count" + ], + "type": "aggregate" + } + ] + } + ], + "height": 200, + "legends": [ + { + "fill": "color", + "gradientLength": { + "signal": "height - 16" + }, + "title": "Count of Records" + } + ], + "marks": [ + { + "encode": { + "update": { + "fill": { + "field": "__count", + "scale": "color" + }, + "x": { + "field": "x_end", + "scale": "x" + }, + "x2": { + "field": "x_start", + "scale": "x" + }, + "y": { + "field": "y_end", + "scale": "y" + }, + "y2": { + "field": "y_start", + "scale": "y" + } + } + }, + "from": { + "data": "heatmap_bins" + }, + "name": "marks", + "style": [ + "rect" + ], + "type": "rect" + }, + { + "encode": { + "enter": { + "fill": { + "value": "black" + }, + "fillOpacity": { + "value": 1 + } + }, + "update": { + "size": { + "value": 10 + }, + "x": { + "field": { + "signal": "x_name" + }, + "scale": "x" + }, + "y": { + "field": { + "signal": "y_name" + }, + "scale": "y" + } + } + }, + "from": { + "data": "points" + }, + "type": "symbol" + } + ], + "scales": [ + { + "bins": { + "signal": "x_bins" + }, + "domain": { + "signal": "[x_bins.start, x_bins.stop]" + }, + "name": "x", + "range": [ + 0, + { + "signal": "width" + } + ], + "type": "linear", + "zero": false + }, + { + "bins": { + "signal": "y_bins" + }, + "domain": { + "signal": "[y_bins.start, y_bins.stop]" + }, + "name": "y", + "range": [ + { + "signal": "height" + }, + 0 + ], + "type": "linear", + "zero": false + }, + { + "domain": { + "data": "heatmap_bins", + "field": "__count" + }, + "interpolate": "hcl", + "name": "color", + "range": "heatmap", + "type": "linear", + "zero": true + } + ], + "signals": [ + { + "bind": { + "input": "range", + "max": 100, + "min": 1, + "step": 1 + }, + "description": "Max number X bins", + "name": "max_x_bins", + "value": 4 + }, + { + "bind": { + "input": "range", + "max": 100, + "min": 1, + "step": 1 + }, + "description": "Max number Y bins", + "name": "max_y_bins", + "value": 4 + }, + { + "name": "x_name", + "value": "test4" + }, + { + "name": "y_name", + "value": "test5" + } + ], + "width": 300 + } + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "df = pd.DataFrame(3*np.random.random((100, 2)), columns=['test4', 'test5'])\n", + "schema = plots.heatmap_scatter(df, max_x_bins=4, max_y_bins=4)\n", + "plots.save_schema(schema, \"_schema.json\")\n", + "plots.ipy_display(schema, format=\"interactive\")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "6e083016", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + }, + { + "data": { + "application/vnd.vega.v5+json": { + "$schema": "https://vega.github.io/schema/vega/v5.json", + "axes": [ + { + "labelOverlap": true, + "orient": "bottom", + "scale": "x", + "tickCount": { + "signal": "ceil(width/3)" + }, + "title": "gamma" + }, + { + "labelOverlap": true, + "orient": "left", + "scale": "y", + "tickCount": { + "signal": "ceil(height/3)" + }, + "title": "alpha" + } + ], + "data": [ + { + "name": "points", + "values": [ + { + "alpha": 5.9784883316857185, + "gamma": 8.945626575409701 + }, + { + "alpha": 1.97190570168565, + "gamma": 5.603352372388732 + }, + { + "alpha": 8.091437097306919, + "gamma": 3.766757411797653 + }, + { + "alpha": 6.719283797935544, + "gamma": 6.592948316435251 + }, + { + "alpha": 0.8302807742935314, + "gamma": 6.974357899271521 + }, + { + "alpha": 8.97712332766224, + "gamma": 5.630352422593903 + }, + { + "alpha": 0.8980978433162434, + "gamma": 1.9296292855161856 + }, + { + "alpha": 1.114907196144591, + "gamma": 6.077597682137238 + }, + { + "alpha": 9.547709481680823, + "gamma": 5.146462909515672 + }, + { + "alpha": 1.9988505366594433, + "gamma": 5.698685596568787 + }, + { + "alpha": 2.0175887787394418, + "gamma": 2.096583695160273 + }, + { + "alpha": 0.11087500439810105, + "gamma": 1.0573980617461343 + }, + { + "alpha": 1.6543172193979983, + "gamma": 1.2306383886016992 + }, + { + "alpha": 8.22860414224558, + "gamma": 3.0502711540460425 + }, + { + "alpha": 1.0321781510987482, + "gamma": 1.6396565189340295 + }, + { + "alpha": 4.49595306492898, + "gamma": 8.283032157180251 + }, + { + "alpha": 0.2743003894776863, + "gamma": 9.86366496864936 + }, + { + "alpha": 1.8146260626588373, + "gamma": 9.709411574895036 + }, + { + "alpha": 6.963656671726587, + "gamma": 2.6868158714961443 + }, + { + "alpha": 6.536821218336284, + "gamma": 2.197035257435166 + }, + { + "alpha": 1.5149135921322043, + "gamma": 8.436156651617777 + }, + { + "alpha": 0.2785972085521615, + "gamma": 2.2325938777671714 + }, + { + "alpha": 3.3181276403154145, + "gamma": 8.717358568443666 + }, + { + "alpha": 2.474106358507134, + "gamma": 0.386641491579266 + }, + { + "alpha": 5.296282935101208, + "gamma": 3.1728881483746827 + }, + { + "alpha": 5.718592403580539, + "gamma": 3.566812591038677 + }, + { + "alpha": 3.689487911148607, + "gamma": 1.9628702800775388 + }, + { + "alpha": 2.9217203324791994, + "gamma": 6.839246117153723 + }, + { + "alpha": 2.8443453227487425, + "gamma": 7.505494523642388 + }, + { + "alpha": 2.2950335516947016, + "gamma": 4.796755286344181 + }, + { + "alpha": 3.096256778889787, + "gamma": 2.4210371241552298 + }, + { + "alpha": 6.793706575831284, + "gamma": 5.565714011997791 + }, + { + "alpha": 1.0881512415680417, + "gamma": 9.184776428199731 + }, + { + "alpha": 5.575685899560202, + "gamma": 9.229475957746583 + }, + { + "alpha": 0.7429916874172227, + "gamma": 1.4436889581099366 + }, + { + "alpha": 4.014391203442362, + "gamma": 5.348156464650822 + }, + { + "alpha": 9.930139858319244, + "gamma": 2.251169532724686 + }, + { + "alpha": 2.3775880339844138, + "gamma": 2.499144372252969 + }, + { + "alpha": 8.716506264004936, + "gamma": 3.466037761395526 + }, + { + "alpha": 9.56649351937782, + "gamma": 3.095125195602871 + }, + { + "alpha": 5.194171921508484, + "gamma": 8.49882338800778 + }, + { + "alpha": 5.433498150345617, + "gamma": 4.9870936458408615 + }, + { + "alpha": 9.044828467632017, + "gamma": 4.887782135547382 + }, + { + "alpha": 9.80080449102157, + "gamma": 6.133047017336271 + }, + { + "alpha": 6.893436106345878, + "gamma": 9.412341151775985 + }, + { + "alpha": 0.4915038369941327, + "gamma": 5.915008409057517 + }, + { + "alpha": 6.45628844732773, + "gamma": 6.032006215570812 + }, + { + "alpha": 8.730086941424268, + "gamma": 3.215246885129157 + }, + { + "alpha": 6.339664301750898, + "gamma": 9.326154995310219 + }, + { + "alpha": 1.186880390424453, + "gamma": 5.05998632702063 + }, + { + "alpha": 3.543481197691667, + "gamma": 1.2121177958635643 + }, + { + "alpha": 3.8032987466926604, + "gamma": 7.998051073304431 + }, + { + "alpha": 0.16156623215720645, + "gamma": 9.936193111653665 + }, + { + "alpha": 8.088889375699225, + "gamma": 4.390966189990508 + }, + { + "alpha": 0.8684176232504204, + "gamma": 2.7592914315896175 + }, + { + "alpha": 9.371691990461535, + "gamma": 3.5105549898062294 + }, + { + "alpha": 7.135681016473187, + "gamma": 0.16381541400300348 + }, + { + "alpha": 6.198539755106342, + "gamma": 8.667924340029126 + }, + { + "alpha": 7.204821246007685, + "gamma": 3.9624958931966745 + }, + { + "alpha": 0.542091357290958, + "gamma": 3.62803282290856 + }, + { + "alpha": 4.502193386808278, + "gamma": 4.741881575265121 + }, + { + "alpha": 6.3946016136889305, + "gamma": 5.689904025947912 + }, + { + "alpha": 9.206447906015686, + "gamma": 2.878361477635888 + }, + { + "alpha": 3.0701403484232657, + "gamma": 0.012621606291097764 + }, + { + "alpha": 9.179441021534426, + "gamma": 2.49402239024802 + }, + { + "alpha": 4.324923826776729, + "gamma": 4.216521090519461 + }, + { + "alpha": 5.429449136208006, + "gamma": 7.9352614686759715 + }, + { + "alpha": 0.11309549462388935, + "gamma": 2.4031999090392606 + }, + { + "alpha": 4.8729059893101105, + "gamma": 1.8194287226187922 + }, + { + "alpha": 2.7229181599246646, + "gamma": 6.82608287194432 + }, + { + "alpha": 2.120110573972467, + "gamma": 4.486804597037244 + }, + { + "alpha": 6.462215975367914, + "gamma": 9.664411381388742 + }, + { + "alpha": 5.944719068984467, + "gamma": 0.8059835924413405 + }, + { + "alpha": 6.421942274914542, + "gamma": 7.021311096530556 + }, + { + "alpha": 2.399593095457152, + "gamma": 3.1551458751475514 + }, + { + "alpha": 1.6094802807049036, + "gamma": 1.7974988018399929 + }, + { + "alpha": 0.7735513764777624, + "gamma": 0.3605064383241785 + }, + { + "alpha": 9.968517986892211, + "gamma": 5.6595050426192675 + }, + { + "alpha": 5.057528429110262, + "gamma": 2.680764515429328 + }, + { + "alpha": 9.61679099480759, + "gamma": 6.284721433093141 + }, + { + "alpha": 9.287305878916953, + "gamma": 2.8203088845495374 + }, + { + "alpha": 1.0716279774521809, + "gamma": 0.6176054154997013 + }, + { + "alpha": 3.998391037373048, + "gamma": 6.168454220210502 + }, + { + "alpha": 5.518414970533564, + "gamma": 2.0090544995198902 + }, + { + "alpha": 4.878017262401734, + "gamma": 8.680525562900204 + }, + { + "alpha": 7.887914662167519, + "gamma": 6.687600297706644 + }, + { + "alpha": 5.955583903704154, + "gamma": 9.522290412961071 + }, + { + "alpha": 7.318022175779435, + "gamma": 1.3274612866805335 + }, + { + "alpha": 2.3629151886593283, + "gamma": 7.351405954116991 + }, + { + "alpha": 2.2418618806852186, + "gamma": 6.6030199401732155 + }, + { + "alpha": 8.01875971303388, + "gamma": 6.433771120559827 + }, + { + "alpha": 5.585783359608416, + "gamma": 5.966928279523178 + }, + { + "alpha": 8.970646831230432, + "gamma": 7.487108631296909 + }, + { + "alpha": 1.7203414856913113, + "gamma": 7.496808099473677 + }, + { + "alpha": 3.7615505683990236, + "gamma": 4.665213854056351 + }, + { + "alpha": 4.890100371737165, + "gamma": 6.713397497814646 + }, + { + "alpha": 1.5132750750905566, + "gamma": 0.4791103262244045 + }, + { + "alpha": 4.5830335131339535, + "gamma": 1.7743642674865034 + }, + { + "alpha": 7.280682169822569, + "gamma": 2.3118861486970363 + }, + { + "alpha": 8.517101335669354, + "gamma": 0.6752709207759078 + } + ] + }, + { + "name": "mesh", + "values": [ + { + "__count": 0, + "x_end": 0.5555555555555556, + "x_start": -0.5555555555555556, + "y_end": 0.5555555555555556, + "y_start": -0.5555555555555556 + }, + { + "__count": 1.234567901234568, + "x_end": 1.6666666666666667, + "x_start": 0.5555555555555556, + "y_end": 0.5555555555555556, + "y_start": -0.5555555555555556 + }, + { + "__count": 4.938271604938272, + "x_end": 2.7777777777777777, + "x_start": 1.6666666666666667, + "y_end": 0.5555555555555556, + "y_start": -0.5555555555555556 + }, + { + "__count": 11.111111111111112, + "x_end": 3.8888888888888893, + "x_start": 2.7777777777777777, + "y_end": 0.5555555555555556, + "y_start": -0.5555555555555556 + }, + { + "__count": 19.75308641975309, + "x_end": 5, + "x_start": 3.8888888888888893, + "y_end": 0.5555555555555556, + "y_start": -0.5555555555555556 + }, + { + "__count": 30.864197530864196, + "x_end": 6.111111111111111, + "x_start": 5, + "y_end": 0.5555555555555556, + "y_start": -0.5555555555555556 + }, + { + "__count": 44.44444444444445, + "x_end": 7.222222222222222, + "x_start": 6.111111111111112, + "y_end": 0.5555555555555556, + "y_start": -0.5555555555555556 + }, + { + "__count": 60.49382716049384, + "x_end": 8.333333333333334, + "x_start": 7.222222222222223, + "y_end": 0.5555555555555556, + "y_start": -0.5555555555555556 + }, + { + "__count": 79.01234567901236, + "x_end": 9.444444444444445, + "x_start": 8.333333333333334, + "y_end": 0.5555555555555556, + "y_start": -0.5555555555555556 + }, + { + "__count": 100, + "x_end": 10.555555555555555, + "x_start": 9.444444444444445, + "y_end": 0.5555555555555556, + "y_start": -0.5555555555555556 + }, + { + "__count": 1.234567901234568, + "x_end": 0.5555555555555556, + "x_start": -0.5555555555555556, + "y_end": 1.6666666666666667, + "y_start": 0.5555555555555556 + }, + { + "__count": 2.469135802469136, + "x_end": 1.6666666666666667, + "x_start": 0.5555555555555556, + "y_end": 1.6666666666666667, + "y_start": 0.5555555555555556 + }, + { + "__count": 6.17283950617284, + "x_end": 2.7777777777777777, + "x_start": 1.6666666666666667, + "y_end": 1.6666666666666667, + "y_start": 0.5555555555555556 + }, + { + "__count": 12.34567901234568, + "x_end": 3.8888888888888893, + "x_start": 2.7777777777777777, + "y_end": 1.6666666666666667, + "y_start": 0.5555555555555556 + }, + { + "__count": 20.98765432098766, + "x_end": 5, + "x_start": 3.8888888888888893, + "y_end": 1.6666666666666667, + "y_start": 0.5555555555555556 + }, + { + "__count": 32.098765432098766, + "x_end": 6.111111111111111, + "x_start": 5, + "y_end": 1.6666666666666667, + "y_start": 0.5555555555555556 + }, + { + "__count": 45.67901234567902, + "x_end": 7.222222222222222, + "x_start": 6.111111111111112, + "y_end": 1.6666666666666667, + "y_start": 0.5555555555555556 + }, + { + "__count": 61.728395061728406, + "x_end": 8.333333333333334, + "x_start": 7.222222222222223, + "y_end": 1.6666666666666667, + "y_start": 0.5555555555555556 + }, + { + "__count": 80.24691358024693, + "x_end": 9.444444444444445, + "x_start": 8.333333333333334, + "y_end": 1.6666666666666667, + "y_start": 0.5555555555555556 + }, + { + "__count": 101.23456790123457, + "x_end": 10.555555555555555, + "x_start": 9.444444444444445, + "y_end": 1.6666666666666667, + "y_start": 0.5555555555555556 + }, + { + "__count": 4.938271604938272, + "x_end": 0.5555555555555556, + "x_start": -0.5555555555555556, + "y_end": 2.7777777777777777, + "y_start": 1.6666666666666667 + }, + { + "__count": 6.17283950617284, + "x_end": 1.6666666666666667, + "x_start": 0.5555555555555556, + "y_end": 2.7777777777777777, + "y_start": 1.6666666666666667 + }, + { + "__count": 9.876543209876544, + "x_end": 2.7777777777777777, + "x_start": 1.6666666666666667, + "y_end": 2.7777777777777777, + "y_start": 1.6666666666666667 + }, + { + "__count": 16.049382716049386, + "x_end": 3.8888888888888893, + "x_start": 2.7777777777777777, + "y_end": 2.7777777777777777, + "y_start": 1.6666666666666667 + }, + { + "__count": 24.69135802469136, + "x_end": 5, + "x_start": 3.8888888888888893, + "y_end": 2.7777777777777777, + "y_start": 1.6666666666666667 + }, + { + "__count": 35.80246913580247, + "x_end": 6.111111111111111, + "x_start": 5, + "y_end": 2.7777777777777777, + "y_start": 1.6666666666666667 + }, + { + "__count": 49.38271604938272, + "x_end": 7.222222222222222, + "x_start": 6.111111111111112, + "y_end": 2.7777777777777777, + "y_start": 1.6666666666666667 + }, + { + "__count": 65.4320987654321, + "x_end": 8.333333333333334, + "x_start": 7.222222222222223, + "y_end": 2.7777777777777777, + "y_start": 1.6666666666666667 + }, + { + "__count": 83.95061728395063, + "x_end": 9.444444444444445, + "x_start": 8.333333333333334, + "y_end": 2.7777777777777777, + "y_start": 1.6666666666666667 + }, + { + "__count": 104.93827160493828, + "x_end": 10.555555555555555, + "x_start": 9.444444444444445, + "y_end": 2.7777777777777777, + "y_start": 1.6666666666666667 + }, + { + "__count": 11.111111111111112, + "x_end": 0.5555555555555556, + "x_start": -0.5555555555555556, + "y_end": 3.8888888888888893, + "y_start": 2.7777777777777777 + }, + { + "__count": 12.34567901234568, + "x_end": 1.6666666666666667, + "x_start": 0.5555555555555556, + "y_end": 3.8888888888888893, + "y_start": 2.7777777777777777 + }, + { + "__count": 16.049382716049386, + "x_end": 2.7777777777777777, + "x_start": 1.6666666666666667, + "y_end": 3.8888888888888893, + "y_start": 2.7777777777777777 + }, + { + "__count": 22.222222222222225, + "x_end": 3.8888888888888893, + "x_start": 2.7777777777777777, + "y_end": 3.8888888888888893, + "y_start": 2.7777777777777777 + }, + { + "__count": 30.864197530864203, + "x_end": 5, + "x_start": 3.8888888888888893, + "y_end": 3.8888888888888893, + "y_start": 2.7777777777777777 + }, + { + "__count": 41.97530864197531, + "x_end": 6.111111111111111, + "x_start": 5, + "y_end": 3.8888888888888893, + "y_start": 2.7777777777777777 + }, + { + "__count": 55.555555555555564, + "x_end": 7.222222222222222, + "x_start": 6.111111111111112, + "y_end": 3.8888888888888893, + "y_start": 2.7777777777777777 + }, + { + "__count": 71.60493827160495, + "x_end": 8.333333333333334, + "x_start": 7.222222222222223, + "y_end": 3.8888888888888893, + "y_start": 2.7777777777777777 + }, + { + "__count": 90.12345679012347, + "x_end": 9.444444444444445, + "x_start": 8.333333333333334, + "y_end": 3.8888888888888893, + "y_start": 2.7777777777777777 + }, + { + "__count": 111.11111111111111, + "x_end": 10.555555555555555, + "x_start": 9.444444444444445, + "y_end": 3.8888888888888893, + "y_start": 2.7777777777777777 + }, + { + "__count": 19.75308641975309, + "x_end": 0.5555555555555556, + "x_start": -0.5555555555555556, + "y_end": 5, + "y_start": 3.8888888888888893 + }, + { + "__count": 20.98765432098766, + "x_end": 1.6666666666666667, + "x_start": 0.5555555555555556, + "y_end": 5, + "y_start": 3.8888888888888893 + }, + { + "__count": 24.69135802469136, + "x_end": 2.7777777777777777, + "x_start": 1.6666666666666667, + "y_end": 5, + "y_start": 3.8888888888888893 + }, + { + "__count": 30.864197530864203, + "x_end": 3.8888888888888893, + "x_start": 2.7777777777777777, + "y_end": 5, + "y_start": 3.8888888888888893 + }, + { + "__count": 39.50617283950618, + "x_end": 5, + "x_start": 3.8888888888888893, + "y_end": 5, + "y_start": 3.8888888888888893 + }, + { + "__count": 50.617283950617285, + "x_end": 6.111111111111111, + "x_start": 5, + "y_end": 5, + "y_start": 3.8888888888888893 + }, + { + "__count": 64.19753086419755, + "x_end": 7.222222222222222, + "x_start": 6.111111111111112, + "y_end": 5, + "y_start": 3.8888888888888893 + }, + { + "__count": 80.24691358024693, + "x_end": 8.333333333333334, + "x_start": 7.222222222222223, + "y_end": 5, + "y_start": 3.8888888888888893 + }, + { + "__count": 98.76543209876544, + "x_end": 9.444444444444445, + "x_start": 8.333333333333334, + "y_end": 5, + "y_start": 3.8888888888888893 + }, + { + "__count": 119.75308641975309, + "x_end": 10.555555555555555, + "x_start": 9.444444444444445, + "y_end": 5, + "y_start": 3.8888888888888893 + }, + { + "__count": 30.864197530864196, + "x_end": 0.5555555555555556, + "x_start": -0.5555555555555556, + "y_end": 6.111111111111111, + "y_start": 5 + }, + { + "__count": 32.098765432098766, + "x_end": 1.6666666666666667, + "x_start": 0.5555555555555556, + "y_end": 6.111111111111111, + "y_start": 5 + }, + { + "__count": 35.80246913580247, + "x_end": 2.7777777777777777, + "x_start": 1.6666666666666667, + "y_end": 6.111111111111111, + "y_start": 5 + }, + { + "__count": 41.97530864197531, + "x_end": 3.8888888888888893, + "x_start": 2.7777777777777777, + "y_end": 6.111111111111111, + "y_start": 5 + }, + { + "__count": 50.617283950617285, + "x_end": 5, + "x_start": 3.8888888888888893, + "y_end": 6.111111111111111, + "y_start": 5 + }, + { + "__count": 61.72839506172839, + "x_end": 6.111111111111111, + "x_start": 5, + "y_end": 6.111111111111111, + "y_start": 5 + }, + { + "__count": 75.30864197530865, + "x_end": 7.222222222222222, + "x_start": 6.111111111111112, + "y_end": 6.111111111111111, + "y_start": 5 + }, + { + "__count": 91.35802469135803, + "x_end": 8.333333333333334, + "x_start": 7.222222222222223, + "y_end": 6.111111111111111, + "y_start": 5 + }, + { + "__count": 109.87654320987656, + "x_end": 9.444444444444445, + "x_start": 8.333333333333334, + "y_end": 6.111111111111111, + "y_start": 5 + }, + { + "__count": 130.8641975308642, + "x_end": 10.555555555555555, + "x_start": 9.444444444444445, + "y_end": 6.111111111111111, + "y_start": 5 + }, + { + "__count": 44.44444444444445, + "x_end": 0.5555555555555556, + "x_start": -0.5555555555555556, + "y_end": 7.222222222222222, + "y_start": 6.111111111111112 + }, + { + "__count": 45.67901234567902, + "x_end": 1.6666666666666667, + "x_start": 0.5555555555555556, + "y_end": 7.222222222222222, + "y_start": 6.111111111111112 + }, + { + "__count": 49.38271604938272, + "x_end": 2.7777777777777777, + "x_start": 1.6666666666666667, + "y_end": 7.222222222222222, + "y_start": 6.111111111111112 + }, + { + "__count": 55.555555555555564, + "x_end": 3.8888888888888893, + "x_start": 2.7777777777777777, + "y_end": 7.222222222222222, + "y_start": 6.111111111111112 + }, + { + "__count": 64.19753086419755, + "x_end": 5, + "x_start": 3.8888888888888893, + "y_end": 7.222222222222222, + "y_start": 6.111111111111112 + }, + { + "__count": 75.30864197530865, + "x_end": 6.111111111111111, + "x_start": 5, + "y_end": 7.222222222222222, + "y_start": 6.111111111111112 + }, + { + "__count": 88.8888888888889, + "x_end": 7.222222222222222, + "x_start": 6.111111111111112, + "y_end": 7.222222222222222, + "y_start": 6.111111111111112 + }, + { + "__count": 104.93827160493828, + "x_end": 8.333333333333334, + "x_start": 7.222222222222223, + "y_end": 7.222222222222222, + "y_start": 6.111111111111112 + }, + { + "__count": 123.45679012345681, + "x_end": 9.444444444444445, + "x_start": 8.333333333333334, + "y_end": 7.222222222222222, + "y_start": 6.111111111111112 + }, + { + "__count": 144.44444444444446, + "x_end": 10.555555555555555, + "x_start": 9.444444444444445, + "y_end": 7.222222222222222, + "y_start": 6.111111111111112 + }, + { + "__count": 60.49382716049384, + "x_end": 0.5555555555555556, + "x_start": -0.5555555555555556, + "y_end": 8.333333333333334, + "y_start": 7.222222222222223 + }, + { + "__count": 61.728395061728406, + "x_end": 1.6666666666666667, + "x_start": 0.5555555555555556, + "y_end": 8.333333333333334, + "y_start": 7.222222222222223 + }, + { + "__count": 65.4320987654321, + "x_end": 2.7777777777777777, + "x_start": 1.6666666666666667, + "y_end": 8.333333333333334, + "y_start": 7.222222222222223 + }, + { + "__count": 71.60493827160495, + "x_end": 3.8888888888888893, + "x_start": 2.7777777777777777, + "y_end": 8.333333333333334, + "y_start": 7.222222222222223 + }, + { + "__count": 80.24691358024693, + "x_end": 5, + "x_start": 3.8888888888888893, + "y_end": 8.333333333333334, + "y_start": 7.222222222222223 + }, + { + "__count": 91.35802469135803, + "x_end": 6.111111111111111, + "x_start": 5, + "y_end": 8.333333333333334, + "y_start": 7.222222222222223 + }, + { + "__count": 104.93827160493828, + "x_end": 7.222222222222222, + "x_start": 6.111111111111112, + "y_end": 8.333333333333334, + "y_start": 7.222222222222223 + }, + { + "__count": 120.98765432098767, + "x_end": 8.333333333333334, + "x_start": 7.222222222222223, + "y_end": 8.333333333333334, + "y_start": 7.222222222222223 + }, + { + "__count": 139.50617283950618, + "x_end": 9.444444444444445, + "x_start": 8.333333333333334, + "y_end": 8.333333333333334, + "y_start": 7.222222222222223 + }, + { + "__count": 160.49382716049382, + "x_end": 10.555555555555555, + "x_start": 9.444444444444445, + "y_end": 8.333333333333334, + "y_start": 7.222222222222223 + }, + { + "__count": 79.01234567901236, + "x_end": 0.5555555555555556, + "x_start": -0.5555555555555556, + "y_end": 9.444444444444445, + "y_start": 8.333333333333334 + }, + { + "__count": 80.24691358024693, + "x_end": 1.6666666666666667, + "x_start": 0.5555555555555556, + "y_end": 9.444444444444445, + "y_start": 8.333333333333334 + }, + { + "__count": 83.95061728395063, + "x_end": 2.7777777777777777, + "x_start": 1.6666666666666667, + "y_end": 9.444444444444445, + "y_start": 8.333333333333334 + }, + { + "__count": 90.12345679012347, + "x_end": 3.8888888888888893, + "x_start": 2.7777777777777777, + "y_end": 9.444444444444445, + "y_start": 8.333333333333334 + }, + { + "__count": 98.76543209876544, + "x_end": 5, + "x_start": 3.8888888888888893, + "y_end": 9.444444444444445, + "y_start": 8.333333333333334 + }, + { + "__count": 109.87654320987656, + "x_end": 6.111111111111111, + "x_start": 5, + "y_end": 9.444444444444445, + "y_start": 8.333333333333334 + }, + { + "__count": 123.45679012345681, + "x_end": 7.222222222222222, + "x_start": 6.111111111111112, + "y_end": 9.444444444444445, + "y_start": 8.333333333333334 + }, + { + "__count": 139.50617283950618, + "x_end": 8.333333333333334, + "x_start": 7.222222222222223, + "y_end": 9.444444444444445, + "y_start": 8.333333333333334 + }, + { + "__count": 158.0246913580247, + "x_end": 9.444444444444445, + "x_start": 8.333333333333334, + "y_end": 9.444444444444445, + "y_start": 8.333333333333334 + }, + { + "__count": 179.01234567901236, + "x_end": 10.555555555555555, + "x_start": 9.444444444444445, + "y_end": 9.444444444444445, + "y_start": 8.333333333333334 + }, + { + "__count": 100, + "x_end": 0.5555555555555556, + "x_start": -0.5555555555555556, + "y_end": 10.555555555555555, + "y_start": 9.444444444444445 + }, + { + "__count": 101.23456790123457, + "x_end": 1.6666666666666667, + "x_start": 0.5555555555555556, + "y_end": 10.555555555555555, + "y_start": 9.444444444444445 + }, + { + "__count": 104.93827160493828, + "x_end": 2.7777777777777777, + "x_start": 1.6666666666666667, + "y_end": 10.555555555555555, + "y_start": 9.444444444444445 + }, + { + "__count": 111.11111111111111, + "x_end": 3.8888888888888893, + "x_start": 2.7777777777777777, + "y_end": 10.555555555555555, + "y_start": 9.444444444444445 + }, + { + "__count": 119.75308641975309, + "x_end": 5, + "x_start": 3.8888888888888893, + "y_end": 10.555555555555555, + "y_start": 9.444444444444445 + }, + { + "__count": 130.8641975308642, + "x_end": 6.111111111111111, + "x_start": 5, + "y_end": 10.555555555555555, + "y_start": 9.444444444444445 + }, + { + "__count": 144.44444444444446, + "x_end": 7.222222222222222, + "x_start": 6.111111111111112, + "y_end": 10.555555555555555, + "y_start": 9.444444444444445 + }, + { + "__count": 160.49382716049382, + "x_end": 8.333333333333334, + "x_start": 7.222222222222223, + "y_end": 10.555555555555555, + "y_start": 9.444444444444445 + }, + { + "__count": 179.01234567901236, + "x_end": 9.444444444444445, + "x_start": 8.333333333333334, + "y_end": 10.555555555555555, + "y_start": 9.444444444444445 + }, + { + "__count": 200, + "x_end": 10.555555555555555, + "x_start": 9.444444444444445, + "y_end": 10.555555555555555, + "y_start": 9.444444444444445 + } + ] + } + ], + "height": 200, + "legends": [ + { + "fill": "color", + "gradientLength": { + "signal": "height - 16" + }, + "title": "Histogram Values" + } + ], + "marks": [ + { + "encode": { + "update": { + "fill": { + "field": "__count", + "scale": "color" + }, + "x": { + "field": "x_end", + "scale": "x" + }, + "x2": { + "field": "x_start", + "scale": "x" + }, + "y": { + "field": "y_end", + "scale": "y" + }, + "y2": { + "field": "y_start", + "scale": "y" + } + } + }, + "from": { + "data": "mesh" + }, + "name": "marks", + "style": [ + "rect" + ], + "type": "rect" + }, + { + "encode": { + "enter": { + "fill": { + "value": "black" + }, + "fillOpacity": { + "value": 1 + } + }, + "update": { + "size": { + "value": 10 + }, + "x": { + "field": "gamma", + "scale": "x" + }, + "y": { + "field": "alpha", + "scale": "y" + } + } + }, + "from": { + "data": "points" + }, + "type": "symbol" + } + ], + "scales": [ + { + "domain": { + "fields": [ + { + "data": "points", + "field": "gamma" + }, + { + "data": "mesh", + "field": "x_start" + }, + { + "data": "mesh", + "field": "y_end" + } + ] + }, + "name": "x", + "range": [ + 0, + { + "signal": "width" + } + ], + "type": "linear" + }, + { + "domain": { + "fields": [ + { + "data": "points", + "field": "alpha" + }, + { + "data": "mesh", + "field": "y_start" + }, + { + "data": "mesh", + "field": "y_end" + } + ] + }, + "name": "y", + "range": [ + { + "signal": "height" + }, + 0 + ], + "type": "linear" + }, + { + "domain": { + "data": "mesh", + "field": "__count" + }, + "interpolate": "hcl", + "name": "color", + "range": "heatmap", + "type": "linear", + "zero": true + } + ], + "width": 300 + } + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "def create_fake_data():\n", + " nx, ny = (10, 10)\n", + " x = np.linspace(0, 10, nx)\n", + " y, a = np.linspace(0, 10, ny, retstep = True)\n", + "\n", + " # create mesh data\n", + " xv, yv = np.meshgrid(x, y)\n", + " zz = xv**2 + yv**2\n", + "\n", + " # create scatter plot\n", + " df = pd.DataFrame(10*np.random.random((100, 2)), columns=['alpha', 'gamma'])\n", + " return (xv, yv, zz), df\n", + "\n", + "mesh_data, scatter_data = create_fake_data()\n", + "\n", + "schema = plots.heatmap_scatter(scatter_data, mesh_data)\n", + "plots.save_schema(schema, \"_schema.json\")\n", + "plots.ipy_display(schema, format=\"interactive\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9d3ad843", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/src/pyciemss/visuals/graphs.py b/src/pyciemss/visuals/graphs.py index b152753af..360a7a5e5 100644 --- a/src/pyciemss/visuals/graphs.py +++ b/src/pyciemss/visuals/graphs.py @@ -1,14 +1,19 @@ -from typing import Union +from typing import Union, Optional +from numbers import Number import networkx as nx from . import vega +# TODO: The attributed-graph has more complex node_label logic than the spring-force. +# Which one is 'better'? Use it in both places. -def attributed_graph(graph: nx.Graph) -> vega.VegaSchema: + +def attributed_graph( + graph: nx.Graph, *, collapse_all: bool = False, node_labels: Union[str, None] = None +) -> vega.VegaSchema: """Draw a graph with node/edge attributions color-coded. graph (networkx graph): A networkX graph with attributes on nodes/edges as follows. - label -- Node attribute used to label the nodes. Can be anything that vega can interpret as a string. attribution -- Node attribute. List of values used to color portions of the node border. attribution -- Edge attribute. list of values used to color the edge. @@ -16,25 +21,82 @@ def attributed_graph(graph: nx.Graph) -> vega.VegaSchema: NOTE: Other properties will be propogated through. TODO: Make layout in the vega schema optional (accept passed- in schemas) - TODO: Add interaction to the vega schema The color coding of attribution will be shared between nodes and edges. + + node_labels + Node labels will be constructed as follows: + -- If node_labels is None, the node-id will be used + -- If node_labels is not "label", the attribute specified will be copied into a "label" attribute + collapse_all: If True AND an attribution includes all of the attributions used, + the attribution will be set to just "*all*". This can be used to + simplify the visualization in some cases. """ + + if node_labels is None: + graph = nx.convert_node_labels_to_integers(graph, label_attribute="label") + elif node_labels == "label": + nx.set_node_attributes( + graph, nx.get_node_attributes(graph, node_labels), "label" + ) + gjson = nx.json_graph.node_link_data(graph) + possible_attributions = set() for n in gjson["nodes"]: if "attribution" not in n or len(n["attribution"]) == 0: raise ValueError( - "Every node must have an 'attribution' property with at least one element in the list." + f"Every node must have an 'attribution' property with at least one element in the list. Failed for {n}" ) + possible_attributions.update(n["attribution"]) for e in gjson["links"]: - if "attribution" not in n or len(n["attribution"]) == 0: + if "attribution" not in e or len(e["attribution"]) == 0: raise ValueError( - "Every edge must have an 'attribution' property with at least one element in the list." + f"Every edge must have an 'attribution' property with at least one element in the list. Failed for {e}" ) + possible_attributions.update(n["attribution"]) schema = vega.load_schema("multigraph.vg.json") + if collapse_all: + # This is category20, but with the light-gray moved up and the dark-gray removed + colormap = [ + "#c7c7c7", + "#1f77b4", + "#aec7e8", + "#ff7f0e", + "#ffbb78", + "#2ca02c", + "#98df8a", + "#d62728", + "#ff9896", + "#9467bd", + "#c5b0d5", + "#8c564b", + "#c49c94", + "#e377c2", + "#f7b6d2", + "#bcbd22", + "#dbdb8d", + "#17becf", + "#9edae5", + ] + did_replace = False + for n in gjson["nodes"]: + if len(possible_attributions.difference(n["attribution"])) == 0: + n["attribution"] = ["*all*"] + did_replace = True + + for e in gjson["links"]: + if len(possible_attributions.difference(e["attribution"])) == 0: + e["attribution"] = ["*all*"] + did_replace = True + + if did_replace: + schema["scales"] = vega.replace_named_with( + schema["scales"], "color", ["range"], colormap + ) + schema["data"] = vega.replace_named_with( schema["data"], "node-data", ["values"], gjson["nodes"] ) @@ -46,21 +108,36 @@ def attributed_graph(graph: nx.Graph) -> vega.VegaSchema: def spring_force_graph( - graph: nx.Graph, node_labels: Union[str, None] = "label" + graph: nx.Graph, + node_labels: Union[str, None] = "label", + layout: Optional[dict[any, (Number, Number)]] = None, + directed_graph: bool = True, ) -> vega.VegaSchema: """Draw a general spring-force graph graph -- Networkx graph to draw + input_layout -- input of locations of nodes in graph as fx and fy items in data list of dictionaries labels -- If it is a string, that field name is used ('label' is the default; 'id' will give the networkx node-id). If it is None, no label is drawn. """ - gjson = nx.json_graph.node_link_data(graph) + def _layout_get(id): + x, y = layout.get(id, (None, None)) + return dict(zip(["inputX", "inputY", "fx", "fy"], [x, y, None, None])) + + graph = nx.convert_node_labels_to_integers(graph, label_attribute=node_labels) + gjson = nx.json_graph.node_link_data(graph) schema = vega.load_schema("spring_graph.vg.json") + if layout: + gjson["nodes"] = [ + {**item, **_layout_get(item[node_labels])} for item in gjson["nodes"] + ] + schema["data"] = vega.replace_named_with( schema["data"], "node-data", ["values"], gjson["nodes"] ) + schema["data"] = vega.replace_named_with( schema["data"], "link-data", ["values"], gjson["links"] ) @@ -71,4 +148,7 @@ def spring_force_graph( labels = vega.find_named(schema["marks"], "labels") labels["encode"]["enter"]["text"]["field"] = f"datum.{node_labels}" + if not directed_graph: + schema["marks"] = vega.delete_named(schema["marks"], "arrows") + return schema diff --git a/src/pyciemss/visuals/schemas/multigraph.vg.json b/src/pyciemss/visuals/schemas/multigraph.vg.json index ed57e2de1..44344cd05 100644 --- a/src/pyciemss/visuals/schemas/multigraph.vg.json +++ b/src/pyciemss/visuals/schemas/multigraph.vg.json @@ -58,7 +58,8 @@ "type": "ordinal", "range": {"scheme": "category20"}, "domain": { - "fields": [ + "sort": {"order": "ascending"}, + "fields": [ {"data": "node-attributions", "field": "attribution"}, {"data": "link-attributions", "field": "attribution"} ] @@ -222,14 +223,17 @@ "from": {"data": "nodes"}, "encode": { "enter": { - "fill": {"value": "black"}, "text": {"field": "datum.label"}, "align": {"value": "center"}, "baseline": {"value": "middle"} }, "update": { "x": {"field": "x"}, - "y": {"field": "y"} + "y": {"field": "y"}, + "fill": [ + {"test": "indexof(datum.datum.attribution, '*all*')>=0", "value": "lightgray"}, + {"value": "gray"} + ] } } } diff --git a/src/pyciemss/visuals/schemas/spring_graph.vg.json b/src/pyciemss/visuals/schemas/spring_graph.vg.json index 12a01dccb..be2b24eb4 100644 --- a/src/pyciemss/visuals/schemas/spring_graph.vg.json +++ b/src/pyciemss/visuals/schemas/spring_graph.vg.json @@ -1,188 +1,266 @@ { - "$schema": "https://vega.github.io/schema/vega/v5.json", - "description": "A node-link diagram with force-directed layout.", - "width": 300, - "height": 300, - "padding": 0, - - "signals": [ - { "name": "cx", "update": "width / 2" }, - { "name": "cy", "update": "height / 2" }, - { "name": "nodeRadius", "value": 15}, - { "name": "nodeCharge", "value": -80}, - { "name": "linkDistance", "value": 80}, - { "name": "static", "value": true, - "bind": {"input": "checkbox"} }, - { - "description": "State variable for active node fix status.", - "name": "fix", "value": false, - "on": [ - { - "events": "symbol:mouseout[!event.buttons], window:mouseup", - "update": "false" - }, - { - "events": "symbol:mouseover", - "update": "fix || true" + "$schema": "https://vega.github.io/schema/vega/v5.json", + "description": "A node-link diagram with force-directed layout.", + "width": 500, + "height": 500, + "padding": 0, + + "signals": [ + { "name": "cx", "update": "width / 2" }, + { "name": "cy", "update": "height / 2" }, + { "name": "nodeRadius", "value": 15}, + { "name": "nodeCharge", "value": -80}, + { "name": "linkDistance", "value": 80}, + { + "description": "State variable for active node fix status.", + "name": "fix", "value": false, + "on": [ + { + "events": "symbol:mouseout[!event.buttons], window:mouseup", + "update": "false" + }, + { + "events": "symbol:mouseover", + "update": "fix || true" + }, + { + "events": "[symbol:mousedown, window:mouseup] > window:mousemove!", + "update": "xy()", + "force": true + } + + ] + }, + { + "description": "Unfix node", + "name": "reset", "value": null, + "on": [ + { + "events": "symbol:dblclick", + "update": "item().datum" + } + ] + }, + + { + "description": "Graph node most recently interacted with.", + "name": "node", "value": null, + "on": [ + { + "events": "symbol:mouseover", + "update": "fix === true ? item() : node" + } + ] + }, + + { + "description": "Flag to restart Force simulation upon data changes.", + "name": "restart", "value": false, + "on": [ + {"events": {"signal": "fix"}, "update": "fix && fix.length"}, + {"events": {"signal": "reset"}, "update": "true"} + ] + + } + ], + + "scales": [ + { + "name": "color", + "type": "ordinal", + "domain": {"data": "node-data", "field": "group"}, + "range": {"scheme": "category20c"} + }, + + { + "name": "colorlink", + "type": "ordinal", + "domain": {"data": "link-data", "field": "group"}, + "range": {"scheme": "category20c"} + }, + { + "name": "xscale", + "domain": {"data": "node-data", "field": "inputX"}, + "range": [10, {"signal": "width - 10"}] + }, + { + "name": "yscale", + "domain": {"data": "node-data", "field": "inputY"}, + "range": [10, {"signal": "height - 10"}] + } + ], + + "legends": [ + { + "title": "Group", + "stroke": "color", + "symbolType": "stroke" + }, + + { + "title": "Link Group", + "stroke": "colorlink", + "symbolType": "stroke" + } + ], + + "marks": [ + { + "name": "nodes", + "type": "symbol", + "zindex": 1, + "from": {"data": "node-data"}, + "transform": [ + { + "type": "force", + "iterations": 300, + "restart": {"signal": "restart"}, + "static": true, + "signal": "force", + "forces": [ + {"force": "center", "x": {"signal": "cx"}, "y": {"signal": "cy"}}, + {"force": "collide", "radius": {"signal": "nodeRadius"}}, + {"force": "nbody", "strength": {"signal": "nodeCharge"}}, + {"force": "link", "links": "link-data", "distance": {"signal": "linkDistance"}} + ] + } + ], + "encode": { + "enter": { + "fill": {"scale": "color", "field": "group"}, + "stroke": {"value": "white"} }, - { - "events": "[symbol:mousedown, window:mouseup] > window:mousemove!", - "update": "xy()", - "force": true + "update": { + "size": {"signal": "2 * nodeRadius * nodeRadius"}, + "cursor": {"value": "pointer"}, + "fx": {"signal": "datum.interactionX == null ? scale('xscale', datum.inputX) : datum.interactionX"}, + "fy": {"signal": "datum.interactionY == null ? scale('yscale', datum.inputY) : datum.interactionY"} + } + } + }, + { + "type": "text", + "name": "labels", + "from": {"data": "nodes"}, + "zindex": 2, + "interactive": false, + "transform": [ + { + "type": "label", + "avoidMarks": ["nodes"], + "anchor": ["top", "bottom", "right", "left"], + "offset": [1], + "size": { + "signal": "[width + 60, height]" } - ] + } + ], + "encode": { + "enter": { + "fill": {"value": "black"}, + "align": {"value": "center"}, + "baseline": {"value": "middle"}, + "fontSize": {"value": 10}, + "text": {"field": "datum.id"} + }, + "update": { + "x": {"field": "x"}, + "y": {"field": "y"} + } + } + }, + { + "type": "path", + "from": {"data": "link-data"}, + "interactive": false, + "encode": { + "update": { + "stroke": {"scale": "colorlink", "field": "group"}, + "strokeWidth": {"value": 0.5} + } }, - { - "description": "Graph node most recently interacted with.", - "name": "node", "value": null, - "on": [ - { - "events": "symbol:mouseover", - "update": "fix === true ? item() : node" + "transform": [ + { + "type": "linkpath", + "require": {"signal": "force"}, + "shape": "line", + "sourceX": "datum.source.x", "sourceY": "datum.source.y", + "targetX": "datum.target.x", "targetY": "datum.target.y" + } + ] + }, + { + "name":"arrows", + "type": "symbol", + "from": {"data": "link-data"}, + "zindex": {"value": 40}, + "encode": { + "enter": { + "fill": {"scale": "colorlink", "field": "group"}, + "stroke": {"scale": "colorlink", "field": "group"}, + "shape": {"value": "triangle-right"}, + "size": {"value": 40} + }, + "update": { + "x": {"field": "target.x"}, + "y": {"field": "target.y"} + }, + "hover": { + "opacity": {"value": 1} } - ] }, - { - "description": "Flag to restart Force simulation upon data changes.", - "name": "restart", "value": false, - "on": [ - {"events": {"signal": "fix"}, "update": "fix && fix.length"} - ] - } - ], - - "scales": [ - { - "name": "color", - "type": "ordinal", - "domain": {"data": "node-data", "field": "group"}, - "range": {"scheme": "category20c"} - } - ], - - "legends": [ - { - "title": "Group", - "stroke": "color", - "symbolType": "stroke" - } - ], - - "marks": [ - { - "name": "nodes", - "type": "symbol", - "zindex": 1, - - "from": {"data": "node-data"}, - "on": [ + "transform": [ { - "trigger": "fix", - "modify": "node", - "values": "fix === true ? {fx: node.x, fy: node.y} : {fx: fix[0], fy: fix[1]}" + "type": "formula", + "as": "tan", + "expr": "atan2((datum.datum.target.y-datum.datum.source.y),(datum.datum.target.x-datum.datum.source.x))" }, { - "trigger": "!fix", - "modify": "node", "values": "{fx: null, fy: null}" - } - ], - - "encode": { - "enter": { - "fill": {"scale": "color", "field": "group"}, - "stroke": {"value": "white"} + "type": "formula", + "as": "angle", + "expr": "datum.tan*180/PI" }, - "update": { - "size": {"signal": "2 * nodeRadius * nodeRadius"}, - "cursor": {"value": "pointer"} - } - }, - - "transform": [ { - "type": "force", - "iterations": 300, - "restart": {"signal": "restart"}, - "static": {"signal": "static"}, - "signal": "force", - "forces": [ - {"force": "center", "x": {"signal": "cx"}, "y": {"signal": "cy"}}, - {"force": "collide", "radius": {"signal": "nodeRadius"}}, - {"force": "nbody", "strength": {"signal": "nodeCharge"}}, - {"force": "link", "links": "link-data", "distance": {"signal": "linkDistance"}} - ] - } - ] - }, - { - "type": "text", - "name": "labels", - "from": {"data": "nodes"}, - "zindex": 2, - "interactive": false, - "transform" : [ - {"type": "formula", "as": "y", "expr": "datum.y"} - ], - "encode": { - "enter": { - "fill": {"value": "black"}, - "align": {"value": "center"}, - "baseline": {"value": "middle"}, - "fontSize": {"value":15}, - "text": {"field": "datum.label"} + "type": "formula", + "as": "y", + "expr": "datum.datum.target.y - nodeRadius*sin(datum.tan)" }, - "update": { - "x": {"field": "x"}, - "y": {"field": "y"} - } - } - }, - { - "type": "path", - "from": {"data": "link-data"}, - "interactive": false, - "encode": { - "update": { - "stroke": {"value": "#ccc"}, - "strokeWidth": {"value": 0.5} - } - }, - "transform": [ { - "type": "linkpath", - "require": {"signal": "force"}, - "shape": "line", - "sourceX": "datum.source.x", "sourceY": "datum.source.y", - "targetX": "datum.target.x", "targetY": "datum.target.y" - } - ] + "type": "formula", + "as": "x", + "expr": "datum.datum.target.x - nodeRadius*cos(datum.tan)" + } + ] } - ], + ], - "data": [ - { - "name": "node-data", - "values": [ - {"id": 0, "label": "Zero"}, - {"id": 1, "label": "One"}, - {"id": 2, "label": "Two"}, - {"id": 3, "label": "Three"}, - {"id": 4, "label": "Four"}, - {"id": 5, "label": "Five"}, - {"id": 6, "label": "Six"} - ] - }, - { - "name": "link-data", - "values": [ - {"id": 1, "source": 0, "target": 1}, - {"id": 2, "source": 0, "target": 2}, - {"id": 3, "source": 0, "target": 3}, - {"id": 4, "source": 0, "target": 4}, - {"id": 5, "source": 0, "target": 5}, - {"id": 6, "source": 0, "target": 6} - ] - } + "data": [ + { + "name": "node-data", + "values": [ + {"id": 4, "label": "Zero", "group": "A", "inputX": 2, "inputY": 5, "interactionX": null, "interactionY": null}, + {"id": 1, "label": "One", "group": "A", "inputX": 2, "inputY": 10, "interactionX": null, "interactionY": null}, + {"id": 2, "label": "Two", "group": "B", "inputX": 2, "inputY": 5, "interactionX": null, "interactionY": null}, + {"id": 3, "label": "Three", "group": "B", "inputX": 2, "inputY": 3, "interactionX": null, "interactionY": null}, + {"id": 0, "label": "Four", "group": "C", "inputX": 1, "inputY": 5, "interactionX": null, "interactionY": null}, + {"id": 5, "label": "Five", "group": "C", "inputX": 2, "inputY": 5, "interactionX": null, "interactionY": null}, + {"id": 6, "label": "Six", "group": "C", "inputX": 2, "inputY": 5, "interactionX": null, "interactionY": null} + ], + "on": [ + {"trigger": "fix.length == 2", "modify": "node.datum", "values": "{interactionX: fix[0], interactionY: fix[1]}"}, + {"trigger": "reset", "modify": "reset", "values": "{interactionX: null, interactionY: null}"} ] - } - \ No newline at end of file + }, + { + "name": "link-data", + "values": [ + {"id": 1, "source": 0, "target": 1, "group": "A"}, + {"id": 2, "source": 4, "target": 2, "group": "C"}, + {"id": 3, "source": 3, "target": 0, "group": "A"}, + {"id": 4, "source": 2, "target": 5, "group": "C"}, + {"id": 5, "source": 0, "target": 4, "group": "B"}, + {"id": 6, "source": 5, "target": 0, "group": "B"} + ] + } + ] +} \ No newline at end of file diff --git a/test/test_visuals/test_plots.py b/test/test_visuals/test_plots.py index 1258cfba2..e9e7e92d2 100644 --- a/test/test_visuals/test_plots.py +++ b/test/test_visuals/test_plots.py @@ -2,10 +2,13 @@ import pandas as pd import xarray as xr import numpy as np +import networkx as nx import json import torch +import random from pathlib import Path +from itertools import chain from pyciemss.visuals import plots, vega from pyciemss.utils import get_tspan @@ -389,3 +392,75 @@ def create_fake_data(): self.assertTrue( all(mesh["__count"].isin(mesh_data[2].ravel())), "Unexpected count found" ) + + +class TestGraph(unittest.TestCase): + def setUp(self): + def rand_attributions(): + possible = "ABCD" + return random.sample(possible, random.randint(1, len(possible))) + + def rand_label(): + possible = "TUVWXYZ" + return random.randint(1, 10) + return random.sample(possible, 1)[0] + + self.g = nx.generators.barabasi_albert_graph(5, 3) + node_properties = { + n: {"attribution": rand_attributions(), "label": rand_label()} + for n in self.g.nodes() + } + + edge_attributions = { + e: {"attribution": rand_attributions()} for e in self.g.edges() + } + + nx.set_node_attributes(self.g, node_properties) + nx.set_edge_attributes(self.g, edge_attributions) + + def test_multigraph(self): + uncollapsed = plots.attributed_graph(self.g) + nodes = vega.find_named(uncollapsed["data"], "node-data")["values"] + edges = vega.find_named(uncollapsed["data"], "link-data")["values"] + self.assertEqual(len(self.g.nodes), len(nodes), "Nodes issue in conversion") + self.assertEqual(len(self.g.edges), len(edges), "Edges issue in conversion") + + all_attributions = set( + chain(*nx.get_node_attributes(self.g, "attribution").values()) + ) + nx.set_node_attributes(self.g, {0: {"attribution": all_attributions}}) + collapsed = plots.attributed_graph(self.g, collapse_all=True) + nodes = vega.find_named(collapsed["data"], "node-data")["values"] + edges = vega.find_named(collapsed["data"], "link-data")["values"] + self.assertEqual( + len(self.g.nodes), len(nodes), "Nodes issue in conversion (collapse-case)" + ) + self.assertEqual( + len(self.g.edges), len(edges), "Edges issue in conversion (collapse-case)" + ) + self.assertEqual( + [["*all*"]], + [n["attribution"] for n in nodes if n["label"] == 0], + "All tag not found as expected", + ) + + def test_springgraph(self): + schema = plots.spring_force_graph(self.g, node_labels="label") + nodes = vega.find_named(schema["data"], "node-data")["values"] + edges = vega.find_named(schema["data"], "link-data")["values"] + self.assertEqual(len(self.g.nodes), len(nodes), "Nodes issue in conversion") + self.assertEqual(len(self.g.edges), len(edges), "Edges issue in conversion") + + def test_provided_layout(self): + pos = nx.fruchterman_reingold_layout(self.g) + schema = plots.spring_force_graph(self.g, node_labels="label", layout=pos) + + nodes = vega.find_named(schema["data"], "node-data")["values"] + edges = vega.find_named(schema["data"], "link-data")["values"] + self.assertEqual(len(self.g.nodes), len(nodes), "Nodes issue in conversion") + self.assertEqual(len(self.g.edges), len(edges), "Edges issue in conversion") + + for id, (x, y) in pos.items(): + n = [n for n in nodes if n["label"] == id][0] + self.assertEqual(n["inputX"], x, f"Layout lost for {id}") + self.assertEqual(n["inputY"], y, f"Layout lost for {id}")