1/* 2 * Copyright 2015-2017 ARM Limited 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17var EventPlot = (function () { 18 19 /* EventPlot receives data that is hashed by the keys 20 * and each element in the data is sorted by start time. 21 * Since events on each lane are mutually exclusive, they 22 * they are also sorted by the end time. We use this information 23 * and binary search on the input data for filtering events 24 * This maintains filtering complexity to O[KLogN] 25 */ 26 27 var GUIDER_WIDTH = 2; 28 29 infoProps = { 30 START_GUIDER_COLOR: "green", 31 END_GUIDER_COLOR: "red", 32 DELTA_COLOR: "blue", 33 GUIDER_WIDTH: 2, 34 TOP_MARGIN: 20, 35 HEIGHT: 30, 36 START_PREFIX: "A = ", 37 END_PREFIX: "B = ", 38 DELTA_PREFIX: "A - B = ", 39 XPAD: 10, 40 YPAD: 5, 41 BOX_BUFFER: 2, 42 BOX_WIDTH_RATIO: 0.6 43 } 44 45 var search_data = function (data, key, value, left, right) { 46 47 var mid; 48 49 while (left < right) { 50 51 mid = Math.floor((left + right) / 2) 52 if (data[mid][key] > value) 53 right = mid; 54 else 55 left = mid + 1; 56 } 57 return left; 58 } 59 60 61 /* Return the information for the current process 62 * pointed by the mouse 63 */ 64 var getCurrentInfo = function(ePlot, x0, y0) { 65 66 for (name in ePlot.items) { 67 68 var data = ePlot.items[name]; 69 var xMax = ePlot.zoomScale.domain()[1]; 70 var right = search_data(data, 0, xMax, 0, data.length - 1); 71 var left = search_data(data, 1, x0, 0, right); 72 73 if (data) { 74 var candidate = data[left]; 75 if (candidate[0] <= x0 && 76 candidate[1] >= x0 && 77 candidate[2] == y0) 78 return { 79 name: name, 80 info: candidate 81 }; 82 } 83 } 84 } 85 86 var generate = function (div_name, base, chart_data) { 87 88 var margin, brush, x, ext, yMain, chart, main, 89 mainAxis, 90 itemRects, items, colourAxis, tip, lanes; 91 92 var process_chart_data = function (d) { 93 items = d.data; 94 lanes = d.lanes; 95 var names = d.keys; 96 var showSummary = d.showSummary; 97 var div = $("#" + div_name); 98 99 margin = { 100 top: 15, 101 right: 15, 102 bottom: 15, 103 left: 70 104 }, width = div.width() - margin.left - margin.right, 105 106 mainHeight = 50 * lanes.length - margin.top - margin.bottom; 107 108 x = d3.scale.linear() 109 .domain(d.xDomain) 110 .range([0, width]); 111 112 var zoomScale = d3.scale.linear() 113 .domain(d.xDomain) 114 .range([0, width]); 115 116 var xMin = x.domain()[0]; 117 var xMax = x.domain()[1]; 118 119 if (!d.colorMap) { 120 // Colour Ordinal scale. Uses Category20 Colors 121 colours = d3.scale.category20().range(); 122 } else { 123 // Use colours provided by user 124 var colours = []; 125 for (var i in names) 126 if (names[i] in d.colorMap) 127 colours.push(d.colorMap[names[i]]); 128 } 129 colourAxis = d3.scale.ordinal() 130 .range(colours) 131 .domain(names); 132 133 brushScale = d3.scale.linear() 134 .range([0, width]); 135 ext = d3.extent(lanes, function (d) { 136 return d.id; 137 }); 138 yMain = d3.scale.linear() 139 .domain([ext[0], ext[1] + 140 1 141 ]) 142 .range([0, mainHeight]); 143 144 145 var ePlot; 146 147 148 $("#" + div_name) 149 .append('<div class="pull-right">' + 150 '<button type="button" class="btn btn-sm btn-info" ' + 151 'onclick="EventPlot.create_help_dialog(' + base + 152 ')">Help</button></div>') 153 154 var iDesc = drawInfo(div_name, margin, width); 155 156 chart = d3.select('#' + div_name) 157 .append('svg:svg') 158 .attr('width', width + margin.right + 159 margin.left) 160 .attr('height', mainHeight + margin.top + 161 margin.bottom + 5) 162 .attr('class', 'chart') 163 164 165 main = chart.append('g') 166 .attr('transform', 'translate(' + margin.left + 167 ',' + margin.top + ')') 168 .attr('width', width) 169 .attr('height', mainHeight) 170 .attr('class', 'main') 171 172 main.append('g') 173 .selectAll('.laneLines') 174 .data(lanes) 175 .enter() 176 .append('line') 177 .attr('x1', 0) 178 .attr('y1', function (d) { 179 return d3.round(yMain(d.id)) + 0.5; 180 }) 181 .attr('x2', width) 182 .attr('y2', function (d) { 183 return d3.round(yMain(d.id)) + 0.5; 184 }) 185 .attr('stroke', function (d) { 186 return d.label === '' ? 'white' : 187 'lightgray' 188 }); 189 190 main.append('g') 191 .selectAll('.laneText') 192 .data(lanes) 193 .enter() 194 .append('text') 195 .attr('x', 0) 196 .text(function (d) { 197 return d.label; 198 }) 199 .attr('y', function (d) { 200 return yMain(d.id + .5); 201 }) 202 .attr('dy', '0.5ex') 203 .attr('text-anchor', 'end') 204 .attr('class', 'laneText'); 205 206 mainAxis = d3.svg.axis() 207 .scale(brushScale) 208 .orient('bottom'); 209 210 tip = d3.tip() 211 .attr('class', 'd3-tip') 212 .html(function (d) { 213 return "<span style='color:white'>" + 214 d.name + "</span>"; 215 }) 216 217 main.append('g') 218 .attr('transform', 'translate(0,' + 219 mainHeight + ')') 220 .attr('class', 'main axis') 221 .call(mainAxis); 222 223 var ePlot; 224 225 ePlot = { 226 div: div, 227 div_name: div_name, 228 margin: margin, 229 chart: chart, 230 mainHeight: mainHeight, 231 width: width, 232 x: x, 233 brushScale: brushScale, 234 ext: ext, 235 yMain: yMain, 236 main: main, 237 mainAxis: mainAxis, 238 items: items, 239 colourAxis: colourAxis, 240 tip: tip, 241 lanes: lanes, 242 names: names, 243 iDesc: iDesc, 244 }; 245 ePlot.zoomScale = zoomScale; 246 247 if (showSummary) 248 drawMini(ePlot); 249 250 var outgoing; 251 var zoomed = function () { 252 253 if (zoomScale.domain()[0] < xMin) { 254 zoom.translate([zoom.translate()[ 255 0] - zoomScale( 256 xMin) + 257 zoomScale.range()[0], 258 zoom.translate()[ 259 1] 260 ]); 261 } else if (zoomScale.domain()[1] > 262 xMax) { 263 zoom.translate([zoom.translate()[ 264 0] - zoomScale( 265 xMax) + 266 zoomScale.range()[1], 267 zoom.translate()[ 268 1] 269 ]); 270 271 } 272 273 outgoing = main.selectAll(".mItem") 274 .attr("visibility", "hidden"); 275 drawMain(ePlot, zoomScale.domain()[0], 276 zoomScale.domain()[1]); 277 if (showSummary) { 278 brush.extent(zoomScale.domain()); 279 ePlot.mini.select(".brush") 280 .call( 281 brush); 282 } 283 284 brushScale.domain(zoomScale.domain()); 285 ePlot.main.select('.main.axis') 286 .call(ePlot.mainAxis) 287 288 updateInfo(ePlot); 289 }; 290 291 var rightClickCtrlAltHandler = function(x0, y0) { 292 293 x0 = ePlot.zoomScale.invert(x0); 294 y0 = Math.floor(ePlot.yMain.invert(y0)); 295 var current = getCurrentInfo(ePlot, x0, y0); 296 297 if (current) { 298 ePlot.iDesc.currentProc.text(current.name) 299 ePlot.iDesc.currentInfo.text( 300 current.info[0].toFixed(6) 301 + " to " + 302 current.info[1].toFixed(6) + 303 " (" + (current.info[1] - current.info[0]) 304 .toFixed(6) + ")") 305 306 removeContextRect(ePlot); 307 ePlot.contextRect = drawContextRect(ePlot, current.info[0], current.info[1], current.info[2], true) 308 ePlot.iDesc.currentDisp.attr("stroke", ePlot.colourAxis(current.name)); 309 } 310 } 311 312 var contextMenuHandler = function() { 313 314 var e = d3.event; 315 var x0 = d3.mouse(this)[0] - ePlot.margin.left; 316 var y0 = d3.mouse(this)[1] - ePlot.margin.top; 317 318 if (e.ctrlKey && e.altKey) 319 rightClickCtrlAltHandler(x0, y0); 320 321 else if (e.ctrlKey) { 322 323 if (ePlot.endGuider) 324 ePlot.endGuider = ePlot.endGuider.remove(); 325 326 ePlot.endGuider = drawVerticalLine(ePlot, x0, 327 infoProps.END_GUIDER_COLOR, "B"); 328 ePlot.endGuider._x_pos = ePlot.zoomScale.invert(x0); 329 iDesc.endText.text(infoProps.END_PREFIX + ePlot.endGuider._x_pos.toFixed(6)) 330 331 } else { 332 333 if (ePlot.startGuider) 334 ePlot.startGuider = ePlot.startGuider.remove(); 335 336 ePlot.startGuider = drawVerticalLine(ePlot, x0, 337 infoProps.START_GUIDER_COLOR, "A"); 338 ePlot.startGuider._x_pos = ePlot.zoomScale.invert(x0); 339 iDesc.startText.text(infoProps.START_PREFIX + ePlot.startGuider._x_pos.toFixed(6)) 340 } 341 342 if (ePlot.endGuider && ePlot.startGuider) 343 iDesc.deltaText.text(infoProps.DELTA_PREFIX + 344 (ePlot.endGuider._x_pos - ePlot.startGuider._x_pos) 345 .toFixed(6) 346 ) 347 348 d3.event.preventDefault(); 349 } 350 351 chart.on("contextmenu", contextMenuHandler); 352 353 if (showSummary) { 354 var _brushed_event = function () { 355 main.selectAll("path") 356 .remove(); 357 var brush_xmin = brush.extent()[0]; 358 var brush_xmax = brush.extent()[1]; 359 360 var t = zoom.translate(), 361 new_domain = brush.extent(), 362 scale; 363 364 /* 365 * scale = x.range()[1] - x.range[0] 366 * -------------------------- 367 * x(x.domain()[1] - x.domain()[0]) 368 * 369 * _ _ 370 * new_domain[0] = x.invert | x.range()[0] - z.translate()[0] | 371 * | ------------------- | 372 * |_ z.scale() _| 373 * 374 * 375 * 376 * translate[0] = x.range()[0] - x(new_domain[0])) * zoom.scale() 377 */ 378 379 scale = (width) / x(x.domain()[0] + 380 new_domain[1] - 381 new_domain[0]); 382 zoom.scale(scale); 383 t[0] = x.range()[0] - (x(new_domain[ 384 0]) * scale); 385 zoom.translate(t); 386 387 388 brushScale.domain(brush.extent()) 389 drawMain(ePlot, brush_xmin, 390 brush_xmax); 391 ePlot.main.select('.main.axis') 392 .call(ePlot.mainAxis) 393 394 updateInfo(ePlot); 395 }; 396 397 brush = d3.svg.brush() 398 .x(x) 399 .extent(x.domain()) 400 .on("brush", _brushed_event); 401 402 ePlot.mini.append('g') 403 .attr('class', 'brush') 404 .call(brush) 405 .selectAll('rect') 406 .attr('y', 1) 407 .attr('height', ePlot.miniHeight - 1); 408 } 409 410 var zoom = d3.behavior.zoom() 411 .x(zoomScale) 412 .on( 413 "zoom", zoomed) 414 .on("zoomend", function () { 415 if (outgoing) 416 outgoing.remove() 417 }) 418 .scaleExtent([1, 4096]); 419 chart.call(zoom); 420 421 drawMain(ePlot, xMin, xMax); 422 ePlot.main.select('.main.axis') 423 .call(ePlot.mainAxis) 424 425 var resize = function() { 426 427 var width = div.width() - margin.left 428 - margin.right; 429 430 /* Update scale ranges */ 431 x.range([0, width]); 432 zoomScale.range([0, width]); 433 brushScale.range([0, width]); 434 ePlot.width = width; 435 436 resize_main(ePlot); 437 resize_info(ePlot); 438 resize_mini(ePlot); 439 zoomed(); 440 441 } 442 443 d3.select(window) 444 .on("resize." + ePlot.div_name, resize) 445 446 return ePlot; 447 448 } 449 450 /* 451 * If chart_data is passed, process data directly 452 */ 453 process_chart_data(chart_data); 454 }; 455 456 457 var resize_mini = function(ePlot) { 458 459 d3.select(ePlot.mini.node().parentNode) 460 .attr("width", ePlot.div.width()); 461 ePlot.iDesc.info_svg 462 .attr("width", ePlot.div.width()); 463 ePlot.mini.selectAll("line") 464 .attr("x2", ePlot.width); 465 ePlot.mini.call(ePlot.miniAxis); 466 ePlot.mini.selectAll(".miniItem").remove(); 467 drawMiniPaths(ePlot); 468 } 469 470 var resize_main = function(ePlot) { 471 472 d3.select(ePlot.main.node().parentNode) 473 .attr("width", ePlot.div.width()); 474 ePlot.main.selectAll("line") 475 .attr("x2", ePlot.width); 476 } 477 478 var resize_info = function(ePlot) { 479 480 var width_box_one = infoProps.BOX_WIDTH_RATIO * ePlot.width; 481 var width_box_two = ePlot.width - width_box_one; 482 483 ePlot.iDesc.info 484 .attr("width", width); 485 ePlot.iDesc.guiderInfo 486 .attr("width", width_box_one - infoProps.BOX_BUFFER); 487 ePlot.iDesc.currentDisp 488 .attr("x", width_box_one + infoProps.BOX_BUFFER); 489 ePlot.iDesc.currentDisp 490 .attr("width", width_box_two - infoProps.BOX_BUFFER); 491 ePlot.iDesc.deltaText 492 .attr("x", (width_box_one / 2) - infoProps.XPAD) 493 ePlot.iDesc.endText 494 .attr("x", width_box_one - infoProps.XPAD) 495 ePlot.iDesc.currentProc 496 .attr("x", width_box_one + infoProps.XPAD + infoProps.BOX_BUFFER) 497 ePlot.iDesc.currentInfo 498 .attr("x", ePlot.width - infoProps.XPAD) 499 } 500 501 var drawInfo = function (div_name, margin, width) { 502 503 var infoHeight = 30; 504 var _top = 20; 505 var LINE_WIDTH = 2 506 507 var iDesc = {}; 508 509 var width_box_one = infoProps.BOX_WIDTH_RATIO * width; 510 var width_box_two = width - width_box_one 511 512 iDesc.info_svg = d3.select("#" + div_name) 513 .append( 514 "svg:svg") 515 .attr('width', width + margin.right + 516 margin.left) 517 .attr('height', infoHeight + infoProps.TOP_MARGIN + LINE_WIDTH) 518 .attr('class', 'info') 519 520 iDesc.info = iDesc.info_svg.append("g") 521 .attr("transform", "translate(" + margin.left + 522 "," + infoProps.TOP_MARGIN + ")") 523 .attr('width', width) 524 .attr("class", "main") 525 .attr('height', infoProps.HEIGHT) 526 527 iDesc.guiderInfo = iDesc.info.append("rect") 528 .attr("x", 0) 529 .attr("y", 0) 530 .attr("width", width_box_one - infoProps.BOX_BUFFER) 531 .attr("height", infoHeight) 532 .attr("stroke", "lightgray") 533 .attr("fill", "none") 534 .attr("stroke-width", 1); 535 536 iDesc.currentDisp = iDesc.info.append("rect") 537 .attr("x", width_box_one + infoProps.BOX_BUFFER) 538 .attr("y", 0) 539 .attr("width", width_box_two - infoProps.BOX_BUFFER) 540 .attr("height", infoHeight) 541 .attr("stroke", "lightgray") 542 .attr("fill", "none") 543 .attr("stroke-width", 1); 544 545 iDesc.startText = iDesc.info.append("text") 546 .text("") 547 .attr("x", infoProps.XPAD) 548 .attr("y", infoProps.HEIGHT / 2 + infoProps.YPAD) 549 .attr("fill", infoProps.START_GUIDER_COLOR); 550 551 iDesc.deltaText = iDesc.info.append("text") 552 .text("") 553 .attr("x", (width_box_one / 2) - infoProps.XPAD) 554 .attr("y", infoProps.HEIGHT / 2 + infoProps.YPAD) 555 .attr("fill", infoProps.DELTA_COLOR); 556 557 iDesc.endText = iDesc.info.append("text") 558 .text("") 559 .attr("x", width_box_one - infoProps.XPAD) 560 .attr("text-anchor", "end") 561 .attr("y", infoProps.HEIGHT / 2 + infoProps.YPAD) 562 .attr("fill", infoProps.END_GUIDER_COLOR); 563 564 iDesc.currentProc = iDesc.info.append("text") 565 .text("") 566 .attr("x", width_box_one + infoProps.XPAD + infoProps.BOX_BUFFER) 567 .attr("text-anchor", "start") 568 .attr("y", infoProps.HEIGHT / 2 + infoProps.YPAD) 569 570 iDesc.currentInfo = iDesc.info.append("text") 571 .text("") 572 .attr("x", width - infoProps.XPAD) 573 .attr("text-anchor", "end") 574 .attr("y", infoProps.HEIGHT / 2 + infoProps.YPAD) 575 576 return iDesc; 577 578 } 579 580 var drawVerticalLine = function (ePlot, x, color, text) { 581 582 var line = ePlot.main.append("g") 583 584 line.append("line") 585 .style("stroke", color) 586 .style("stroke-width", GUIDER_WIDTH) 587 .attr("x1", x) 588 .attr("x2", x) 589 .attr("y1", 0) 590 .attr("y2", ePlot.mainHeight) 591 592 line.append("text") 593 .text(text) 594 .attr("y", -1) 595 .attr("x", x) 596 .attr("text-anchor", "middle") 597 .attr("fill", color) 598 599 return line; 600 }; 601 602 var removeContextRect = function(ePlot) { 603 if (ePlot.contextRect && ePlot.contextRect.rect) 604 ePlot.contextRect.rect.remove(); 605 } 606 607 var drawContextRect = function (ePlot, x0, x1, y, animate) { 608 609 var xMin = ePlot.zoomScale.domain()[0]; 610 var xMax = ePlot.zoomScale.domain()[1]; 611 var bounds = [Math.max(x0, xMin), Math.min(x1, 612 xMax)] 613 614 if (bounds[0] >= bounds[1]) 615 return { 616 rect: false, 617 x0: x0, 618 x1: x1, 619 y: y, 620 } 621 622 var rect = ePlot.main.selectAll(".contextRect").data([""]) 623 624 if (animate) 625 rect.enter().append("rect") 626 .attr("x", ePlot.zoomScale(bounds[0])) 627 .attr("y", ePlot.yMain(y)) 628 .attr("height", ePlot.yMain(1)) 629 .attr("class", "contextRect") 630 .attr("width", 0) 631 .transition() 632 .attr("width", ePlot.zoomScale(bounds[1]) - ePlot.zoomScale(bounds[0])) 633 else 634 rect.enter().append("rect") 635 .attr("x", ePlot.zoomScale(bounds[0])) 636 .attr("y", ePlot.yMain(y)) 637 .attr("class", "contextRect") 638 .attr("height", ePlot.yMain(1)) 639 .attr("width", ePlot.zoomScale(bounds[1]) - ePlot.zoomScale(bounds[0])) 640 641 return { 642 rect: rect, 643 x0: x0, 644 x1: x1, 645 y: y, 646 } 647 } 648 649 var checkGuiderRange = function (ePlot, xpos) { 650 651 if (xpos >= ePlot.zoomScale.domain()[0] && 652 xpos <= ePlot.zoomScale.domain()[1]) 653 return true; 654 else 655 return false; 656 } 657 658 var updateInfo = function (ePlot) { 659 660 if (ePlot.endGuider) { 661 662 var xpos = ePlot.endGuider._x_pos; 663 ePlot.endGuider.remove(); 664 665 if (checkGuiderRange(ePlot, xpos)) { 666 ePlot.endGuider = drawVerticalLine(ePlot, ePlot.zoomScale(xpos), 667 infoProps.END_GUIDER_COLOR, "B"); 668 ePlot.endGuider._x_pos = xpos; 669 } 670 } 671 672 if (ePlot.startGuider) { 673 674 var xpos = ePlot.startGuider._x_pos; 675 ePlot.startGuider.remove(); 676 677 if (checkGuiderRange(ePlot, xpos)) { 678 ePlot.startGuider = drawVerticalLine(ePlot, ePlot.zoomScale(xpos), 679 infoProps.START_GUIDER_COLOR, "A"); 680 ePlot.startGuider._x_pos = xpos 681 } 682 } 683 684 if (ePlot.contextRect) { 685 removeContextRect(ePlot); 686 ePlot.contextRect = drawContextRect(ePlot, ePlot.contextRect.x0, 687 ePlot.contextRect.x1, 688 ePlot.contextRect.y, 689 false); 690 } 691 692 } 693 694 var drawMiniPaths = function(ePlot) { 695 696 ePlot.mini.append('g') 697 .selectAll('miniItems') 698 .data(getPaths(ePlot, ePlot.x, ePlot.yMini)) 699 .enter() 700 .append('path') 701 .attr('class', function (d) { 702 return 'miniItem' 703 }) 704 .attr('d', function (d) { 705 return d.path; 706 }) 707 .attr("stroke", function (d) { 708 return d.color 709 }) 710 .attr("class", "miniItem"); 711 } 712 713 var drawMini = function (ePlot) { 714 715 var miniHeight = ePlot.lanes.length * 12 + 50; 716 717 var miniAxis = d3.svg.axis() 718 .scale(ePlot.x) 719 .orient('bottom'); 720 721 var yMini = d3.scale.linear() 722 .domain([ePlot.ext[0], ePlot.ext[1] + 723 1 724 ]) 725 .range([0, miniHeight]); 726 727 ePlot.yMini = yMini; 728 ePlot.miniAxis = miniAxis; 729 ePlot.miniHeight = miniHeight; 730 731 var summary = d3.select("#" + ePlot.div_name) 732 .append( 733 "svg:svg") 734 .attr('width', ePlot.width + ePlot.margin.right + 735 ePlot.margin.left) 736 .attr('height', miniHeight + ePlot.margin.bottom + 737 ePlot.margin.top) 738 .attr('class', 'chart') 739 740 var mini = summary.append('g') 741 .attr("transform", "translate(" + ePlot.margin.left + 742 "," + ePlot.margin.top + ")") 743 .attr('width', ePlot.width) 744 .attr('height', ePlot.miniHeight) 745 .attr('class', 'mini'); 746 747 mini.append('g') 748 .selectAll('.laneLines') 749 .data(ePlot.lanes) 750 .enter() 751 .append('line') 752 .attr('x1', 0) 753 .attr('y1', function (d) { 754 return d3.round(ePlot.yMini(d.id)) + 0.5; 755 }) 756 .attr('x2', ePlot.width) 757 .attr('y2', function (d) { 758 return d3.round(ePlot.yMini(d.id)) + 0.5; 759 }) 760 .attr('stroke', function (d) { 761 return d.label === '' ? 'white' : 762 'lightgray' 763 }); 764 765 mini.append('g') 766 .attr('transform', 'translate(0,' + 767 ePlot.miniHeight + ')') 768 .attr('class', 'axis') 769 .call(ePlot.miniAxis); 770 771 ePlot.mini = mini 772 drawMiniPaths(ePlot); 773 774 mini.append('g') 775 .selectAll('.laneText') 776 .data(ePlot.lanes) 777 .enter() 778 .append('text') 779 .text(function (d) { 780 return d.label; 781 }) 782 .attr('x', -10) 783 .attr('y', function (d) { 784 return ePlot.yMini(d.id + .5); 785 }) 786 .attr('dy', '0.5ex') 787 .attr('text-anchor', 'end') 788 .attr('class', 'laneText'); 789 790 return mini; 791 }; 792 793 794 var drawMain = function (ePlot, xMin, xMax) { 795 796 var rects, labels; 797 var dMin = 10000; 798 var paths = getPaths(ePlot, ePlot.zoomScale, ePlot.yMain); 799 ePlot.brushScale.domain([xMin, xMax]); 800 801 if (paths.length == 0) 802 return; 803 804 ePlot.main 805 .selectAll('mainItems') 806 .data(paths) 807 .enter() 808 .append('path') 809 .attr("shape-rendering", "crispEdges") 810 .attr('d', function (d) { 811 return d.path; 812 }) 813 .attr("class", "mItem") 814 .attr("stroke-width", function(d) { 815 return 0.8 * ePlot.yMain(1); 816 }) 817 .attr("stroke", function (d) { 818 return d.color 819 }) 820 .call(ePlot.tip) 821 .on("mouseover", ePlot.tip.show) 822 .on('mouseout', ePlot.tip.hide) 823 .on('mousemove', function () { 824 var xDisp = parseFloat(ePlot.tip.style("width")) / 825 2.0 826 ePlot.tip.style("left", (d3.event.pageX - xDisp) + 827 "px") 828 .style("top", Math.max(0, d3.event.pageY - 829 47) + "px"); 830 }) 831 }; 832 833 834 function _handle_equality(d, xMin, xMax, x, y, lane) { 835 var offset = 0.5 * y(1) + 0.5 836 var bounds = [Math.max(d[0], xMin), Math.min(d[1], 837 xMax)] 838 if (bounds[0] < bounds[1]) 839 return 'M' + ' ' + x(bounds[0]) + ' ' + (y(lane) + offset) + ' H ' + x(bounds[1]); 840 else 841 return ''; 842 }; 843 844 function _process(path, d, xMin, xMax, x, y, offset, lane) { 845 846 var start = d[0]; 847 if (start < xMin) 848 start = xMin; 849 var end = d[1]; 850 if (end > xMax) 851 end = xMax; 852 853 start = x(start); 854 end = x(end); 855 856 if ((end - start) < 0.01) 857 return path; 858 else if ((end - start) < 1) 859 end = start + 1; 860 861 path += 'M' + ' ' + start + ' ' + (y(lane) + offset) + ' H ' + end; 862 return path; 863 } 864 865 var _get_path = function(new_data, xMin, xMax, offset, x, y, stride) { 866 867 var path = '' 868 var max_rects = 2000; 869 870 for (var lane in new_data) { 871 var data = new_data[lane]; 872 var right = search_data(data, 0, xMax, 0, data.length - 1) 873 var left = search_data(data, 1, xMin, 0, right) 874 875 //Handle Equality 876 if (left == right) 877 path += _handle_equality(data[left], xMin, xMax, x, y, lane); 878 879 data = data.slice(left, right + 1); 880 881 var stride_length = 1; 882 if (stride) 883 stride_length = Math.max(Math.ceil(data.length / max_rects), 1); 884 885 for (var i = 0; i < data.length; i+= stride_length) 886 path = _process(path, data[i], xMin, xMax, x, y, offset, lane); 887 } 888 889 return path; 890 } 891 892 var getPaths = function (ePlot, x, y, stride) { 893 894 var keys = ePlot.names; 895 var items = ePlot.items; 896 var colourAxis = ePlot.colourAxis; 897 898 var xMin = x.domain()[0]; 899 var xMax = x.domain()[1]; 900 var paths = {}, 901 d, offset = 0.5 * y(1) + 0.5, 902 result = []; 903 904 for (var i in keys) { 905 var name = keys[i]; 906 var path = _get_path(items[name], xMin, xMax, offset, x, y, stride) 907 /* This is critical. Adding paths for non 908 * existent processes in the window* can be 909 * very expensive as there is one SVG per process 910 * and SVG rendering is expensive 911 */ 912 if (!path || path == "") 913 continue 914 915 result.push({ 916 color: colourAxis(name), 917 path: path, 918 name: name 919 }); 920 } 921 922 return result; 923 924 } 925 926 var create_dialog_body = function (body, title) { 927 928 var element = $("<div/>") 929 .addClass("modal fade") 930 .attr("role", "dialog") 931 .attr("tabindex", -1) 932 933 element.append( 934 $("<div/>") 935 .addClass("modal-dialog") 936 .attr("role", "document") 937 .append( 938 $("<div/>") 939 .addClass("modal-content") 940 .append( 941 $("<div/>") 942 .addClass("modal-header") 943 .append( 944 $("<button>") 945 .addClass("close") 946 .attr("data-dismiss", 947 "modal") 948 .append($("<span/>") 949 .html("×")) 950 ) 951 .append($("<h4/>") 952 .addClass("modal-title") 953 .text(title) 954 ) 955 .append($("<div/>") 956 .addClass("modal-body") 957 .append(body) 958 ) 959 .append( 960 $("<div/>") 961 .addClass("modal-footer") 962 .append( 963 $("<button>") 964 .addClass("btn btn-default") 965 .attr("data-dismiss", "modal") 966 .text("Close") 967 ) 968 ) 969 ) 970 ) 971 ) 972 973 return element.modal(); 974 975 } 976 977 var create_help_dialog = function (base) { 978 979 var HELP_IMAGE = "plotter_scripts/EventPlot/EventPlot_help.jpg" 980 981 var element = $('<div/>'); 982 983 // The documentation 984 var doc = $('<div/>') 985 .addClass('alert alert-info'); 986 987 doc.append( 988 'EventPlot is a multi-lane timeline plot ' + 989 'which supports interative zooming and timing calculation' 990 ); 991 992 element.append(doc); 993 994 var zoom = $("<div/>") 995 .addClass("media-left"); 996 997 var addLabel = function (txt, cls) { 998 return '<span class="label label-' + cls + '" + ">' + 999 txt + '</span>' 1000 } 1001 1002 var addListItem = function (txt) { 1003 return '<li class="list-group-item">' + txt + 1004 '</li>' 1005 } 1006 1007 var addPlus = function () { 1008 return " + " 1009 } 1010 1011 var addBadge = function (txt) { 1012 return '<span class="label label-default" style="border-radius: 10px">' + 1013 txt + '</span>' 1014 } 1015 1016 element.append( 1017 '<img style="width: 100%;" class="media-object" src="' + base + 1018 HELP_IMAGE + '"/>' 1019 ) 1020 1021 element.append('<ul class="list-group">') 1022 element.append(addListItem('Scroll in the main area ' + 1023 addBadge("1") + " to zoom interactively")) 1024 element.append(addListItem( 1025 'Click and drag in the main area ' + addBadge( 1026 "1") + " to pan the zoom")) 1027 element.append(addListItem( 1028 'The summary of the plot is shown in ' + 1029 addBadge("2"))) 1030 element.append(addListItem('Adjust the size of window ' + 1031 addBadge("4") + 1032 " set the X-Limits of the chart")) 1033 1034 element.append(addListItem(addLabel("Right-Click", 1035 "default") + 1036 " to place marker " + addLabel("A", "success"))) 1037 1038 element.append(addListItem(addLabel("Ctrl", "primary") + 1039 " + " + addLabel("Right-Click", "default") + 1040 " to place marker " + addLabel("B", "danger"))) 1041 1042 element.append(addListItem( 1043 "The marker positions and delta will be shown in " + 1044 addBadge("3"))) 1045 1046 element.append( 1047 addListItem(addLabel("Ctrl", "primary") + addPlus() + 1048 addLabel("Alt", "primary") + addPlus() + 1049 addLabel("Right-Click", "default") + 1050 " on the rectange (eg. " + addBadge("6") + 1051 " ) to show info in " + addBadge("5"))) 1052 1053 element.append('</ul>') 1054 1055 var dialog = create_dialog_body(element, "Help: EventPlot"); 1056 dialog.show(); 1057 1058 } 1059 1060 return { 1061 generate: generate, 1062 create_help_dialog: create_help_dialog 1063 }; 1064 1065}()); 1066