├── README.md ├── css └── styles.css ├── demo ├── css │ └── styles.css ├── data │ ├── aframe-data.json │ └── contour-data.json ├── images │ └── bg_light.png ├── index.html └── js │ ├── conrec.js │ ├── main.js │ └── textures.min.js ├── images ├── preview.png └── wide.png └── index.html /README.md: -------------------------------------------------------------------------------- 1 | # Visualizing in VR using A‑Frame and D3 2 | 3 | I was interested in seeing what a compelling visualization in 3D, and by extension in VR, might look like. In this demo, one learns about various cities by exploring a terrain. The fake data is encoded using elevation at the city-level. Turning your head, which you can either do with a headset or by clicking and dragging your mouse, and looking at a mound of black sand provides some insight into that city. 4 | 5 | * [Post](http://almossawi.com/aframe-d3-visualization/) 6 | * [Demo](http://almossawi.com/aframe-d3-visualization/) 7 | 8 | ![Visualizing in VR using A-Frame and D3](http://almossawi.com/aframe-d3-visualization/images/preview.png "Visualizing in VR using A-Frame and D3") 9 | -------------------------------------------------------------------------------- /css/styles.css: -------------------------------------------------------------------------------- 1 | 2 | .anchor i { 3 | color: #525151; 4 | font-size: 12px; 5 | } 6 | 7 | a, a:active, a:link, a:visited { 8 | color: #3b3b3b; 9 | text-decoration: underline; 10 | } 11 | 12 | .footer { 13 | font-size: 14px; 14 | font-style: italic; 15 | opacity: 0.6; 16 | } 17 | 18 | .high-top { 19 | padding-top: 70px; 20 | } 21 | 22 | html, body { 23 | background: #fff; 24 | font: 13px; 25 | font-family: 'Old Standard TT', 'PT Serif', Georgia, Times, 'Times New Roman', serif; 26 | margin: 0; 27 | padding: 0; 28 | } 29 | 30 | h1 { 31 | clear: both; 32 | color: black; 33 | font-size: 38px; 34 | font-style: italic; 35 | font-weight: 300; 36 | margin: 40px 0 40px 0; 37 | padding: 0; 38 | text-align: center; 39 | } 40 | 41 | .link { 42 | fill: none; 43 | stroke: #ccc; 44 | } 45 | 46 | .link-text { 47 | cursor: pointer; 48 | fill: #ccc; 49 | font-family: 'Open Sans', Arial, sans-serif; 50 | font-size: 11px; 51 | } 52 | 53 | .link-text.active { 54 | fill: #4e4e4e; 55 | } 56 | 57 | .link.active { 58 | stroke: #4e4e4e; 59 | } 60 | 61 | .main-container { 62 | margin: 0 auto 50px auto; 63 | max-width: 560px; 64 | width: 90%; 65 | } 66 | 67 | p { 68 | font-size: 16px; 69 | line-height: 30px; 70 | opacity: 0.9; 71 | text-indent: 50px; 72 | } 73 | 74 | p.no-indent { 75 | text-indent: 0; 76 | } 77 | 78 | sup, sub { 79 | font-size: 13px; 80 | position: relative; 81 | top: -0.4em; 82 | vertical-align: baseline; 83 | } 84 | 85 | sub { 86 | top: 0.4em; 87 | } 88 | 89 | .underline { 90 | border-bottom: 1px solid #ccc; 91 | } 92 | -------------------------------------------------------------------------------- /demo/css/styles.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 14px; 3 | } 4 | 5 | body { 6 | background-color: #fff; 7 | } 8 | 9 | .loading { 10 | width: 100%; 11 | height: 100%; 12 | z-index: 999; 13 | opacity: 0; 14 | position: absolute; 15 | background-color: red; 16 | } 17 | 18 | .overlay { 19 | width: 100%; 20 | height: 100%; 21 | z-index: 1; 22 | background: url(../images/bg_light.png); 23 | opacity: 0; 24 | } 25 | 26 | .camera { 27 | right: 0; 28 | margin: 30px; 29 | color: #666; 30 | position: absolute; 31 | top: 0; 32 | z-index: 3; 33 | cursor: pointer; 34 | } 35 | 36 | a-scene { 37 | opacity: 0.9; 38 | z-index: 3; 39 | } 40 | 41 | p { 42 | font-family: 'Fira Sans'; 43 | line-height: 24px; 44 | } 45 | 46 | .a-enter-vr { 47 | font-size: 0.8rem !important; 48 | } 49 | 50 | .a-enter-vr-modal p { 51 | line-height: 16px !important; 52 | font-size: 0.8rem !important; 53 | } 54 | 55 | .a-enter-vr-modal { 56 | height: 50px !important; 57 | } 58 | 59 | h1 { 60 | font-family: 'Fira Sans'; 61 | font-size: 2.5rem; 62 | font-weight: 400; 63 | margin: 0; 64 | } 65 | 66 | .card { 67 | color: black; 68 | font-family: 'Fira Sans'; 69 | margin: 50px 10px 0 40px; 70 | padding: 0 10px 0 10px; 71 | position: absolute; 72 | width: 90%; 73 | max-width: 450px; 74 | z-index: 3; 75 | text-shadow: 76 | -1px -1px 0 #e7e7e7, 77 | 1px -1px 0 #e7e7e7, 78 | -1px 1px 0 #e7e7e7, 79 | 1px 1px 0 #e7e7e7; 80 | } 81 | 82 | .contour-plot { 83 | border: 1px solid #1a1a1a; 84 | height: 316px; 85 | left: 0; 86 | margin: 10px; 87 | opacity: 0; 88 | overflow: hidden; 89 | position: absolute; 90 | top: 0; 91 | width: 211px; 92 | z-index: 3; 93 | } 94 | 95 | .isoline { 96 | fill: none; 97 | } 98 | 99 | g.contours path { 100 | fill: #16214c; 101 | stroke: #16214c; 102 | } 103 | 104 | g.map { 105 | opacity: 1; 106 | } 107 | 108 | g.map path { 109 | fill: #0d0d0d; 110 | stroke: black; 111 | stroke-width: 0.1; 112 | } 113 | 114 | rect.back { 115 | opacity: 0.3; 116 | } 117 | 118 | .arrow { 119 | transform-origin: 50% 50%; 120 | } 121 | 122 | .key { 123 | font-family: Monaco, 'Lucida Console', 'Fira Sans'; 124 | background-color: #fff; 125 | border: 1px solid #ccc; 126 | border-radius: 4px; 127 | color: #585757; 128 | padding: 3px 4px 3px 4px; 129 | font-size: 0.8rem; 130 | text-shadow: none; 131 | } 132 | 133 | .hide { 134 | display: none !important; 135 | } 136 | 137 | @media screen and (min-width: 0px) and (max-width: 720px) { 138 | .hide-on-mobile { 139 | display: none; 140 | } 141 | 142 | h1 { 143 | font-size: 1.8rem; 144 | } 145 | 146 | .card { 147 | font-size: 0.8rem; 148 | margin: 10px 0 0 0; 149 | } 150 | 151 | p { 152 | line-height: 22px; 153 | } 154 | } -------------------------------------------------------------------------------- /demo/data/contour-data.json: -------------------------------------------------------------------------------- 1 | [ 2 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 3 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 4 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 5 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 6 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 7 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 8 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 77, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 9 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 10 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 11 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 12 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 13 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 68, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 14 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 67, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 15 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 0, 0, 0, 0, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 16 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 91, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 17 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 18 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 0, 58, 0, 0, 0, 97, 0, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 19 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 55, 123, 58, 0, 59, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 20 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 39, 6, 30, 0, 17, 0, 0, 0, 4, 0, 0, 65, 54, 53, 0, 0, 48, 43.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 21 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 0, 0, 51, 29, 4, 83, 0, 27, 56, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 22 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 57, 25, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 23 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 37, 0, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 24 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 25 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 26 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 27 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 28 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24.5, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 29 | [0, 0, 0, 0, 23, 0, 0, 39, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 101, 0, 0, 0, 0, 0, 47.25, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 30 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 31, 63, 71, 46, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 31 | [0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 0, 0, 114, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 32 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 76, 0, 0, 0, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 37, 0, 27, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 33 | [0, 0, 0, 0, 14, 0, 0, 0, 0, 81, 0, 0, 0, 0, 0, 0, 0, 71, 22, 14, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 34, 48, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 34 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, 0, 0, 0, 0, 11, 0, 0, 0, 43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 35 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 36 | [35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 0, 0, 0, 31, 0, 0, 0, 0, 0, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 0, 0, 0, 0, 0, 0, 0, 0, 0, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 37 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 90, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 38 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 39 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 91, 0, 0, 0, 0, 0, 0, 0, 75, 0, 0, 0, 0, 36, 61, 0, 0, 94, 0, 0, 0, 0, 0, 0, 61, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 40 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49, 0, 0, 0, 0, 0, 40, 0, 0, 12, 0, 40, 87, 23, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 41 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 0, 9, 87, 0, 0, 9, 65, 61, 0, 0, 0, 23.5, 58.5, 21, 89, 0, 0, 0, 0, 93, 0, 0, 0, 0, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 42 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 0, 35.5, 7, 18.5, 8, 0, 0, 39.5, 41.75, 23.25, 38, 0, 49.5, 22, 0, 0, 37, 0, 0, 0, 0, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 43 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 34, 41, 94, 0, 62, 21.25, 50, 48, 0, 0, 50, 0, 0, 71, 0, 0, 0, 0, 0, 0, 0, 0, 46, 0, 0, 0, 9, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 44 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 0, 0, 26.5, 21.5, 3, 26, 147, 16.75, 42.5, 0, 0, 0, 0, 0, 0, 40, 0, 87, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 45 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24.25, 69.5, 39, 39, 0, 80, 5, 71, 7, 18.25, 0, 18, 2, 0, 0, 0, 106, 82, 0, 0, 0, 0, 0, 55, 0, 0, 0, 0, 0, 0, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 46 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 53, 49, 0, 17, 51.5, 0, 0, 10, 0, 40, 0, 40, 0, 47, 0, 54, 44, 43, 0, 14, 0, 0, 0, 0, 40.5, 8, 0, 42, 0, 46, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 47 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 66, 32, 0, 79, 0, 0, 62, 37, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 98, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 48 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 99, 0, 0, 0, 44, 58, 37.5, 3, 0, 16, 0, 0, 0, 0, 52, 27, 7, 79, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 49 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 45, 52, 24.75, 0, 80.5, 0, 0, 0, 0, 0, 0, 51, 0, 0, 0, 61, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 50 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 94, 0, 0, 0, 0, 0, 122, 0, 0, 0, 0, 0, 49, 0, 0, 0, 35, 0, 2, 0, 0, 0, 0, 0, 73, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 51 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 66, 0, 31, 0, 0, 0, 0, 0, 46, 93, 0, 0, 57, 0, 51, 0, 0, 33, 0, 0, 134, 0, 0, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0], 52 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 66, 0, 35, 36, 0, 0, 0, 41, 27, 52, 0, 0, 0, 0, 57, 0, 0, 9, 0, 0, 69, 0, 68, 0, 7, 0, 0, 0, 0, 144, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 53 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 44, 22, 15.5, 30, 40, 0, 0, 0, 0, 0, 90, 0, 0, 0, 0, 0, 0, 0, 0, 76, 16, 0, 141, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 92, 0, 0, 0, 27], 54 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 78, 0, 0, 0, 0, 0, 0, 0, 0, 58, 0, 0, 53, 25.8125, 54.25, 0, 0, 0, 0, 28, 51, 0, 0, 0, 73, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 53, 24], 55 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 136, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46, 0, 0, 0, 0, 34, 26, 54, 0, 0, 58, 0, 0, 0, 0, 62, 0, 0, 0, 0, 0, 0, 0, 0, 0, 294, 0, 0, 0, 0, 46, 0, 0, 0, 0, 0, 0, 0, 0, 0], 56 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 75, 48, 0, 0, 158, 0, 0, 0, 0, 50, 0, 93, 0, 64, 42, 0, 0, 0, 195, 0, 0, 0, 0, 0, 58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 57 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 105, 0, 0, 241, 0, 0, 0, 0, 0, 0, 0, 0, 104, 0, 0, 0, 0, 0, 0, 0], 58 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 97, 0, 0, 0, 0, 0, 80, 0, 0, 0, 0, 0, 70.5, 0, 5, 81, 45, 38, 0, 67, 0, 0, 0, 0, 44, 0, 91, 0, 0, 0, 0, 86, 36, 0, 0, 0, 0], 59 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 176, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 156, 0, 0, 8, 0, 15, 0, 44.5, 103, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 7, 0, 0, 0], 60 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 105, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 101, 8, 77, 0, 0, 0, 0, 0, 0, 0, 0, 58, 0, 0, 0, 0, 42, 58, 80, 0, 45, 0, 0, 0, 31, 0, 0, 0, 51, 0, 0, 87, 0, 0, 0, 0, 0, 0, 0, 0], 61 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 89, 0, 0, 0, 0, 0, 0, 0, 0, 0, 94, 0, 0, 0, 0, 0, 39, 23, 0, 28, 0, 0, 0, 0, 39, 0, 0, 105, 0, 0, 0, 0, 0, 75, 95, 0, 0, 168, 0, 81, 0, 67, 0, 41, 80, 72, 35, 80, 66, 29.5, 41, 0, 0, 0, 66, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 62 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 39, 0, 0, 0, 0, 0, 0, 0, 0, 59, 0, 0, 61, 0, 0, 0, 15.5, 0, 123, 44, 39.5, 0, 0, 0, 54, 0, 0, 37, 0, 0, 0, 93, 0, 0, 0, 0, 0, 99, 0, 0, 0, 0, 0, 0, 54.5, 8, 59.5, 71.5, 70, 0, 9, 40, 0, 0, 0, 0, 0, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 63 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 81, 9, 0, 50, 0, 0, 53.5, 84, 0, 55, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 48, 0, 0, 0, 0, 58, 0, 0, 0, 33, 63, 72, 15, 356, 69, 0, 0, 44.5, 51, 43.25, 53, 20, 37, 16, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 64 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 0, 89, 0, 0, 0, 50.5, 0, 0, 18, 6, 96, 2, 0, 0, 0, 75, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 92, 0, 89, 103.5, 45, 79.125, 60.375, 0, 36, 0, 33, 22.5, 29.25, 57.5, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0], 65 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 104, 72, 34, 75, 0, 0, 0, 0, 0, 0, 112, 54, 44, 0, 0, 75, 117.75, 0, 104, 54, 33.3125, 32.625, 81, 26, 73, 0, 18, 0, 99, 52, 63, 0, 0, 63, 68, 0, 0, 35.25, 0, 0, 0], 66 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 0, 0, 44, 31.75, 104, 62, 0, 64.5, 24.75, 32, 19, 18, 36, 0, 0, 65, 0, 0, 0, 91, 0, 149, 0, 0, 0, 0, 0, 0], 67 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 129, 0, 0, 0, 47, 0, 0, 60, 0, 117, 0, 0, 0, 0, 0, 85, 0, 0, 0, 0, 0, 72, 62, 0, 158, 0, 0, 0, 0, 28, 0, 0, 0, 37.5, 0, 0, 0, 0, 0, 88, 0, 0, 0, 57, 21, 0, 0, 0], 68 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 0, 0, 0, 0, 55, 0, 0, 0, 0, 0, 59, 0, 0, 0, 81, 49, 0, 83, 0, 68, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 0, 0, 0, 0, 0], 69 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 126, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 98, 0, 0, 0, 0, 105, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 45, 0, 41, 0, 0, 0, 75, 0, 0, 0, 0, 0, 0, 53, 91, 0, 108, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 70 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 179, 0, 0, 0, 0, 0, 0, 0, 69, 0, 0, 45, 0, 0, 0, 59.5, 0, 82, 0, 27, 43, 71, 0, 17, 0, 0, 48, 25, 133, 194, 0, 0, 61, 0, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 71 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 0, 0, 0, 75, 0, 0, 0, 0, 0, 0, 10, 13, 0, 50, 17, 34, 0, 53, 0, 56.5, 0, 0, 0, 34, 0, 24, 61, 0, 0, 0, 0, 61, 0, 39, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 72 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 0, 0, 51, 0, 0, 0, 0, 0, 0, 0, 89, 0, 0, 0, 0, 40, 60, 0, 0, 0, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 73 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 113, 0, 0, 0, 0, 116, 0, 49.75, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 74 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 69, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 75 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 76, 52, 0, 30, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 76 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 163, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 77 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 84, 0, 72, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 78 | ] -------------------------------------------------------------------------------- /demo/images/bg_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/almossawi/aframe-d3-visualization/ebdf6eea23f3ad008660109eeef9dda96792cd8b/demo/images/bg_light.png -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 |
21 |
22 |

Where is Piers Morgan disliked the most?

23 |
24 |
25 |
26 |

Find out by clicking and dragging on the terrain using your mouse or by 27 | breaking out of auto-pilot and navigating with the 28 | W 29 | A 30 | S 31 | D 32 | keys on your keyboard. Look at locations to learn about them. If you have a 33 | VR headset, then use that instead.

34 | 35 |

The black sand forms mounds that are tallest in cities with the greatest amount of 36 | dislike for the smug croissant. Cities are areas with 10K or more people. Modulo the 37 | clearly fake data, the city-level data is from 38 | here. The contour map below 39 | is draggable and will help you orient yourself.

40 |
41 |
42 | 43 |
44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 |
264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 281 | 282 | 290 | 291 | 292 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 |
309 | 310 |
311 | 312 |
313 | 314 | 315 | 316 | 317 | 318 | -------------------------------------------------------------------------------- /demo/js/conrec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010, Jason Davies. 3 | * 4 | * All rights reserved. This code is based on Bradley White's Java version, 5 | * which is in turn based on Nicholas Yue's C++ version, which in turn is based 6 | * on Paul D. Bourke's original Fortran version. See below for the respective 7 | * copyright notices. 8 | * 9 | * See http://local.wasp.uwa.edu.au/~pbourke/papers/conrec/ for the original 10 | * paper by Paul D. Bourke. 11 | * 12 | * The vector conversion code is based on http://apptree.net/conrec.htm by 13 | * Graham Cox. 14 | * 15 | * Redistribution and use in source and binary forms, with or without 16 | * modification, are permitted provided that the following conditions are met: 17 | * * Redistributions of source code must retain the above copyright 18 | * notice, this list of conditions and the following disclaimer. 19 | * * Redistributions in binary form must reproduce the above copyright 20 | * notice, this list of conditions and the following disclaimer in the 21 | * documentation and/or other materials provided with the distribution. 22 | * * Neither the name of the nor the 23 | * names of its contributors may be used to endorse or promote products 24 | * derived from this software without specific prior written permission. 25 | * 26 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 27 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 28 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 29 | * ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 30 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 31 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 32 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 33 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 34 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 35 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 36 | */ 37 | 38 | /* 39 | * Copyright (c) 1996-1997 Nicholas Yue 40 | * 41 | * This software is copyrighted by Nicholas Yue. This code is based on Paul D. 42 | * Bourke's CONREC.F routine. 43 | * 44 | * The authors hereby grant permission to use, copy, and distribute this 45 | * software and its documentation for any purpose, provided that existing 46 | * copyright notices are retained in all copies and that this notice is 47 | * included verbatim in any distributions. Additionally, the authors grant 48 | * permission to modify this software and its documentation for any purpose, 49 | * provided that such modifications are not distributed without the explicit 50 | * consent of the authors and that existing copyright notices are retained in 51 | * all copies. Some of the algorithms implemented by this software are 52 | * patented, observe all applicable patent law. 53 | * 54 | * IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR 55 | * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT 56 | * OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY DERIVATIVES THEREOF, 57 | * EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 58 | * 59 | * THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, 60 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, 61 | * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE IS 62 | * PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE NO 63 | * OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR 64 | * MODIFICATIONS. 65 | */ 66 | 67 | (function(exports) { 68 | exports.Conrec = Conrec; 69 | 70 | var EPSILON = 1e-10; 71 | 72 | function pointsEqual(a, b) { 73 | var x = a.x - b.x, y = a.y - b.y; 74 | return x * x + y * y < EPSILON; 75 | } 76 | 77 | function reverseList(list) { 78 | var pp = list.head; 79 | 80 | while (pp) { 81 | // swap prev/next pointers 82 | var temp = pp.next; 83 | pp.next = pp.prev; 84 | pp.prev = temp; 85 | 86 | // continue through the list 87 | pp = temp; 88 | } 89 | 90 | // swap head/tail pointers 91 | var temp = list.head; 92 | list.head = list.tail; 93 | list.tail = temp; 94 | } 95 | 96 | function ContourBuilder(level) { 97 | this.level = level; 98 | this.s = null; 99 | this.count = 0; 100 | } 101 | ContourBuilder.prototype.remove_seq = function(list) { 102 | // if list is the first item, static ptr s is updated 103 | if (list.prev) { 104 | list.prev.next = list.next; 105 | } else { 106 | this.s = list.next; 107 | } 108 | 109 | if (list.next) { 110 | list.next.prev = list.prev; 111 | } 112 | --this.count; 113 | } 114 | ContourBuilder.prototype.addSegment = function(a, b) { 115 | var ss = this.s; 116 | var ma = null; 117 | var mb = null; 118 | var prependA = false; 119 | var prependB = false; 120 | 121 | while (ss) { 122 | if (ma == null) { 123 | // no match for a yet 124 | if (pointsEqual(a, ss.head.p)) { 125 | ma = ss; 126 | prependA = true; 127 | } else if (pointsEqual(a, ss.tail.p)) { 128 | ma = ss; 129 | } 130 | } 131 | if (mb == null) { 132 | // no match for b yet 133 | if (pointsEqual(b, ss.head.p)) { 134 | mb = ss; 135 | prependB = true; 136 | } else if (pointsEqual(b, ss.tail.p)) { 137 | mb = ss; 138 | } 139 | } 140 | // if we matched both no need to continue searching 141 | if (mb != null && ma != null) { 142 | break; 143 | } else { 144 | ss = ss.next; 145 | } 146 | } 147 | 148 | // c is the case selector based on which of ma and/or mb are set 149 | var c = ((ma != null) ? 1 : 0) | ((mb != null) ? 2 : 0); 150 | 151 | switch(c) { 152 | case 0: // both unmatched, add as new sequence 153 | var aa = {p: a, prev: null}; 154 | var bb = {p: b, next: null}; 155 | aa.next = bb; 156 | bb.prev = aa; 157 | 158 | // create sequence element and push onto head of main list. The order 159 | // of items in this list is unimportant 160 | ma = {head: aa, tail: bb, next: this.s, prev: null, closed: false}; 161 | if (this.s) { 162 | this.s.prev = ma; 163 | } 164 | this.s = ma; 165 | 166 | ++this.count; // not essential - tracks number of unmerged sequences 167 | break; 168 | 169 | case 1: // a matched, b did not - thus b extends sequence ma 170 | var pp = {p: b}; 171 | 172 | if (prependA) { 173 | pp.next = ma.head; 174 | pp.prev = null; 175 | ma.head.prev = pp; 176 | ma.head = pp; 177 | } else { 178 | pp.next = null; 179 | pp.prev = ma.tail; 180 | ma.tail.next = pp; 181 | ma.tail = pp; 182 | } 183 | break; 184 | 185 | case 2: // b matched, a did not - thus a extends sequence mb 186 | var pp = {p: a}; 187 | 188 | if (prependB) { 189 | pp.next = mb.head; 190 | pp.prev = null; 191 | mb.head.prev = pp; 192 | mb.head = pp; 193 | } else { 194 | pp.next = null; 195 | pp.prev = mb.tail; 196 | mb.tail.next = pp; 197 | mb.tail = pp; 198 | } 199 | break; 200 | 201 | case 3: // both matched, can merge sequences 202 | // if the sequences are the same, do nothing, as we are simply closing this path (could set a flag) 203 | 204 | if (ma === mb) { 205 | var pp = {p: ma.tail.p, next: ma.head, prev: null}; 206 | ma.head.prev = pp; 207 | ma.head = pp; 208 | ma.closed = true; 209 | break; 210 | } 211 | 212 | // there are 4 ways the sequence pair can be joined. The current setting of prependA and 213 | // prependB will tell us which type of join is needed. For head/head and tail/tail joins 214 | // one sequence needs to be reversed 215 | switch((prependA ? 1 : 0) | (prependB ? 2 : 0)) { 216 | case 0: // tail-tail 217 | // reverse ma and append to mb 218 | reverseList(ma); 219 | // fall through to head/tail case 220 | case 1: // head-tail 221 | // ma is appended to mb and ma discarded 222 | mb.tail.next = ma.head; 223 | ma.head.prev = mb.tail; 224 | mb.tail = ma.tail; 225 | 226 | //discard ma sequence record 227 | this.remove_seq(ma); 228 | break; 229 | 230 | case 3: // head-head 231 | // reverse ma and append mb to it 232 | reverseList(ma); 233 | // fall through to tail/head case 234 | case 2: // tail-head 235 | // mb is appended to ma and mb is discarded 236 | ma.tail.next = mb.head; 237 | mb.head.prev = ma.tail; 238 | ma.tail = mb.tail; 239 | 240 | //discard mb sequence record 241 | this.remove_seq(mb); 242 | break; 243 | } 244 | } 245 | } 246 | 247 | /** 248 | * Implements CONREC. 249 | * 250 | * @param {function} drawContour function for drawing contour. Defaults to a 251 | * custom "contour builder", which populates the 252 | * contours property. 253 | */ 254 | function Conrec(drawContour) { 255 | if (!drawContour) { 256 | var c = this; 257 | c.contours = {}; 258 | /** 259 | * drawContour - interface for implementing the user supplied method to 260 | * render the countours. 261 | * 262 | * Draws a line between the start and end coordinates. 263 | * 264 | * @param startX - start coordinate for X 265 | * @param startY - start coordinate for Y 266 | * @param endX - end coordinate for X 267 | * @param endY - end coordinate for Y 268 | * @param contourLevel - Contour level for line. 269 | */ 270 | this.drawContour = function(startX, startY, endX, endY, contourLevel, k) { 271 | var cb = c.contours[k]; 272 | if (!cb) { 273 | cb = c.contours[k] = new ContourBuilder(contourLevel); 274 | } 275 | cb.addSegment({x: startX, y: startY}, {x: endX, y: endY}); 276 | } 277 | this.contourList = function() { 278 | var l = []; 279 | var a = c.contours; 280 | for (var k in a) { 281 | var s = a[k].s; 282 | var level = a[k].level; 283 | while (s) { 284 | var h = s.head; 285 | var l2 = []; 286 | l2.level = level; 287 | l2.k = k; 288 | while (h && h.p) { 289 | l2.push(h.p); 290 | h = h.next; 291 | } 292 | l.push(l2); 293 | s = s.next; 294 | } 295 | } 296 | l.sort(function(a, b) { return a.k - b.k }); 297 | return l; 298 | } 299 | } else { 300 | this.drawContour = drawContour; 301 | } 302 | this.h = new Array(5); 303 | this.sh = new Array(5); 304 | this.xh = new Array(5); 305 | this.yh = new Array(5); 306 | } 307 | 308 | /** 309 | * contour is a contouring subroutine for rectangularily spaced data 310 | * 311 | * It emits calls to a line drawing subroutine supplied by the user which 312 | * draws a contour map corresponding to real*4data on a randomly spaced 313 | * rectangular grid. The coordinates emitted are in the same units given in 314 | * the x() and y() arrays. 315 | * 316 | * Any number of contour levels may be specified but they must be in order of 317 | * increasing value. 318 | * 319 | * 320 | * @param {number[][]} d - matrix of data to contour 321 | * @param {number} ilb,iub,jlb,jub - index bounds of data matrix 322 | * 323 | * The following two, one dimensional arrays (x and y) contain 324 | * the horizontal and vertical coordinates of each sample points. 325 | * @param {number[]} x - data matrix column coordinates 326 | * @param {number[]} y - data matrix row coordinates 327 | * @param {number} nc - number of contour levels 328 | * @param {number[]} z - contour levels in increasing order. 329 | */ 330 | Conrec.prototype.contour = function(d, ilb, iub, jlb, jub, x, y, nc, z) { 331 | var h = this.h, sh = this.sh, xh = this.xh, yh = this.yh; 332 | var drawContour = this.drawContour; 333 | this.contours = {}; 334 | 335 | /** private */ 336 | var xsect = function(p1, p2){ 337 | return (h[p2]*xh[p1]-h[p1]*xh[p2])/(h[p2]-h[p1]); 338 | } 339 | 340 | var ysect = function(p1, p2){ 341 | return (h[p2]*yh[p1]-h[p1]*yh[p2])/(h[p2]-h[p1]); 342 | } 343 | var m1; 344 | var m2; 345 | var m3; 346 | var case_value; 347 | var dmin; 348 | var dmax; 349 | var x1 = 0.0; 350 | var x2 = 0.0; 351 | var y1 = 0.0; 352 | var y2 = 0.0; 353 | 354 | // The indexing of im and jm should be noted as it has to start from zero 355 | // unlike the fortran counter part 356 | var im = [0, 1, 1, 0]; 357 | var jm = [0, 0, 1, 1]; 358 | 359 | // Note that castab is arranged differently from the FORTRAN code because 360 | // Fortran and C/C++ arrays are transposed of each other, in this case 361 | // it is more tricky as castab is in 3 dimensions 362 | var castab = [ 363 | [ 364 | [0, 0, 8], [0, 2, 5], [7, 6, 9] 365 | ], 366 | [ 367 | [0, 3, 4], [1, 3, 1], [4, 3, 0] 368 | ], 369 | [ 370 | [9, 6, 7], [5, 2, 0], [8, 0, 0] 371 | ] 372 | ]; 373 | 374 | for (var j=(jub-1);j>=jlb;j--) { 375 | for (var i=ilb;i<=iub-1;i++) { 376 | var temp1, temp2; 377 | temp1 = Math.min(d[i][j],d[i][j+1]); 378 | temp2 = Math.min(d[i+1][j],d[i+1][j+1]); 379 | dmin = Math.min(temp1,temp2); 380 | temp1 = Math.max(d[i][j],d[i][j+1]); 381 | temp2 = Math.max(d[i+1][j],d[i+1][j+1]); 382 | dmax = Math.max(temp1,temp2); 383 | 384 | if (dmax>=z[0]&&dmin<=z[nc-1]) { 385 | for (var k=0;k=dmin&&z[k]<=dmax) { 387 | for (var m=4;m>=0;m--) { 388 | if (m>0) { 389 | // The indexing of im and jm should be noted as it has to 390 | // start from zero 391 | h[m] = d[i+im[m-1]][j+jm[m-1]]-z[k]; 392 | xh[m] = x[i+im[m-1]]; 393 | yh[m] = y[j+jm[m-1]]; 394 | } else { 395 | h[0] = 0.25*(h[1]+h[2]+h[3]+h[4]); 396 | xh[0]=0.5*(x[i]+x[i+1]); 397 | yh[0]=0.5*(y[j]+y[j+1]); 398 | } 399 | if (h[m]>EPSILON) { 400 | sh[m] = 1; 401 | } else if (h[m]<-EPSILON) { 402 | sh[m] = -1; 403 | } else 404 | sh[m] = 0; 405 | } 406 | // 407 | // Note: at this stage the relative heights of the corners and the 408 | // centre are in the h array, and the corresponding coordinates are 409 | // in the xh and yh arrays. The centre of the box is indexed by 0 410 | // and the 4 corners by 1 to 4 as shown below. 411 | // Each triangle is then indexed by the parameter m, and the 3 412 | // vertices of each triangle are indexed by parameters m1,m2,and 413 | // m3. 414 | // It is assumed that the centre of the box is always vertex 2 415 | // though this isimportant only when all 3 vertices lie exactly on 416 | // the same contour level, in which case only the side of the box 417 | // is drawn. 418 | // 419 | // 420 | // vertex 4 +-------------------+ vertex 3 421 | // | \ / | 422 | // | \ m-3 / | 423 | // | \ / | 424 | // | \ / | 425 | // | m=2 X m=2 | the centre is vertex 0 426 | // | / \ | 427 | // | / \ | 428 | // | / m=1 \ | 429 | // | / \ | 430 | // vertex 1 +-------------------+ vertex 2 431 | // 432 | // 433 | // 434 | // Scan each triangle in the box 435 | // 436 | for (m=1;m<=4;m++) { 437 | m1 = m; 438 | m2 = 0; 439 | if (m!=4) { 440 | m3 = m+1; 441 | } else { 442 | m3 = 1; 443 | } 444 | case_value = castab[sh[m1]+1][sh[m2]+1][sh[m3]+1]; 445 | if (case_value!=0) { 446 | switch (case_value) { 447 | case 1: // Line between vertices 1 and 2 448 | x1=xh[m1]; 449 | y1=yh[m1]; 450 | x2=xh[m2]; 451 | y2=yh[m2]; 452 | break; 453 | case 2: // Line between vertices 2 and 3 454 | x1=xh[m2]; 455 | y1=yh[m2]; 456 | x2=xh[m3]; 457 | y2=yh[m3]; 458 | break; 459 | case 3: // Line between vertices 3 and 1 460 | x1=xh[m3]; 461 | y1=yh[m3]; 462 | x2=xh[m1]; 463 | y2=yh[m1]; 464 | break; 465 | case 4: // Line between vertex 1 and side 2-3 466 | x1=xh[m1]; 467 | y1=yh[m1]; 468 | x2=xsect(m2,m3); 469 | y2=ysect(m2,m3); 470 | break; 471 | case 5: // Line between vertex 2 and side 3-1 472 | x1=xh[m2]; 473 | y1=yh[m2]; 474 | x2=xsect(m3,m1); 475 | y2=ysect(m3,m1); 476 | break; 477 | case 6: // Line between vertex 3 and side 1-2 478 | x1=xh[m3]; 479 | y1=yh[m3]; 480 | x2=xsect(m1,m2); 481 | y2=ysect(m1,m2); 482 | break; 483 | case 7: // Line between sides 1-2 and 2-3 484 | x1=xsect(m1,m2); 485 | y1=ysect(m1,m2); 486 | x2=xsect(m2,m3); 487 | y2=ysect(m2,m3); 488 | break; 489 | case 8: // Line between sides 2-3 and 3-1 490 | x1=xsect(m2,m3); 491 | y1=ysect(m2,m3); 492 | x2=xsect(m3,m1); 493 | y2=ysect(m3,m1); 494 | break; 495 | case 9: // Line between sides 3-1 and 1-2 496 | x1=xsect(m3,m1); 497 | y1=ysect(m3,m1); 498 | x2=xsect(m1,m2); 499 | y2=ysect(m1,m2); 500 | break; 501 | default: 502 | break; 503 | } 504 | // Put your processing code here and comment out the printf 505 | //printf("%f %f %f %f %f\n",x1,y1,x2,y2,z[k]); 506 | drawContour(x1,y1,x2,y2,z[k],k); 507 | } 508 | } 509 | } 510 | } 511 | } 512 | } 513 | } 514 | } 515 | })(typeof exports !== "undefined" ? exports : window); 516 | -------------------------------------------------------------------------------- /demo/js/main.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var global = {}; 3 | global.autopilot = true; 4 | 5 | // webvr scene 6 | d3.json('data/aframe-data.json', function(error, data) { 7 | global.data = data; 8 | global.number_of_cities = data.length; 9 | 10 | var dislikes_normalized_min_max = d3.extent(data, function(d) { 11 | return d.dislikes_normalized; 12 | }); 13 | 14 | var longitude_min_max = d3.extent(data, function(d) { 15 | if(d !== undefined) { 16 | return d.longitude; 17 | } 18 | }); 19 | 20 | var latitude_min_max = d3.extent(data, function(d) { 21 | if(d !== undefined) { 22 | return d.latitude; 23 | } 24 | }); 25 | 26 | var sessions_min_max = d3.extent(data, function(d) { 27 | if(d !== undefined) { 28 | return d.dislikes_normalized; 29 | } 30 | }); 31 | 32 | // scales used to generate the scene 33 | var y_scale = d3.scale.linear() 34 | .domain([0, dislikes_normalized_min_max[1]]) 35 | .range([0, 25]); 36 | 37 | var x_scale = d3.scale.linear() 38 | .domain([longitude_min_max[0], longitude_min_max[1]]) 39 | .range([200, 0]); 40 | 41 | var z_scale = d3.scale.linear() 42 | .domain([latitude_min_max[0], latitude_min_max[1]]) 43 | .range([0, 300]); 44 | 45 | // other scales 46 | var median = d3.median(data, function(d) { return d.dislikes_normalized; }); 47 | global.percentile_scale = d3.scale.linear() 48 | .domain([dislikes_normalized_min_max[0], median, 200]) 49 | .range([1, 70, 140]) 50 | .clamp(true); 51 | 52 | var median = d3.median(data, function(d) { return d.dislikes_normalized; }); 53 | global.color_scale = d3.scale.linear() 54 | .domain([dislikes_normalized_min_max[0], median]) 55 | .range(['#4CC3D9', '#fb237a']) 56 | .clamp(true); 57 | 58 | data.forEach(function(d, i) { 59 | var scene = d3.select('a-scene'); 60 | var mounds = scene.selectAll('a-cone.mound') 61 | .data(data) 62 | .enter() 63 | .append('a-cone') 64 | .classed('mound', true) 65 | .attr('visible', 'false') 66 | .attr('position', function(d, i) { 67 | var x = x_scale(d.longitude); 68 | var z = z_scale(d.latitude); 69 | var y = y_scale(d.dislikes_normalized) / 2; 70 | 71 | return x + " " + y + " " + z; 72 | }) 73 | .attr('height', function(d, i) { 74 | return y_scale(d.dislikes_normalized); 75 | }) 76 | .attr('segments-radial', 4) 77 | .attr('radius-bottom', 2) 78 | .attr('radius-top', 0.1) 79 | .attr('material', function(d) { 80 | return 'color: #000; roughness: 1; metalness: 0'; 81 | }) 82 | .append('a-animation') 83 | .attr('attribute', 'visible') 84 | .attr('begin', '1000') 85 | .attr('to', 'true'); 86 | }); 87 | 88 | d3.selectAll('.mound') 89 | .on('click', function (d, i) { 90 | updateCardText(d, i); 91 | updateRankGraphic(d, i); 92 | }); 93 | }); 94 | 95 | // contour plot 96 | d3.json('data/contour-data.json', function(error, data) { 97 | d3.select('.overlay') 98 | .style('opacity', 1); 99 | 100 | var zs = [2, 22, 42, 62, 82, 102]; 101 | var cliff = 1; 102 | data.push(d3.range(data[0].length).map(function() { return cliff; })); 103 | data.unshift(d3.range(data[0].length).map(function() { return cliff; })); 104 | data.forEach(function(d) { 105 | d.push(cliff); 106 | d.unshift(cliff); 107 | }); 108 | 109 | var xs = d3.range(0, data.length); 110 | var ys = d3.range(0, data[0].length); 111 | var c = new Conrec; 112 | 113 | var width = 200; 114 | var height = 300; 115 | var multiplier = 1.05; 116 | 117 | global.contour_width = width; 118 | global.contour_height = height; 119 | 120 | d3.select('.contour-plot') 121 | .style('top', (window.innerHeight - (height * multiplier) - 20) + 'px') 122 | .style('opacity', 1); 123 | 124 | d3.selectAll('.contour-plot svg, .contour-plot svg rect') 125 | .attr('width', Math.round(width * multiplier)) 126 | .attr('height', Math.round(height * multiplier)); 127 | 128 | var svg = d3.select('.contour-plot svg'); 129 | 130 | var t = textures.lines() 131 | .orientation("vertical", "horizontal") 132 | .size(4) 133 | .strokeWidth(1) 134 | .shapeRendering("crispEdges") 135 | .stroke("#1a1a1a"); 136 | 137 | svg.call(t); 138 | 139 | svg.select('rect.back') 140 | .style('fill', t.url()); 141 | 142 | // scales to generate the contour map 143 | var contour_x_scale = d3.scale.linear() 144 | .domain([1, d3.max(xs) - 1]) 145 | .range([0, height]); 146 | 147 | var contour_y_scale = d3.scale.linear() 148 | .domain([1, d3.max(ys) - 1]) 149 | .range([0, width]); 150 | 151 | // scales to map the scene to the arrow on the contour map 152 | global.camera_to_contour_x_scale = d3.scale.linear() 153 | .domain([-40, 140]) //camera: left edge to right edge 154 | .range([-20, 183]) //contour map: left edge to right edge 155 | .clamp(true); 156 | 157 | global.camera_to_contour_z_scale = d3.scale.linear() 158 | .domain([-245, 10]) // camera: top edge to bottom edge 159 | .range([-10, 300]) // contour map: top edge to bottom edge 160 | .clamp(true); 161 | 162 | global.world_position = d3.select(d3.select('a-camera').node().parentNode) 163 | .attr('position'); 164 | 165 | global.world_rotation = d3.select(d3.select('a-camera').node().parentNode) 166 | .attr('rotation'); 167 | 168 | var colors = d3.scale.linear() 169 | .domain([zs[0], 50, zs[zs.length - 1]]) 170 | .range(['#16214c', '#4CC3D9', '#fff']); 171 | 172 | c.contour(data, 0, xs.length - 1, 0, ys.length - 1, xs, ys, zs.length, zs); 173 | 174 | var contourList = c.contourList() 175 | .sort(function(a,b) { 176 | return d3.min(a.map(function(d) { return d.x; })) 177 | - d3.min(b.map(function(d) { return d.x; }) 178 | ); 179 | }); 180 | 181 | var g = d3.selectAll(".contours"); 182 | 183 | g.selectAll("path") 184 | .data(contourList) 185 | .enter() 186 | .append("path") 187 | .attr("class", function(d) { 188 | return "level_" + d.level; 189 | }) 190 | .style("fill",function(d) { 191 | return colors(d.level); 192 | }) 193 | .style("fill-opacity", "0.3") 194 | .style("stroke", function(d) { 195 | return colors(d.level); 196 | }) 197 | .style("stroke-opacity", function(d) { 198 | if(d.level > 2) { 199 | return 0.2; 200 | } else { 201 | return 0; 202 | } 203 | }) 204 | .style('opacity', 1) 205 | .attr("d", d3.svg.line() 206 | .x(function(d) { 207 | return contour_x_scale(d.x); 208 | }) 209 | .y(function(d) { 210 | return contour_y_scale(d.y); 211 | })); 212 | 213 | g.attr('transform', 'translate(0 ' + width + ') scale(1,-1) rotate(-90 ' + width/2 + ' ' + width/2 + ')'); 214 | 215 | // add arrow 216 | svg.append('path') 217 | .attr('d', 'M20 34 L30 36 L32 48 L34 36 L44 34 L34 32 L32 20 L30 32 Z') 218 | .attr('fill', 'white') 219 | .attr('class', 'arrow'); 220 | 221 | updateContour(); 222 | }); 223 | 224 | function updateCardText(d, i) { 225 | var suffix = 'th'; 226 | ++i; 227 | 228 | var rank = Math.ceil(global.percentile_scale(d.dislikes_normalized)); 229 | 230 | // what is the last digit? 231 | if(i % 10 == 1 && i !== 11) { 232 | suffix = 'st'; 233 | } else if(i % 10 == 2 && i !== 12) { 234 | suffix = 'nd'; 235 | } else if(i % 10 == 3) { 236 | suffix = 'rd'; 237 | } 238 | 239 | if(!document.querySelector('.rank-graphic svg')) { 240 | var data = '

' 241 | + '
Dislikes (normalized)
' 242 | + '
Population
' 243 | + '
Rank
' 244 | + '
Dislike level
'; 245 | 246 | d3.select('.card').html(data); 247 | } 248 | 249 | // update card 250 | d3.select('.city').html(d.key); 251 | d3.select('.dislikes_normalized').html(Math.round(d.dislikes_normalized)); 252 | d3.select('.population').html(addCommas(d.population)); 253 | d3.select('.rank').html(i + suffix + ' out of ' + global.number_of_cities + ' cities'); 254 | } 255 | 256 | function addCommas(x) { 257 | return x.toString() 258 | .replace(/\B(?=(\d{3})+(?!\d))/g, ','); 259 | } 260 | 261 | function updateRankGraphic(d, i) { 262 | var rank = Math.ceil(global.percentile_scale(d.dislikes_normalized)); 263 | 264 | if(!document.querySelector('.rank-graphic svg')) { 265 | // add svg 266 | var svg = d3.select('.rank-graphic').append('svg') 267 | .attr('width', 150) 268 | .attr('height', 30); 269 | 270 | svg.append('rect') 271 | .attr('width', 140) 272 | .attr('height', 6) 273 | .attr('x', 1) 274 | .attr('y', 5) 275 | .attr('fill', 'none') 276 | .style('stroke-width', 1) 277 | .style('stroke', 'black'); 278 | 279 | svg.append('rect') 280 | .attr('class', 'rank-graphic-indicator') 281 | .attr('fill', '#4CC3D9') 282 | .attr('x', 1) 283 | .attr('y', 5) 284 | .attr('width', 2) 285 | .attr('height', 6); 286 | } 287 | 288 | d3.select('.rank-graphic-indicator') 289 | .transition() 290 | .duration(1500) 291 | .attr('fill', global.color_scale(d.dislikes_normalized)) 292 | .attr('width', rank); 293 | } 294 | 295 | // handlers for draggable components 296 | function drag_start(event) { 297 | var style = window.getComputedStyle(event.target, null); 298 | event.dataTransfer.setData("text/plain", 299 | (parseInt(style.getPropertyValue("left"),10) - event.clientX) + ',' + (parseInt(style.getPropertyValue("top"),10) - event.clientY)); 300 | } 301 | 302 | function drag_over(event) { 303 | event.preventDefault(); 304 | return false; 305 | } 306 | 307 | function drop(event) { 308 | var offset = event.dataTransfer.getData("text/plain").split(','); 309 | var dm = d3.select('.contour-plot').node(); 310 | dm.style.left = (event.clientX + parseInt(offset[0],10)) + 'px'; 311 | dm.style.top = (event.clientY + parseInt(offset[1],10)) + 'px'; 312 | event.preventDefault(); 313 | return false; 314 | } 315 | 316 | var dm = d3.select('.contour-plot').node(); 317 | dm.addEventListener('dragstart',drag_start,false); 318 | document.body.addEventListener('dragover',drag_over,false); 319 | document.body.addEventListener('drop',drop,false); 320 | 321 | setInterval(function() { 322 | updateContour(); 323 | }, 80); 324 | 325 | function updateContour() { 326 | var position = d3.select('a-camera') 327 | .attr('position'); 328 | 329 | var x = global.camera_to_contour_x_scale(global.world_position.x + position.x); 330 | var z = global.camera_to_contour_z_scale(global.world_position.z + position.z); 331 | 332 | var facing = d3.select('a-camera') 333 | .attr('rotation'); 334 | 335 | d3.selectAll('.arrow') 336 | .attr('transform', function() { 337 | return 'translate(' + x + ' ' + z + ')'; 338 | }); 339 | } 340 | 341 | d3.select('.camera-button') 342 | .on('click', function() { 343 | d3.select('.loading') 344 | .classed('hide', false) 345 | 346 | document.querySelector('.hand-of-frog') 347 | .emit('change-camera'); 348 | 349 | var am_flying = d3.select('.camera-button') 350 | .classed('fa-plane'); 351 | 352 | if(am_flying) { 353 | d3.select('.camera-button') 354 | .classed('fa-plane', false) 355 | .classed('fa-car', true); 356 | 357 | // add spotlight when driving 358 | d3.select('a-camera') 359 | .append('a-entity') 360 | .transition() 361 | .delay(2000) 362 | .attr('class', 'spotlight') 363 | .attr('light', 'type: point; intensity: 6; distance: 150; decay: 3') 364 | .attr('position', '0 0 1') 365 | .attr('color', 'white'); 366 | } else { 367 | d3.select('.camera-button') 368 | .classed('fa-plane', true) 369 | .classed('fa-car', false); 370 | 371 | // remove spotlight when flying 372 | d3.select('a-camera .spotlight') 373 | .remove(); 374 | } 375 | 376 | setTimeout(function() { 377 | d3.select('.loading') 378 | .classed('hide', true) 379 | }, 2000); 380 | }); 381 | 382 | // disable animation when w, a, s, d are pressed 383 | d3.select('body') 384 | .on('keydown', function() { 385 | if(!global.autopilot) { 386 | return; 387 | } 388 | 389 | var keys = [87, 65, 68, 83]; 390 | if(keys.indexOf(d3.event.keyCode) != -1) { 391 | d3.select('.autopilot') 392 | .remove(); 393 | global.autopilot = false; 394 | } 395 | }); 396 | })(); -------------------------------------------------------------------------------- /demo/js/textures.min.js: -------------------------------------------------------------------------------- 1 | (function(){var rand,umd,slice=[].slice;rand=function(){return(Math.random().toString(36)+"00000000000000000").replace(/[^a-z]+/g,"").slice(0,5)};umd=function(factory){if(typeof exports==="object"){return module.exports=factory()}else if(typeof define==="function"&&define.amd){return define([],factory)}else{return this.textures=factory()}};umd(function(){return{circles:function(){var background,circles,complement,fill,id,radius,size,stroke,strokeWidth;size=20;background="";radius=2;complement=false;fill="#343434";stroke="#343434";strokeWidth=0;id=rand();circles=function(){var corner,g,i,len,ref,results;g=this.append("defs").append("pattern").attr({id:id,patternUnits:"userSpaceOnUse",width:size,height:size});if(background){g.append("rect").attr({width:size,height:size,fill:background})}g.append("circle").attr({cx:size/2,cy:size/2,r:radius,fill:fill,stroke:stroke,"stroke-width":strokeWidth});if(complement){ref=[[0,0],[0,size],[size,0],[size,size]];results=[];for(i=0,len=ref.length;i 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Visualizing in VR using A‑Frame and D3 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 30 | 31 | 32 | 33 | 44 | 45 | 46 |
47 |

Visualizing in VR using A‑Frame and D3

48 |

I was interested in seeing what a compelling visualization in 3D, and by extension in VR, might 49 | look like. In this demo, titled Where is Piers Morgan disliked the most?, one learns about 50 | various cities by exploring a terrain. The fake data is encoded using elevation at the city level. Turning your head, 51 | which you can either do with a headset or by clicking and dragging your mouse, and looking at a mound of black sand 52 | provides some insight into that city.

53 |

The scene is done in A-Frame, the contour plot was generated 54 | in conrec.js and is updated in plain JavaScript. Everything else is D3 and JavaScript. You can take a look at 55 | the source code to see how everything is put 56 | together. Though still very much an amateur with 57 | WebVR, I'm liking the great breadth of possibilities that the platform affords. If you have any questions, feel free 58 | to reach out.

59 | 60 |

61 | 62 | 63 |
64 | 65 | 66 |
67 |
68 | 69 | 70 | --------------------------------------------------------------------------------