513 lines
14 KiB
JavaScript
Executable File
513 lines
14 KiB
JavaScript
Executable File
/*
|
|
* Class SVGMap
|
|
*
|
|
* @elementId - the html DOM id
|
|
*
|
|
*
|
|
* Requires project.js, heatmap.js
|
|
*/
|
|
|
|
var SVGMap = function(elementId, mapConf) {
|
|
|
|
var vBoxWidth = 1200,
|
|
vBoxHeight = 900,
|
|
bgWidth = 900,
|
|
bgHeight = 1500,
|
|
overlayID = "telemetry",
|
|
tooltip = new CustomTooltip(elementId + "-tooltip"),
|
|
clicked;
|
|
|
|
// Initialise the map
|
|
loadSvg();
|
|
|
|
function loadSvg() {
|
|
//block();
|
|
$.ajax({
|
|
url: mapConf.svgFile,
|
|
type: "GET",
|
|
data: {},
|
|
dataType: "xml",
|
|
async: false,
|
|
|
|
success: function(xml, textStatus, jqXHR) {
|
|
var importedNode;
|
|
try {
|
|
importedNode = document.importNode(xml.documentElement, true);
|
|
}
|
|
catch(e) {
|
|
// IE case
|
|
importedNode = ieImportNode(xml.documentElement, document);
|
|
}
|
|
|
|
$("#" + elementId).append(importedNode);
|
|
},
|
|
error: function (xhr, ajaxOptions, thrownError){
|
|
console.error(this.url + "\n" + ajaxOptions + " " + xhr.status + " " + thrownError );
|
|
},
|
|
complete: function() {
|
|
var svg = d3.select("#" + elementId + " svg")
|
|
.attr("width", $("#" + elementId).width())
|
|
.attr("height", $("#" + elementId).height())
|
|
.attr("enable-background", "new 0 0 " + bgWidth + " " + bgHeight)
|
|
.call(svg_interact);
|
|
|
|
drawGrid();
|
|
}
|
|
});
|
|
//unBlock();
|
|
}
|
|
|
|
function drawGrid() {
|
|
var gridStep = 500/mapConf.scale; // The step of the grid in map coordinates
|
|
|
|
var grid = d3.select("#" + elementId + " svg")
|
|
.append("svg:g")
|
|
.attr("id", "grid");
|
|
|
|
var xAxisDataNeg = d3.range(convert2WebXCoord(0), 0, -gridStep);
|
|
var xAxisDataPos = d3.range(convert2WebXCoord(0), bgWidth, gridStep);
|
|
|
|
var yAxisDataNeg = d3.range(convert2WebYCoord(0), 0, -gridStep);
|
|
var yAxisDataPos = d3.range(convert2WebYCoord(0), bgHeight, gridStep);
|
|
|
|
grid.selectAll("line.vertical")
|
|
.data(xAxisDataNeg.concat(xAxisDataPos))
|
|
.enter()
|
|
.append("svg:line")
|
|
.attr("x1", function(d) {return d;})
|
|
.attr("y1", 0)
|
|
.attr("x2", function(d) {return d;})
|
|
.attr("y2", bgHeight)
|
|
.attr("class", "map-grid");
|
|
|
|
grid.selectAll("line.horizontal")
|
|
.data(yAxisDataNeg.concat(yAxisDataPos))
|
|
.enter()
|
|
.append("svg:line")
|
|
.attr("x1", 0)
|
|
.attr("y1", function(d) {return d;})
|
|
.attr("x2", bgWidth)
|
|
.attr("y2", function(d) {return d;})
|
|
.attr("class", "map-grid");
|
|
}
|
|
|
|
function drawLayers(layersElementId) {
|
|
$("#" + layersElementId)
|
|
.append(
|
|
$("<fieldset>")
|
|
.attr("id", "map-layers")
|
|
.append(
|
|
$("<legend>").text("Map Layers")
|
|
)
|
|
)
|
|
|
|
mapConf.layers.push(overlayID);
|
|
|
|
$.each(mapConf.layers, function(i, layer) {
|
|
$("#map-layers")
|
|
.append(
|
|
$("<input>")
|
|
.attr("type", "checkbox")
|
|
.attr("id", "map-layer-" + layer)
|
|
.attr("checked", true)
|
|
.val(layer)
|
|
)
|
|
.append(
|
|
$("<label>")
|
|
.attr("for", "map-layer-" + layer)
|
|
.text(capitaliseString(layer))
|
|
)
|
|
.append($("<br />"));
|
|
});
|
|
|
|
$("#map-layers input[type=checkbox]").click(function() {
|
|
var layer = d3.select("#" + elementId + " svg").select("g#" + $(this).val());
|
|
|
|
if ($(this).is(":checked")) {
|
|
layer
|
|
.transition()
|
|
.attr("opacity", 1);
|
|
}
|
|
else {
|
|
layer
|
|
.transition()
|
|
.attr("opacity", 0.01);
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
function show() {
|
|
$("#" + elementId)
|
|
.removeClass("hidden");
|
|
}
|
|
|
|
function hide() {
|
|
$("#" + elementId)
|
|
.addClass("hidden");
|
|
}
|
|
|
|
function removeOverlay() {
|
|
d3.select("#" + elementId + " svg")
|
|
.selectAll("g#" + overlayID)
|
|
.transition()
|
|
.attr("opacity", 0.01)
|
|
.remove();
|
|
}
|
|
|
|
function drawCircles(dataObjects, dataFunctions) {
|
|
var minPercent = 2,
|
|
maxPercent = 12;
|
|
|
|
var circles = d3.select("#" + elementId + " svg")
|
|
.append("svg:g")
|
|
.attr("id", overlayID);
|
|
|
|
var valuesSum = d3.sum(dataObjects, function(d) {
|
|
return dataFunctions.getValue(d);
|
|
});
|
|
|
|
circles
|
|
.selectAll("circle")
|
|
.data(dataObjects)
|
|
.enter()
|
|
.append("svg:circle")
|
|
.attr("id", function(d, i) {return "circle_" + i;})
|
|
.attr("cx", function(d) {
|
|
return convert2WebXCoord(dataFunctions.getX(d));
|
|
})
|
|
.attr("cy", function(d) {
|
|
return convert2WebYCoord(dataFunctions.getY(d));
|
|
})
|
|
.attr("r", function(d, i) {
|
|
var radius = 50/mapConf.scale;
|
|
if (dataFunctions.getValue(d)) {
|
|
radius = (dataFunctions.getValue(d)/valuesSum) * 100;
|
|
|
|
radius = (radius < minPercent) ? minPercent : radius;
|
|
radius = (radius > maxPercent) ? maxPercent : radius;
|
|
}
|
|
|
|
return radius;
|
|
})
|
|
.attr("fill", function(d) {return dataFunctions.getColour(d)})
|
|
.attr("stroke", "rgba(0, 0, 0, 0.5)")
|
|
.attr("stroke-width", 0.2)
|
|
.attr("opacity", 0.85)
|
|
.on("mouseover", function(d, i) {
|
|
tooltip.showTooltip(dataFunctions.getTooltipContent(d), d3.event);
|
|
})
|
|
.on("mouseout", function(d, i) {
|
|
tooltip.hideTooltip();
|
|
});
|
|
/*
|
|
.append("title")
|
|
.text(function(d, i) {
|
|
var title = ""
|
|
+ "Name: " + dataFunctions.getName(d)
|
|
+ ", \nLocation: (x=" + dataFunctions.getX(d)
|
|
+ ", y="
|
|
+ dataFunctions.getY(d)
|
|
+ ")";
|
|
|
|
return title;
|
|
|
|
});
|
|
*/
|
|
|
|
}
|
|
|
|
function drawHeatmap(dataObjects, dataFunctions) {
|
|
|
|
var min = 0, max = 0;
|
|
$.each(dataObjects, function(i, d) {
|
|
min = ((dataFunctions.getValue(d) < min) ? dataFunctions.getValue(d) : min);
|
|
max = ((dataFunctions.getValue(d) > max) ? dataFunctions.getValue(d) : max);
|
|
});
|
|
var med = ((min+max)/2);
|
|
|
|
//var thresholdSlider = $("#" + domElement + " div.threshold-slider");
|
|
//thresholdSlider.slider("option", "min", min);
|
|
//thresholdSlider.slider("option", "max", max);
|
|
//thresholdSlider.slider("refresh");
|
|
|
|
//var valuesSlider = $("#" + domElement + " div.value-slider");
|
|
//valuesSlider.slider("option", "min", min);
|
|
//valuesSlider.slider("option", "max", max);
|
|
//$("#" + domElement + " span.value-min").text(min.toFixed(2));
|
|
//$("#" + domElement + " span.value-max").text(max.toFixed(2));
|
|
|
|
//if ((valuesSlider.slider("values")[0] == min) && (valuesSlider.slider("values")[1] == min)) {
|
|
// valuesSlider.slider("values", 0, min);
|
|
// valuesSlider.slider("values", 1, max);
|
|
//}
|
|
//valuesSlider.slider("refresh");
|
|
|
|
//rangeSliderValues = valuesSlider.slider("values");
|
|
//var heatmapSliderValues = $("#" + domElement + " div.heatmap-slider-placeholder").slider("values");
|
|
/*
|
|
var colourInterpolator = getColourInterpolator(rangeSliderValues[0], rangeSliderValues[1],
|
|
heatmapSliderValues[1], heatmapSliderValues[0]);
|
|
*/
|
|
var colourInterpolator = getColourInterpolator(min, max, 0, 120);
|
|
|
|
//$("#" + domElement + " span.heatmap-gradient" + " span.heatmap-min").text(rangeSliderValues[0].toFixed(2));
|
|
//$("#" + domElement + " span.heatmap-gradient" + " span.heatmap-med").text(
|
|
// ((rangeSliderValues[0] + rangeSliderValues[1]) / 2).toFixed(2)
|
|
//);
|
|
//$("#" + domElement + " span.heatmap-gradient" + " span.heatmap-max").text(rangeSliderValues[1].toFixed(2));
|
|
|
|
|
|
var heatmap = d3.select("#" + elementId + " svg").append("svg:g")
|
|
.attr("id", overlayID)
|
|
//.attr("opacity", 0.01);
|
|
|
|
heatmap
|
|
.selectAll("rect")
|
|
.data(dataObjects)
|
|
.enter()
|
|
.append("svg:rect")
|
|
.attr("x", function(d) {
|
|
return convert2WebXCoord(dataFunctions.getX(d));
|
|
})
|
|
.attr("y", function(d) {
|
|
return convert2WebYCoord(dataFunctions.getY(d) + dataFunctions.getResolution());
|
|
})
|
|
.attr("width", dataFunctions.getResolution()/project.map.scale)
|
|
.attr("height", dataFunctions.getResolution()/project.map.scale)
|
|
.attr("fill", function(d) {
|
|
return colourInterpolator(dataFunctions.getValue(d));
|
|
})
|
|
//.attr("visibility", function(d) { // Hide if below the threshold limit
|
|
// return (valueFunc(d) >= thresholdSlider.slider("value")) ? "visible" : "hidden";
|
|
//})
|
|
.on("click", heatmapHighlight)
|
|
.append("title")
|
|
.text(dataFunctions.getTitle);
|
|
|
|
heatmap
|
|
.transition()
|
|
.attr("opacity", 1);
|
|
|
|
function heatmapHighlight(d) {
|
|
|
|
if (d && clicked !== d) {
|
|
clicked = d;
|
|
}
|
|
else {
|
|
clicked = null;
|
|
}
|
|
|
|
heatmap.selectAll("rect")
|
|
.classed("clicked", clicked && function(d) { return d === clicked; });
|
|
}
|
|
|
|
}
|
|
// end of drawHeatmap
|
|
|
|
function exportToImage() {
|
|
var canvasId = "viewport-canvas";
|
|
|
|
$("#" + canvasId).remove();
|
|
|
|
$("#" + elementId)
|
|
.append(
|
|
$("<canvas>")
|
|
.attr("id", canvasId)
|
|
.addClass("hidden")
|
|
.css("width", 1000)
|
|
.css("height", 1000)
|
|
);
|
|
|
|
var svgMarkup = $("<div>").append(
|
|
$("#" + elementId + " svg")
|
|
.clone()
|
|
.attr("width", 1000)
|
|
.attr("height", 1000)
|
|
)
|
|
.html()
|
|
//.replace(/xmlns=\"http:\/\/www\.w3\.org\/2000\/svg\"/, '') // IE fixes
|
|
//.replace(/xmlns:NS1=\"\"/, '')
|
|
//.replace(/xmlns:NS2=\"\"/, '')
|
|
//.replace(/NS2:xml:space=\"preserve\"/, '')
|
|
//.replace(/NS1:xmlns:xlink=\"http:\/\/www\.w3\.org\/1999\/xlink\"/, '');
|
|
|
|
canvg(canvasId, svgMarkup);
|
|
|
|
var canvas = document.getElementById(canvasId);
|
|
var img = canvas.toDataURL("image/png");
|
|
|
|
window.open(img);
|
|
}
|
|
|
|
function convert2WebXCoord(x) {
|
|
return ((x + mapConf.coords.x)/mapConf.scale);
|
|
}
|
|
|
|
function convert2WebYCoord(y) {
|
|
return (((-1)*y + mapConf.coords.y)/mapConf.scale);
|
|
}
|
|
|
|
function convert2WebCoords(p) {
|
|
return [convert2WebXCoord(p[0]), convert2WebYCoord(p[1])];
|
|
}
|
|
|
|
/* Copied from ie-hacks.js */
|
|
function ieImportNode(node, allChildren) {
|
|
// Manually imports node to the provided document
|
|
switch (node.nodeType) {
|
|
case document.ELEMENT_NODE:
|
|
var newNode = document.createElementNS(node.namespaceURI, node.nodeName);
|
|
if(node.attributes && node.attributes.length > 0) {
|
|
for(var i = 0, il = node.attributes.length; i < il; i++) {
|
|
newNode.setAttribute(node.attributes[i].nodeName, node.getAttribute(node.attributes[i].nodeName));
|
|
}
|
|
}
|
|
if(allChildren && node.childNodes && node.childNodes.length > 0) {
|
|
for(var i = 0, il = node.childNodes.length; i < il; i++){
|
|
newNode.appendChild(ieImportNode(node.childNodes[i], allChildren));
|
|
}
|
|
}
|
|
|
|
return newNode;
|
|
break;
|
|
|
|
case document.TEXT_NODE:
|
|
|
|
case document.CDATA_SECTION_NODE:
|
|
|
|
case document.COMMENT_NODE:
|
|
return document.createTextNode(node.nodeValue);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Copied from svg_interact.js */
|
|
function svg_interact(svg,p) {
|
|
return new svg_interactObj(svg,p);
|
|
}
|
|
|
|
function svg_interactObj(svg,p) {
|
|
var p = p ? p : {},
|
|
zoom_speed = p.zoom_speed ? p.zoom_speed : 1.15,
|
|
viewBox = svg[0][0].viewBox.baseVal;
|
|
panning = null,
|
|
current_mouse = null;
|
|
|
|
/* Panning moves the viewbox */
|
|
|
|
function mousemove(){
|
|
//console.log(current_mouse);
|
|
if (d3.mouse)
|
|
current_mouse = d3.mouse(this);
|
|
else
|
|
current_mouse = d3.svg.mouse(this);
|
|
if (panning) {
|
|
viewBox.x += (panning[0] - current_mouse[0]);
|
|
viewBox.y += (panning[1] - current_mouse[1]);
|
|
|
|
updateViewBoxAttr(viewBox);
|
|
}
|
|
};
|
|
|
|
function mousedown() {
|
|
if (d3.mouse)
|
|
panning = d3.mouse(this);
|
|
else
|
|
panning = d3.svg.mouse(this);
|
|
}
|
|
|
|
/* Zoom with mousewheel - keeping mouse position in same location*/
|
|
|
|
function wheel(event) {
|
|
var delta = 0;
|
|
|
|
if (event.wheelDelta) {
|
|
delta = event.wheelDelta/120;
|
|
}
|
|
else if (event.detail) {
|
|
delta = -event.detail/3;
|
|
}
|
|
move = (delta<0) ? -delta * zoom_speed : 1/(delta*zoom_speed);
|
|
|
|
viewBox.x = (current_mouse[0] - (current_mouse[0]-viewBox.x) * move);
|
|
viewBox.y = (current_mouse[1] - (current_mouse[1]-viewBox.y) * move);
|
|
viewBox.height = viewBox.height * move;
|
|
viewBox.width = viewBox.width * move;
|
|
|
|
updateViewBoxAttr(viewBox);
|
|
};
|
|
|
|
// Webkit fix - otherwise the viewbox is't always picking up the changes and zoom/pan fails
|
|
function updateViewBoxAttr(viewBox) {
|
|
svg[0][0].setAttribute("viewBox", viewBox.x + " " + viewBox.y + " " + viewBox.width + " " + viewBox.height);
|
|
}
|
|
|
|
if (navigator.userAgent.toLowerCase().indexOf("firefox") > -1)
|
|
svg[0][0].addEventListener("DOMMouseScroll", wheel, false);
|
|
else
|
|
svg[0][0].addEventListener("mousewheel", wheel, false);
|
|
svg[0][0].onmousewheel = wheel;
|
|
svg.onmousewheel = wheel;
|
|
|
|
svg.on("mousemove", mousemove);
|
|
svg.on("mousedown", mousedown);
|
|
|
|
d3.select(window).on("mouseup", function () { panning = null;})
|
|
svg[0][0].ondragstart = function() { return false } // Firefox fix
|
|
|
|
return svg;
|
|
}
|
|
|
|
function getColourInterpolator(minVal, maxVal, minHue, maxHue) {
|
|
// copying Michael Taschler's code of interpolation
|
|
|
|
return function (val) {
|
|
// Give the values outside the limits the limit colours
|
|
if (val < minVal)
|
|
val = minVal;
|
|
else if (val > maxVal)
|
|
val = maxVal;
|
|
|
|
// 0.0 to 1.0 scale convertion
|
|
val -= minVal;
|
|
if ((maxVal - minVal) != 0) // avoid division by zero
|
|
val /= (maxVal - minVal);
|
|
else
|
|
val = 0;
|
|
|
|
var lowHue = minHue / 360.0;
|
|
var highHue = maxHue / 360.0;
|
|
var diffHue = highHue - lowHue;
|
|
|
|
var h = (lowHue + (val * diffHue)) * 360;
|
|
var s = 0.9;
|
|
var v = 0.9;
|
|
|
|
return hsv2hsl(h, s, v);
|
|
}
|
|
}
|
|
|
|
function hsv2hsl(h, s, v) {
|
|
// Hue stays the same
|
|
// Saturation is very different between the two color spaces
|
|
// If (2-s)*v < 1 set it to s*v/((2-s)*v)
|
|
// Otherwise s*v/(2-(2-s)*v)
|
|
// Conditional is not operating with hue, it is reassigned!
|
|
s = (s * v) / ((((2-s) * v) < 1) ? ((2-s) * v) : 2-((2-s) * v));
|
|
l = (2-s)*v/2; // Lightness is (2-s)*v/2
|
|
|
|
return d3.hsl(h, s, l).toString();
|
|
}
|
|
|
|
return {
|
|
removeOverlay: removeOverlay,
|
|
drawCircles: drawCircles,
|
|
drawHeatmap: drawHeatmap,
|
|
drawLayers: drawLayers,
|
|
show: show,
|
|
hide: hide,
|
|
exportToImage: exportToImage,
|
|
};
|
|
|
|
} |