diff --git a/heatmap/heatmap.js b/heatmap/heatmap.js deleted file mode 100644 index 41c5747..0000000 --- a/heatmap/heatmap.js +++ /dev/null @@ -1,559 +0,0 @@ -hideInfoPanel = function() { - d3.select("#rightpanel").classed("on",false); -} - -showInfoPanel = function() { - d3.select("#rightpanel").classed("on",true); -} - -clearInfoPanel = function() { - d3.select("#itemName").node().innerText=""; - d3.select("#detailsLayer").select("*").remove(); -} - -// utility functions -getFirstWord = function (str) { - if (str.indexOf(' ') === -1) - return str; - else { - words=str.split(/\s+/) - return _.first(words,5).join(' '); - } -}; - -substringMatcher = function(strs) { - return function findMatches(q, cb) { - var matches, substringRegex; - - // an array that will be populated with substring matches - matches = []; - - // regex used to determine if a string contains the substring `q` - substrRegex = new RegExp(q, 'i'); - - // iterate through the pool of strings and for any string that - // contains the substring `q`, add it to the `matches` array - $.each(strs, function(i, str) { - if (substrRegex.test(str)) { - matches.push(str); - } - }); - cb(matches); - }; -}; - -function toDegrees(rad) { - return rad * (180/Math.PI); -} - -d3.json("risks.json", function(error, jsondata) { - console.log(error); - - // three.js initialization - var camera, renderer, controls; - var width = window.innerWidth, height = window.innerHeight; - var container = d3.select('#container').node(); - - // set up the scene components - camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 3000 ); - camera.position.x = 750; - camera.position.y = 450; - camera.position.z = 750; - - // add controls - controls = new THREE.OrbitControls( camera ); - controls.rotateSpeed = .5; - controls.zoomSpeed = .5; - controls.panSpeed = 0.1; - controls.enableKeys=false; - controls.addEventListener( 'change', render ); - - // Cubes/sizes - var boxWidth = 75; - var boxHeight = 50; - var boxDepth = 70; - var squareSize = 75; - - // create the scene - scene = new THREE.Scene(); - - // track mouse clicks - raycaster = new THREE.Raycaster(); - mouse = new THREE.Vector2(); - - // make the basic box - var geometry = new THREE.BoxGeometry( boxWidth, boxHeight, boxDepth ); - - // Lights - var ambientLight = new THREE.AmbientLight( 0x404040); - - var light = new THREE.HemisphereLight( 0xffffbb, 0x080820, .5 ); - var directionalLight = new THREE.DirectionalLight( 0xffffff, 0.5 ); - directionalLight.position.set( 0, 1, 0 ); - - - renderer = new THREE.WebGLRenderer( { alpha: true , - precision: 'highp', - premultipliedAlpha: false, - antialias: true, - stencil: false, - preserveDrawingBuffer: false, - depth: true - }); - renderer.setClearColor( 0xf0f0f0 ); - renderer.setPixelRatio( window.devicePixelRatio ); - renderer.setSize( window.innerWidth, window.innerHeight ); - container.appendChild( renderer.domElement ); - - //css renderer for non webgl elements - cssRenderer = new THREE.CSS3DRenderer(); - cssRenderer.setSize(window.innerWidth,window.innerHeight); - cssRenderer.domElement.style.position = 'absolute'; - cssRenderer.domElement.style.top = 0; - container.appendChild( cssRenderer.domElement ) - - //function to move the camera towards a specific object in a cool pan - //http://stackoverflow.com/a/20558135 - function panToObject(obj) { - clearInfoPanel(); - hideInfoPanel(); - //rotate the camera when it stops to slighty ahead and slightly looking down - var rotateTween = new TWEEN.Tween( controls.target ) - .to( { x: obj.position.x, y: obj.position.y-100, z: obj.position.z-50 }, 2000 ) - .interpolation(TWEEN.Interpolation.CatmullRom) - .easing( TWEEN.Easing.Quintic.InOut ) - .start(); - - //move the camera to slight in front of the object. - var goTween = new TWEEN.Tween( camera.position ) - .to( { x: obj.position.x, y: obj.position.y, z: obj.position.z + 10 }, 2000 ) - .interpolation(TWEEN.Interpolation.CatmullRom) - .easing(TWEEN.Easing.Quintic.InOut) - .onComplete(function(){showInfoPanel()}) - .start(); - } - - // handle the risk data - // https://wiki.mozilla.org/Security/Standard_Levels - var riskColors = [ - {name: 'maximum', color: '#d04437'}, - {name: 'high', color: '#ffd351'}, - {name: 'medium', color: '#4a6785'}, - {name: 'low', color: '#cccccc'}, - {name: 'none', color: '#ffffff'}, - {name: 'unknown', color: '#ffffff'}, - ]; - var defaultColors = d3.scale.category20c(); - var riskLabels =[]; - var riskScores= []; - var riskScale; - var risks=[]; - var names=[]; - var rows=[]; - var gridPositions=[]; - data=[]; - // debug - //window.data=data; - //window.jsondata=jsondata; - - // build the item selections - // omit the 'indicator' detail in the pull down - riskSections= _.keys(_.omit(jsondata,'indicators')); - - var options = d3.select('#sections') - .selectAll('option') - .data(riskSections).enter() - .append('option') - .text(function (d) { return d; }); - - resetScene=function(){ - // clear the grid - gridPositions=[]; - // clear up the scene elements - scene.remove.apply(scene, scene.children); - // add the lights back - scene.add( ambientLight ); - scene.add( light ); - scene.add( directionalLight ); - } - - populateGrid=function(){ - // for each item, make a box and put it on the grid - data.forEach(function(d,i){ - // figure out what the color of this box should be - // start with a safe choice from the template - riskColor = d3.hcl(defaultColors(i)); - - //set the color according to the worse case risk name from our list of riskColors - try { - aColor=_.findWhere(riskColors, {name: d.label}); - if ( ! _.isUndefined(aColor)) { - riskColor=d3.hcl(aColor.color) - } - } catch(e){ - console.log(e) - } - - //lighten by risk score - scaleColor=riskColor.brighter(d.score); - d.record.color=scaleColor.toString(); - - //set the material using the color - var material = new THREE.MeshPhongMaterial( { color: scaleColor.toString(), - opacity: .7, - transparent: true, - shading: THREE.SmoothShading - } ); - var cube = new THREE.Mesh(geometry,material); - cube.record=d.record; - cube.name=d.name; - cube.score=d.score; - cube.label=d.label; - cube.scale.y = riskScale(d.score); - cube.position.y = (cube.scale.y * boxHeight)/2 ; - cube.position.x = (gridPositions[i].x * squareSize) - boxWidth/2; - cube.position.z = (gridPositions[i].z * squareSize) - boxDepth/2; - - //add a plain css element for the label/name of the box. - var cubeLabel = document.createElement('div'); - cubeLabel.className='label'; - cubeLabel.textContent=getFirstWord(d.name); - - //position the label - //var label = new THREE.CSS3DObject(labelDIV.node()); - var label = new THREE.CSS3DObject(cubeLabel); - label.position.copy(cube.position); - label.position.y=cube.scale.y*boxHeight - label.rotateX(THREE.Math.degToRad(-90)); - - scene.add(cube); - scene.add(label); - - }); - }; - - setupGrid=function(){ - // figure out how big the underlying grid should be - // to match out data - // rows should be one more than the square root of the data length - // since 2 rows holds 4 squares. - // add one for asthetics to show the grid. - rows = Math.floor(Math.sqrt(data.length))+1; - // grid - var grid = new THREE.GridHelper((squareSize * rows),rows, 0x0000ff, 0x808080 ); - scene.add( grid ); - - // calc the positions on the grid in order of closest to farthest - // for assigning boxes by their risk - - gridSize=squareSize*(rows/2); - var maxZ = gridSize/squareSize; - var maxX = gridSize/squareSize; - var lastX = maxX; - var lastZ = maxZ; - // console.log(maxZ,maxX,lastX,lastZ); - // add the starting point - gridPositions.push({x:maxX,z:maxZ}); - for (var i = maxX-1; i > maxX*-1; i--) { - // console.log('rows: ' + i) - lastX=i; - - for (var z=lastZ;z >lastX-1;z--){ - //console.log('in maxZ'); - var position={} - position.x=i; - position.z=z; - gridPositions.push(position); - } - for (var x=lastX;x <=maxX;x++){ - //console.log('in maxX'); - var position={} - position.x=x; - position.z=z; - gridPositions.push(position); - } - - lastX--; - } - } - - clearFilters = function(){ - //run through the cubes and set opacity to viewable - scene.children.forEach(function(element,index) { - if (_.has(element,'record')) { - element.material.opacity=0.7; - } - }); - } - - mapData=function(){ - // walk the data we have chosen and setup color ranges, map key elements, etc - section = d3.select('#sections').property('value') - data = _.map(jsondata[section],function(risk) { - if ( section == 'services' ){ - risk.section=section; - return { - name: risk.name, - record: risk, - score: Number(Number(risk.recommendations).toFixed()), - label: risk.highest_risk_impact - }; - }else if (section == 'assets'){ - risk.section=section; - return { - name: risk.asset_identifier, - record: risk, - score: 0, - label: 'UNKNOWN' - } - }else{ - return null; - } - - }); - //data = _.filter(data, function(d){ return _.isObject(d)}); - // sort the data by risk score - data=_.sortBy(data, 'score'); - - // reset filters - names=[]; - riskScores=[]; - data.forEach(function(d, i) { - - if ( names.indexOf(d.name)==-1) { - names.push(d.name); - } - if ( riskScores.indexOf(d.score)==-1) { - riskScores.push(d.score); - } - }); - //reset and hook up typeahead filters - $('#nameFilter .typeahead').typeahead('destroy'); - d3.select('#btnClearCriteria').on('click')(); - d3.select('#btnFilter').node().textContent="Filter"; - $('#nameFilter .typeahead').typeahead({ - hint: true, - highlight: true, - minLength: 1 - }, - { - name: 'names', - source: substringMatcher(names) - }); - //with the list of risk scores in the data, - //setup a d3 scale to size the boxes on the heatmap accordingly. - riskScale=d3.scale.linear() - .domain([d3.min(riskScores),d3.max(riskScores)]) - .range([.5,10]) - resetScene(); - setupGrid(); - populateGrid(); - }; - - - function onWindowResize() { - - camera.left = window.innerWidth / - 2; - camera.right = window.innerWidth / 2; - camera.top = window.innerHeight / 2; - camera.bottom = window.innerHeight / - 2; - - camera.updateProjectionMatrix(); - - renderer.setSize( window.innerWidth, window.innerHeight ); - cssRenderer.setSize( window.innerWidth, window.innerHeight ); - - } - - function animate() { - - requestAnimationFrame( animate ); - controls.update(); - render(); - - } - - function render() { - - TWEEN.update(); - renderer.render( scene, camera ); - cssRenderer.render(scene,camera); - - } - - function onMouseDblClick( event ) { - event.preventDefault(); - mouse.x = ( event.clientX / renderer.domElement.clientWidth ) * 2 - 1; - mouse.y = - ( event.clientY / renderer.domElement.clientHeight ) * 2 + 1; - - raycaster.setFromCamera( mouse, camera ); - - var intersects = raycaster.intersectObjects( scene.children ); - - if ( intersects.length > 0 ) { - target=intersects[0].object; - //debug - window.target=target; - - //visual for selection - target.material.color.setHex( Math.random() * 0xffffff ); - //set color back after we've finished zooming - window.setTimeout(function(){ - target.material.color.setStyle(target.record.color); - },2000); - - //pick a spot for the camera to pan into - targetCamera = target.position.clone(); - targetCamera.setY(2 * boxHeight + target.scale.y * boxHeight); - targetCamera.setZ(target.position.z + boxDepth); - - - //make an invisible scene target for the camera - var hitgeometry = new THREE.BoxGeometry( 0, 0, 0 ); - var hitmaterial = new THREE.MeshPhongMaterial( { color: 0x0000, - opacity: .001, - transparent: true, - shading: THREE.SmoothShading - } ); - var particle = new THREE.Mesh(hitgeometry,hitmaterial); - particle.position.copy(targetCamera) - particle.scale.x = particle.scale.y = 1; - //particle.rotateOnAxis(new THREE.Vector3(0,1,0), -.70) - scene.add( particle ); - panToObject(particle); - - clearInfoPanel(); - //fill the details panel with key/values - dTable = d3.select("#detailsLayer") - .append("li") - .append("table"); - - dTable.append("thead") - .append("th") - .attr("colspan","5") - .html(target.record.name); - - tbody=dTable.append("tbody"); - _.pairs(target.record).forEach(function(d,i){ - // console.log(d) - // don't display null values or minutia like id, color - // otherwise, just display key/value - if ( d[1] && ! _.contains(['id','color','masked','timestamp_utc'], d[0]) ){ - var rows = tbody.append("tr"); - var columns = rows.selectAll("td") - .data(d) - .enter().append("td") - .classed('firstUpper',true) - .html(function(d){ - return d;}); - } - }); - if ( target.record.section == 'assets' ){ - indicators = _.where(jsondata.indicators,{"asset_id": target.record.id}); - if ( indicators.length > 0 ){ - // console.log(indicators); - indicators.forEach(function(indicator,index){ - //add event_source "Mozilla Observatory" to Web compliance - if ( indicator.event_source_name == 'Mozilla Observatory' ) { - //for each host, summarize - dTable = d3.select("#detailsLayer") - .append("li") - .append("table"); - - dTable.append("thead") - .append("th") - .attr("colspan","5") - .html(target.record.asset_identifier + ': Grade ' + indicator.details.grade); - - tbody=dTable.append("tbody"); - indicator.details.tests.forEach(function(detail,detail_index){ - var rows = tbody.append("tr"); - - var columns = rows.selectAll("td") - .data(_.pairs(detail)) - .enter().append("td") - .html(function(d){return d[0] + ': ' + d[1];}); - }); - } // end Observatory - }); - } - } - } //end mouse intersected a box - } //end onMouseDblClick - - - function keyHandler(event) { - event = event || window.event; - if (event.keyCode == 27 || event.keyCode == 32 ) { - //reset the scene to default position - controls.reset(); - clearInfoPanel(); - hideInfoPanel(); - } - } - - function disableControls(event) { - controls.enabled=false; - } - - function enableControls(event) { - controls.enabled=true; - } - - //add our event listeners - window.addEventListener( 'resize', onWindowResize, false ); - document.addEventListener( 'dblclick', onMouseDblClick, false ); - document.addEventListener('keyup',keyHandler,false); - - //if the navigation/info panes are showing, disable the three.js scene controls - //to allow mose movements. - d3.selectAll("leftnav").node().addEventListener('mouseenter',disableControls,false); - d3.selectAll("leftnav").node().addEventListener('mouseleave',enableControls,false); - d3.selectAll("#rightpanel").node().addEventListener('mouseenter',disableControls,false); - d3.selectAll("#rightpanel").node().addEventListener('mouseleave',enableControls,false); - - d3.selectAll("#btnClearCriteria").on("click",function(){ - d3.selectAll("#name").node().value=""; - }); - - d3.selectAll("#btnFilter").on("click", function () { - btn=d3.selectAll("#btnFilter").node(); - clearFilter=true; - name=d3.selectAll("#name").node().value; - //filtered currently? - btnState=btn.textContent; - if (btnState=='Filter off') { - btn.textContent='Filter'; - } else if (btnState =='Filter') { - btn.textContent='Filter off'; - clearFilter=false; - } - if (clearFilter) { - clearFilters(); - }else{ - //run through the cubes and set opacity to non viewable - scene.children.forEach(function(element,index,array) { - if ( element.record != undefined) { - //it's a cube and we should set opacity - //console.log(element) - - //hide any cube where we don't match a filter, or the filtered field is undefined - if (name.length >1 - && ( _.isUndefined(element.name) - || element.name.indexOf(name) == -1)) { - element.material.opacity=.001; - } - } - }); - } - }); - d3.select('#sections').on('change',mapData); - //make it go - animate(); - mapData(); - -}); - -document.addEventListener('DOMContentLoaded', function () { - document.querySelector('#rightPanelClose a').addEventListener('click', hideInfoPanel); -}); \ No newline at end of file