diff --git a/examples/js/example-papaparsing.js b/examples/js/example-papaparsing.js index 319d2aa..e7a89d6 100644 --- a/examples/js/example-papaparsing.js +++ b/examples/js/example-papaparsing.js @@ -1,409 +1,409 @@ -// Get the width and heigh of the SVG element. -var width = +document.getElementById('svg').clientWidth, - height = +document.getElementById('svg').clientHeight; - -var svg = d3.select("svg") - .call(d3.zoom().scaleExtent([0.0001, 1000000]).on("zoom", function () { - svg.attr("transform", d3.event.transform); - })) - .append("g"); - -var div = d3.select("body").append("div") - .attr("class", "tooltip") - .style("opacity", 0); - -var brush = d3.brush() - .extent([[-9999999, -9999999], [9999999, 9999999]]) - .on("end", brushEnded); - -svg.append("g") - .attr("class", "brush") - .call(brush); - -//var intercom = Intercom.getInstance(); - -//intercom.on("select", unSelectNodes); - -var nodes, // as in Data points - node, // as in SVG object that have all small circles on screen - props, - norm, - p1 = 0, - p2 = 0, - size, - distanceFunction, - simulation, - velocities = [], - rendering = true, // Rendering during the execution. - forceName = "forces", - springForce = false, - tooltipWidth = 0, - fileName = "", - selectedData, - clickedIndex = -1, - paused = false, - alreadyRanIterations, - tweakedVerOfLink, - manualStop = false; - -// Default parameters -var MULTIPLIER = 50, - PERPLEXITY = 30, - LEARNING_RATE = 10, - NEIGHBOUR_SIZE = 10, - SAMPLE_SIZE = 10, - PIVOTS = false, - NUM_PIVOTS = 3, - ITERATIONS = 300, - FULL_ITERATIONS = 20, - NODE_SIZE = 10, - COLOR_ATTRIBUTE = "", - FULL_NEIGHBOUR_SIZE = 10, - FULL_SAMPLE_SIZE = 10, - INTERP_ENDING_ITS = 20; - -// Create a color scheme for a range of numbers. -var color = d3.scaleOrdinal(d3.schemeCategory10); - -$(document).ready(function() { - distanceFunction = calculateDistance; - d3.select('#startSimulation').on('click', startHybridSimulation); - $("#HLParameters").show(); -}); - -/** - * Parse the data from the provided csv file using Papa Parse library - * @param {file} evt - csv file. - */ -function parseFile(evt) { - // Clear the previous nodes - d3.selectAll(".nodes").remove(); - springForce = false; - - fileName = evt.target.files[0].name; - Papa.parse(evt.target.files[0], { - header: true, - dynamicTyping: true, - skipEmptyLines: true, - complete: function (results) { - processData(results.data, results.error); - } - }); -} - -/** - * Process the data and pass it into D3 force simulation. - * @param {array} data - * @param {object} error - */ -function processData(data, error) { - if (error) throw error.message; - - nodes = data; - size = nodes.length; - simulation = d3.forceSimulation(); - - // Calculate normalization parameters for distance fns - norm = calculateNormalization(nodes); - props = Object.keys(nodes[0]); // Properties to consider by distance fn - - COLOR_ATTRIBUTE = props[props.length-1]; - - var opts = document.getElementById('color_attr').options; - - props.forEach(function (d) { - opts.add(new Option(d, d, (d === COLOR_ATTRIBUTE) ? true : false)); - }); - opts.selectedIndex = props.length-1; - //props.pop(); //Hide Iris index / last column from the distance function - - - //Put the nodes at (0,0) - nodes.forEach(function (d) { - d.x = 0; - d.y = 0; - }); - - addNodesToDOM(nodes); - - // Pass the nodes to the D3 force simulation. - simulation - .nodes(nodes) - .stop(); - - ticked(); -}; - -function addNodesToDOM(data) { - node = svg.append("g") - .attr("class", "nodes") - .selectAll("circle") - .data(data) - .enter().append("circle") - .attr("r", NODE_SIZE) - .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")") - // Color code the data points by a property (for Poker Hands, - // it is a CLASS property). - .attr("fill", function (d) { - return color(d[COLOR_ATTRIBUTE]); - }) - .on("mouseover", function (d) { - div.transition() - .duration(200) - .style("opacity", .9); - div.html(formatTooltip(d)) - .style("left", (d3.event.pageX) + "px") - .style("top", (d3.event.pageY - (15 * props.length)) + "px") - .style("width", (6 * tooltipWidth) + "px") - .style("height", (14 * props.length) + "px"); - highlightOnHover(d[COLOR_ATTRIBUTE]); - }) - .on("mouseout", function (d) { - div.transition() - .duration(500) - .style("opacity", 0); - node.attr("opacity", 1); - }) - .on("click", function (d) { - console.log("click", clickedIndex); - if (clickedIndex !== d.index) { - if (springForce) { - highlightNeighbours(Array.from(simulation.force(forceName).nodeNeighbours(d.index).keys())); - clickedIndex = d.index; - } - } else { - node.attr("r", NODE_SIZE).attr("stroke-width", 0); - clickedIndex = -1; - } - }); - if (selectedData) - unSelectNodes(selectedData); -} - -function ticked() { - alreadyRanIterations++; - // If rendering is selected, then draw at every iteration. - if (rendering === true) { - node // Each sub-circle in the SVG, update cx and cy - .attr("cx", function (d) { - return d.x*MULTIPLIER; - }) - .attr("cy", function (d) { - return d.y*MULTIPLIER; - }); - } - // Legacy: Emit the distribution data to allow the drawing of the bar graph - //if (springForce) { - // intercom.emit("passedData", simulation.force(forceName).distributionData()); - //} - if(manualStop && alreadyRanIterations == ITERATIONS) { - ended(); - } -} - -function ended() { - simulation.stop(); - simulation.force(forceName, null); - if (rendering !== true) { // Never drawn anything before? Now it's time. - node - .attr("cx", function (d) { - return d.x*MULTIPLIER; - }) - .attr("cy", function (d) { - return d.y*MULTIPLIER; - }); - } - - if (p1 !== 0) { - // Performance time measurement - p2 = performance.now(); - console.log("Execution time: " + (p2 - p1)); - p1 = 0; - p2 = 0; - } -} - -function brushEnded() { - var s = d3.event.selection, - results = []; - - if (s) { - - var x0 = s[0][0] - width / 2, - y0 = s[0][1] - height / 2, - x1 = s[1][0] - width / 2, - y1 = s[1][1] - height / 2; - - if (nodes) { - var sel = node.filter(function (d) { - if (d.x > x0 && d.x < x1 && d.y > y0 && d.y < y1) { - return true; - } - return false; - }).data(); - - results = sel.map(function (a) { return a.index; }); - } - - //intercom.emit("select", { name: fileName, indices: results }); - - d3.select(".brush").call(brush.move, null); - } -} - - -/** - * Format the tooltip for the data - * @param {*} node - */ -function formatTooltip(node) { - var textString = "", - temp = ""; - - tooltipWidth = 0; - props.forEach(function (element) { - temp = element + ": " + node[element] + "
"; - textString += temp; - if (temp.length > tooltipWidth) { - tooltipWidth = temp.length; - } - }); - return textString; -} - -/** - * Halt the execution. - */ -function stopSimulation() { - simulation.stop(); - if (typeof hybridSimulation !== 'undefined') { - hybridSimulation.stop(); - } -} - -/** - * Calculate the average values of the array. - * @param {array} array - * @return {number} the mean of the array. - */ -function getAverage(array) { - console.log("getAverage", array); - var total = 0; - for (var i = 0; i < array.length; i++) { - total += array[i]; - } - return total / array.length; -} - -/** - * Deselect the nodes to match the selection from other window. - * @param {*} data - */ -function unSelectNodes(data) { - selectedData = data; - if (fileName === data.name && nodes) { - node - .classed("notSelected", function (d) { - if (data.indices.indexOf(d.index) < 0) { - return true; - } - return false; - }); - } -} - - -/** - * Highlight the neighbours for neighbour and sampling algorithm - * @param {*} indices - */ -function highlightNeighbours(indices) { - node - .attr("r", function (d) { - if (indices.indexOf(d.index) >= 0) { - return NODE_SIZE * 2; - } - return NODE_SIZE; - }) - .attr("stroke-width", function (d) { - if (indices.indexOf(d.index) >= 0) { - return NODE_SIZE * 0.2 + "px"; - } - return "0px"; - }) - .attr("stroke", "white"); -} - - -/** - * Highlight all the nodes with the same class on hover - * @param {*} highlighValue - */ -function highlightOnHover(highlighValue) { - node.attr("opacity", function (d) { - return (highlighValue === d[COLOR_ATTRIBUTE]) ? 1 : 0.3; - }); -} - -/** - * Color the nodes according to given attribute. - */ -function colorToAttribute() { - node.attr("fill", function (d) { - return color(d[COLOR_ATTRIBUTE]) - }); -} - - -/** - * Update the distance range. - -function updateDistanceRange() { - if (springForce) { - simulation.force(forceName).distanceRange(SELECTED_DISTANCE); - } -} - - -/** - * Implemented pause/resume functionality - */ -function pauseUnPause() { - if (simulation) { - if (paused) { - simulation.force(forceName); - simulation.restart(); - d3.select("#pauseButton").text("Pause"); - paused = false; - } else { - simulation.stop(); - d3.select("#pauseButton").text("Resume"); - paused = true; - } - } -} - - -/** - * Average distances for each node. - * @param {*} dataNodes - * @param {*} properties - * @param {*} normalization - -function calculateAverageDistance(dataNodes, properties, normalization) { - var sum = 0, - n = nodes.length; - - for (var i = 0; i < n; i++) { - var sumNode = 0; - for (var j = 0; j < n; j++) { - if (i !== j) { - sumNode += distanceFunction(nodes[i], nodes[j], properties, normalization); - // console.log(sumNode); - } - } - sum += sumNode / (n - 1); - } - - return sum / n; -}*/ +// Get the width and heigh of the SVG element. +var width = +document.getElementById('svg').clientWidth, + height = +document.getElementById('svg').clientHeight; + +var svg = d3.select("svg") + .call(d3.zoom().scaleExtent([0.0001, 1000000]).on("zoom", function () { + svg.attr("transform", d3.event.transform); + })) + .append("g"); + +var div = d3.select("body").append("div") + .attr("class", "tooltip") + .style("opacity", 0); + +var brush = d3.brush() + .extent([[-9999999, -9999999], [9999999, 9999999]]) + .on("end", brushEnded); + +svg.append("g") + .attr("class", "brush") + .call(brush); + +//var intercom = Intercom.getInstance(); + +//intercom.on("select", unSelectNodes); + +var nodes, // as in Data points + node, // as in SVG object that have all small circles on screen + props, + norm, + p1 = 0, + p2 = 0, + size, + distanceFunction, + simulation, + velocities = [], + rendering = true, // Rendering during the execution. + forceName = "forces", + springForce = false, + tooltipWidth = 0, + fileName = "", + selectedData, + clickedIndex = -1, + paused = false, + alreadyRanIterations, + tweakedVerOfLink, + manualStop = false; + +// Default parameters +var MULTIPLIER = 50, + PERPLEXITY = 30, + LEARNING_RATE = 10, + NEIGHBOUR_SIZE = 10, + SAMPLE_SIZE = 10, + PIVOTS = false, + NUM_PIVOTS = 3, + ITERATIONS = 300, + FULL_ITERATIONS = 20, + NODE_SIZE = 10, + COLOR_ATTRIBUTE = "", + FULL_NEIGHBOUR_SIZE = 10, + FULL_SAMPLE_SIZE = 10, + INTERP_ENDING_ITS = 20; + +// Create a color scheme for a range of numbers. +var color = d3.scaleOrdinal(d3.schemeCategory10); + +$(document).ready(function() { + distanceFunction = calculateDistance; + d3.select('#startSimulation').on('click', startHybridSimulation); + $("#HLParameters").show(); +}); + +/** + * Parse the data from the provided csv file using Papa Parse library + * @param {file} evt - csv file. + */ +function parseFile(evt) { + // Clear the previous nodes + d3.selectAll(".nodes").remove(); + springForce = false; + + fileName = evt.target.files[0].name; + Papa.parse(evt.target.files[0], { + header: true, + dynamicTyping: true, + skipEmptyLines: true, + complete: function (results) { + processData(results.data, results.error); + } + }); +} + +/** + * Process the data and pass it into D3 force simulation. + * @param {array} data + * @param {object} error + */ +function processData(data, error) { + if (error) throw error.message; + + nodes = data; + size = nodes.length; + simulation = d3.forceSimulation(); + + // Calculate normalization parameters for distance fns + norm = calculateNormalization(nodes); + props = Object.keys(nodes[0]); // Properties to consider by distance fn + + COLOR_ATTRIBUTE = props[props.length-1]; + + var opts = document.getElementById('color_attr').options; + + props.forEach(function (d) { + opts.add(new Option(d, d, (d === COLOR_ATTRIBUTE) ? true : false)); + }); + opts.selectedIndex = props.length-1; + //props.pop(); //Hide Iris index / last column from the distance function + + + //Put the nodes at (0,0) + nodes.forEach(function (d) { + d.x = 0; + d.y = 0; + }); + + addNodesToDOM(nodes); + + // Pass the nodes to the D3 force simulation. + simulation + .nodes(nodes) + .stop(); + + ticked(); +}; + +function addNodesToDOM(data) { + node = svg.append("g") + .attr("class", "nodes") + .selectAll("circle") + .data(data) + .enter().append("circle") + .attr("r", NODE_SIZE) + .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")") + // Color code the data points by a property (for Poker Hands, + // it is a CLASS property). + .attr("fill", function (d) { + return color(d[COLOR_ATTRIBUTE]); + }) + .on("mouseover", function (d) { + div.transition() + .duration(200) + .style("opacity", .9); + div.html(formatTooltip(d)) + .style("left", (d3.event.pageX) + "px") + .style("top", (d3.event.pageY - (15 * props.length)) + "px") + .style("width", (6 * tooltipWidth) + "px") + .style("height", (14 * props.length) + "px"); + highlightOnHover(d[COLOR_ATTRIBUTE]); + }) + .on("mouseout", function (d) { + div.transition() + .duration(500) + .style("opacity", 0); + node.attr("opacity", 1); + }) + .on("click", function (d) { + console.log("click", clickedIndex); + if (clickedIndex !== d.index) { + if (springForce) { + highlightNeighbours(Array.from(simulation.force(forceName).nodeNeighbours(d.index).keys())); + clickedIndex = d.index; + } + } else { + node.attr("r", NODE_SIZE).attr("stroke-width", 0); + clickedIndex = -1; + } + }); + if (selectedData) + unSelectNodes(selectedData); +} + +function ticked() { + alreadyRanIterations++; + // If rendering is selected, then draw at every iteration. + if (rendering === true) { + node // Each sub-circle in the SVG, update cx and cy + .attr("cx", function (d) { + return d.x*MULTIPLIER; + }) + .attr("cy", function (d) { + return d.y*MULTIPLIER; + }); + } + // Legacy: Emit the distribution data to allow the drawing of the bar graph + //if (springForce) { + // intercom.emit("passedData", simulation.force(forceName).distributionData()); + //} + if(manualStop && alreadyRanIterations == ITERATIONS) { + ended(); + } +} + +function ended() { + simulation.stop(); + simulation.force(forceName, null); + if (rendering !== true) { // Never drawn anything before? Now it's time. + node + .attr("cx", function (d) { + return d.x*MULTIPLIER; + }) + .attr("cy", function (d) { + return d.y*MULTIPLIER; + }); + } + + if (p1 !== 0) { + // Performance time measurement + p2 = performance.now(); + console.log("Execution time: " + (p2 - p1)); + p1 = 0; + p2 = 0; + } +} + +function brushEnded() { + var s = d3.event.selection, + results = []; + + if (s) { + + var x0 = s[0][0] - width / 2, + y0 = s[0][1] - height / 2, + x1 = s[1][0] - width / 2, + y1 = s[1][1] - height / 2; + + if (nodes) { + var sel = node.filter(function (d) { + if (d.x > x0 && d.x < x1 && d.y > y0 && d.y < y1) { + return true; + } + return false; + }).data(); + + results = sel.map(function (a) { return a.index; }); + } + + //intercom.emit("select", { name: fileName, indices: results }); + + d3.select(".brush").call(brush.move, null); + } +} + + +/** + * Format the tooltip for the data + * @param {*} node + */ +function formatTooltip(node) { + var textString = "", + temp = ""; + + tooltipWidth = 0; + props.forEach(function (element) { + temp = element + ": " + node[element] + "
"; + textString += temp; + if (temp.length > tooltipWidth) { + tooltipWidth = temp.length; + } + }); + return textString; +} + +/** + * Halt the execution. + */ +function stopSimulation() { + simulation.stop(); + if (typeof hybridSimulation !== 'undefined') { + hybridSimulation.stop(); + } +} + +/** + * Calculate the average values of the array. + * @param {array} array + * @return {number} the mean of the array. + */ +function getAverage(array) { + console.log("getAverage", array); + var total = 0; + for (var i = 0; i < array.length; i++) { + total += array[i]; + } + return total / array.length; +} + +/** + * Deselect the nodes to match the selection from other window. + * @param {*} data + */ +function unSelectNodes(data) { + selectedData = data; + if (fileName === data.name && nodes) { + node + .classed("notSelected", function (d) { + if (data.indices.indexOf(d.index) < 0) { + return true; + } + return false; + }); + } +} + + +/** + * Highlight the neighbours for neighbour and sampling algorithm + * @param {*} indices + */ +function highlightNeighbours(indices) { + node + .attr("r", function (d) { + if (indices.indexOf(d.index) >= 0) { + return NODE_SIZE * 2; + } + return NODE_SIZE; + }) + .attr("stroke-width", function (d) { + if (indices.indexOf(d.index) >= 0) { + return NODE_SIZE * 0.2 + "px"; + } + return "0px"; + }) + .attr("stroke", "white"); +} + + +/** + * Highlight all the nodes with the same class on hover + * @param {*} highlighValue + */ +function highlightOnHover(highlighValue) { + node.attr("opacity", function (d) { + return (highlighValue === d[COLOR_ATTRIBUTE]) ? 1 : 0.3; + }); +} + +/** + * Color the nodes according to given attribute. + */ +function colorToAttribute() { + node.attr("fill", function (d) { + return color(d[COLOR_ATTRIBUTE]) + }); +} + + +/** + * Update the distance range. + +function updateDistanceRange() { + if (springForce) { + simulation.force(forceName).distanceRange(SELECTED_DISTANCE); + } +} + + +/** + * Implemented pause/resume functionality + */ +function pauseUnPause() { + if (simulation) { + if (paused) { + simulation.force(forceName); + simulation.restart(); + d3.select("#pauseButton").text("Pause"); + paused = false; + } else { + simulation.stop(); + d3.select("#pauseButton").text("Resume"); + paused = true; + } + } +} + + +/** + * Average distances for each node. + * @param {*} dataNodes + * @param {*} properties + * @param {*} normalization + +function calculateAverageDistance(dataNodes, properties, normalization) { + var sum = 0, + n = nodes.length; + + for (var i = 0; i < n; i++) { + var sumNode = 0; + for (var j = 0; j < n; j++) { + if (i !== j) { + sumNode += distanceFunction(nodes[i], nodes[j], properties, normalization); + // console.log(sumNode); + } + } + sum += sumNode / (n - 1); + } + + return sum / n; +}*/