Skip to content

Instantly share code, notes, and snippets.

@vkuchinov
Last active October 17, 2019 16:55
Show Gist options
  • Save vkuchinov/a8248a3487da0d7c7f6e002845d00038 to your computer and use it in GitHub Desktop.
Save vkuchinov/a8248a3487da0d7c7f6e002845d00038 to your computer and use it in GitHub Desktop.
D3.JS / THREE.JS : A hybrid 3D scatter plot MkI

A very rough example of using THREE.JS math and TrackBallControls to make 3D scatter plot out of D3.JS. Doesn't have the renderer.domElement, everything is rendered as SVG. DragControls was heavily modified to stack with D3.JS.

TrackBallControls could be replaced by OrbitControls, as well as PerspectiveCamera by Orthographic one. Don't forget to replace renderer.domElement with document

controls = new THREE.OrbitControls( camera, document );

id x y z colorid radius
1 4.482339877 -3.858535069 -4.466362452 9 190
2 6.06070237 -2.416998983 -1.556150519 2 472
3 5.72501809 -1.785750425 6.442992045 5 69
4 0.8025236617 -3.445751373 0.748589644 8 104
5 -2.040992897 0.2825309251 -0.0666878286 6 500
6 3.429120936 -1.507411016 -1.601075745 3 37
7 -1.58038841 0.6887202924 2.340087009 10 366
8 2.613144321 -0.7398779626 2.534196837 7 96
9 0.3859048242 1.705626991 10.68581231 4 323
10 2.060262558 -0.090579813 0.598218811 7 453
11 -1.573490398 -2.740830799 3.559481967 3 33
12 0.259324269 -0.8356099489 -0.4705839817 5 308
13 4.474273791 0.6449189057 0.3318801124 5 371
14 0.503862094 -1.029535782 -3.063317745 1 47
15 2.800118232 -0.3262068538 6.772912345 4 243
16 2.821844367 -2.911453444 -0.7522593594 3 467
17 -0.7483699574 -1.637750921 4.006477576 5 52
18 3.919926063 1.101892412 -7.021235655 6 85
19 0.5472089118 -0.6489655001 -3.112843659 1 365
20 1.017649108 0.467474673 -5.529214543 7 436
21 4.362372266 -0.3901083595 -0.2553296577 1 271
22 8.3701943 -3.170232403 6.507204661 6 360
23 -0.6345359492 -2.985823588 -5.158898941 4 190
24 2.285618547 0.2956785124 -5.440463871 4 136
25 -1.829370757 -2.741227013 -1.523032522 7 159
26 1.192071577 -0.4039159227 0.5176009868 7 114
27 2.825941013 -0.7137279042 1.988156281 7 335
28 0.8476052049 -4.614315735 -5.605282215 3 373
29 -3.761166959 1.868274892 6.860755689 7 43
30 2.228408793 -1.361769692 0.5100737156 9 48
31 0.1893628307 -1.337407098 8.399195582 7 166
32 2.606413774 0.3329580751 -2.082302643 1 15
33 4.679760062 0.8929715919 1.874565239 5 38
34 -0.70099157 -0.1385377189 -0.4194491651 7 369
35 -3.46580238 0.1695962566 -0.7606974248 1 216
36 -0.9028887483 -2.031838516 7.036592817 4 249
37 3.348113204 0.2999559961 6.199595348 9 70
38 5.24339694 0.07376166821 -2.581581077 4 102
39 -1.224334931 -1.223643593 2.047072981 4 330
40 -0.9378161219 -2.227102466 -3.403787699 2 434
41 -3.1001964 0.01266793467 -4.360014953 6 218
42 -1.416428618 -0.7445283946 -2.224576509 7 65
43 2.379371171 -2.036039495 6.390134334 2 355
44 -0.4261813065 -3.289516939 6.217816651 5 14
45 0.6397218519 -2.535916581 -12.35643942 1 193
46 4.497297556 -1.690192646 -0.4552707233 7 479
47 5.765875403 -1.247495734 -0.3353507473 7 246
48 -2.207118909 -2.227118948 -6.041511561 4 114
49 -2.072302039 2.916027463 1.492594418 4 36
50 -4.315692427 -2.391108164 3.09182016 9 223
51 -2.886370694 -1.504870818 5.027405626 9 406
52 3.994847976 1.803160507 2.876087373 6 468
53 2.416688432 -1.212287413 8.485022323 8 74
54 2.269403497 0.8228506059 1.98328284 7 166
55 1.180251238 -0.2444284997 -1.554412824 9 230
56 -2.859874042 -1.23514539 3.863829935 6 23
57 -1.385575037 -2.155677967 3.191314364 7 155
58 0.2717919017 2.89524005 -1.919524703 8 110
59 -0.8929032543 1.141162062 -1.849140817 9 315
60 2.538018428 0.9864380563 2.455553923 6 403
61 1.268958359 2.171760346 -0.3483309099 5 330
62 4.563875657 0.5951910361 -3.407356824 1 411
63 1.554130376 0.2387495851 -3.520653265 3 370
64 -1.217342608 -5.087350013 0.4459298342 7 269
65 -3.084204817 -2.646182125 3.21598289 6 155
66 -0.2966085985 0.5262995789 -1.87751911 6 485
67 4.197791039 1.618979226 2.201506115 4 359
68 -1.029914674 -2.885648568 -3.035082384 6 137
69 -2.482393241 -6.993660174 -4.874650816 2 370
70 -0.6066044553 -6.908968042 2.492838862 4 196
71 -0.1422670849 -2.372655161 8.394659964 8 151
72 1.988227349 -4.895044871 -1.904022719 1 21
73 4.000090801 -0.8684559946 -4.295235973 6 212
74 -1.249810939 -0.1964712067 -0.455365154 7 102
75 1.510891505 -2.791589729 0.6658998548 1 141
76 0.259759314 0.02643963578 -2.204858223 1 24
77 1.791441333 -7.090766909 0.9637145503 3 170
78 -2.640177003 -0.1335109077 4.048550909 2 300
79 3.204001931 -3.865529917 -12.62370265 9 199
80 -2.080061565 -1.00360148 -5.9071611 9 291
81 -1.454456246 -4.56220579 3.852605418 2 250
82 -3.451573906 -4.638741026 6.392428552 5 56
83 -0.9164897038 0.8347000778 -3.77010017 5 322
84 -0.9898018772 -3.955191441 9.033400771 1 405
85 -3.215943133 0.6396778863 -8.088847059 9 52
86 1.887346723 -0.3757330898 5.60723664 8 412
87 -1.212605241 -3.634833529 9.852661553 9 218
88 0.4575703952 -3.776889826 -0.6357365465 4 204
89 1.000206856 1.180461692 7.183484281 10 434
90 3.991980672 -3.119541182 -2.291057293 5 494
91 3.148096685 0.660074024 1.646828236 7 135
92 3.757003436 1.691072709 6.652611785 8 290
93 -1.175184091 0.253947733 4.06789772 7 330
94 -2.01266346 -2.002267507 3.634406206 4 130
95 1.51803949 -3.716944736 0.1083323603 4 252
96 0.8290121091 -0.2510098422 -12.32325899 10 471
97 3.937185551 1.459966877 -6.114019644 1 174
98 -3.801581684 0.2704052951 -9.950370169 9 428
99 4.400536621 0.8708577188 7.693085685 3 283
100 0.7096924036 -0.5269381067 -3.745397591 9 355
101 0.2508476169 -0.7492739626 3.87423747 5 224
102 -3.534026131 -0.004468464466 5.936880748 4 67
103 -2.998507112 1.488914432 7.114224987 8 490
104 3.013481459 -2.430649097 4.905641151 5 19
105 1.29990998 1.372236091 5.140207336 5 295
106 -2.554991052 0.3667789531 4.058210317 6 167
107 1.604996355 1.897431544 -2.404243031 5 86
108 -1.709317063 -1.566626738 0.7385438477 4 379
109 3.642224823 3.047397772 -1.991450451 6 325
110 1.012055276 3.340189534 -8.405616583 9 185
111 -0.6044469527 2.578390749 2.820234273 7 174
112 -5.137864626 -6.041315028 -2.535793246 5 437
113 -2.830461138 -3.571706532 5.896343651 3 472
114 -4.18962624 0.4124038906 3.016398105 1 342
115 3.703531729 -1.59199886 0.6119123439 5 243
116 3.464679941 -0.1827347235 -2.706711571 8 270
117 1.774740376 -2.63823051 -6.881772605 1 473
118 3.817522564 -2.294583722 -7.454056997 3 341
119 1.512707243 -3.294511769 1.108873827 2 274
120 -0.1773438082 -0.1749495435 -3.68120562 10 217
121 4.465674328 1.408029384 2.027454682 7 444
122 -4.73226317 -0.4261750287 0.04445084115 3 259
123 0.8995207935 -0.442625909 -4.00290775 10 414
124 -6.602214586 0.5788109601 -2.058200147 5 389
125 2.348234861 -0.1642339903 11.32243203 3 178
126 1.54582728 3.38436122 -4.171230038 3 46
127 -1.905651022 -1.836132133 -0.7930360171 4 335
128 5.598196528 2.047962088 -1.73243339 5 25
129 1.265043109 1.511219884 0.8665718372 2 486
130 -1.723838267 -1.863922558 5.741547344 1 260
131 7.610274096 -1.562715475 -3.391796789 7 334
132 0.7449588712 -1.604241525 2.640844276 9 449
133 3.009271787 -1.908787681 -0.3977628628 9 326
134 0.9787898265 -1.891361104 -1.503800683 7 254
135 -0.3423956537 -2.023428678 -3.516815231 2 122
136 0.4672862382 -0.5797585737 4.022959292 7 213
137 3.773642784 -4.956875872 3.45526531 6 72
138 0.5116408932 -6.410042268 9.577598538 8 187
139 -0.9206394109 1.804133584 -10.39355528 4 176
140 0.6690137223 -3.044678386 -5.777699596 6 409
141 -3.402169216 -2.09622438 3.949329111 3 277
142 1.093508501 0.5896416473 1.289395682 7 28
143 0.03018293409 -0.4813050486 -5.165644548 4 492
144 2.855021128 -2.854722329 10.13158759 6 16
145 0.55912314 -3.231235567 0.1702737766 1 75
146 2.766385371 0.7562061905 -7.903760785 7 134
147 -4.2539117 -2.732028921 -3.133456924 9 135
148 -0.02521865821 3.136838471 3.119469423 4 85
149 -1.314909327 -2.01034686 -5.37864583 1 405
150 2.697082352 0.4900173748 -1.642320691 6 29
151 3.504188385 -2.736478881 -8.128141955 4 122
152 2.173806393 -2.020463585 -2.610113285 9 360
153 0.6231183111 -0.8465809738 0.9140431289 1 192
154 -0.8319773373 -1.827977706 -2.844159501 5 227
155 -0.307455075 -2.475200733 -4.542229996 10 456
156 3.319616071 1.079863401 2.893022188 4 103
157 1.442338582 -0.46234663 6.506135029 6 385
158 -4.269178223 -1.4174265 -2.349592638 7 117
159 11.08371198 2.720121038 -3.773052279 5 58
160 2.88651955 -1.927467624 -4.362905513 10 7
161 -0.8630325965 -2.855807777 1.954610226 7 140
162 0.6140463988 4.912806043 12.14822075 2 250
163 -1.097776223 0.2253234193 1.214721325 2 339
164 0.9169856765 3.468961752 7.511568162 7 39
165 1.734757298 -4.134320531 5.340284841 8 244
166 0.9575602778 0.6440991922 0.6879637417 2 109
167 2.082999527 1.31744689 -5.323603858 6 224
168 3.195904559 0.3078603374 -0.4490530943 6 57
169 7.788457731 -2.893718465 1.892125032 5 337
170 3.31851417 -2.344642912 5.349601675 3 74
171 -0.2204259924 -4.246707265 -2.602960653 6 450
172 -1.50120167 2.182364771 -8.134973938 7 206
173 3.521490144 -4.335186154 -4.311449169 6 120
174 0.1920023785 0.4446393539 -6.83571217 8 421
175 2.364651102 1.041621559 -1.477080683 9 351
176 4.064711382 0.112822511 -1.750193036 1 302
177 5.547667872 1.625080625 5.703752789 9 109
178 -2.156390532 -1.231003224 0.971017726 2 469
179 0.7468041114 2.998206892 -4.246549821 3 364
180 -0.5937925872 -1.725175117 2.853594058 5 182
181 4.634091423 -1.900343956 -1.780078552 7 121
182 4.322784234 -0.02800943323 6.109667386 9 284
183 -0.1633564937 -3.376072412 6.843331712 9 177
184 3.71381409 -0.7255510755 -7.401172117 8 58
185 -0.4429915355 -4.098828825 -7.622207517 10 140
186 4.626637212 -2.818210453 4.412723695 10 473
187 4.726871471 0.7745512648 0.5160346967 1 39
188 -2.048713617 3.680638344 -4.480787577 2 10
189 3.842468777 -2.574667154 -2.872819641 3 468
190 0.5202150677 -1.626831576 -1.589327429 10 24
191 5.855603553 -0.7628637139 2.27208959 1 351
192 -1.445359447 -2.724954853 -5.498680548 7 20
193 5.981980075 -1.143839398 -5.883979682 10 188
194 -0.1827975719 -0.3986336841 -1.978201805 7 22
195 0.05038021122 0.8063352507 7.57544709 3 477
196 -1.995681238 3.074781574 5.98454445 3 200
197 2.084807282 -0.8099439468 10.55498706 5 88
198 -3.6163859 -1.837353511 -7.479888428 6 138
199 1.525489218 -2.768055416 4.605818247 2 173
200 1.419045296 -0.9217519312 5.081529479 3 129
201 -1.179410644 -1.803355335 -6.510394446 7 299
202 8.174817451 1.671196348 3.670962478 10 77
203 3.969456614 -0.3769042039 3.153340024 2 14
204 -3.510521251 -0.4885388305 -0.1366992208 8 300
205 1.826937405 -4.720226667 2.499582127 10 23
206 6.637764989 -1.502159384 9.719033941 4 192
207 2.659828708 -2.735513466 2.525907392 1 432
208 0.5974903032 -2.354154695 -3.491373781 9 226
209 -0.1991238014 -4.099492796 2.955639501 5 259
210 -1.783764881 0.03963428585 -3.416409343 2 428
211 1.287861768 -2.243651063 -5.27727244 3 106
212 1.403375596 -0.2015628868 4.298266741 3 153
213 6.221427915 -0.1592802635 1.335541533 10 126
214 1.770599719 0.8969798302 -1.161481263 9 304
215 2.890182445 -0.5036565754 -5.341659507 9 500
216 5.527484607 -0.5349384451 8.021744779 6 155
217 2.070373964 3.151711974 6.786808143 6 458
218 2.728852887 3.388031134 -1.044565559 9 391
219 1.323276056 -0.5078843246 -2.81834301 4 480
220 0.5127549501 0.5688528009 1.024423159 7 89
221 0.5475059809 0.0006064243935 -6.007079703 3 412
222 -2.072244172 1.380716259 -11.6743955 7 362
223 -5.268566173 0.9045962182 1.188833589 6 43
224 2.205885669 -1.313354087 0.0749670464 8 247
225 -0.1952395385 0.5891383005 -0.6954121981 9 320
226 1.990337079 1.345415097 -11.44780535 6 403
227 0.3818823362 0.3822895971 -2.296285885 9 336
228 2.146984162 -2.617218909 -1.879975304 8 491
229 3.969698912 0.3608257731 2.057759671 7 144
230 2.330484191 -0.9218226967 8.908476341 2 203
231 -2.409326765 2.467323569 6.514733622 3 438
232 -0.374073876 -0.294374026 2.034232889 4 102
233 -0.9638425012 1.177179979 1.442536631 8 125
234 -3.874384794 1.309375722 -4.105864573 9 74
235 2.508982046 3.816586775 -6.455756243 8 168
236 -1.384928178 -0.02861923486 2.165773286 3 158
237 0.4020590467 -1.744230452 2.900547132 7 237
238 -3.715680216 -0.9877393895 -0.8595520771 6 12
239 -0.2161101704 -2.056877246 3.254626375 6 68
240 1.667725533 -1.84107345 -1.732818476 7 57
241 0.8498293351 0.3100162782 -0.6953755836 7 278
242 -0.0406952062 0.3021411409 -5.035428466 9 282
243 7.972474491 -2.214267653 6.679337266 3 34
244 -1.640587005 -1.924811465 -1.98742659 1 213
245 2.030601793 -0.5475607999 3.707307433 3 234
246 1.427936203 -0.8044619249 -1.610625965 3 411
247 2.32765769 1.832532742 2.367470721 2 441
248 -1.025494308 0.03107451884 -4.572442628 7 245
249 -0.2847716563 -1.422046008 -0.3680170925 2 44
250 6.91002173 -1.453395911 -8.136692442 5 43
251 2.525896821 -3.448849921 0.003623200085 1 44
252 2.350368048 -4.017176863 2.706762456 1 17
253 -3.731041131 -2.109840253 -3.536905592 4 230
254 0.01777154189 0.5309864861 5.302710102 3 290
255 4.660792805 -1.974615035 5.50948696 5 62
/*
* @author zz85 / https://github.com/zz85
* @author mrdoob / http://mrdoob.com
* Running this will allow you to drag three.js objects around the screen.
* Modified for D3.JS / THREE.JS stack by
* @author vkuchinov / https://github.com/vkuchinov
*
*/
THREE.DragControls = function ( _object, _camera) {
var _domElement = document;
var rect = {
bottom: window.innerHeight,
height: window.innerHeight,
left: 0,
right: window.innerWidth,
top: 0,
width: window.innerWidth,
x: 0,
y: 0
};
var _plane = new THREE.Plane();
var _raycaster = new THREE.Raycaster();
var _mouse = new THREE.Vector2();
var _offset = new THREE.Vector3();
var _intersection = new THREE.Vector3();
var _mouseDown = false;
var scope = this;
function activate() {
_domElement.addEventListener( "mousedown", onDocumentMouseDown, false );
_domElement.addEventListener( "mousemove", onDocumentMouseMove, false );
_domElement.addEventListener( "mouseup", onDocumentMouseUp, false );
_domElement.addEventListener( "mouseleave", onDocumentMouseUp, false );
}
function deactivate() {
_domElement.removeEventListener( "mousedown", onDocumentMouseDown, false );
_domElement.removeEventListener( "mousemove", onDocumentMouseMove, false );
_domElement.removeEventListener( "mouseup", onDocumentMouseUp, false );
_domElement.removeEventListener( "mouseleave", onDocumentMouseUp, false );
}
function dispose() { deactivate(); }
function onDocumentMouseDown( event_ ) {
_mouseDown = true;
event_.preventDefault();
_raycaster.setFromCamera( _mouse, _camera );
if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) {
_offset.copy( _intersection ).sub( _object.position );
}
document.getElementById("scatter3D").style.cursor = "move";
scope.dispatchEvent( { type: "dragstart", object: _object } );
}
function onDocumentMouseMove( event_ ) {
_plane.normal = new THREE.Vector3(_camera.position.x, _camera.position.y, _camera.position.z);
_plane.normal.normalize();
event_.preventDefault();
if(_mouseDown){
_mouse.x = ( ( event.clientX - rect.left ) / rect.width ) * 2 - 1;
_mouse.y = - ( ( event.clientY - rect.top ) / rect.height ) * 2 + 1;
_raycaster.setFromCamera( _mouse, _camera );
if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) {
_object.position.copy( _intersection.sub( _offset ) );
}
scope.dispatchEvent( { type: "drag", object: _object } );
return;
}
}
function onDocumentMouseUp( event_ ) {
_mouseDown = false;
event_.preventDefault();
scope.dispatchEvent( { type: "dragend", object: _object } );
document.getElementById("scatter3D").style.cursor = "auto";
}
// API
this.enabled = false;
this.activate = activate;
this.deactivate = deactivate;
this.dispose = dispose;
// Backward compatibility
this.setObjects = function () {
console.error( "THREE.DragControls: setObjects() has been removed." );
};
this.on = function ( type, listener ) {
console.warn( "THREE.DragControls: on() has been deprecated. Use addEventListener() instead." );
scope.addEventListener( type, listener );
};
this.off = function ( type, listener ) {
console.warn( "THREE.DragControls: off() has been deprecated. Use removeEventListener() instead." );
scope.removeEventListener( type, listener );
};
this.notify = function ( type ) {
console.error( "THREE.DragControls: notify() has been deprecated. Use dispatchEvent() instead." );
scope.dispatchEvent( { type: type } );
};
};
THREE.DragControls.prototype = Object.create( THREE.EventDispatcher.prototype );
THREE.DragControls.prototype.constructor = THREE.DragControls;
<!doctype html>
<html>
<head>
<title>D3.JS & THREE.JS Hybrid Scatter Plot 3D</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/98/three.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/stats.js/r16/Stats.min.js"></script>
<script src="TrackballControls.js"></script>
<script src="DragControls4D3.js"></script>
<link href="styles.css" rel="stylesheet"/>
</head>
<body>
<button style="right:0" id="switch" class="ui" onclick="switchUI()">Rotate</button>
<svg id="scatter3D" width="0" height="0"></svg>
<div id="tooltip">
<div id="x_value" class="xl">{x}</div>
<div id="y_value" class="yl">{y}</div>
<div id="z_value" class="zl">{z}</div>
</div>
<script src="main.js"></script>
</body>
</html>
var stats, scene, renderer, controls, dragControls;
var camera, cameraControl;
var pointCLoud, points = [], axes = [], labels = [];
var offset = new THREE.Vector3();
var drag = false;
var svg;
var colors = d3.scaleOrdinal(d3.schemeCategory10);
var radiusRatio = 2.5E-2;
var w = window.innerWidth, h = window.innerHeight;
d3.csv("data.csv", function(error_, data_) {
if (error_) throw error_;
data_.forEach(function(d_) {
points.push({x: Number(d_.x), y: Number(d_.y), z: Number(d_.z), c: d_.colorid, r: d_.radius })
});
svg = d3.select("svg").attr("width", w).attr("height", h);
init();
});
function init(){
renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
stats = new Stats();
stats.domElement.style.position = "absolute";
stats.domElement.style.bottom = "0px";
document.body.appendChild( stats.domElement );
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.set(256, 512, 512);
camera.lookAt(0, 0, 0)
scene.add(camera);
var cloud = new THREE.Geometry();
points.forEach(function(d_) {
var p = new THREE.Vector3(d_.x * 16 , d_.y * 16 , d_.z * 16);
cloud.vertices.push( p );
});
var cloudmaterial = new THREE.PointsMaterial( { color: 0xFFFFFF } );
pointCloud = new THREE.Points( cloud , cloudmaterial );
scene.add( pointCloud );
controls = new THREE.TrackballControls( camera );
controls.rotateSpeed = 1.0;
controls.zoomSpeed = 1.2;
controls.panSpeed = 0.8;
controls.noZoom = false;
controls.noPan = true;
controls.staticMoving = true;
controls.dynamicDampingFactor = 0.3;
dragControls = new THREE.DragControls( pointCloud, camera );
animate();
}
function animate() {
var sv = new THREE.Vector3(0, 0, 0);
var scale = 768.0 / sv.subVectors(new THREE.Vector3(0, 0, 0), camera.position).length();
axes = [
[new THREE.Vector3(0, 0, 0), new THREE.Vector3(196.0 / scale, 1E-5, 1E-5)],
[new THREE.Vector3(0, 0, 0), new THREE.Vector3(1E-5, 196.0 / scale, 1E-5)],
[new THREE.Vector3(0, 0, 0), new THREE.Vector3(1E-5, 1E-5, 196.0 / scale)]
];
labels = [
new THREE.Vector3(212.0 / scale, 1E-5, 1E-5),
new THREE.Vector3(1E-5, 212.0 / scale, 1E-5),
new THREE.Vector3(1E-5, 1E-5, 222.0 / scale)
];
controls.update();
renderSVG(pointCloud.position);
requestAnimationFrame( animate );
stats.update();
renderer.render( scene, camera );
}
function toScreenXY (x_, y_, z_, camera_) {
var p = new THREE.Vector3(x_, y_, z_);
var vector = p.project(camera_);
vector.x = (vector.x + 1) / 2 * window.innerWidth;
vector.y = -(vector.y - 1) / 2 * window.innerHeight;
return {x: vector.x, y: vector.y };
}
function renderSVG(p_){
var projectedPoints = svg.selectAll("circle").data(pointCloud.geometry.vertices);
projectedPoints.enter()
.append("circle")
.attr("class", "_3d")
.attr("opacity", 1.0)
.merge(projectedPoints)
.attr("cx", function(d_){ return valueValidation(toScreenXY(d_.x + p_.x, d_.y + p_.y, d_.z + p_.z, camera).x); })
.attr("cy", function(d_){ return valueValidation(toScreenXY(d_.x + p_.x, d_.y + p_.y, d_.z + p_.z, camera).y); })
.attr("r", function(d_, i_) { return points[i_].r * radiusRatio; })
.attr("fill", function(d_, i_) { return colors(points[i_].c); })
.attr("opacity", 0.5)
.on("mouseover", function(d_){
if(!drag){
d3.select(this).attr("stroke", "#DEDEDE")
.attr("stroke-width", 6);
var t = d3.select("#tooltip")
.style("left", d3.select(this).attr("cx") + "px")
.style("top", d3.select(this).attr("cy") + "px");
document.getElementById("x_value").innerHTML = "x: " + (d_.x / 16.0).toFixed(2);
document.getElementById("y_value").innerHTML = "y: " + (d_.y / 16.0).toFixed(2);
document.getElementById("z_value").innerHTML = "z: " + (d_.z / 16.0).toFixed(2);
}
})
.on("mouseout", function(d_){
if(!drag){
d3.select(this).attr("stroke", "none")
var t = d3.select("#tooltip")
.style("left","-999px")
.style("top", "-999px");
}
})
projectedPoints.exit().remove();
var projectedAxes = svg.selectAll("line").data(axes);
projectedAxes.enter()
.append("line")
.attr("class", "_3d")
.merge(projectedAxes)
.attr("x1", function(d_) { return valueValidation(toScreenXY(d_[0].x, d_[0].y, d_[0].z, camera).x); })
.attr("y1", function(d_) { return valueValidation(toScreenXY(d_[0].x, d_[0].y, d_[0].z, camera).y); })
.attr("x2", function(d_) { return valueValidation(toScreenXY(d_[1].x, d_[1].y, d_[1].z, camera).x); })
.attr("y2", function(d_,){ return valueValidation(toScreenXY(d_[1].x, d_[1].y, d_[1].z, camera).y); })
.attr("stroke", "#000000");
projectedAxes.exit().remove();
var projectedLabels = svg.selectAll("text").data(labels);
projectedLabels.enter()
.append("text")
.attr("class", "_3d")
.merge(projectedLabels)
.attr("dx", function (d_) {
var dx = valueValidation(toScreenXY(d_.x, d_.y, d_.z, camera).x);
return dx;
})
.attr("dy", function (d_) {
var dy = valueValidation(toScreenXY(d_.x, d_.y, d_.z, camera).y);
return dy;
})
.attr("font-family", "sans-serif")
.text(function(d_, i_) { console.log(i_); var l = ["x", "y", "z"]; return l[i_]; });
projectedLabels.exit().remove();
}
function valueValidation(v_){ return v_ || 0; };
function switchUI(){
var b = document.getElementById("switch");
if(b.innerHTML == "Rotate") {
b.innerHTML = "Drag";
controls.enabled = false;
drag = true;
dragControls.enabled = true;
dragControls.activate();
}
else {
b.innerHTML = "Rotate";
controls.enabled = true;
drag = false;
dragControls.enabled = false;
dragControls.deactivate();
}
}
body {
overflow: hidden;
margin: 0;
}
svg { z-index: 1; }
.ui { position: absolute; z-index: 2; }
button{ margin: 20px; }
.xl { grid-area: xv; text-align: center; }
.yl { grid-area: yv; text-align: center; }
.zl { grid-area: zv; text-align: center; }
#tooltip > div {
color: #FEFEFE; font-size: 12px;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none
-ms-user-select: none;
user-select: none;
}
#tooltip {
position: absolute;
text-align: center;
width: 192px;
font: 10px sans-serif;
border: 0px;
border-radius: 6px;
opacity: 0.6;
top: -9999px;
left: -9999px;
display: grid;
grid-template-areas: "xv yv zv";
grid-gap: 10px;
background-color: #000000;
padding: 10px;
z-index: 99;
}
/**
* @author Eberhard Graether / http://egraether.com/
* @author Mark Lundin / http://mark-lundin.com
* @author Simone Manini / http://daron1337.github.io
* @author Luca Antiga / http://lantiga.github.io
*/
THREE.TrackballControls = function ( object, domElement ) {
var _this = this;
var STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 };
this.object = object;
this.domElement = ( domElement !== undefined ) ? domElement : document;
// API
this.enabled = true;
this.screen = { left: 0, top: 0, width: 0, height: 0 };
this.rotateSpeed = 1.0;
this.zoomSpeed = 1.2;
this.panSpeed = 0.3;
this.noRotate = false;
this.noZoom = false;
this.noPan = false;
this.staticMoving = false;
this.dynamicDampingFactor = 0.2;
this.minDistance = 0;
this.maxDistance = Infinity;
this.keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ];
// internals
this.target = new THREE.Vector3();
var EPS = 0.000001;
var lastPosition = new THREE.Vector3();
var _state = STATE.NONE,
_prevState = STATE.NONE,
_eye = new THREE.Vector3(),
_movePrev = new THREE.Vector2(),
_moveCurr = new THREE.Vector2(),
_lastAxis = new THREE.Vector3(),
_lastAngle = 0,
_zoomStart = new THREE.Vector2(),
_zoomEnd = new THREE.Vector2(),
_touchZoomDistanceStart = 0,
_touchZoomDistanceEnd = 0,
_panStart = new THREE.Vector2(),
_panEnd = new THREE.Vector2();
// for reset
this.target0 = this.target.clone();
this.position0 = this.object.position.clone();
this.up0 = this.object.up.clone();
// events
var changeEvent = { type: 'change' };
var startEvent = { type: 'start' };
var endEvent = { type: 'end' };
// methods
this.handleResize = function () {
if ( this.domElement === document ) {
this.screen.left = 0;
this.screen.top = 0;
this.screen.width = window.innerWidth;
this.screen.height = window.innerHeight;
} else {
var box = this.domElement.getBoundingClientRect();
// adjustments come from similar code in the jquery offset() function
var d = this.domElement.ownerDocument.documentElement;
this.screen.left = box.left + window.pageXOffset - d.clientLeft;
this.screen.top = box.top + window.pageYOffset - d.clientTop;
this.screen.width = box.width;
this.screen.height = box.height;
}
};
this.handleEvent = function ( event ) {
if ( typeof this[ event.type ] == 'function' ) {
this[ event.type ]( event );
}
};
var getMouseOnScreen = ( function () {
var vector = new THREE.Vector2();
return function getMouseOnScreen( pageX, pageY ) {
vector.set(
( pageX - _this.screen.left ) / _this.screen.width,
( pageY - _this.screen.top ) / _this.screen.height
);
return vector;
};
}() );
var getMouseOnCircle = ( function () {
var vector = new THREE.Vector2();
return function getMouseOnCircle( pageX, pageY ) {
vector.set(
( ( pageX - _this.screen.width * 0.5 - _this.screen.left ) / ( _this.screen.width * 0.5 ) ),
( ( _this.screen.height + 2 * ( _this.screen.top - pageY ) ) / _this.screen.width ) // screen.width intentional
);
return vector;
};
}() );
this.rotateCamera = ( function() {
var axis = new THREE.Vector3(),
quaternion = new THREE.Quaternion(),
eyeDirection = new THREE.Vector3(),
objectUpDirection = new THREE.Vector3(),
objectSidewaysDirection = new THREE.Vector3(),
moveDirection = new THREE.Vector3(),
angle;
return function rotateCamera() {
moveDirection.set( _moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0 );
angle = moveDirection.length();
if ( angle ) {
_eye.copy( _this.object.position ).sub( _this.target );
eyeDirection.copy( _eye ).normalize();
objectUpDirection.copy( _this.object.up ).normalize();
objectSidewaysDirection.crossVectors( objectUpDirection, eyeDirection ).normalize();
objectUpDirection.setLength( _moveCurr.y - _movePrev.y );
objectSidewaysDirection.setLength( _moveCurr.x - _movePrev.x );
moveDirection.copy( objectUpDirection.add( objectSidewaysDirection ) );
//contrain to X axis only
axis.crossVectors( moveDirection, _eye ).normalize();
axis.z = axis.x = 0.0;
angle *= _this.rotateSpeed;
quaternion.setFromAxisAngle( axis, angle );
_eye.applyQuaternion( quaternion );
_this.object.up.applyQuaternion( quaternion );
_lastAxis.copy( axis );
_lastAngle = angle;
} else if ( ! _this.staticMoving && _lastAngle ) {
_lastAngle *= Math.sqrt( 1.0 - _this.dynamicDampingFactor );
_eye.copy( _this.object.position ).sub( _this.target );
quaternion.setFromAxisAngle( _lastAxis, _lastAngle );
_eye.applyQuaternion( quaternion );
_this.object.up.applyQuaternion( quaternion );
}
_movePrev.copy( _moveCurr );
};
}() );
this.zoomCamera = function () {
var factor;
if ( _state === STATE.TOUCH_ZOOM_PAN ) {
factor = _touchZoomDistanceStart / _touchZoomDistanceEnd;
_touchZoomDistanceStart = _touchZoomDistanceEnd;
_eye.multiplyScalar( factor );
} else {
factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * _this.zoomSpeed;
if ( factor !== 1.0 && factor > 0.0 ) {
_eye.multiplyScalar( factor );
}
if ( _this.staticMoving ) {
_zoomStart.copy( _zoomEnd );
} else {
_zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor;
}
}
};
this.panCamera = ( function() {
var mouseChange = new THREE.Vector2(),
objectUp = new THREE.Vector3(),
pan = new THREE.Vector3();
return function panCamera() {
mouseChange.copy( _panEnd ).sub( _panStart );
if ( mouseChange.lengthSq() ) {
mouseChange.multiplyScalar( _eye.length() * _this.panSpeed );
pan.copy( _eye ).cross( _this.object.up ).setLength( mouseChange.x );
pan.add( objectUp.copy( _this.object.up ).setLength( mouseChange.y ) );
_this.object.position.add( pan );
_this.target.add( pan );
if ( _this.staticMoving ) {
_panStart.copy( _panEnd );
} else {
_panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( _this.dynamicDampingFactor ) );
}
}
};
}() );
this.checkDistances = function () {
if ( ! _this.noZoom || ! _this.noPan ) {
if ( _eye.lengthSq() > _this.maxDistance * _this.maxDistance ) {
_this.object.position.addVectors( _this.target, _eye.setLength( _this.maxDistance ) );
_zoomStart.copy( _zoomEnd );
}
if ( _eye.lengthSq() < _this.minDistance * _this.minDistance ) {
_this.object.position.addVectors( _this.target, _eye.setLength( _this.minDistance ) );
_zoomStart.copy( _zoomEnd );
}
}
};
this.update = function () {
_eye.subVectors( _this.object.position, _this.target );
if ( ! _this.noRotate ) {
_this.rotateCamera();
}
if ( ! _this.noZoom ) {
_this.zoomCamera();
}
if ( ! _this.noPan ) {
_this.panCamera();
}
_this.object.position.addVectors( _this.target, _eye );
_this.checkDistances();
_this.object.lookAt( _this.target );
if ( lastPosition.distanceToSquared( _this.object.position ) > EPS ) {
_this.dispatchEvent( changeEvent );
lastPosition.copy( _this.object.position );
}
};
this.reset = function () {
_state = STATE.NONE;
_prevState = STATE.NONE;
_this.target.copy( _this.target0 );
_this.object.position.copy( _this.position0 );
_this.object.up.copy( _this.up0 );
_eye.subVectors( _this.object.position, _this.target );
_this.object.lookAt( _this.target );
_this.dispatchEvent( changeEvent );
lastPosition.copy( _this.object.position );
};
// listeners
function keydown( event ) {
if ( _this.enabled === false ) return;
window.removeEventListener( 'keydown', keydown );
_prevState = _state;
if ( _state !== STATE.NONE ) {
return;
} else if ( event.keyCode === _this.keys[ STATE.ROTATE ] && ! _this.noRotate ) {
_state = STATE.ROTATE;
} else if ( event.keyCode === _this.keys[ STATE.ZOOM ] && ! _this.noZoom ) {
_state = STATE.ZOOM;
} else if ( event.keyCode === _this.keys[ STATE.PAN ] && ! _this.noPan ) {
_state = STATE.PAN;
}
}
function keyup( event ) {
if ( _this.enabled === false ) return;
_state = _prevState;
window.addEventListener( 'keydown', keydown, false );
}
function mousedown( event ) {
if ( _this.enabled === false ) return;
event.preventDefault();
event.stopPropagation();
if ( _state === STATE.NONE ) {
_state = event.button;
}
if ( _state === STATE.ROTATE && ! _this.noRotate ) {
_moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
_movePrev.copy( _moveCurr );
} else if ( _state === STATE.ZOOM && ! _this.noZoom ) {
_zoomStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
_zoomEnd.copy( _zoomStart );
} else if ( _state === STATE.PAN && ! _this.noPan ) {
_panStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
_panEnd.copy( _panStart );
}
document.addEventListener( 'mousemove', mousemove, false );
document.addEventListener( 'mouseup', mouseup, false );
_this.dispatchEvent( startEvent );
}
function mousemove( event ) {
if ( _this.enabled === false ) return;
event.preventDefault();
event.stopPropagation();
if ( _state === STATE.ROTATE && ! _this.noRotate ) {
_movePrev.copy( _moveCurr );
_moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
} else if ( _state === STATE.ZOOM && ! _this.noZoom ) {
_zoomEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
} else if ( _state === STATE.PAN && ! _this.noPan ) {
_panEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
}
}
function mouseup( event ) {
if ( _this.enabled === false ) return;
event.preventDefault();
event.stopPropagation();
_state = STATE.NONE;
document.removeEventListener( 'mousemove', mousemove );
document.removeEventListener( 'mouseup', mouseup );
_this.dispatchEvent( endEvent );
}
function mousewheel( event ) {
if ( _this.enabled === false ) return;
event.preventDefault();
event.stopPropagation();
switch ( event.deltaMode ) {
case 2:
// Zoom in pages
_zoomStart.y -= event.deltaY * 0.025;
break;
case 1:
// Zoom in lines
_zoomStart.y -= event.deltaY * 0.01;
break;
default:
// undefined, 0, assume pixels
_zoomStart.y -= event.deltaY * 0.00025;
break;
}
_this.dispatchEvent( startEvent );
_this.dispatchEvent( endEvent );
}
function touchstart( event ) {
if ( _this.enabled === false ) return;
switch ( event.touches.length ) {
case 1:
_state = STATE.TOUCH_ROTATE;
_moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
_movePrev.copy( _moveCurr );
break;
default: // 2 or more
_state = STATE.TOUCH_ROTATE;
_moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
_movePrev.copy( _moveCurr );
break;
break;
}
_this.dispatchEvent( startEvent );
}
function touchmove( event ) {
if ( _this.enabled === false ) return;
event.preventDefault();
event.stopPropagation();
switch ( event.touches.length ) {
case 1:
_movePrev.copy( _moveCurr );
_moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
break;
default: // 2 or more
var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
_touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy );
var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2;
var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2;
_panEnd.copy( getMouseOnScreen( x, y ) );
break;
}
}
function touchend( event ) {
if ( _this.enabled === false ) return;
switch ( event.touches.length ) {
case 0:
_state = STATE.NONE;
break;
case 1:
_state = STATE.TOUCH_ROTATE;
_moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
_movePrev.copy( _moveCurr );
break;
}
_this.dispatchEvent( endEvent );
}
function contextmenu( event ) {
if ( _this.enabled === false ) return;
event.preventDefault();
}
this.dispose = function() {
this.domElement.removeEventListener( 'contextmenu', contextmenu, false );
this.domElement.removeEventListener( 'mousedown', mousedown, false );
//this.domElement.removeEventListener( 'wheel', mousewheel, false );
//this.domElement.removeEventListener( 'touchstart', touchstart, false );
//this.domElement.removeEventListener( 'touchend', touchend, false );
//this.domElement.removeEventListener( 'touchmove', touchmove, false );
document.removeEventListener( 'mousemove', mousemove, false );
document.removeEventListener( 'mouseup', mouseup, false );
window.removeEventListener( 'keydown', keydown, false );
window.removeEventListener( 'keyup', keyup, false );
};
this.domElement.addEventListener( 'contextmenu', contextmenu, false );
this.domElement.addEventListener( 'mousedown', mousedown, false );
//this.domElement.addEventListener( 'wheel', mousewheel, false );
//this.domElement.addEventListener( 'touchstart', touchstart, false );
//this.domElement.addEventListener( 'touchend', touchend, false );
//this.domElement.addEventListener( 'touchmove', touchmove, false );
window.addEventListener( 'keydown', keydown, false );
window.addEventListener( 'keyup', keyup, false );
this.handleResize();
// force an update at start
this.update();
};
THREE.TrackballControls.prototype = Object.create( THREE.EventDispatcher.prototype );
THREE.TrackballControls.prototype.constructor = THREE.TrackballControls;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment