This uses a simple grid search to find good force-directed graph layout parameters by measuring the average readability metric of each layout using Greadability.js.
See also:
license: gpl-3.0 | |
height: 600 | |
scrolling: no | |
border: yes | |
This uses a simple grid search to find good force-directed graph layout parameters by measuring the average readability metric of each layout using Greadability.js.
See also:
{"category": "subway", "gragnostics": {"bridge": 0.6035502958579881, "star": 0.03423499577345731, "disconnected": 0.0, "links": 182, "density": 0.012669683257918552, "isolation": 0.0, "line": 0.08235294117647059, "constricted": 0.5892857142857143, "tree": 0.9981693367682378, "nodes": 170}, "nodes": [{"y": "13.424722", "x": "52.487222", "shape": "0", "id": 0}, {"y": "13.386111", "x": "52.478056", "shape": "0", "id": 1}, {"y": "13.375278", "x": "52.498333", "shape": "0", "id": 2}, {"y": "13.403611", "x": "52.511111", "shape": "0", "id": 3}, {"y": "13.343056", "x": "52.501944", "shape": "0", "id": 4}, {"y": "13.390556", "x": "52.505833", "shape": "0", "id": 5}, {"y": "13.247778", "x": "52.538056", "shape": "0", "id": 6}, {"y": "13.314444", "x": "52.473333", "shape": "0", "id": 7}, {"y": "13.295833", "x": "52.464167", "shape": "0", "id": 8}, {"y": "13.293611", "x": "52.534444", "shape": "0", "id": 9}, {"y": "13.232778", "x": "52.538889", "shape": "0", "id": 10}, {"y": "13.3825", "x": "52.531111", "shape": "0", "id": 11}, {"y": "13.393056", "x": "52.542222", "shape": "0", "id": 12}, {"y": "13.412222", "x": "52.516111", "shape": "0", "id": 13}, {"y": "13.575", "x": "52.5125", "shape": "0", "id": 14}, {"y": "13.464444", "x": "52.514722", "shape": "0", "id": 15}, {"y": "13.34", "x": "52.488611", "shape": "0", "id": 16}, {"y": "13.428056", "x": "52.473611", "shape": "0", "id": 17}, {"y": "13.441667", "x": "52.500833", "shape": "0", "id": 18}, {"y": "13.344", "x": "52.536", "shape": "0", "id": 19}, {"y": "13.396667", "x": "52.5375", "shape": "0", "id": 20}, {"y": "13.421944", "x": "52.492778", "shape": "0", "id": 21}, {"y": "13.483889", "x": "52.423333", "shape": "0", "id": 22}, {"y": "13.308611", "x": "52.466944", "shape": "0", "id": 23}, {"y": "13.396667", "x": "52.513056", "shape": "0", "id": 24}, {"y": "13.475", "x": "52.423", "shape": "0", "id": 25}, {"y": "13.2625", "x": "52.537222", "shape": "0", "id": 26}, {"y": "13.367778", "x": "52.492222", "shape": "0", "id": 27}, {"y": "13.415833", "x": "52.510278", "shape": "0", "id": 28}, {"y": "13.324444", "x": "52.460833", "shape": "0", "id": 29}, {"y": "13.439167", "x": "52.476389", "shape": "0", "id": 30}, {"y": "13.336667", "x": "52.500556", "shape": "0", "id": 31}, {"y": "13.413333", "x": "52.559444", "shape": "0", "id": 32}, {"y": "13.321667", "x": "52.512778", "shape": "0", "id": 33}, {"y": "13.384722", "x": "52.46", "shape": "0", "id": 34}, {"y": "13.3875", "x": "52.439722", "shape": "0", "id": 35}, {"y": "13.329", "x": "52.578", "shape": "0", "id": 36}, {"y": "13.283611", "x": "52.589444", "shape": "0", "id": 37}, {"y": "13.302778", "x": "52.571111", "shape": "0", "id": 38}, {"y": "13.412222", "x": "52.541944", "shape": "0", "id": 39}, {"y": "13.486389", "x": "52.5125", "shape": "0", "id": 40}, {"y": "13.386944", "x": "52.520278", "shape": "0", "id": 41}, {"y": "13.431", "x": "52.468", "shape": "0", "id": 42}, {"y": "13.360278", "x": "52.490556", "shape": "0", "id": 43}, {"y": "13.449", "x": "52.506", "shape": "0", "id": 44}, {"y": "13.349167", "x": "52.574167", "shape": "0", "id": 45}, {"y": "13.324722", "x": "52.494167", "shape": "0", "id": 46}, {"y": "13.38535", "x": "52.470756", "shape": "0", "id": 47}, {"y": "13.328611", "x": "52.4775", "shape": "0", "id": 48}, {"y": "13.3125", "x": "52.480278", "shape": "0", "id": 49}, {"y": "13.590556", "x": "52.528611", "shape": "0", "id": 50}, {"y": "13.331111", "x": "52.496667", "shape": "0", "id": 51}, {"y": "13.434167", "x": "52.481667", "shape": "0", "id": 52}, {"y": "13.321", "x": "52.456", "shape": "0", "id": 53}, {"y": "13.430833", "x": "52.518056", "shape": "0", "id": 54}, {"y": "13.241111", "x": "52.443333", "shape": "0", "id": 55}, {"y": "13.286389", "x": "52.536667", "shape": "0", "id": 56}, {"y": "13.343889", "x": "52.478611", "shape": "0", "id": 57}, {"y": "13.391111", "x": "52.497778", "shape": "0", "id": 58}, {"y": "13.410833", "x": "52.503611", "shape": "0", "id": 59}, {"y": "13.388611", "x": "52.494444", "shape": "0", "id": 60}, {"y": "13.241389", "x": "52.525556", "shape": "0", "id": 61}, {"y": "13.384444", "x": "52.453889", "shape": "0", "id": 62}, {"y": "13.463", "x": "52.425", "shape": "0", "id": 63}, {"y": "13.305", "x": "52.526389", "shape": "0", "id": 64}, {"y": "13.339167", "x": "52.575278", "shape": "0", "id": 65}, {"y": "13.363889", "x": "52.564722", "shape": "0", "id": 66}, {"y": "13.3275", "x": "52.563333", "shape": "0", "id": 67}, {"y": "13.328333", "x": "52.466111", "shape": "0", "id": 68}, {"y": "13.310556", "x": "52.511944", "shape": "0", "id": 69}, {"y": "13.445", "x": "52.516667", "shape": "0", "id": 70}, {"y": "13.3675", "x": "52.551667", "shape": "0", "id": 71}, {"y": "13.341389", "x": "52.532222", "shape": "0", "id": 72}, {"y": "13.350278", "x": "52.489444", "shape": "0", "id": 73}, {"y": "13.341944", "x": "52.483056", "shape": "0", "id": 74}, {"y": "13.273056", "x": "52.536667", "shape": "0", "id": 75}, {"y": "13.445", "x": "52.463", "shape": "0", "id": 76}, {"y": "13.282222", "x": "52.51", "shape": "0", "id": 77}, {"y": "13.309444", "x": "52.494167", "shape": "0", "id": 78}, {"y": "13.546389", "x": "52.499722", "shape": "0", "id": 79}, {"y": "13.418", "x": "52.515", "shape": "0", "id": 80}, {"y": "13.328611", "x": "52.471944", "shape": "0", "id": 81}, {"y": "13.495278", "x": "52.416111", "shape": "0", "id": 82}, {"y": "13.385833", "x": "52.485556", "shape": "0", "id": 83}, {"y": "13.200278", "x": "52.535", "shape": "0", "id": 84}, {"y": "13.281944", "x": "52.451111", "shape": "0", "id": 85}, {"y": "13.304722", "x": "52.511389", "shape": "0", "id": 86}, {"y": "13.341389", "x": "52.525833", "shape": "0", "id": 87}, {"y": "13.410278", "x": "52.528333", "shape": "0", "id": 88}, {"y": "13.4125", "x": "52.532222", "shape": "0", "id": 89}, {"y": "13.290556", "x": "52.581667", "shape": "0", "id": 90}, {"y": "13.253333", "x": "52.450278", "shape": "0", "id": 91}, {"y": "13.411944", "x": "52.521389", "shape": "0", "id": 92}, {"y": "13.347", "x": "52.548", "shape": "0", "id": 93}, {"y": "13.384722", "x": "52.511667", "shape": "0", "id": 94}, {"y": "13.428", "x": "52.499", "shape": "0", "id": 95}, {"y": "13.261111", "x": "52.515556", "shape": "0", "id": 96}, {"y": "13.406111", "x": "52.498333", "shape": "0", "id": 97}, {"y": "13.359167", "x": "52.546667", "shape": "0", "id": 98}, {"y": "13.360556", "x": "52.571111", "shape": "0", "id": 99}, {"y": "13.342778", "x": "52.496111", "shape": "0", "id": 100}, {"y": "13.453", "x": "52.429", "shape": "0", "id": 101}, {"y": "13.382778", "x": "52.499167", "shape": "0", "id": 102}, {"y": "13.633889", "x": "52.538333", "shape": "0", "id": 103}, {"y": "13.361944", "x": "52.5", "shape": "0", "id": 104}, {"y": "13.522778", "x": "52.498056", "shape": "0", "id": 105}, {"y": "13.407778", "x": "52.489444", "shape": "0", "id": 106}, {"y": "13.618333", "x": "52.538889", "shape": "0", "id": 107}, {"y": "13.330833", "x": "52.491944", "shape": "0", "id": 108}, {"y": "13.385556", "x": "52.465833", "shape": "0", "id": 109}, {"y": "13.596389", "x": "52.533889", "shape": "0", "id": 110}, {"y": "13.366111", "x": "52.5425", "shape": "0", "id": 111}, {"y": "13.206389", "x": "52.538889", "shape": "0", "id": 112}, {"y": "13.312778", "x": "52.566944", "shape": "0", "id": 113}, {"y": "13.353889", "x": "52.499167", "shape": "0", "id": 114}, {"y": "13.408889", "x": "52.511944", "shape": "0", "id": 115}, {"y": "13.25", "x": "52.517222", "shape": "0", "id": 116}, {"y": "13.387222", "x": "52.525556", "shape": "0", "id": 117}, {"y": "13.606389", "x": "52.536667", "shape": "0", "id": 118}, {"y": "13.474722", "x": "52.514167", "shape": "0", "id": 119}, {"y": "13.401", "x": "52.53", "shape": "0", "id": 120}, {"y": "13.411111", "x": "52.566667", "shape": "0", "id": 121}, {"y": "13.375", "x": "52.503889", "shape": "0", "id": 122}, {"y": "13.389444", "x": "52.548611", "shape": "0", "id": 123}, {"y": "13.334722", "x": "52.560278", "shape": "0", "id": 124}, {"y": "13.3325", "x": "52.507222", "shape": "0", "id": 125}, {"y": "13.373611", "x": "52.556944", "shape": "0", "id": 126}, {"y": "13.421111", "x": "52.520556", "shape": "0", "id": 127}, {"y": "13.296111", "x": "52.575833", "shape": "0", "id": 128}, {"y": "13.45", "x": "52.445", "shape": "0", "id": 129}, {"y": "13.377222", "x": "52.535278", "shape": "0", "id": 130}, {"y": "13.441667", "x": "52.469167", "shape": "0", "id": 131}, {"y": "13.332778", "x": "52.504167", "shape": "0", "id": 132}, {"y": "13.342222", "x": "52.517778", "shape": "0", "id": 133}, {"y": "13.389167", "x": "52.514722", "shape": "0", "id": 134}, {"y": "13.331111", "x": "52.4875", "shape": "0", "id": 135}, {"y": "13.454167", "x": "52.515833", "shape": "0", "id": 136}, {"y": "13.306944", "x": "52.506389", "shape": "0", "id": 137}, {"y": "13.425278", "x": "52.480278", "shape": "0", "id": 138}, {"y": "13.389444", "x": "52.512222", "shape": "0", "id": 139}, {"y": "13.396111", "x": "52.491389", "shape": "0", "id": 140}, {"y": "13.325556", "x": "52.5025", "shape": "0", "id": 141}, {"y": "13.307222", "x": "52.5", "shape": "0", "id": 142}, {"y": "13.449167", "x": "52.4525", "shape": "0", "id": 143}, {"y": "13.336667", "x": "52.595833", "shape": "0", "id": 144}, {"y": "13.560556", "x": "52.505556", "shape": "0", "id": 145}, {"y": "13.413889", "x": "52.549444", "shape": "0", "id": 146}, {"y": "13.299", "x": "52.53", "shape": "0", "id": 147}, {"y": "13.386", "x": "52.446", "shape": "0", "id": 148}, {"y": "13.314444", "x": "52.490556", "shape": "0", "id": 149}, {"y": "13.306667", "x": "52.516944", "shape": "0", "id": 150}, {"y": "13.496389", "x": "52.510556", "shape": "0", "id": 151}, {"y": "13.217778", "x": "52.537778", "shape": "0", "id": 152}, {"y": "13.448333", "x": "52.437778", "shape": "0", "id": 153}, {"y": "13.341", "x": "52.557", "shape": "0", "id": 154}, {"y": "13.418056", "x": "52.499167", "shape": "0", "id": 155}, {"y": "13.3675", "x": "52.494167", "shape": "0", "id": 156}, {"y": "13.321667", "x": "52.486389", "shape": "0", "id": 157}, {"y": "13.273056", "x": "52.509722", "shape": "0", "id": 158}, {"y": "13.375833", "x": "52.509444", "shape": "0", "id": 159}, {"y": "13.381667", "x": "52.552222", "shape": "0", "id": 160}, {"y": "13.405", "x": "52.525", "shape": "0", "id": 161}, {"y": "13.350278", "x": "52.5425", "shape": "0", "id": 162}, {"y": "13.588889", "x": "52.521111", "shape": "0", "id": 163}, {"y": "13.296667", "x": "52.510833", "shape": "0", "id": 164}, {"y": "13.512778", "x": "52.506111", "shape": "0", "id": 165}, {"y": "13.289722", "x": "52.4575", "shape": "0", "id": 166}, {"y": "13.266111", "x": "52.450278", "shape": "0", "id": 167}, {"y": "13.325833", "x": "52.588333", "shape": "0", "id": 168}, {"y": "13.370278", "x": "52.54", "shape": "0", "id": 169}], "name": "subway/Berlin", "links": [{"source": 0, "weight": "1.0", "target": 138, "key": "1"}, {"source": 0, "weight": "1.0", "target": 138, "key": "0"}, {"source": 0, "weight": "1.0", "target": 106, "key": "1"}, {"source": 0, "weight": "1.0", "target": 106, "key": "0"}, {"source": 0, "weight": "1.0", "target": 21, "key": "1"}, {"source": 0, "weight": "1.0", "target": 21, "key": "0"}, {"source": 0, "weight": "1.0", "target": 52, "key": "1"}, {"source": 0, "weight": "1.0", "target": 52, "key": "0"}, {"source": 1, "weight": "1.0", "target": 47, "key": "1"}, {"source": 1, "weight": "1.0", "target": 47, "key": "0"}, {"source": 1, "weight": "1.0", "target": 83, "key": "1"}, {"source": 1, "weight": "1.0", "target": 83, "key": "0"}, {"source": 2, "weight": "1.0", "target": 122, "key": "1"}, {"source": 2, "weight": "1.0", "target": 122, "key": "0"}, {"source": 2, "weight": "1.0", "target": 102, "key": "1"}, {"source": 2, "weight": "1.0", "target": 102, "key": "0"}, {"source": 2, "weight": "1.0", "target": 156, "key": "1"}, {"source": 2, "weight": "1.0", "target": 156, "key": "0"}, {"source": 2, "weight": "1.0", "target": 104, "key": "1"}, {"source": 2, "weight": "1.0", "target": 104, "key": "0"}, {"source": 3, "weight": "1.0", "target": 115, "key": "1"}, {"source": 3, "weight": "1.0", "target": 115, "key": "0"}, {"source": 3, "weight": "1.0", "target": 24, "key": "1"}, {"source": 3, "weight": "1.0", "target": 24, "key": "0"}, {"source": 4, "weight": "1.0", "target": 31, "key": "1"}, {"source": 4, "weight": "1.0", "target": 31, "key": "0"}, {"source": 4, "weight": "1.0", "target": 125, "key": "1"}, {"source": 4, "weight": "1.0", "target": 125, "key": "0"}, {"source": 4, "weight": "1.0", "target": 132, "key": "1"}, {"source": 4, "weight": "1.0", "target": 132, "key": "0"}, {"source": 4, "weight": "1.0", "target": 114, "key": "1"}, {"source": 4, "weight": "1.0", "target": 114, "key": "0"}, {"source": 5, "weight": "1.0", "target": 58, "key": "1"}, {"source": 5, "weight": "1.0", "target": 58, "key": "0"}, {"source": 5, "weight": "1.0", "target": 139, "key": "1"}, {"source": 5, "weight": "1.0", "target": 139, "key": "0"}, {"source": 6, "weight": "1.0", "target": 26, "key": "1"}, {"source": 6, "weight": "1.0", "target": 26, "key": "0"}, {"source": 6, "weight": "1.0", "target": 10, "key": "1"}, {"source": 6, "weight": "1.0", "target": 10, "key": "0"}, {"source": 7, "weight": "1.0", "target": 49, "key": "1"}, {"source": 7, "weight": "1.0", "target": 49, "key": "0"}, {"source": 7, "weight": "1.0", "target": 23, "key": "1"}, {"source": 7, "weight": "1.0", "target": 23, "key": "0"}, {"source": 8, "weight": "1.0", "target": 166, "key": "1"}, {"source": 8, "weight": "1.0", "target": 166, "key": "0"}, {"source": 8, "weight": "1.0", "target": 23, "key": "1"}, {"source": 8, "weight": "1.0", "target": 23, "key": "0"}, {"source": 9, "weight": "1.0", "target": 56, "key": "1"}, {"source": 9, "weight": "1.0", "target": 56, "key": "0"}, {"source": 9, "weight": "1.0", "target": 147, "key": "1"}, {"source": 9, "weight": "1.0", "target": 147, "key": "0"}, {"source": 10, "weight": "1.0", "target": 152, "key": "1"}, {"source": 10, "weight": "1.0", "target": 152, "key": "0"}, {"source": 11, "weight": "1.0", "target": 117, "key": "1"}, {"source": 11, "weight": "1.0", "target": 117, "key": "0"}, {"source": 11, "weight": "1.0", "target": 130, "key": "1"}, {"source": 11, "weight": "1.0", "target": 130, "key": "0"}, {"source": 12, "weight": "1.0", "target": 20, "key": "1"}, {"source": 12, "weight": "1.0", "target": 20, "key": "0"}, {"source": 12, "weight": "1.0", "target": 123, "key": "1"}, {"source": 12, "weight": "1.0", "target": 123, "key": "0"}, {"source": 13, "weight": "1.0", "target": 115, "key": "1"}, {"source": 13, "weight": "1.0", "target": 115, "key": "0"}, {"source": 13, "weight": "1.0", "target": 92, "key": "1"}, {"source": 13, "weight": "1.0", "target": 92, "key": "0"}, {"source": 14, "weight": "1.0", "target": 145, "key": "1"}, {"source": 14, "weight": "1.0", "target": 145, "key": "0"}, {"source": 14, "weight": "1.0", "target": 163, "key": "1"}, {"source": 14, "weight": "1.0", "target": 163, "key": "0"}, {"source": 15, "weight": "1.0", "target": 136, "key": "1"}, {"source": 15, "weight": "1.0", "target": 136, "key": "0"}, {"source": 15, "weight": "1.0", "target": 119, "key": "1"}, {"source": 15, "weight": "1.0", "target": 119, "key": "0"}, {"source": 16, "weight": "1.0", "target": 73, "key": "1"}, {"source": 16, "weight": "1.0", "target": 73, "key": "0"}, {"source": 16, "weight": "1.0", "target": 74, "key": "1"}, {"source": 16, "weight": "1.0", "target": 74, "key": "0"}, {"source": 16, "weight": "1.0", "target": 100, "key": "1"}, {"source": 16, "weight": "1.0", "target": 100, "key": "0"}, {"source": 16, "weight": "1.0", "target": 135, "key": "1"}, {"source": 16, "weight": "1.0", "target": 135, "key": "0"}, {"source": 17, "weight": "1.0", "target": 138, "key": "1"}, {"source": 17, "weight": "1.0", "target": 138, "key": "0"}, {"source": 17, "weight": "1.0", "target": 42, "key": "1"}, {"source": 17, "weight": "1.0", "target": 42, "key": "0"}, {"source": 18, "weight": "1.0", "target": 95, "key": "1"}, {"source": 18, "weight": "1.0", "target": 95, "key": "0"}, {"source": 18, "weight": "1.0", "target": 44, "key": "1"}, {"source": 18, "weight": "1.0", "target": 44, "key": "0"}, {"source": 19, "weight": "1.0", "target": 72, "key": "1"}, {"source": 19, "weight": "1.0", "target": 72, "key": "0"}, {"source": 19, "weight": "1.0", "target": 162, "key": "1"}, {"source": 19, "weight": "1.0", "target": 162, "key": "0"}, {"source": 20, "weight": "1.0", "target": 120, "key": "1"}, {"source": 20, "weight": "1.0", "target": 120, "key": "0"}, {"source": 21, "weight": "1.0", "target": 155, "key": "1"}, {"source": 21, "weight": "1.0", "target": 155, "key": "0"}, {"source": 22, "weight": "1.0", "target": 82, "key": "1"}, {"source": 22, "weight": "1.0", "target": 82, "key": "0"}, {"source": 22, "weight": "1.0", "target": 25, "key": "1"}, {"source": 22, "weight": "1.0", "target": 25, "key": "0"}, {"source": 24, "weight": "1.0", "target": 139, "key": "1"}, {"source": 24, "weight": "1.0", "target": 139, "key": "0"}, {"source": 25, "weight": "1.0", "target": 63, "key": "1"}, {"source": 25, "weight": "1.0", "target": 63, "key": "0"}, {"source": 26, "weight": "1.0", "target": 75, "key": "1"}, {"source": 26, "weight": "1.0", "target": 75, "key": "0"}, {"source": 27, "weight": "1.0", "target": 102, "key": "1"}, {"source": 27, "weight": "1.0", "target": 102, "key": "0"}, {"source": 27, "weight": "1.0", "target": 43, "key": "1"}, {"source": 27, "weight": "1.0", "target": 43, "key": "0"}, {"source": 28, "weight": "1.0", "target": 80, "key": "1"}, {"source": 28, "weight": "1.0", "target": 80, "key": "0"}, {"source": 28, "weight": "1.0", "target": 59, "key": "1"}, {"source": 28, "weight": "1.0", "target": 59, "key": "0"}, {"source": 29, "weight": "1.0", "target": 68, "key": "1"}, {"source": 29, "weight": "1.0", "target": 68, "key": "0"}, {"source": 29, "weight": "1.0", "target": 53, "key": "1"}, {"source": 29, "weight": "1.0", "target": 53, "key": "0"}, {"source": 30, "weight": "1.0", "target": 131, "key": "1"}, {"source": 30, "weight": "1.0", "target": 131, "key": "0"}, {"source": 30, "weight": "1.0", "target": 52, "key": "1"}, {"source": 30, "weight": "1.0", "target": 52, "key": "0"}, {"source": 31, "weight": "1.0", "target": 51, "key": "1"}, {"source": 31, "weight": "1.0", "target": 51, "key": "0"}, {"source": 32, "weight": "1.0", "target": 121, "key": "1"}, {"source": 32, "weight": "1.0", "target": 121, "key": "0"}, {"source": 32, "weight": "1.0", "target": 146, "key": "1"}, {"source": 32, "weight": "1.0", "target": 146, "key": "0"}, {"source": 33, "weight": "1.0", "target": 69, "key": "1"}, {"source": 33, "weight": "1.0", "target": 69, "key": "0"}, {"source": 33, "weight": "1.0", "target": 125, "key": "1"}, {"source": 33, "weight": "1.0", "target": 125, "key": "0"}, {"source": 34, "weight": "1.0", "target": 62, "key": "1"}, {"source": 34, "weight": "1.0", "target": 62, "key": "0"}, {"source": 34, "weight": "1.0", "target": 109, "key": "1"}, {"source": 34, "weight": "1.0", "target": 109, "key": "0"}, {"source": 35, "weight": "1.0", "target": 148, "key": "1"}, {"source": 35, "weight": "1.0", "target": 148, "key": "0"}, {"source": 36, "weight": "1.0", "target": 65, "key": "1"}, {"source": 36, "weight": "1.0", "target": 65, "key": "0"}, {"source": 36, "weight": "1.0", "target": 168, "key": "1"}, {"source": 36, "weight": "1.0", "target": 168, "key": "0"}, {"source": 37, "weight": "1.0", "target": 90, "key": "1"}, {"source": 37, "weight": "1.0", "target": 90, "key": "0"}, {"source": 38, "weight": "1.0", "target": 128, "key": "1"}, {"source": 38, "weight": "1.0", "target": 128, "key": "0"}, {"source": 38, "weight": "1.0", "target": 113, "key": "1"}, {"source": 38, "weight": "1.0", "target": 113, "key": "0"}, {"source": 39, "weight": "1.0", "target": 89, "key": "1"}, {"source": 39, "weight": "1.0", "target": 89, "key": "0"}, {"source": 39, "weight": "1.0", "target": 146, "key": "1"}, {"source": 39, "weight": "1.0", "target": 146, "key": "0"}, {"source": 40, "weight": "1.0", "target": 119, "key": "1"}, {"source": 40, "weight": "1.0", "target": 119, "key": "0"}, {"source": 40, "weight": "1.0", "target": 151, "key": "1"}, {"source": 40, "weight": "1.0", "target": 151, "key": "0"}, {"source": 41, "weight": "1.0", "target": 117, "key": "1"}, {"source": 41, "weight": "1.0", "target": 117, "key": "0"}, {"source": 41, "weight": "1.0", "target": 134, "key": "1"}, {"source": 41, "weight": "1.0", "target": 134, "key": "0"}, {"source": 43, "weight": "1.0", "target": 73, "key": "1"}, {"source": 43, "weight": "1.0", "target": 73, "key": "0"}, {"source": 45, "weight": "1.0", "target": 99, "key": "1"}, {"source": 45, "weight": "1.0", "target": 99, "key": "0"}, {"source": 45, "weight": "1.0", "target": 65, "key": "1"}, {"source": 45, "weight": "1.0", "target": 65, "key": "0"}, {"source": 46, "weight": "1.0", "target": 149, "key": "1"}, {"source": 46, "weight": "1.0", "target": 149, "key": "0"}, {"source": 46, "weight": "1.0", "target": 51, "key": "1"}, {"source": 46, "weight": "1.0", "target": 51, "key": "0"}, {"source": 47, "weight": "1.0", "target": 109, "key": "1"}, {"source": 47, "weight": "1.0", "target": 109, "key": "0"}, {"source": 48, "weight": "1.0", "target": 81, "key": "1"}, {"source": 48, "weight": "1.0", "target": 81, "key": "0"}, {"source": 48, "weight": "1.0", "target": 135, "key": "1"}, {"source": 48, "weight": "1.0", "target": 135, "key": "0"}, {"source": 49, "weight": "1.0", "target": 149, "key": "1"}, {"source": 49, "weight": "1.0", "target": 149, "key": "0"}, {"source": 50, "weight": "1.0", "target": 110, "key": "1"}, {"source": 50, "weight": "1.0", "target": 110, "key": "0"}, {"source": 50, "weight": "1.0", "target": 163, "key": "1"}, {"source": 50, "weight": "1.0", "target": 163, "key": "0"}, {"source": 51, "weight": "1.0", "target": 132, "key": "1"}, {"source": 51, "weight": "1.0", "target": 132, "key": "0"}, {"source": 51, "weight": "1.0", "target": 108, "key": "1"}, {"source": 51, "weight": "1.0", "target": 108, "key": "0"}, {"source": 54, "weight": "1.0", "target": 70, "key": "1"}, {"source": 54, "weight": "1.0", "target": 70, "key": "0"}, {"source": 54, "weight": "1.0", "target": 127, "key": "1"}, {"source": 54, "weight": "1.0", "target": 127, "key": "0"}, {"source": 55, "weight": "1.0", "target": 91, "key": "1"}, {"source": 55, "weight": "1.0", "target": 91, "key": "0"}, {"source": 56, "weight": "1.0", "target": 75, "key": "1"}, {"source": 56, "weight": "1.0", "target": 75, "key": "0"}, {"source": 57, "weight": "1.0", "target": 74, "key": "1"}, {"source": 57, "weight": "1.0", "target": 74, "key": "0"}, {"source": 58, "weight": "1.0", "target": 60, "key": "1"}, {"source": 58, "weight": "1.0", "target": 60, "key": "0"}, {"source": 58, "weight": "1.0", "target": 102, "key": "1"}, {"source": 58, "weight": "1.0", "target": 102, "key": "0"}, {"source": 58, "weight": "1.0", "target": 97, "key": "1"}, {"source": 58, "weight": "1.0", "target": 97, "key": "0"}, {"source": 59, "weight": "1.0", "target": 155, "key": "1"}, {"source": 59, "weight": "1.0", "target": 155, "key": "0"}, {"source": 60, "weight": "1.0", "target": 102, "key": "1"}, {"source": 60, "weight": "1.0", "target": 102, "key": "0"}, {"source": 60, "weight": "1.0", "target": 83, "key": "1"}, {"source": 60, "weight": "1.0", "target": 83, "key": "0"}, {"source": 60, "weight": "1.0", "target": 140, "key": "1"}, {"source": 60, "weight": "1.0", "target": 140, "key": "0"}, {"source": 61, "weight": "1.0", "target": 116, "key": "1"}, {"source": 61, "weight": "1.0", "target": 116, "key": "0"}, {"source": 62, "weight": "1.0", "target": 148, "key": "1"}, {"source": 62, "weight": "1.0", "target": 148, "key": "0"}, {"source": 63, "weight": "1.0", "target": 101, "key": "1"}, {"source": 63, "weight": "1.0", "target": 101, "key": "0"}, {"source": 64, "weight": "1.0", "target": 150, "key": "1"}, {"source": 64, "weight": "1.0", "target": 150, "key": "0"}, {"source": 64, "weight": "1.0", "target": 147, "key": "1"}, {"source": 64, "weight": "1.0", "target": 147, "key": "0"}, {"source": 66, "weight": "1.0", "target": 99, "key": "1"}, {"source": 66, "weight": "1.0", "target": 99, "key": "0"}, {"source": 66, "weight": "1.0", "target": 126, "key": "1"}, {"source": 66, "weight": "1.0", "target": 126, "key": "0"}, {"source": 67, "weight": "1.0", "target": 124, "key": "1"}, {"source": 67, "weight": "1.0", "target": 124, "key": "0"}, {"source": 67, "weight": "1.0", "target": 113, "key": "1"}, {"source": 67, "weight": "1.0", "target": 113, "key": "0"}, {"source": 68, "weight": "1.0", "target": 81, "key": "1"}, {"source": 68, "weight": "1.0", "target": 81, "key": "0"}, {"source": 69, "weight": "1.0", "target": 86, "key": "1"}, {"source": 69, "weight": "1.0", "target": 86, "key": "0"}, {"source": 70, "weight": "1.0", "target": 136, "key": "1"}, {"source": 70, "weight": "1.0", "target": 136, "key": "0"}, {"source": 71, "weight": "1.0", "target": 126, "key": "1"}, {"source": 71, "weight": "1.0", "target": 126, "key": "0"}, {"source": 71, "weight": "1.0", "target": 98, "key": "1"}, {"source": 71, "weight": "1.0", "target": 98, "key": "0"}, {"source": 72, "weight": "1.0", "target": 87, "key": "1"}, {"source": 72, "weight": "1.0", "target": 87, "key": "0"}, {"source": 76, "weight": "1.0", "target": 143, "key": "1"}, {"source": 76, "weight": "1.0", "target": 143, "key": "0"}, {"source": 76, "weight": "1.0", "target": 131, "key": "1"}, {"source": 76, "weight": "1.0", "target": 131, "key": "0"}, {"source": 77, "weight": "1.0", "target": 164, "key": "1"}, {"source": 77, "weight": "1.0", "target": 164, "key": "0"}, {"source": 77, "weight": "1.0", "target": 158, "key": "1"}, {"source": 77, "weight": "1.0", "target": 158, "key": "0"}, {"source": 78, "weight": "1.0", "target": 149, "key": "1"}, {"source": 78, "weight": "1.0", "target": 149, "key": "0"}, {"source": 78, "weight": "1.0", "target": 142, "key": "1"}, {"source": 78, "weight": "1.0", "target": 142, "key": "0"}, {"source": 79, "weight": "1.0", "target": 145, "key": "1"}, {"source": 79, "weight": "1.0", "target": 145, "key": "0"}, {"source": 79, "weight": "1.0", "target": 105, "key": "1"}, {"source": 79, "weight": "1.0", "target": 105, "key": "0"}, {"source": 80, "weight": "1.0", "target": 92, "key": "1"}, {"source": 80, "weight": "1.0", "target": 92, "key": "0"}, {"source": 84, "weight": "1.0", "target": 112, "key": "1"}, {"source": 84, "weight": "1.0", "target": 112, "key": "0"}, {"source": 85, "weight": "1.0", "target": 167, "key": "1"}, {"source": 85, "weight": "1.0", "target": 167, "key": "0"}, {"source": 85, "weight": "1.0", "target": 166, "key": "1"}, {"source": 85, "weight": "1.0", "target": 166, "key": "0"}, {"source": 86, "weight": "1.0", "target": 150, "key": "1"}, {"source": 86, "weight": "1.0", "target": 150, "key": "0"}, {"source": 86, "weight": "1.0", "target": 137, "key": "1"}, {"source": 86, "weight": "1.0", "target": 137, "key": "0"}, {"source": 86, "weight": "1.0", "target": 164, "key": "1"}, {"source": 86, "weight": "1.0", "target": 164, "key": "0"}, {"source": 87, "weight": "1.0", "target": 133, "key": "1"}, {"source": 87, "weight": "1.0", "target": 133, "key": "0"}, {"source": 88, "weight": "1.0", "target": 89, "key": "1"}, {"source": 88, "weight": "1.0", "target": 89, "key": "0"}, {"source": 88, "weight": "1.0", "target": 92, "key": "1"}, {"source": 88, "weight": "1.0", "target": 92, "key": "0"}, {"source": 90, "weight": "1.0", "target": 128, "key": "1"}, {"source": 90, "weight": "1.0", "target": 128, "key": "0"}, {"source": 91, "weight": "1.0", "target": 167, "key": "1"}, {"source": 91, "weight": "1.0", "target": 167, "key": "0"}, {"source": 92, "weight": "1.0", "target": 161, "key": "1"}, {"source": 92, "weight": "1.0", "target": 161, "key": "0"}, {"source": 92, "weight": "1.0", "target": 127, "key": "1"}, {"source": 92, "weight": "1.0", "target": 127, "key": "0"}, {"source": 93, "weight": "1.0", "target": 154, "key": "1"}, {"source": 93, "weight": "1.0", "target": 154, "key": "0"}, {"source": 93, "weight": "1.0", "target": 98, "key": "1"}, {"source": 93, "weight": "1.0", "target": 98, "key": "0"}, {"source": 94, "weight": "1.0", "target": 139, "key": "1"}, {"source": 94, "weight": "1.0", "target": 139, "key": "0"}, {"source": 94, "weight": "1.0", "target": 159, "key": "1"}, {"source": 94, "weight": "1.0", "target": 159, "key": "0"}, {"source": 95, "weight": "1.0", "target": 155, "key": "1"}, {"source": 95, "weight": "1.0", "target": 155, "key": "0"}, {"source": 96, "weight": "1.0", "target": 116, "key": "1"}, {"source": 96, "weight": "1.0", "target": 116, "key": "0"}, {"source": 96, "weight": "1.0", "target": 158, "key": "1"}, {"source": 96, "weight": "1.0", "target": 158, "key": "0"}, {"source": 97, "weight": "1.0", "target": 155, "key": "1"}, {"source": 97, "weight": "1.0", "target": 155, "key": "0"}, {"source": 98, "weight": "1.0", "target": 162, "key": "1"}, {"source": 98, "weight": "1.0", "target": 162, "key": "0"}, {"source": 98, "weight": "1.0", "target": 111, "key": "1"}, {"source": 98, "weight": "1.0", "target": 111, "key": "0"}, {"source": 100, "weight": "1.0", "target": 114, "key": "1"}, {"source": 100, "weight": "1.0", "target": 114, "key": "0"}, {"source": 101, "weight": "1.0", "target": 153, "key": "1"}, {"source": 101, "weight": "1.0", "target": 153, "key": "0"}, {"source": 103, "weight": "1.0", "target": 107, "key": "1"}, {"source": 103, "weight": "1.0", "target": 107, "key": "0"}, {"source": 104, "weight": "1.0", "target": 114, "key": "1"}, {"source": 104, "weight": "1.0", "target": 114, "key": "0"}, {"source": 105, "weight": "1.0", "target": 165, "key": "1"}, {"source": 105, "weight": "1.0", "target": 165, "key": "0"}, {"source": 106, "weight": "1.0", "target": 140, "key": "1"}, {"source": 106, "weight": "1.0", "target": 140, "key": "0"}, {"source": 107, "weight": "1.0", "target": 118, "key": "1"}, {"source": 107, "weight": "1.0", "target": 118, "key": "0"}, {"source": 108, "weight": "1.0", "target": 135, "key": "1"}, {"source": 108, "weight": "1.0", "target": 135, "key": "0"}, {"source": 110, "weight": "1.0", "target": 118, "key": "1"}, {"source": 110, "weight": "1.0", "target": 118, "key": "0"}, {"source": 111, "weight": "1.0", "target": 169, "key": "1"}, {"source": 111, "weight": "1.0", "target": 169, "key": "0"}, {"source": 112, "weight": "1.0", "target": 152, "key": "1"}, {"source": 112, "weight": "1.0", "target": 152, "key": "0"}, {"source": 114, "weight": "1.0", "target": 156, "key": "1"}, {"source": 114, "weight": "1.0", "target": 156, "key": "0"}, {"source": 120, "weight": "1.0", "target": 161, "key": "1"}, {"source": 120, "weight": "1.0", "target": 161, "key": "0"}, {"source": 122, "weight": "1.0", "target": 159, "key": "1"}, {"source": 122, "weight": "1.0", "target": 159, "key": "0"}, {"source": 123, "weight": "1.0", "target": 160, "key": "1"}, {"source": 123, "weight": "1.0", "target": 160, "key": "0"}, {"source": 124, "weight": "1.0", "target": 154, "key": "1"}, {"source": 124, "weight": "1.0", "target": 154, "key": "0"}, {"source": 125, "weight": "1.0", "target": 132, "key": "1"}, {"source": 125, "weight": "1.0", "target": 132, "key": "0"}, {"source": 125, "weight": "1.0", "target": 133, "key": "1"}, {"source": 125, "weight": "1.0", "target": 133, "key": "0"}, {"source": 126, "weight": "1.0", "target": 160, "key": "1"}, {"source": 126, "weight": "1.0", "target": 160, "key": "0"}, {"source": 129, "weight": "1.0", "target": 153, "key": "1"}, {"source": 129, "weight": "1.0", "target": 153, "key": "0"}, {"source": 129, "weight": "1.0", "target": 143, "key": "1"}, {"source": 129, "weight": "1.0", "target": 143, "key": "0"}, {"source": 130, "weight": "1.0", "target": 169, "key": "1"}, {"source": 130, "weight": "1.0", "target": 169, "key": "0"}, {"source": 132, "weight": "1.0", "target": 141, "key": "1"}, {"source": 132, "weight": "1.0", "target": 141, "key": "0"}, {"source": 134, "weight": "1.0", "target": 139, "key": "1"}, {"source": 134, "weight": "1.0", "target": 139, "key": "0"}, {"source": 135, "weight": "1.0", "target": 157, "key": "1"}, {"source": 135, "weight": "1.0", "target": 157, "key": "0"}, {"source": 137, "weight": "1.0", "target": 142, "key": "1"}, {"source": 137, "weight": "1.0", "target": 142, "key": "0"}, {"source": 144, "weight": "1.0", "target": 168, "key": "1"}, {"source": 144, "weight": "1.0", "target": 168, "key": "0"}, {"source": 149, "weight": "1.0", "target": 157, "key": "1"}, {"source": 149, "weight": "1.0", "target": 157, "key": "0"}, {"source": 151, "weight": "1.0", "target": 165, "key": "1"}, {"source": 151, "weight": "1.0", "target": 165, "key": "0"}]} |
(function (global, factory) { | |
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : | |
typeof define === 'function' && define.amd ? define(['exports'], factory) : | |
(factory((global.greadability = global.greadability || {}))); | |
}(this, (function (exports) { 'use strict'; | |
var greadability = function (nodes, links, id) { | |
var i, | |
j, | |
n = nodes.length, | |
m, | |
degree = new Array(nodes.length), | |
cMax, | |
idealAngle = 70, | |
dMax; | |
/* | |
* Tracks the global graph readability metrics. | |
*/ | |
var graphStats = { | |
crossing: 0, // Normalized link crossings | |
crossingAngle: 0, // Normalized average dev from 70 deg | |
angularResolutionMin: 0, // Normalized avg dev from ideal min angle | |
angularResolutionDev: 0, // Normalized avg dev from each link | |
}; | |
var getSumOfArray = function (numArray) { | |
var i = 0, n = numArray.length, sum = 0; | |
for (; i < n; ++i) sum += numArray[i]; | |
return sum; | |
}; | |
var initialize = function () { | |
var i, j, link; | |
var nodeById = {}; | |
// Filter out self loops | |
links = links.filter(function (l) { | |
return l.source !== l.target; | |
}); | |
m = links.length; | |
if (!id) { | |
id = function (d) { return d.index; }; | |
} | |
for (i = 0; i < n; ++i) { | |
nodes[i].index = i; | |
degree[i] = []; | |
nodeById[id(nodes[i], i, nodeById)] = nodes[i]; | |
} | |
// Make sure source and target are nodes and not indices. | |
for (i = 0; i < m; ++i) { | |
link = links[i]; | |
if (typeof link.source !== "object") link.source = nodeById[link.source]; | |
if (typeof link.target !== "object") link.target = nodeById[link.target]; | |
} | |
// Filter out duplicate links | |
var filteredLinks = []; | |
links.forEach(function (l) { | |
var s = l.source, t = l.target; | |
if (s.index > t.index) { | |
filteredLinks.push({source: t, target: s}); | |
} else { | |
filteredLinks.push({source: s, target: t}); | |
} | |
}); | |
links = filteredLinks; | |
links.sort(function (a, b) { | |
if (a.source.index < b.source.index) return -1; | |
if (a.source.index > b.source.index) return 1; | |
if (a.target.index < b.target.index) return -1; | |
if (a.target.index > b.target.index) return 1; | |
return 0; | |
}); | |
i = 1; | |
while (i < links.length) { | |
if (links[i-1].source.index === links[i].source.index && | |
links[i-1].target.index === links[i].target.index) { | |
links.splice(i, 1); | |
} | |
else ++i; | |
} | |
// Update length, if a duplicate was deleted. | |
m = links.length; | |
// Calculate degree. | |
for (i = 0; i < m; ++i) { | |
link = links[i]; | |
link.index = i; | |
degree[link.source.index].push(link); | |
degree[link.target.index].push(link); | |
}; | |
} | |
// Assume node.x and node.y are the coordinates | |
function direction (pi, pj, pk) { | |
var p1 = [pk[0] - pi[0], pk[1] - pi[1]]; | |
var p2 = [pj[0] - pi[0], pj[1] - pi[1]]; | |
return p1[0] * p2[1] - p2[0] * p1[1]; | |
} | |
// Is point k on the line segment formed by points i and j? | |
// Inclusive, so if pk == pi or pk == pj then return true. | |
function onSegment (pi, pj, pk) { | |
return Math.min(pi[0], pj[0]) <= pk[0] && | |
pk[0] <= Math.max(pi[0], pj[0]) && | |
Math.min(pi[1], pj[1]) <= pk[1] && | |
pk[1] <= Math.max(pi[1], pj[1]); | |
} | |
function linesCross (line1, line2) { | |
var d1, d2, d3, d4; | |
// CLRS 2nd ed. pg. 937 | |
d1 = direction(line2[0], line2[1], line1[0]); | |
d2 = direction(line2[0], line2[1], line1[1]); | |
d3 = direction(line1[0], line1[1], line2[0]); | |
d4 = direction(line1[0], line1[1], line2[1]); | |
if (((d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0)) && | |
((d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0))) { | |
return true; | |
} else if (d1 === 0 && onSegment(line2[0], line2[1], line1[0])) { | |
return true; | |
} else if (d2 === 0 && onSegment(line2[0], line2[1], line1[1])) { | |
return true; | |
} else if (d3 === 0 && onSegment(line1[0], line1[1], line2[0])) { | |
return true; | |
} else if (d4 === 0 && onSegment(line1[0], line1[1], line2[1])) { | |
return true; | |
} | |
return false; | |
} | |
function linksCross (link1, link2) { | |
// Self loops are not intersections | |
if (link1.index === link2.index || | |
link1.source === link1.target || | |
link2.source === link2.target) { | |
return false; | |
} | |
// Links cannot intersect if they share a node | |
if (link1.source === link2.source || | |
link1.source === link2.target || | |
link1.target === link2.source || | |
link1.target === link2.target) { | |
return false; | |
} | |
var line1 = [ | |
[link1.source.x, link1.source.y], | |
[link1.target.x, link1.target.y] | |
]; | |
var line2 = [ | |
[link2.source.x, link2.source.y], | |
[link2.target.x, link2.target.y] | |
]; | |
return linesCross(line1, line2); | |
} | |
function linkCrossings () { | |
var i, j, c = 0, d = 0, link1, link2, line1, line2;; | |
// Sum the upper diagonal of the edge crossing matrix. | |
for (i = 0; i < m; ++i) { | |
for (j = i + 1; j < m; ++j) { | |
link1 = links[i], link2 = links[j]; | |
// Check if link i and link j intersect | |
if (linksCross(link1, link2)) { | |
line1 = [ | |
[link1.source.x, link1.source.y], | |
[link1.target.x, link1.target.y] | |
]; | |
line2 = [ | |
[link2.source.x, link2.source.y], | |
[link2.target.x, link2.target.y] | |
]; | |
++c; | |
d += Math.abs(idealAngle - acuteLinesAngle(line1, line2)); | |
} | |
} | |
} | |
return {c: 2*c, d: 2*d}; | |
} | |
function linesegmentsAngle (line1, line2) { | |
// Finds the (counterclockwise) angle from line segement line1 to | |
// line segment line2. Assumes the lines share one end point. | |
// If both endpoints are the same, or if both lines have zero | |
// length, then return 0 angle. | |
// Param order matters: | |
// linesegmentsAngle(line1, line2) != linesegmentsAngle(line2, line1) | |
var temp, len, angle1, angle2, sLine1, sLine2; | |
// Re-orient so that line1[0] and line2[0] are the same. | |
if (line1[0][0] === line2[1][0] && line1[0][1] === line2[1][1]) { | |
temp = line2[1]; | |
line2[1] = line2[0]; | |
line2[0] = temp; | |
} else if (line1[1][0] === line2[0][0] && line1[1][1] === line2[0][1]) { | |
temp = line1[1]; | |
line1[1] = line1[0]; | |
line1[0] = temp; | |
} else if (line1[1][0] === line2[1][0] && line1[1][1] === line2[1][1]) { | |
temp = line1[1]; | |
line1[1] = line1[0]; | |
line1[0] = temp; | |
temp = line2[1]; | |
line2[1] = line2[0]; | |
line2[0] = temp; | |
} | |
// Shift the line so that the first point is at (0,0). | |
sLine1 = [ | |
[line1[0][0] - line1[0][0], line1[0][1] - line1[0][1]], | |
[line1[1][0] - line1[0][0], line1[1][1] - line1[0][1]] | |
]; | |
// Normalize the line length. | |
len = Math.hypot(sLine1[1][0], sLine1[1][1]); | |
if (len === 0) return 0; | |
sLine1[1][0] /= len; | |
sLine1[1][1] /= len; | |
// If y < 0, angle = acos(x), otherwise angle = 360 - acos(x) | |
angle1 = Math.acos(sLine1[1][0]) * 180 / Math.PI; | |
if (sLine1[1][1] < 0) angle1 = 360 - angle1; | |
// Shift the line so that the first point is at (0,0). | |
sLine2 = [ | |
[line2[0][0] - line2[0][0], line2[0][1] - line2[0][1]], | |
[line2[1][0] - line2[0][0], line2[1][1] - line2[0][1]] | |
]; | |
// Normalize the line length. | |
len = Math.hypot(sLine2[1][0], sLine2[1][1]); | |
if (len === 0) return 0; | |
sLine2[1][0] /= len; | |
sLine2[1][1] /= len; | |
// If y < 0, angle = acos(x), otherwise angle = 360 - acos(x) | |
angle2 = Math.acos(sLine2[1][0]) * 180 / Math.PI; | |
if (sLine2[1][1] < 0) angle2 = 360 - angle2; | |
return angle1 <= angle2 ? angle2 - angle1 : 360 - (angle1 - angle2); | |
} | |
function acuteLinesAngle (line1, line2) { | |
// Acute angle of intersection, in degrees. Assumes these lines | |
// intersect. | |
var slope1 = (line1[1][1] - line1[0][1]) / (line1[1][0] - line1[0][0]); | |
var slope2 = (line2[1][1] - line2[0][1]) / (line2[1][0] - line2[0][0]); | |
// If these lines are two links incident on the same node, need | |
// to check if the angle is 0 or 180. | |
if (slope1 === slope2) { | |
// If line2 is not on line1 and line1 is not on line2, then | |
// the lines share only one point and the angle must be 180. | |
if (!(onSegment(line1[0], line1[1], line2[0]) && onSegment(line1[0], line1[1], line2[1])) || | |
!(onSegment(line2[0], line2[1], line1[0]) && onSegment(line2[0], line2[1], line1[1]))) | |
return 180; | |
else return 0; | |
} | |
var angle = Math.abs(Math.atan(slope1) - Math.atan(slope2)); | |
return (angle > Math.PI / 2 ? Math.PI - angle : angle) * 180 / Math.PI; | |
} | |
function angularRes () { | |
var j, | |
resMin = 0, | |
resDev = 0, | |
nonZeroDeg, | |
node, | |
minAngle, | |
idealMinAngle, | |
incident, | |
line0, | |
line1, | |
line2, | |
incidentLinkAngles, | |
nextLink; | |
nonZeroDeg = degree.filter(function (d) { return d.length >= 1; }).length; | |
for (j = 0; j < n; ++j) { | |
node = nodes[j]; | |
line0 = [[node.x, node.y], [node.x+1, node.y]]; | |
// Links that are incident to this node (already filtered out self loops) | |
incident = degree[j]; | |
if (incident.length <= 1) continue; | |
idealMinAngle = 360 / incident.length; | |
// Sort edges by the angle they make from an imaginary vector | |
// emerging at angle 0 on the unit circle. | |
// Necessary for calculating angles of incident edges correctly | |
incident.sort(function (a, b) { | |
line1 = [ | |
[a.source.x, a.source.y], | |
[a.target.x, a.target.y] | |
]; | |
line2 = [ | |
[b.source.x, b.source.y], | |
[b.target.x, b.target.y] | |
]; | |
var angleA = linesegmentsAngle(line0, line1); | |
var angleB = linesegmentsAngle(line0, line2); | |
return angleA < angleB ? -1 : angleA > angleB ? 1 : 0; | |
}); | |
incidentLinkAngles = incident.map(function (l, i) { | |
nextLink = incident[(i + 1) % incident.length]; | |
line1 = [ | |
[l.source.x, l.source.y], | |
[l.target.x, l.target.y] | |
]; | |
line2 = [ | |
[nextLink.source.x, nextLink.source.y], | |
[nextLink.target.x, nextLink.target.y] | |
]; | |
return linesegmentsAngle(line1, line2); | |
}); | |
minAngle = Math.min.apply(null, incidentLinkAngles); | |
resMin += Math.abs(idealMinAngle - minAngle) / idealMinAngle; | |
resDev += getSumOfArray(incidentLinkAngles.map(function (angle) { | |
return Math.abs(idealMinAngle - angle) / idealMinAngle; | |
})) / (2 * incident.length - 2); | |
} | |
// Divide by number of nodes with degree != 0 | |
resMin = resMin / nonZeroDeg; | |
// Divide by number of nodes with degree != 0 | |
resDev = resDev / nonZeroDeg; | |
return {resMin: resMin, resDev: resDev}; | |
} | |
initialize(); | |
cMax = (m * (m - 1) / 2) - getSumOfArray(degree.map(function (d) { return d.length * (d.length - 1); })) / 2; | |
var crossInfo = linkCrossings(); | |
dMax = crossInfo.c * idealAngle; | |
graphStats.crossing = 1 - (cMax > 0 ? crossInfo.c / cMax : 0); | |
graphStats.crossingAngle = 1 - (dMax > 0 ? crossInfo.d / dMax : 0); | |
var angularResInfo = angularRes(); | |
graphStats.angularResolutionMin = 1 - angularResInfo.resMin; | |
graphStats.angularResolutionDev = 1 - angularResInfo.resDev; | |
return graphStats; | |
}; | |
exports.greadability = greadability; | |
Object.defineProperty(exports, '__esModule', { value: true }); | |
}))); |
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<style> | |
html, body { | |
font: 12px sans-serif; | |
} | |
svg { | |
display: block; | |
} | |
.links line { | |
stroke: #999; | |
stroke-opacity: 0.6; | |
stroke-width: 2px; | |
} | |
.nodes circle { | |
fill: #d30000; | |
stroke: #fff; | |
stroke-width: 1px; | |
} | |
.chart circle { | |
fill: #aaa; | |
fill-opacity: 0.1; | |
stroke: #aaa; | |
stroke-opacity: 0.4; | |
cursor: pointer; | |
} | |
.chart circle.selected { | |
fill: #d30000; | |
fill-opacity: 0.6; | |
stroke: #d30000; | |
stroke-opacity: 0.8; | |
} | |
.column { | |
float: left; | |
margin: 0 10px; | |
} | |
</style> | |
<div class="column"> | |
<svg class="chart"></svg> | |
<svg class="graph"></svg> | |
</div> | |
<div class="column"> | |
<p class="progress">Testing</p> | |
<p>Best parameters so far:</p> | |
<ul class="best"></ul> | |
</div> | |
<script src="greadability.js"></script> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<script> | |
var width = 600; | |
var height = 500; | |
var chartWidth = 600; | |
var chartHeight = 60; | |
var margin = {left: 10, right: 10, top: 10, bottom: 40}; | |
var numTicks = 150; | |
var selectedParams; | |
var bestParams; | |
var dispatch = d3.dispatch('layoutend'); | |
var svg = d3.select('svg.graph') | |
.attr('width', width) | |
.attr('height', height); | |
var chartSvg = d3.select('svg.chart') | |
.attr('width', chartWidth) | |
.attr('height', chartHeight) | |
.append('g') | |
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | |
chartWidth = chartWidth - margin.left - margin.right; | |
chartHeight = chartHeight - margin.top - margin.bottom; | |
var x = d3.scaleLinear() | |
.domain([0, 1]) | |
.range([0, chartWidth]); | |
chartSvg.append('g') | |
.attr('transform', 'translate(0,' + chartHeight + ')') | |
.call(d3.axisBottom(x).ticks(7)) | |
.append("text") | |
.attr("fill", "#000") | |
.attr('transform', 'translate(' + chartWidth/2 + ',' + 0 + ')') | |
.attr("y", chartHeight + 10) | |
.attr("dy", "0.71em") | |
.attr("text-anchor", "middle") | |
.text("Average readability score"); | |
var readabilityCircles = chartSvg.append('g').selectAll('circle'); | |
d3.json('Berlin.json', function (error, graph) { | |
if (error) throw error; | |
var link = svg.append('g') | |
.attr('class', 'links') | |
.selectAll('line') | |
.data(graph.links) | |
.enter().append('line'); | |
var node = svg.append('g') | |
.attr('class', 'nodes') | |
.selectAll('circle') | |
.data(graph.nodes) | |
.enter().append('circle') | |
.attr('r', 4); | |
node.append('title').text(function (d) { return d.name; }); | |
var paramGroups = [ | |
{name: 'chargeStrength', values: [-30, -80]}, | |
{name: 'linkDistance', values: [30, -80]}, | |
{name: 'linkStrength', values: [null, 0.25]}, | |
{name: 'gravity', values: [0, 0.5]}, | |
{name: 'iterations', values: [1, 2]}, | |
{name: 'alphaDecay', values: [0, 0.0228, 0.05]}, | |
{name: 'velocityDecay', values: [0.4, 0.8]} | |
]; | |
var paramList = generateParams(paramGroups); | |
var bestSoFar = d3.select('.best').selectAll('li') | |
.data(paramGroups.map(function (d) { return d.name; })) | |
.enter().append('li') | |
.text(function (d) { return d; }); | |
dispatch.on('layoutend', function (params, i) { | |
if (!bestParams || params.graphReadability > bestParams.graphReadability) { | |
bestParams = params; | |
selectedParams = bestParams; | |
bestSoFar | |
.data(d3.map(bestParams).keys().filter(function (d) { return d !== 'positions' && d !== 'graphReadability'; })) | |
.text(function (d) { return d + ' = ' + bestParams[d]; }); | |
} | |
d3.select('.progress').text('Testing ' + (i + 1) + ' of ' + paramList.length + ' parameter settings'); | |
// Plot the number line. | |
readabilityCircles = readabilityCircles | |
.data(readabilityCircles.data().concat(params)) | |
.enter().append('circle') | |
.attr('cx', function (d) { return x(d.graphReadability); }) | |
.attr('cy', 5) | |
.attr('r', 4) | |
.on('click', function (d) { | |
selectedParams = d; | |
readabilityCircles.classed('selected', false); | |
d3.select(this).classed('selected', true).raise(); | |
bestSoFar | |
.data(d3.map(selectedParams).keys().filter(function (d) { return d !== 'positions' && d !== 'graphReadability'; })) | |
.text(function (d) { return d + ' = ' + selectedParams[d]; }); | |
drawGraph(); | |
}) | |
.merge(readabilityCircles) | |
.classed('selected', function (d) { return d === selectedParams; }); | |
readabilityCircles.filter(function (d) { return d === selectedParams; }) | |
.raise(); | |
drawGraph(); | |
}); | |
var i = 0; | |
var stepper = d3.timer(function () { | |
var p = paramList[i]; | |
var forceSim = getForceSimFromParams(p); | |
// Reset node attributes. | |
graph.nodes.forEach(function (n) { | |
n.x = n.y = n.vx = n.vy = 0; | |
}); | |
forceSim.nodes(graph.nodes) | |
.stop(); | |
forceSim.force('link') | |
.links(graph.links); | |
for (var t = 0; t < numTicks; ++t) { | |
forceSim.tick(); | |
} | |
p.graphReadability = greadability.greadability(graph.nodes, graph.links); | |
p.graphReadability = (p.graphReadability.crossing + p.graphReadability.crossingAngle + | |
p.graphReadability.angularResolutionMin + p.graphReadability.angularResolutionDev) / 4 | |
p.positions = graph.nodes.map(function (n) { return {x: n.x, y: n.y}; }); | |
dispatch.call('layoutend', forceSim, p, i); | |
++i; | |
if (i >= paramList.length) { | |
stepper.stop(); | |
} | |
}); | |
function drawGraph () { | |
graph.nodes.forEach(function (n, i) { | |
n.x = selectedParams.positions[i].x; | |
n.y = selectedParams.positions[i].y; | |
}); | |
var xDistance = d3.extent(graph.nodes, function (n) { return n.x; }); | |
var xMin = xDistance[0]; | |
xDistance = xDistance[1] - xDistance[0]; | |
var yDistance = d3.extent(graph.nodes, function (n) { return n.y; }); | |
var yMin = yDistance[0]; | |
yDistance = yDistance[1] - yDistance[0]; | |
graph.nodes.forEach(function (n, i) { | |
n.x = (height - 10) * (n.x - xMin) / Math.max(xDistance, yDistance); | |
n.y = (height - 10) * (n.y - yMin) / Math.max(xDistance, yDistance); | |
}); | |
xDistance = d3.extent(graph.nodes, function (n) { return n.x; }); | |
xMid = (xDistance[1] + xDistance[0]) / 2; | |
yDistance = d3.extent(graph.nodes, function (n) { return n.y; }); | |
yMid = (yDistance[1] - yDistance[0]) / 2; | |
graph.nodes.forEach(function (n, i) { | |
n.x = n.x + width/2 - xMid; | |
n.y = n.y + height/2 - yMid; | |
}); | |
link | |
.attr('x1', function (d) { return d.source.x; }) | |
.attr('x2', function (d) { return d.target.x; }) | |
.attr('y1', function (d) { return d.source.y; }) | |
.attr('y2', function (d) { return d.target.y; }); | |
node | |
.attr('cx', function (d) { return d.x; }) | |
.attr('cy', function (d) { return d.y; }); | |
} | |
}); | |
function generateParams (paramGroups, paramList, currParam) { | |
var p = paramGroups[0]; | |
if (!paramList) paramList = []; | |
if (!currParam) currParam = {}; | |
p.values.forEach(function (v) { | |
var setting = {}; | |
setting[p.name] = v; | |
if (paramGroups.length === 1) { | |
paramList.push(Object.assign(setting, currParam)); | |
} else { | |
generateParams(paramGroups.slice(1), paramList, Object.assign(setting, currParam)); | |
} | |
}); | |
return paramList; | |
} | |
function getForceSimFromParams (params) { | |
var forceSim = d3.forceSimulation() | |
.force('link', d3.forceLink().iterations(params.iterations)) | |
.force('charge', d3.forceManyBody().strength(params.chargeStrength)) | |
.force('x', d3.forceX(0).strength(params.gravity)) | |
.force('y', d3.forceY(0).strength(params.gravity)) | |
.force('center', d3.forceCenter(0, 0)) | |
.alphaDecay(params.alphaDecay) | |
.velocityDecay(params.velocityDecay); | |
if (params.linkStrength !== null) { | |
forceSim.force('link').strength(params.linkStrength); | |
} | |
return forceSim; | |
} | |
</script> |