Files
2025-09-29 00:52:08 +02:00

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,
};
}