зеркало из https://github.com/mozilla/riskHeatMap.git
rename/remove
This commit is contained in:
Родитель
3dd64bcb2a
Коммит
03abe6e9e1
|
@ -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);
|
||||
});
|
Загрузка…
Ссылка в новой задаче