| /* |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, |
| software distributed under the License is distributed on an |
| "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| KIND, either express or implied. See the License for the |
| specific language governing permissions and limitations |
| under the License. |
| */ |
| var layerVal = "flow"; |
| var layer = ""; |
| var refreshInt = 5000; |
| var metricChartType = 'barChart'; |
| |
| var stopTimer = false; |
| var startGraph = null; |
| var run = null; |
| var refreshedRowValues = []; |
| var stateTooltip = null; |
| var rowsTooltip = null; |
| |
| var tableMetrics = null; |
| var metricsTooltip = null; |
| |
| var tagsColors = {}; |
| var propWindow; |
| |
| var resetAll = function(bNew) { |
| clearInterval(run); |
| clearTableGraphs(); |
| d3.select("#graphLoading").style("display", "none"); |
| var selectedJob = d3.select("#jobs").node().value; |
| getCounterMetricsForJob(renderGraph, selectedJob, bNew); |
| if (bNew) { |
| startGraph(refreshInt); |
| } |
| }; |
| |
| var isRect = function(kind) { |
| var k = kind.toUpperCase(); |
| return k.endsWith("COUNTEROP") |
| || k.endsWith("STREAMSCOPE"); |
| }; |
| |
| d3.select("#jobs") |
| .on("change", function() { |
| tagsArray = []; |
| streamsTags = {}; |
| |
| resetAll(true); |
| }); |
| |
| d3.select("#layers") |
| .on("change", function() { |
| layerVal = this.value; |
| |
| clearInterval(run); |
| clearTableGraphs(); |
| |
| d3.select("#graphLoading").style("display", "none"); |
| var selectedJob = d3.select("#jobs").node().value; |
| getCounterMetricsForJob(renderGraph, selectedJob); |
| startGraph(refreshInt); |
| }); |
| |
| d3.select("#metrics") |
| .on("change", function() { |
| // determine if the just selected metric is associated with multiple oplets |
| var theOption = d3.select(this) |
| .selectAll("option") |
| .filter(function (d, i) { |
| return this.selected; |
| }); |
| |
| var chartType = d3.select("#mChartType"); |
| var multipleOps = theOption.attr("multipleops"); |
| |
| var lineChartOption = chartType.selectAll("option") |
| .filter(function (d, i){ |
| return this.value === "lineChart"; |
| }); |
| |
| var chartValue = chartType.node().value; |
| if (multipleOps === "false") { |
| lineChartOption.property("disabled", false); |
| } else { |
| // disable it even if it is not selected |
| lineChartOption.property("disabled", true); |
| if (chartValue === "lineChart") { |
| // if it is selected, deselect it and select barChart |
| chartType.node().value = "barChart"; |
| } |
| } |
| |
| |
| if (chartValue === "barChart") { |
| fetchMetrics(); |
| } else if (chartValue === "lineChart") { |
| if (multipleOps === "false") { |
| fetchLineChart(); |
| } |
| } |
| }); |
| |
| d3.select("#mChartType") |
| .on("change", function() { |
| metricChartType = this.value; |
| if (metricChartType === "barChart") { |
| fetchMetrics(); |
| } else if (metricChartType === "lineChart") { |
| fetchLineChart(); |
| } |
| }); |
| |
| d3.select("#refreshInterval") |
| .on("change", function() { |
| var isValid = this.checkValidity(); |
| if (isValid) { |
| clearInterval(run); |
| refreshInt = this.value * 1000; |
| startGraph(refreshInt); |
| } else { |
| alert("The refresh interval must be between 3 and 20 seconds"); |
| this.value = 5; |
| } |
| }); |
| |
| d3.select("#toggleTimer") |
| .on("click", function() { |
| if (stopTimer === false){ |
| stopTimer = true; |
| d3.select(this).text("Resume graph"); |
| d3.select(this) |
| .attr("class", "start") |
| .attr("title", "Resume graph") |
| } else { |
| stopTimer = false; |
| d3.select(this).text("Pause graph"); |
| d3.select(this) |
| .attr("class", "stop") |
| .attr("title", "Pause graph"); |
| } |
| }); |
| |
| var clearTableGraphs = function() { |
| d3.select("#chart").selectAll("*").remove(); |
| d3.select("#graphLoading"). |
| style("display", "block"); |
| }; |
| |
| var margin = {top: 30, right: 5, bottom: 6, left: 30}, |
| width = 860 - margin.left - margin.right, |
| height = 600 - margin.top - margin.bottom; |
| |
| |
| var svgLegend = d3.select("#graphLegend") |
| .append("svg") |
| .attr("height", 600) |
| .attr("width", 340) |
| .attr("transform", "translate(0," + 30 + ")") |
| .append("g") |
| .attr("width", 340) |
| .attr("height", 600) |
| .attr("id", "legendG") |
| .attr("transform", "translate(0," + 30 + ")"); |
| |
| var formatNumber = d3.format(",.0f"), |
| format = function(d) { return formatNumber(d) + " tuples"; }, |
| makeRandomMetrics = function() { |
| var retObjs = []; |
| var num = 2; |
| var random = d3.random.normal(400, 100); |
| var data = d3.range(num).map(random); |
| var metNames = ["Tuples transmitted", "Tuples submitted"]; |
| var i = 0; |
| data.forEach(function(d) { |
| retObjs.push({"name": metNames[i], "value": formatNumber(d)}); |
| i++; |
| }); |
| return retObjs; |
| }, |
| formatMetric = function(retObjs) { |
| var retString = ""; |
| retObjs.forEach(function(d) { |
| retString += "<div>" + d.name + ": " + d.value + "</div>"; |
| }); |
| return retString; |
| }, |
| color20 = d3.scale.category20(), |
| color10 = d3.scale.category10(), |
| // colors of d3.scale.category10() to do - just call color10.range(); |
| tupleColorRange = ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf" ]; |
| |
| |
| var showTimeout = null, |
| hideTimeout = null, |
| showTime = 800, |
| hideTime = 300; |
| |
| var clearHideTimeout = function(){ |
| if (hideTimeout){ |
| clearTimeout(hideTimeout); |
| hideTimeout = null; |
| } |
| }; |
| |
| var hideTooltip = function(d, i) { |
| clearHideTimeout(); |
| hideTimeout = setTimeout(function(){ |
| hideTimeout = null; |
| if(showTimeout){ |
| clearTimeout(showTimeout); |
| } |
| |
| tooltip.style("display", "none"); |
| |
| }, hideTime); |
| }; |
| |
| var svg = d3.select("#chart").append("svg") |
| .attr("width", width + margin.left + margin.right + 5) |
| .attr("height", height + margin.top + margin.bottom) |
| .append("g") |
| .attr("id", "parentG") |
| .attr("transform", "translate(20,10)"); |
| |
| var sankey = d3.sankey() |
| .nodeWidth(30) |
| .nodePadding(10) |
| .size([width, height]); |
| |
| var path = d3.svg.diagonal() |
| .source(function(d) { |
| return {"x":d.source.y + d.source.dy/2, "y":d.source.x + sankey.nodeWidth()/2}; |
| }) |
| .target(function(d) { |
| return {"x":d.target.y + d.target.dy/2, "y":d.target.x + sankey.nodeWidth()/2}; |
| }) |
| .projection(function(d) { |
| return [d.y, d.x]; |
| }); |
| |
| var showAllLink = d3.select("#showAll") |
| .on("click", function() { |
| displayRowsTooltip(true); |
| }) |
| .on('keydown', function() { |
| if (d3.event.keyCode && d3.event.keyCode === 13) { |
| displayRowsTooltip(true); |
| } |
| }); |
| |
| var showMetricsTimeout = null; |
| |
| d3.select("#showMetricsTable") |
| .on('mouseover', function() { |
| showMetricsTooltip(d3.event); |
| }) |
| .on('mouseout', function() { |
| hideMetricsTooltip(); |
| }) |
| .on('keydown', function() { |
| if (d3.event.keyCode && d3.event.keyCode === 13) { |
| showMetricsTooltip(d3.event); |
| } else if (d3.event.keyCode) { |
| hideMetricsTooltip(); |
| } |
| }); |
| |
| var showMetricsTooltip = function(event) { |
| if (showMetricsTimeout) { |
| clearTimeout(showMetricsTimeout); |
| } |
| var jobId = d3.select("#jobs").node().value; |
| var content = "<div style='margin:10px; width: 300px; max-height: 300px;overflow-x: scroll;'>"; |
| |
| var tableMetrics = new Array(); |
| |
| var queryString = "metrics?job=" + jobId + "&getAllMetrics=true"; |
| var getEvent = function(){ |
| return event; |
| } |
| d3.xhr(queryString, function(error, responseData) { |
| if (error) { |
| console.log("error retrieving metrics"); |
| } |
| if (responseData) { |
| if (responseData.response) { |
| var evt = getEvent(); |
| tableMetrics = JSON.parse(responseData.response); |
| |
| if (tableMetrics.ops.length === 0) { |
| content+= "<span>There are no metrics to display.</span>"; |
| } else { |
| var opMetrics = tableMetrics.ops; |
| var countArr = new Array(); |
| var rateArr = new Array(); |
| opMetrics.forEach(function(op) { |
| var metrics = op.metrics; |
| var opId = op.opId; |
| metrics.forEach(function(tm) { |
| var rateIdx = tm["name"].toUpperCase().indexOf("RATE"); |
| // if it starts with Rate it is RateUnit |
| if (opId) { |
| tm.opId = opId; |
| } |
| if (rateIdx !== -1 && rateIdx !== 0) { |
| rateArr.push(tm); |
| } else if (rateIdx !== 0){ |
| countArr.push(tm); |
| } |
| }); |
| |
| }); |
| |
| var rowIdx = 0; |
| content += "<table role='presentation'><caption>Counter oplet values</caption><tr><th tabindex=0>Operator name</th><th tabindex=0>Counter value</th></tr>"; |
| |
| var sortFunc = function(objA, objB) { |
| var a = objA.opId; |
| var b = objB.opId; |
| |
| if (a < b) { |
| return -1; |
| } else if (a > b) { |
| return 1; |
| } else { |
| return 0; |
| } |
| }; |
| |
| var startTr = "<tr><td align='center' tabindex=0>"; |
| var startTd = "<td align='center' tabindex=0>"; |
| var startTdLeft = "<td align='left' tabindex=0>"; |
| var endTd = "</td>" |
| var endTr = "</tr>"; |
| |
| if (countArr.length > 0) { |
| countArr.sort(sortFunc); |
| |
| countArr.forEach(function(counter) { |
| rowIdx++; |
| |
| var opName = counter["opId"].substring("OP_".length, counter["opId"].length); |
| content += startTr + opName + endTd; |
| |
| content += startTd + counter["value"] + endTd + endTr; |
| }); |
| |
| content += "</table>"; |
| } |
| |
| if (rateArr.length > 0) { |
| rateArr.sort(sortFunc); |
| content += "<table role='presentation'><caption>Rate meter oplet values</caption><tr><th tabindex=0>Operator name</th><th tabindex=0>Rate type</th><th tabindex=0>Rate value</th></tr>"; |
| rateArr.forEach(function(rate) { |
| rowIdx++; |
| |
| var opName = rate["opId"].substring("OP_".length, rate["opId"].length); |
| content += startTr + opName + endTd; |
| var rateName = rate["name"]; |
| if (rateName.toUpperCase().endsWith("RATE")) { |
| rateName = rateName.substring(0, rateName.length - "RATE".length); |
| } |
| content += startTdLeft + rateName + endTd; |
| var num = Number.parseFloat(rate["value"]); |
| content += startTd + num.toFixed(4) + endTd + endTr; |
| }); |
| content += "</table>"; |
| } |
| |
| } |
| content += "</div>"; |
| |
| var evtX; |
| var evtY; |
| var target = evt.target; |
| if (target) { |
| if (target.x && target.y) { |
| evtX = target.x; |
| evtY = target.y; |
| } else if (target.offsetLeft && target.offsetTop) { |
| evtX = target.offsetLeft; |
| evtY = target.offsetTop; |
| } |
| } |
| |
| metricsTooltip |
| .html(content) |
| .style("left", (evtX + 140) + "px") |
| .style("top", evtY +"px") |
| .style("padding-x", 22) |
| .style("padding-y", 10) |
| .style("display", "block"); |
| |
| d3.select("#showMetricsTable").node().blur(); |
| metricsTooltip.node().focus(); |
| |
| |
| metricsTooltip |
| .on("keydown", function() { |
| // Escape key closes the popup |
| if (d3.event.keyCode && d3.event.keyCode === 27) { |
| hideMetricsTooltip(); |
| } |
| }); |
| |
| metricsTooltip |
| .on("mouseover", function() { |
| if (showMetricsTimeout) { |
| clearTimeout(showMetricsTimeout); |
| } |
| }); |
| |
| metricsTooltip |
| .on("mouseout", function() { |
| hideMetricsTooltip(); |
| }); |
| |
| } |
| } |
| }); |
| }; |
| |
| |
| |
| var hideMetricsTooltip = function() { |
| if (showMetricsTimeout) { |
| clearTimeout(showMetricsTimeout); |
| } |
| showMetricsTimeout = setTimeout(function() { |
| metricsTooltip |
| .style("display", "none"); |
| }, 400); |
| }; |
| |
| var tooltip = d3.select("body") |
| .append("div") |
| .attr("class", "tooltip") |
| .style("display", "none") |
| .on('mouseover', function(d,i) { |
| clearHideTimeout(); |
| }) |
| .on('mouseout', function(d,i) { |
| hideTooltip(null,i); |
| }); |
| |
| |
| var showTooltip = function(content, d, i, event) { |
| clearHideTimeout(); |
| |
| if(showTimeout){ |
| clearTimeout(showTimeout); |
| } |
| |
| var leftOffset = d.invocation.kind.toUpperCase().endsWith("COUNTEROP") ? 100 : 350; |
| |
| showTimeout = setTimeout(function(){ |
| showTimeout = null; |
| if(content){ |
| tooltip.html(content); |
| |
| tooltip.style("padding-x", -22) |
| .style("padding-y", 0) |
| .style("left", (event.pageX - leftOffset) + "px") |
| .style("top", event.pageY +"px") |
| .style("display", "block"); |
| } |
| |
| }, showTime); |
| |
| |
| }; |
| |
| var refreshTable = true; |
| |
| var displayRowsTooltip = function(newRequest) { |
| var rows = makeRows(); |
| var tableHdr = ""; |
| var content = ""; |
| var firstTime = true; |
| var firstKey = true; |
| var headerStr = ""; |
| |
| for (var key in rows) { |
| var row = rows[key]; |
| content += "<tr>"; |
| for (var newKey in row) { |
| if (firstTime) { |
| if (newKey === "Name") { |
| headerStr += "<th style='width: 100px;' tabindex=0>" + newKey + "</th>"; |
| } else { |
| headerStr += "<th style='width: 150px;' tabindex=0>" + newKey + "</th>"; |
| } |
| } |
| |
| if (newKey === "Name" || newKey === "Tuple count" || newKey === "Oplet kind"){ |
| content += "<td class='center100' tabindex=0>" + row[newKey] + "</td>"; |
| } else { |
| content += "<td class='left' tabindex=0 style='white-space:nowrap;'>" + row[newKey] + "</td>"; |
| } |
| } |
| firstTime = false; |
| if (firstKey) { |
| headerStr += "</tr>"; |
| firstKey = false; |
| } |
| content += "</tr>"; |
| } |
| |
| |
| if (newRequest) { |
| var htmlStr = "<html><head><title>Oplet properties</title><link rel='stylesheet' type='text/css' href='resources/css/main.css'></head>" + |
| "<body>"; |
| var buttonStr = '<button title="Pause table refresh" id="pauseTableRefresh" type="button">Pause table refresh</button>'; |
| var closeWinStr = '<button title="Close window" id="closeTablePropsWindow" type="button">Close window</button>'; |
| var tableHdr = "<table id='allPropsTable' tabindex=0 style='margin: 10px;table-layout:fixed;word-wrap: break-word;'><caption>Oplet properties</caption>"; |
| |
| var str = htmlStr + buttonStr + closeWinStr + tableHdr + headerStr + content + "</table></body><html>"; |
| propWindow = window.open("", "Properties", "width=825,height=500,scrollbars=yes,dependent=yes"); |
| propWindow.document.body.innerHTML = ""; |
| propWindow.document.write(str); |
| propWindow.document.body.focus(); |
| propWindow.onunload = function() { |
| propWindow = null; |
| }; |
| window.onunload = function() { |
| if (propWindow) { |
| propWindow.close(); |
| } |
| }; |
| |
| var btn = propWindow.document.getElementById("pauseTableRefresh"); |
| btn.onclick = |
| function() { |
| if (this.innerHTML === "Pause table refresh") { |
| this.innerHTML = "Resume table refresh"; |
| this.title = "Resume table refresh"; |
| refreshTable = false; |
| } else { |
| this.innerHTML = "Pause table refresh"; |
| this.title = "Pause table refresh"; |
| refreshTable = true; |
| } |
| }; |
| |
| var closeBtn = propWindow.document.getElementById("closeTablePropsWindow"); |
| closeBtn.onclick = |
| function() { |
| if (propWindow) { |
| propWindow.close(); |
| } |
| }; |
| } else { |
| if (refreshTable) { |
| if (typeof(propWindow) === "object") { |
| d3.select("#allPropsTable").innerHTML = content; |
| propWindow.document.body.focus(); |
| } |
| } |
| } |
| }; |
| var showStateTimeout = null; |
| |
| var showStateTooltip = function(event) { |
| if (showStateTimeout) { |
| clearTimeout(showStateTimeout); |
| } |
| var jobId = d3.select("#jobs").node().value; |
| var jobObj = jobMap[jobId]; |
| var content = "<div style='margin:10px'><table><caption>Job State</caption>"; |
| |
| var rowPfx = "stateData"; |
| var startTd = "<td align='center' tabindex=0>"; |
| var endTd = "</td>" |
| var endTr = "</tr>"; |
| |
| var rowIdx = 0; |
| |
| for (var key in jobObj) { |
| rowIdx++; |
| content += "<tr>" + startTd; |
| |
| var idx = key.indexOf("State"); |
| var errIdx = key.indexOf("Error"); |
| |
| if ( idx !== -1) { |
| var name = key.substring(0, idx) + " " + key.substring(idx, key.length).toLowerCase(); |
| var val = jobObj[key]; |
| var value = val.substring(0,1) + val.substring(1,val.length).toLowerCase(); |
| content += name + endTd; |
| content += "<td tabindex=0 id='" + rowPfx + rowIdx + "'>" + value + endTd + endTr; |
| } |
| |
| if (errIdx !== -1) { |
| var name = key.substring(0, errIdx) + " " + key.substring(errIdx, key.length).toLowerCase(); |
| var val = jobObj[key]; |
| var value = ""; |
| if (val) { |
| value = val.substring(0,1) + val.substring(1,val.length).toLowerCase(); |
| } |
| content += name + endTd; |
| content += "<td tabindex=0 id='" + rowPfx + rowIdx + "'>" + value + endTd + endTr; |
| } |
| |
| if (idx === -1 && errIdx === -1) { |
| content += key + endTd; |
| content += "<td tabindex=0 id='" + rowPfx + rowIdx + "'>" + jobObj[key] + endTd + endTr; |
| } |
| |
| } |
| |
| var evtX; |
| var evtY; |
| var target = d3.event.target; |
| if (target) { |
| if (target.x && target.y) { |
| evtX = target.x; |
| evtY = target.y; |
| } else if (target.offsetLeft && target.offsetTop) { |
| evtX = target.offsetLeft; |
| evtY = target.offsetTop; |
| } |
| } |
| |
| content += "</div>"; |
| |
| stateTooltip |
| .html(content) |
| .style("left", (evtX - 160) + "px") |
| .style("top", evtY - 20 +"px") |
| .style("padding-x", 22) |
| .style("padding-y", 10) |
| .style("display", "block"); |
| |
| d3.select("#stateImg").node().blur(); |
| stateTooltip.node().focus(); |
| |
| var lastNode = "#" + rowPfx + rowIdx; |
| |
| d3.select(lastNode) |
| .on("keydown", function() { |
| // the next tab closes the popup |
| if (d3.event.keyCode && d3.event.keyCode === 9) { |
| hideStateTooltip(); |
| } |
| }); |
| |
| |
| stateTooltip |
| .on("keydown", function() { |
| // Escape key closes the popup |
| if (d3.event.keyCode && d3.event.keyCode === 27) { |
| hideStateTooltip(); |
| } |
| }); |
| |
| stateTooltip |
| .on("mouseover", function() { |
| if (showStateTimeout) { |
| clearTimeout(showStateTimeout); |
| } |
| }); |
| |
| stateTooltip |
| .on("mouseout", function() { |
| hideStateTooltip(); |
| }); |
| |
| }; |
| |
| var hideStateTooltip = function() { |
| if (showStateTimeout) { |
| clearTimeout(showStateTimeout); |
| } |
| |
| stateTooltip.node().blur(); |
| // the focus needs to be put on the stateImg so that when the |
| // tooltip is closed, the loss of focus on that retains the tab order. |
| // Now the next element to focus on is 'layers' |
| d3.select("#stateImg").node().focus(); |
| |
| showStateTimeout = setTimeout(function() { |
| stateTooltip |
| .style("display", "none"); |
| }, 400) |
| }; |
| |
| var makeRows = function() { |
| var nodes = refreshedRowValues !== null ? refreshedRowValues : sankey.nodes(); |
| var theRows = []; |
| nodes.forEach(function(n) { |
| var sourceStreamTupleCountsMap = new Map(); |
| var sourceStreamAliasesMap = new Map(); |
| var sourceStreamTagsMap = new Map(); |
| n.targetLinks.forEach(function(trg) { |
| var source = trg.source.idx.toString(); |
| var sourceLinks = trg.source.sourceLinks; |
| for (var i = 0; i < sourceLinks.length; i++) { |
| if (trg.sourceId == sourceLinks[i].sourceId && trg.targetId == sourceLinks[i].targetId) { |
| if (layer == "static") { |
| sourceStreamTupleCountsMap.set(source, parseInt(sourceLinks[i].flowValue)); |
| } else { |
| sourceStreamTupleCountsMap.set(source, parseInt(sourceLinks[i].value)); |
| } |
| if (sourceLinks[i].hasOwnProperty("alias")) { |
| sourceStreamAliasesMap.set(source, sourceLinks[i].alias); |
| } else { |
| sourceStreamAliasesMap.set(source, ""); |
| } |
| } |
| } |
| |
| if (trg.tags && trg.tags.length > 0) { |
| sourceStreamTagsMap.set(source, trg.tags); |
| } else { |
| sourceStreamTagsMap.set(source, []); |
| } |
| }); |
| |
| var targetStreamTupleCountsMap = new Map(); |
| var targetStreamAliasesMap = new Map(); |
| var targetStreamTagsMap = new Map(); |
| n.sourceLinks.forEach(function(src) { |
| var target = src.target.idx.toString(); |
| var targetLinks = src.target.targetLinks; |
| for (var i = 0; i < targetLinks.length; i++) { |
| if (src.sourceId == targetLinks[i].sourceId && src.targetId == targetLinks[i].targetId) { |
| if (layer == "static") { |
| targetStreamTupleCountsMap.set(target, parseInt(targetLinks[i].flowValue)); |
| } else { |
| targetStreamTupleCountsMap.set(target, parseInt(targetLinks[i].value)); |
| } |
| if (targetLinks[i].hasOwnProperty("alias")) { |
| targetStreamAliasesMap.set(target, targetLinks[i].alias); |
| } else { |
| targetStreamAliasesMap.set(target, ""); |
| } |
| } |
| } |
| |
| if (src.tags && src.tags.length > 0) { |
| targetStreamTagsMap.set(target, src.tags); |
| } else { |
| targetStreamTagsMap.set(target, []); |
| } |
| }); |
| |
| var kind = parseOpletKind(n.invocation.kind); |
| var index = kind.indexOf("("); |
| var kindName = kind.substring(0, index-1); |
| var kindPkg = kind.substring(index); |
| |
| var value = ""; |
| if (n.derived === true) { |
| value = "Not applicable - counter not present"; |
| } else if (n.realValue === 0 && value === 0.45) { |
| value = 0; |
| } else { |
| value = formatNumber(n.value); |
| } |
| |
| var sStreamString = ""; |
| var inTupleCount = 0; |
| if (sourceStreamAliasesMap.size == 0 && sourceStreamTagsMap.size == 0) { |
| sStreamString = "None"; |
| } else { |
| for (var [id, alias] of sourceStreamAliasesMap) { |
| var tupleCount = sourceStreamTupleCountsMap.get(id); |
| inTupleCount += parseInt(tupleCount); |
| var tags = sourceStreamTagsMap.get(id); |
| sStreamString += "[" + id + "] "; |
| |
| sStreamString += "<strong>tuples</strong>: " + formatNumber(tupleCount); |
| |
| if (alias != "") { |
| sStreamString += ", <strong>alias</strong>: " + alias; |
| } |
| if (tags.length != 0) { |
| sStreamString += ", <strong>tags</strong>: " + tags.join(", "); |
| } |
| |
| sStreamString += "<br/>"; |
| } |
| } |
| |
| var tStreamString = ""; |
| var outTupleCount = 0; |
| if (targetStreamAliasesMap.size == 0 && targetStreamTagsMap.size == 0) { |
| tStreamString = "None"; |
| } else { |
| for (var [id, alias] of targetStreamAliasesMap) { |
| var tupleCount = targetStreamTupleCountsMap.get(id); |
| outTupleCount += parseInt(tupleCount); |
| var tags = targetStreamTagsMap.get(id); |
| tStreamString += "[" + id + "] "; |
| |
| tStreamString += "<strong>tuples</strong>: " + formatNumber(tupleCount); |
| |
| if (alias != "") { |
| tStreamString += ", <strong>alias</strong>: " + alias; |
| } |
| if (tags.length != 0) { |
| tStreamString += ", <strong>tags</strong>: " + tags.join(", "); |
| } |
| |
| tStreamString += "<br/>"; |
| } |
| } |
| |
| var rowObj = {"Name": n.idx, "Oplet kind": kindName + "<br/>" + kindPkg, |
| "Tuple count": "In: " + formatNumber(inTupleCount) + "<br/>Out: " + formatNumber(outTupleCount), |
| "Source streams": sStreamString, "Target streams": tStreamString}; |
| theRows.push(rowObj); |
| }); |
| return theRows; |
| }; |
| |
| // Convert HSV to RGB |
| var hsvToRGB = function(h, s, v) { |
| var r, g, b, i, f, p, q, t; |
| if (arguments.length === 1) { |
| s = h.s, v = h.v, h = h.h; |
| } |
| i = Math.floor(h * 6); |
| f = h * 6 - i; |
| p = v * (1 - s); |
| q = v * (1 - f * s); |
| t = v * (1 - (1 - f) * s); |
| switch (i % 6) { |
| case 0: |
| r = v, g = t, b = p; |
| break; |
| case 1: |
| r = q, g = v, b = p; |
| break; |
| case 2: |
| r = p, g = v, b = t; |
| break; |
| case 3: |
| r = p, g = q, b = v; |
| break; |
| case 4: |
| r = t, g = p, b = v; |
| break; |
| case 5: |
| r = v, g = p, b = q; |
| break; |
| } |
| return { |
| r: Math.round(r * 255), |
| g: Math.round(g * 255), |
| b: Math.round(b * 255) |
| }; |
| }; |
| |
| var componentToHex = function(c) { |
| var hex = c.toString(16); |
| return hex.length == 1 ? "0" + hex : hex; |
| }; |
| |
| // Convert RGB to Hex |
| var rgbToHex = function(r, g, b) { |
| return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b); |
| }; |
| |
| // Convert Hex to RGB |
| var hexToRGB = function(hex) { |
| var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); |
| return result ? { |
| r: parseInt(result[1], 16), |
| g: parseInt(result[2], 16), |
| b: parseInt(result[3], 16) |
| } : null; |
| }; |
| |
| // Convert RGB to XYZ |
| var rgbToXYZ = function(r, g, b) { |
| var _r = (r / 255); |
| var _g = (g / 255); |
| var _b = (b / 255); |
| |
| if (_r > 0.04045) { |
| _r = Math.pow(((_r + 0.055) / 1.055), 2.4); |
| } else { |
| _r = _r / 12.92; |
| } |
| |
| if (_g > 0.04045) { |
| _g = Math.pow(((_g + 0.055) / 1.055), 2.4); |
| } else { |
| _g = _g / 12.92; |
| } |
| |
| if (_b > 0.04045) { |
| _b = Math.pow(((_b + 0.055) / 1.055), 2.4); |
| } else { |
| _b = _b / 12.92; |
| } |
| |
| _r = _r * 100; |
| _g = _g * 100; |
| _b = _b * 100; |
| |
| var X = _r * 0.4124 + _g * 0.3576 + _b * 0.1805; |
| var Y = _r * 0.2126 + _g * 0.7152 + _b * 0.0722; |
| var Z = _r * 0.0193 + _g * 0.1192 + _b * 0.9505; |
| |
| return [X, Y, Z]; |
| }; |
| |
| // Convert XYZ to LAB |
| var xyzToLAB = function(x, y, z) { |
| var ref_X = 95.047; |
| var ref_Y = 100.000; |
| var ref_Z = 108.883; |
| |
| var _X = x / ref_X; |
| var _Y = y / ref_Y; |
| var _Z = z / ref_Z; |
| |
| if (_X > 0.008856) { |
| _X = Math.pow(_X, (1 / 3)); |
| } else { |
| _X = (7.787 * _X) + (16 / 116); |
| } |
| |
| if (_Y > 0.008856) { |
| _Y = Math.pow(_Y, (1 / 3)); |
| } else { |
| _Y = (7.787 * _Y) + (16 / 116); |
| } |
| |
| if (_Z > 0.008856) { |
| _Z = Math.pow(_Z, (1 / 3)); |
| } else { |
| _Z = (7.787 * _Z) + (16 / 116); |
| } |
| |
| var CIE_L = (116 * _Y) - 16; |
| var CIE_a = 500 * (_X - _Y); |
| var CIE_b = 200 * (_Y - _Z); |
| |
| return [CIE_L, CIE_a, CIE_b]; |
| }; |
| |
| // Compute Delta E using CIE94 |
| var getDeltaE = function(x, y, isTextiles) { |
| var x = { |
| l: x[0], |
| a: x[1], |
| b: x[2] |
| }; |
| var y = { |
| l: y[0], |
| a: y[1], |
| b: y[2] |
| }; |
| labx = x; |
| laby = y; |
| var k2; |
| var k1; |
| var kl; |
| var kh = 1; |
| var kc = 1; |
| if (isTextiles) { |
| k2 = 0.014; |
| k1 = 0.048; |
| kl = 2; |
| } else { |
| k2 = 0.015; |
| k1 = 0.045; |
| kl = 1; |
| } |
| |
| var c1 = Math.sqrt(x.a * x.a + x.b * x.b); |
| var c2 = Math.sqrt(y.a * y.a + y.b * y.b); |
| |
| var sh = 1 + k2 * c1; |
| var sc = 1 + k1 * c1; |
| var sl = 1; |
| |
| var da = x.a - y.a; |
| var db = x.b - y.b; |
| var dc = c1 - c2; |
| |
| var dl = x.l - y.l; |
| var dh = Math.sqrt(da * da + db * db - dc * dc); |
| |
| return Math.sqrt(Math.pow((dl / (kl * sl)), 2) + Math.pow((dc / (kc * sc)), 2) + Math.pow((dh / (kh * sh)), 2)); |
| }; |
| |
| // Generate a random color using the golden ratio conjugate |
| var genRandomColor = function(s, v) { |
| var goldenRatioConjugate = 0.618033988749895; |
| var h = Math.random(); |
| h += goldenRatioConjugate; |
| h %= 1; |
| var hsv = { |
| h: h, |
| s: s, |
| v: v |
| }; |
| var rgb = hsvToRGB(hsv.h, hsv.s, hsv.v); |
| var hex = rgbToHex(rgb.r, rgb.g, rgb.b); |
| return { |
| hsv: hsv, |
| rgb: rgb, |
| hex: hex |
| }; |
| }; |
| |
| var checkDeltaE = function(deltaE) { |
| return deltaE >= 15; |
| }; |
| |
| vertexMap = {}; |
| |
| var renderGraph = function(jobId, counterMetrics, bIsNewJob) { |
| d3.select("#loading").remove(); |
| var qString = "jobs?jobgraph=true&jobId=" + jobId; |
| d3.xhr(qString, function(error, jsonresp) { |
| if (error) { |
| console.log("error retrieving job with id of " + jobId); |
| } |
| if (!jsonresp.response || jsonresp.response === "") { |
| return; |
| } |
| layer = d3.select("#layers") |
| .node().value; |
| var graph = JSON.parse(jsonresp.response); |
| |
| if (counterMetrics && counterMetrics.length > 0) { |
| graph = addValuesToEdges(graph, counterMetrics); |
| } |
| |
| // these are used if the topology has no metrics, and to display the static graph |
| var generatedFlowValues = makeStaticFlowValues(graph.edges.length); |
| |
| d3.select("#chart").selectAll("*").remove(); |
| |
| svg = d3.select("#chart").append("svg") |
| .attr("width", width + margin.left + margin.right + 5) |
| .attr("height", height + margin.top + margin.bottom) |
| .append("g") |
| .attr("id", "parentG") |
| .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); |
| |
| |
| graph.vertices.forEach(function(vertex){ |
| vertex.idx = parseInt(vertex.id.substring("OP_".length, vertex.id.length)); |
| if (!vertexMap[vertex.id]) { |
| vertexMap[vertex.id] = vertex; |
| } |
| }); |
| |
| var i = 0; |
| graph.edges.forEach(function(edge) { |
| // Store the real flow value so that we can access it in the static layer |
| edge.flowValue = edge.value; |
| |
| var value = ""; |
| if (layer === "static" || !edge.value) { |
| value = generatedFlowValues[i]; |
| } else { |
| value = edge.value; |
| } |
| edge.value = value; |
| edge.source = vertexMap[edge.sourceId].idx; |
| edge.target = vertexMap[edge.targetId].idx; |
| i++; |
| if (edge.tags && edge.tags.length > 0) { |
| setAvailableTags(edge.tags); |
| } |
| }); |
| var layers = d3.select("#layers"); |
| var selectedL = layers.node().value; |
| |
| showTagDiv(bIsNewJob); |
| selectedTags = []; |
| if (d3.select("#showTags").property("checked") === true) { |
| // fetch the selected tags, and modify the graph |
| selectedTags = getSelectedTags(); |
| } |
| |
| refreshedRowValues = graph.vertices; |
| |
| sankey |
| .nodes(graph.vertices) |
| .links(graph.edges) |
| .layout(32); |
| |
| refreshedRowValues = sankey.nodes(); |
| |
| var link = svg.append("g").selectAll(".link") |
| .data(graph.edges) |
| .enter().append("path") |
| .attr("class", "link") |
| .style("stroke", function(d){ |
| var matchedTags = []; |
| |
| if (d.tags && selectedTags.length > 0) { |
| var tags = d.tags; |
| /* |
| * if this stream has multiple tags on it |
| * and if the number of selectedTags is greater |
| * than zero, find the matches |
| */ |
| tags.sort(); |
| |
| tags.forEach(function(t){ |
| selectedTags.forEach(function(sTag) { |
| if (t === sTag) { |
| matchedTags.push(sTag); |
| } |
| }); |
| }); |
| |
| if (matchedTags.length > 0) { |
| if (matchedTags.length === 1) { |
| var color = color20(streamsTags[matchedTags[0]]); |
| return d.color = color === "#c7c7c7" ? "#008080" : color; |
| } else { |
| // more than one tag is on this stream |
| return d.color = MULTIPLE_TAGS_COLOR; |
| } |
| |
| } else { |
| return d.color = "#d3d3d3"; |
| } |
| } else { |
| // layer is not flow, but no stream tags available |
| return d.color = "#d3d3d3"; |
| } |
| }) |
| .style("stroke-opacity", function(d){ |
| if (d.tags && selectedTags.length > 0) { |
| // if the link has this color it is not the selected tag, make it more transparent |
| if (d.color === "#d3d3d3") { |
| return 0.6; |
| } |
| } |
| }) |
| .attr("d", path) |
| .style("stroke-width", function(d) { |
| return Math.max(1, Math.sqrt(d.dy)); |
| }) |
| .sort(function(a, b) { return b.dy - a.dy; }); |
| |
| // this is the hover text for the links between the nodes |
| link.append("title") |
| .text(function(d) { |
| var value = (layer == "static") ? format(d.flowValue) : format(d.value); |
| if (d.derived) { |
| value = "No value - counter not present"; |
| } else if (d.isZero) { |
| value = "0"; |
| } |
| var sKind = parseOpletKind(d.source.invocation.kind); |
| var tKind = parseOpletKind(d.target.invocation.kind); |
| var retString = "Oplet name: " + d.source.idx + "\nOplet kind: " + sKind + " --> \n" |
| + "Oplet name: " + d.target.idx + "\nOplet kind: " + tKind + "\n" + value; |
| |
| if (d.alias) { |
| retString += "\nStream alias: " + d.alias; |
| } |
| if (d.tags && d.tags.length > 0) { |
| retString += "\nStream tags: " + d.tags.toString(); |
| } |
| |
| return retString; |
| }); |
| |
| var node = svg.append("g").selectAll(".node") |
| .data(graph.vertices) |
| .enter().append("g") |
| .attr("class", "node") |
| .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; }) |
| .call(d3.behavior.drag() |
| .origin(function(d) { return d; }) |
| .on("dragstart", function() { this.parentNode.appendChild(this); }) |
| .on("drag", dragmove)); |
| |
| node.append(function(d) { |
| if (isRect(d.invocation.kind)) { |
| return document.createElementNS(d3.ns.prefix.svg, 'rect'); |
| } else { |
| return document.createElementNS(d3.ns.prefix.svg, 'circle'); |
| } |
| }); |
| |
| var assignedOpletColors = []; |
| |
| node.selectAll("circle") |
| .attr("cx", sankey.nodeWidth()/2) |
| .attr("cy", function(d){ |
| return d.dy/2; |
| }) |
| .attr("r", function(d){ |
| return Math.sqrt(d.dy); |
| }) |
| .style("fill", function(d) { |
| if (!colorMap[d.id.toString()]) { |
| colorMap[d.id.toString()] = color20(d.id.toString()); |
| } |
| |
| // Generate a random color that is perceptually different than all assigned colors: |
| // 1. Convert the assigned oplet colors from Hex to LAB |
| // 2. Generate a random color in the RGB color space |
| // 3. Convert RGB to XYZ |
| // 4. Convert XYZ to LAB |
| // 5. For each assigned oplet color, compute Delta E between the two LAB colors (new and assigned) |
| // 6. If Delta E >= 15, consider the color to be different enough from the other colors |
| if (!opletColor[d.invocation.kind]) { |
| var assignedOpletColorsLAB = []; |
| for (var i = 0; i < assignedOpletColors.length; i++) { |
| var rgb = hexToRGB(assignedOpletColors[i]); |
| var xyz = rgbToXYZ(rgb.r, rgb.g, rgb.b); |
| var lab = xyzToLAB(xyz[0], xyz[1], xyz[2]); |
| assignedOpletColorsLAB.push(lab); |
| } |
| |
| var deltaEs = []; |
| var c = null; |
| var uniqueColor = false; |
| |
| while (!uniqueColor) { |
| // Use a different color scheme for non-org.apache.edgent defined oplets |
| if (d.invocation.kind.includes("org.apache.edgent")) { |
| c = genRandomColor(0.5, 0.95); |
| } else { |
| c = genRandomColor(0.99, 0.99); |
| } |
| |
| var xyz = rgbToXYZ(c.rgb.r, c.rgb.g, c.rgb.b); |
| var lab = xyzToLAB(xyz[0], xyz[1], xyz[2]); |
| |
| // Compare color to assigned colors and check for similarity |
| deltaEs = []; |
| for (var m = 0; m < assignedOpletColorsLAB.length; m++) { |
| deltaE = getDeltaE(lab, assignedOpletColorsLAB[m], false); |
| deltaEs.push(deltaE); |
| } |
| uniqueColor = deltaEs.every(checkDeltaE); |
| } |
| |
| opletColor[d.invocation.kind] = c.hex; |
| } |
| |
| var color = getVertexFillColor(layer, d, counterMetrics); |
| assignedOpletColors.push(color); |
| return color; |
| |
| }) |
| .attr("data-legend", function(d) { |
| return getLegendText(layer, d); |
| }) |
| .style("stroke", function(d) { |
| return getLegendColor(layer, d, counterMetrics); |
| |
| }); |
| |
| node.selectAll("rect") |
| .attr("x", sankey.nodeWidth()/2 ) |
| .attr("y", function(d) { |
| return d.dy/2 - 3; |
| }) |
| .attr("width", 5) |
| .attr("height", 5) |
| .style("fill", function(d) { |
| if (!colorMap[d.id.toString()]) { |
| colorMap[d.id.toString()] = color20(d.id.toString()); |
| } |
| if (!opletColor[d.invocation.kind]) { |
| opletColor[d.invocation.kind] = color20(d.invocation.kind); |
| } |
| return getVertexFillColor(layer, d, counterMetrics); |
| }) |
| .attr("data-legend", function(d) { |
| return getLegendText(layer, d); |
| }) |
| .style("stroke", function(d) { |
| return getLegendColor(layer, d, counterMetrics); |
| |
| }); |
| |
| svg.selectAll("circle") |
| .on("mouseover", function(d, i) { |
| var kind = parseOpletKind(d.invocation.kind); |
| var index = kind.indexOf("("); |
| var kindName = kind.substring(0, index-1); |
| var kindPkg = kind.substring(index); |
| var headStr1 = "<div style='width:100%;'><table style='width:100%;'><tr><th class='smaller'>Name</th>" + |
| "<th class='smaller'>Oplet kind</th><th class='smaller'>Tuple count</th></tr>"; |
| var valueStr1 = "<tr><td class='smallCenter'>" + d.idx.toString() + "</td><td class='smallCenter'>" + kindName + "<br/>" + kindPkg + |
| "</td><td class='smallCenter'>"; |
| |
| var sourceStreamTupleCountsMap = new Map(); |
| var sourceStreamAliasesMap = new Map(); |
| var sourceStreamTagsMap = new Map(); |
| d.targetLinks.forEach(function(trg) { |
| var source = trg.source.idx.toString(); |
| var sourceLinks = trg.source.sourceLinks; |
| for (var i = 0; i < sourceLinks.length; i++) { |
| if (trg.sourceId == sourceLinks[i].sourceId && trg.targetId == sourceLinks[i].targetId) { |
| if (layer == "static") { |
| sourceStreamTupleCountsMap.set(source, parseInt(sourceLinks[i].flowValue)); |
| } else { |
| sourceStreamTupleCountsMap.set(source, parseInt(sourceLinks[i].value)); |
| } |
| |
| if (sourceLinks[i].hasOwnProperty("alias")) { |
| sourceStreamAliasesMap.set(source, sourceLinks[i].alias); |
| } else { |
| sourceStreamAliasesMap.set(source, ""); |
| } |
| } |
| } |
| |
| if (trg.tags && trg.tags.length > 0) { |
| sourceStreamTagsMap.set(source, trg.tags); |
| } else { |
| sourceStreamTagsMap.set(source, []); |
| } |
| }); |
| |
| var targetStreamTupleCountsMap = new Map(); |
| var targetStreamAliasesMap = new Map(); |
| var targetStreamTagsMap = new Map(); |
| d.sourceLinks.forEach(function(src) { |
| var target = src.target.idx.toString(); |
| var targetLinks = src.target.targetLinks; |
| for (var i = 0; i < targetLinks.length; i++) { |
| if (src.sourceId == targetLinks[i].sourceId && src.targetId == targetLinks[i].targetId) { |
| if (layer == "static") { |
| targetStreamTupleCountsMap.set(target, parseInt(targetLinks[i].flowValue)); |
| } else { |
| targetStreamTupleCountsMap.set(target, parseInt(targetLinks[i].value)); |
| } |
| |
| if (targetLinks[i].hasOwnProperty("alias")) { |
| targetStreamAliasesMap.set(target, targetLinks[i].alias); |
| } else { |
| targetStreamAliasesMap.set(target, ""); |
| } |
| } |
| } |
| |
| if (src.tags && src.tags.length > 0) { |
| targetStreamTagsMap.set(target, src.tags); |
| } else { |
| targetStreamTagsMap.set(target, []); |
| } |
| }); |
| |
| var sStreamString = ""; |
| var inTupleCount = 0; |
| if (sourceStreamAliasesMap.size == 0 && sourceStreamTagsMap.size == 0) { |
| sStreamString = "None"; |
| } else { |
| for (var [id, alias] of sourceStreamAliasesMap) { |
| var tupleCount = sourceStreamTupleCountsMap.get(id); |
| inTupleCount += parseInt(tupleCount); |
| var tags = sourceStreamTagsMap.get(id); |
| sStreamString += "[" + id + "] "; |
| |
| sStreamString += "<strong>tuples</strong>: " + formatNumber(tupleCount); |
| |
| if (alias != "") { |
| sStreamString += ", <strong>alias</strong>: " + alias; |
| } |
| if (tags.length != 0) { |
| sStreamString += ", <strong>tags</strong>: " + tags.join(", "); |
| } |
| |
| sStreamString += "<br/>"; |
| } |
| } |
| |
| var tStreamString = ""; |
| var outTupleCount = 0; |
| if (targetStreamAliasesMap.size == 0 && targetStreamTagsMap.size == 0) { |
| tStreamString = "None"; |
| } else { |
| for (var [id, alias] of targetStreamAliasesMap) { |
| var tupleCount = targetStreamTupleCountsMap.get(id); |
| outTupleCount += parseInt(tupleCount); |
| var tags = targetStreamTagsMap.get(id); |
| tStreamString += "[" + id + "] "; |
| |
| tStreamString += "<strong>tuples</strong>: " + formatNumber(tupleCount); |
| |
| if (alias != "") { |
| tStreamString += ", <strong>alias</strong>: " + alias; |
| } |
| if (tags.length != 0) { |
| tStreamString += ", <strong>tags</strong>: " + tags.join(", "); |
| } |
| |
| tStreamString += "<br/>"; |
| } |
| } |
| |
| valueStr1 += "In: " + formatNumber(inTupleCount) + "<br/>Out: " + formatNumber(outTupleCount) + "</td></tr></table>"; |
| |
| var headStr2 = "<table style='width:100%;'><tr><th class='smaller'>Source streams</th>" + "<th class='smaller'>Target streams</th></tr>"; |
| var valueStr2 = "<tr><td class='smallLeft'>" + sStreamString + "</td>"; |
| valueStr2 += "<td class='smallLeft'>" + tStreamString + "</td></tr></table></div>"; |
| |
| var str = headStr1 + valueStr1 + headStr2 + valueStr2; |
| showTooltip(str, d, i, d3.event); |
| }) |
| .on("mouseout", function(d, i){ |
| hideTooltip(d, i); |
| }); |
| |
| svg.selectAll("rect") |
| .on("mouseover", function(d, i){ |
| var kind = parseOpletKind(d.invocation.kind); |
| var index = kind.indexOf("("); |
| var kindName = kind.substring(0, index-1); |
| var kindPkg = kind.substring(index); |
| var headStr = "<div><table style='table-layout:fixed;word-wrap: break-word;'><tr><th class='smaller'>Name</th>" + |
| "<th class='smaller'>Oplet kind</th></tr>"; |
| var valueStr = "<tr><td class='smallCenter'>" + d.idx.toString() + "</td><td class='smallCenter'>" + kindName + "<br/>" + kindPkg + "</td></tr><table></div>"; |
| var str = headStr + valueStr; |
| showTooltip(str, d, i, d3.event); |
| }) |
| .on("mouseout", function(d, i){ |
| hideTooltip(d, i); |
| }) |
| |
| node.append("text") |
| .attr("x", function (d) { |
| return - 6 + sankey.nodeWidth() / 2 - Math.sqrt(d.dy); |
| }) |
| .attr("y", function (d) { |
| return d.dy / 2; |
| }) |
| .attr("dy", ".35em") |
| .attr("text-anchor", "end") |
| .attr("text-shadow", "0 1px 0 #fff") |
| .attr("transform", null) |
| .text(function (d) { |
| return d.idx; |
| }) |
| .filter(function (d) { |
| return d.x < width / 2; |
| }) |
| .attr("x", function (d) { |
| return 6 + sankey.nodeWidth() / 2 + Math.sqrt(d.dy); |
| }) |
| .attr("text-anchor", "start"); |
| |
| function dragmove(d) { |
| d3.select(this).attr("transform", "translate(" + d.x + "," + (d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))) + ")"); |
| sankey.relayout(); |
| link.attr("d", path); |
| } |
| |
| d3.selectAll(".legend").remove(); |
| |
| if (layer === "opletColor"){ |
| svgLegend |
| .append("g") |
| .attr("class","legend") |
| .attr("transform","translate(10,10)") |
| .style("font-size","11px") |
| .call(d3.legend, svg, null, "Oplet kind"); |
| } |
| |
| if (layer === "flow" && counterMetrics.length > 0) { |
| var maxBucketIdx = getTupleMaxBucketIdx(); |
| var bucketScale = d3.scale.linear().domain([0,maxBucketIdx.buckets.length - 1]).range(tupleColorRange); |
| var flowItems = getFormattedTupleLegend(maxBucketIdx, bucketScale); |
| legend = svgLegend |
| .append("g") |
| .attr("class","legend") |
| .attr("transform","translate(10,10)") |
| .style("font-size","11px") |
| .call(d3.legend, svg, flowItems, "Tuple count"); |
| } |
| |
| var showTagsChecked = $("#showTags").prop("checked"); |
| // add a second legend for tags, even if opletColor has been chosen |
| if (tagsArray.length > 0 && showTagsChecked) { |
| var tItems = getFormattedTagLegend(tagsArray); |
| if (!svgLegend.select("g").empty()) { |
| // get the dimensions of the other legend and append this one after it |
| var otherLegend = svgLegend.select("g")[0][0]; |
| var translateY = otherLegend.getBBox().height + 10 + 10; |
| svgLegend |
| .append("g") |
| .attr("class","legend") |
| .attr("transform","translate(10," + translateY + ")") |
| .style("font-size","11px") |
| .call(d3.legend, svg, tItems, "Stream tags"); |
| } else { |
| svgLegend |
| .append("g") |
| .attr("class","legend") |
| .attr("transform","translate(10,10)") |
| .style("font-size","11px") |
| .call(d3.legend, svg, tItems, "Stream tags"); |
| } |
| } |
| |
| |
| if (bIsNewJob !== undefined) { |
| fetchAvailableMetricsForJob(bIsNewJob); |
| } else { |
| fetchAvailableMetricsForJob(); |
| } |
| }); |
| }; |
| |
| // update the metrics drop down with the metrics that are available for the selected job |
| var fetchAvailableMetricsForJob = function(isNewJob) { |
| var selectedJobId = d3.select("#jobs").node().value; |
| var queryString = "metrics?job=" + selectedJobId + "&availableMetrics=all"; |
| if (isNewJob !== undefined) { |
| metricsAvailable(queryString, selectedJobId, isNewJob); |
| } else { |
| metricsAvailable(queryString, selectedJobId); |
| } |
| }; |
| |
| var fetchMetrics = function() { |
| // this makes a "GET" to the metrics servlet for the currently selected job |
| var selectedJobId = d3.select("#jobs").node().value; |
| var metricSelected = d3.select("#metrics").node().value; |
| var queryString = "metrics?job=" + selectedJobId + "&metric=" + metricSelected; |
| if (metricSelected !== "") { |
| metricFunction(selectedJobId, metricSelected, true); |
| } |
| }; |
| |
| var fetchLineChart = function() { |
| // the question is anything new, if it's not, then just keep refreshing what I have |
| var jobId = d3.select("#jobs").node().value; |
| var metricSelected = d3.select("#metrics").node().value; |
| plotMetricChartType(jobId, metricSelected); |
| |
| }; |
| |
| var jobMap = {}; |
| |
| var fetchJobsInfo = function() { |
| // this makes a "GET" to the context path http://localhost:<myport>/jobs |
| d3.xhr("jobs?jobsInfo=true", |
| function(error, data) { |
| if (error) { |
| console.log("error retrieving job output " + error); |
| } |
| if (data) { |
| var jobObjs = []; |
| jobObjs = JSON.parse(data.response); |
| var jobSelect = d3.select("#jobs"); |
| |
| if (jobObjs.length === 0) { |
| //no jobs were found, put an entry in the select |
| // To Do: if the graph is real, remove it ... |
| jobSelect |
| .append("option") |
| .text("No jobs were found") |
| .attr("value", "none"); |
| } |
| |
| jobObjs.forEach(function(job){ |
| var obj = {}; |
| var jobId = ""; |
| var idText = ""; |
| var nameText = ""; |
| for (var key in job) { |
| obj[key] = job[key]; |
| if (key.toUpperCase() === "ID") { |
| idText = "Job Id: " + job[key]; |
| jobId = job[key]; |
| } |
| |
| if (key.toUpperCase() === "NAME") { |
| nameText = job[key]; |
| } |
| |
| } |
| if (nameText !== "" && !jobMap[jobId]) { |
| jobSelect |
| .append("option") |
| .text(nameText) |
| .attr("value", jobId); |
| } |
| if (!jobMap[jobId]) { |
| jobMap[jobId] = obj; |
| } |
| }); |
| if(jobObjs.length > 0) { |
| var pxStr = jobSelect.style("left"); |
| var pxValue = parseInt(pxStr.substring(0, pxStr.indexOf("px")), 10); |
| var pos = pxValue + 7 + jobSelect.node().clientWidth; |
| d3.select("#stateImg") |
| .style("display", "block") |
| .on('mouseover', function() { |
| showStateTooltip(d3.event); |
| }) |
| .on('mouseout', function() { |
| hideStateTooltip(); |
| }) |
| .on('keydown', function() { |
| if (d3.event.keyCode && d3.event.keyCode === 13) { |
| showStateTooltip(d3.event); |
| } else if (d3.event.keyCode) { |
| hideStateTooltip(); |
| } |
| }); |
| |
| stateTooltip = d3.select("body") |
| .append("div") |
| .style("position", "absolute") |
| .style("z-index", "10") |
| .style("display", "none") |
| .style("background-color", "white") |
| .attr("class", "bshadow") |
| .attr("tabindex", "0"); |
| |
| rowsTooltip = d3.select("body") |
| .append("div") |
| .style("position", "absolute") |
| .style("z-index", "10") |
| .style("display", "none") |
| .style("background-color", "white") |
| .attr("class", "bshadow"); |
| |
| metricsTooltip = d3.select("body") |
| .append("div") |
| .style("position", "absolute") |
| .style("z-index", "10") |
| .style("display", "none") |
| .style("background-color", "white") |
| .attr("class", "bshadow") |
| .attr("tabindex", "0"); |
| |
| // check to see if a job is already selected and it's still in the jobMap object |
| var jobId = d3.select("#jobs").node().value; |
| var jobObj = jobMap[jobId]; |
| // otherwise set it to the first option value |
| if (!jobObj) { |
| var firstValue = d3.select("#jobs").property("options")[0].value; |
| d3.select("#jobs").property("value", firstValue); |
| } |
| } else { |
| // don't show the state image |
| d3.select("#stateImg") |
| .style("display", "none"); |
| } |
| } |
| }); |
| }; |
| |
| fetchJobsInfo(); |
| var first = true; |
| |
| var startGraph = function(restartInterval) { |
| run = setInterval(function() { |
| if (!stopTimer) { |
| if (first) { |
| fetchJobsInfo(); |
| first = false; |
| } |
| var selectedJob = d3.select("#jobs").node().value; |
| getCounterMetricsForJob(renderGraph, selectedJob, first); |
| if (propWindow) { |
| displayRowsTooltip(false); |
| } |
| } |
| |
| |
| }, restartInterval); |
| if (restartInterval < refreshInt) { |
| clearInterval(run); |
| startGraph(refreshInt); |
| } |
| |
| }; |
| |
| startGraph(1000); |
| |