This commit is contained in:
Jeff Bryner 2018-11-26 13:36:17 -08:00
Родитель 3dd64bcb2a
Коммит 03abe6e9e1
1 изменённых файлов: 0 добавлений и 559 удалений

Просмотреть файл

@ -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);
});