bosun.org@v0.0.0-20210513094433-e25bc3e69a1f/cmd/bosun/web/static/js/directives.ts (about) 1 /// <reference path="0-bosun.ts" /> 2 3 bosunApp.directive('tsResults', function() { 4 return { 5 templateUrl: '/partials/results.html', 6 link: (scope: any, elem, attrs) => { 7 scope.isSeries = v => { 8 return typeof (v) === 'object'; 9 }; 10 }, 11 }; 12 }); 13 14 bosunApp.directive('tsComputations', () => { 15 return { 16 scope: { 17 computations: '=tsComputations', 18 time: '=', 19 header: '=', 20 }, 21 templateUrl: '/partials/computations.html', 22 link: (scope: any, elem: any, attrs: any) => { 23 if (scope.time) { 24 var m = moment.utc(scope.time); 25 scope.timeParam = "&date=" + encodeURIComponent(m.format("YYYY-MM-DD")) + "&time=" + encodeURIComponent(m.format("HH:mm")); 26 } 27 scope.btoa = (v: any) => { 28 return encodeURIComponent(btoa(v)); 29 }; 30 }, 31 }; 32 }); 33 34 35 function fmtDuration(v: any) { 36 var diff = (moment.duration(v, 'milliseconds')); 37 var f; 38 if (Math.abs(v) < 60000) { 39 return diff.format('ss[s]'); 40 } 41 return diff.format('d[d]hh[h]mm[m]ss[s]'); 42 } 43 44 45 function fmtTime(v: any) { 46 var m = moment(v).utc(); 47 var now = moment().utc(); 48 var msdiff = now.diff(m); 49 var ago = ''; 50 var inn = ''; 51 if (msdiff >= 0) { 52 ago = ' ago'; 53 } else { 54 inn = 'in '; 55 } 56 return m.format() + ' UTC (' + inn + fmtDuration(Math.abs(msdiff)) + ago + ')'; 57 } 58 59 function parseDuration(v: string) { 60 var pattern = /(\d+)(d|y|n|h|m|s)(-ago)?/; 61 var m = pattern.exec(v); 62 if (m) { 63 return moment.duration(parseInt(m[1]), m[2].replace('n', 'M')) 64 } 65 return moment.duration(0) 66 } 67 68 interface ITimeScope extends IBosunScope { 69 noLink: string; 70 } 71 72 bosunApp.directive("tsTime", function() { 73 return { 74 link: function(scope: ITimeScope, elem: any, attrs: any) { 75 scope.$watch(attrs.tsTime, (v: any) => { 76 var m = moment(v).utc(); 77 var text = fmtTime(v); 78 if (attrs.tsEndTime) { 79 var diff = moment(scope.$eval(attrs.tsEndTime)).diff(m); 80 var duration = fmtDuration(diff); 81 text += " for " + duration; 82 } 83 if (attrs.noLink) { 84 elem.text(text); 85 } else { 86 var el = document.createElement('a'); 87 el.text = text; 88 el.href = 'http://www.timeanddate.com/worldclock/converted.html?iso='; 89 el.href += m.format('YYYYMMDDTHHmm'); 90 el.href += '&p1=0'; 91 angular.forEach(scope.timeanddate, (v, k) => { 92 el.href += '&p' + (k + 2) + '=' + v; 93 }); 94 elem.html(el); 95 } 96 }); 97 }, 98 }; 99 }); 100 101 bosunApp.directive("tsTimeUnix", function() { 102 return { 103 link: function(scope: ITimeScope, elem: any, attrs: any) { 104 scope.$watch(attrs.tsTimeUnix, (v: any) => { 105 var m = moment(v * 1000).utc(); 106 var text = fmtTime(m); 107 if (attrs.tsEndTime) { 108 var diff = moment(scope.$eval(attrs.tsEndTime)).diff(m); 109 var duration = fmtDuration(diff); 110 text += " for " + duration; 111 } 112 if (attrs.noLink) { 113 elem.text(text); 114 } else { 115 var el = document.createElement('a'); 116 el.text = text; 117 el.href = 'http://www.timeanddate.com/worldclock/converted.html?iso='; 118 el.href += m.format('YYYYMMDDTHHmm'); 119 el.href += '&p1=0'; 120 angular.forEach(scope.timeanddate, (v, k) => { 121 el.href += '&p' + (k + 2) + '=' + v; 122 }); 123 elem.html(el); 124 } 125 }); 126 }, 127 }; 128 }); 129 130 bosunApp.directive("tsSince", function() { 131 return { 132 link: function(scope: IBosunScope, elem: any, attrs: any) { 133 scope.$watch(attrs.tsSince, (v: any) => { 134 var m = moment(v).utc(); 135 elem.text(m.fromNow()); 136 }); 137 }, 138 }; 139 }); 140 141 bosunApp.directive("tooltip", function() { 142 return { 143 link: function(scope: IGraphScope, elem: any, attrs: any) { 144 angular.element(elem[0]).tooltip({ placement: "bottom" }); 145 }, 146 }; 147 }); 148 149 150 bosunApp.directive('tsTab', () => { 151 return { 152 link: (scope: any, elem: any, attrs: any) => { 153 var ta = elem[0]; 154 elem.keydown(evt => { 155 if (evt.ctrlKey) { 156 return; 157 } 158 // This is so shift-enter can be caught to run a rule when tsTab is called from 159 // the rule page 160 if (evt.keyCode == 13 && evt.shiftKey) { 161 return; 162 } 163 switch (evt.keyCode) { 164 case 9: // tab 165 evt.preventDefault(); 166 var v = ta.value; 167 var start = ta.selectionStart; 168 ta.value = v.substr(0, start) + "\t" + v.substr(start); 169 ta.selectionStart = ta.selectionEnd = start + 1; 170 return; 171 case 13: // enter 172 if (ta.selectionStart != ta.selectionEnd) { 173 return; 174 } 175 evt.preventDefault(); 176 var v = ta.value; 177 var start = ta.selectionStart; 178 var sub = v.substr(0, start); 179 var last = sub.lastIndexOf("\n") + 1 180 for (var i = last; i < sub.length && /[ \t]/.test(sub[i]); i++) 181 ; 182 var ws = sub.substr(last, i - last); 183 ta.value = v.substr(0, start) + "\n" + ws + v.substr(start); 184 ta.selectionStart = ta.selectionEnd = start + 1 + ws.length; 185 } 186 }); 187 }, 188 }; 189 }); 190 191 interface JQuery { 192 tablesorter(v: any): JQuery; 193 linedtextarea(): void; 194 } 195 196 bosunApp.directive('tsresizable', () => { 197 return { 198 restrict: 'A', 199 scope: { 200 callback: '&onResize' 201 }, 202 link: function postLink(scope: any, elem: any, attrs) { 203 elem.resizable(); 204 elem.on('resizestop', function(evt, ui) { 205 if (scope.callback) { scope.callback(); } 206 }); 207 } 208 }; 209 }); 210 211 bosunApp.directive('tsTableSort', ['$timeout', ($timeout: ng.ITimeoutService) => { 212 return { 213 link: (scope: ng.IScope, elem: any, attrs: any) => { 214 $timeout(() => { 215 $(elem).tablesorter({ 216 sortList: scope.$eval(attrs.tsTableSort), 217 }); 218 }); 219 }, 220 }; 221 }]); 222 223 // https://gist.github.com/mlynch/dd407b93ed288d499778 224 bosunApp.directive('autofocus', ['$timeout', function($timeout) { 225 return { 226 restrict: 'A', 227 link : function($scope, $element) { 228 $timeout(function() { 229 $element[0].focus(); 230 }); 231 } 232 } 233 }]); 234 235 bosunApp.directive('tsTimeLine', () => { 236 var tsdbFormat = d3.time.format.utc("%Y/%m/%d-%X"); 237 function parseDate(s: any) { 238 return moment.utc(s).toDate(); 239 } 240 var margin = { 241 top: 10, 242 right: 10, 243 bottom: 30, 244 left: 250, 245 }; 246 return { 247 link: (scope: any, elem: any, attrs: any) => { 248 scope.shown = {}; 249 scope.collapse = (i: any, entry: any, v: any) => { 250 scope.shown[i] = !scope.shown[i]; 251 if (scope.loadTimelinePanel && entry && scope.shown[i]) { 252 scope.loadTimelinePanel(entry, v); 253 } 254 }; 255 scope.$watch('alert_history', update); 256 function update(history: any) { 257 if (!history) { 258 return; 259 } 260 var entries = d3.entries(history); 261 if (!entries.length) { 262 return; 263 } 264 entries.sort((a, b) => { 265 return a.key.localeCompare(b.key); 266 }); 267 scope.entries = entries; 268 var values = entries.map(v => { return v.value }); 269 var keys = entries.map(v => { return v.key }); 270 var barheight = 500 / values.length; 271 barheight = Math.min(barheight, 45); 272 barheight = Math.max(barheight, 15); 273 var svgHeight = values.length * barheight + margin.top + margin.bottom; 274 var height = svgHeight - margin.top - margin.bottom; 275 var svgWidth = elem.width(); 276 var width = svgWidth - margin.left - margin.right; 277 var xScale = d3.time.scale.utc().range([0, width]); 278 var xAxis = d3.svg.axis() 279 .scale(xScale) 280 .orient('bottom'); 281 elem.empty(); 282 var svg = d3.select(elem[0]) 283 .append('svg') 284 .attr('width', svgWidth) 285 .attr('height', svgHeight) 286 .append('g') 287 .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); 288 svg.append('g') 289 .attr('class', 'x axis tl-axis') 290 .attr('transform', 'translate(0,' + height + ')'); 291 xScale.domain([ 292 d3.min(values, (d: any) => { return d3.min(d.History, (c: any) => { return parseDate(c.Time); }); }), 293 d3.max(values, (d: any) => { return d3.max(d.History, (c: any) => { return parseDate(c.EndTime); }); }), 294 ]); 295 var legend = d3.select(elem[0]) 296 .append('div') 297 .attr('class', 'tl-legend'); 298 var time_legend = legend 299 .append('div') 300 .text(values[0].History[0].Time); 301 var alert_legend = legend 302 .append('div') 303 .text(keys[0]); 304 svg.select('.x.axis') 305 .transition() 306 .call(xAxis); 307 var chart = svg.append('g'); 308 angular.forEach(entries, function(entry: any, i: number) { 309 chart.selectAll('.bars') 310 .data(entry.value.History) 311 .enter() 312 .append('rect') 313 .attr('class', (d: any) => { return 'tl-' + d.Status; }) 314 .attr('x', (d: any) => { return xScale(parseDate(d.Time)); }) 315 .attr('y', i * barheight) 316 .attr('height', barheight) 317 .attr('width', (d: any) => { 318 return xScale(parseDate(d.EndTime)) - xScale(parseDate(d.Time)); 319 }) 320 .on('mousemove.x', mousemove_x) 321 .on('mousemove.y', function(d) { 322 alert_legend.text(entry.key); 323 }) 324 .on('click', function(d, j) { 325 var id = 'panel' + i + '-' + j; 326 scope.shown['group' + i] = true; 327 scope.shown[id] = true; 328 if (scope.loadTimelinePanel) { 329 scope.loadTimelinePanel(entry, d); 330 } 331 332 scope.$apply(); 333 setTimeout(() => { 334 var e = $("#" + id); 335 if (!e) { 336 console.log('no', id, e); 337 return; 338 } 339 $('html, body').scrollTop(e.offset().top); 340 }); 341 }); 342 }); 343 chart.selectAll('.labels') 344 .data(keys) 345 .enter() 346 .append('text') 347 .attr('text-anchor', 'end') 348 .attr('x', 0) 349 .attr('dx', '-.5em') 350 .attr('dy', '.25em') 351 .attr('y', function(d: any, i: number) { return (i + .5) * barheight; }) 352 .text(function(d: any) { return d; }); 353 chart.selectAll('.sep') 354 .data(values) 355 .enter() 356 .append('rect') 357 .attr('y', function(d: any, i: number) { return (i + 1) * barheight }) 358 .attr('height', 1) 359 .attr('x', 0) 360 .attr('width', width) 361 .on('mousemove.x', mousemove_x); 362 function mousemove_x() { 363 var x = xScale.invert(d3.mouse(this)[0]); 364 time_legend 365 .text(tsdbFormat(x)); 366 } 367 }; 368 }, 369 }; 370 }); 371 372 var fmtUnits = ['', 'k', 'M', 'G', 'T', 'P', 'E']; 373 374 function nfmt(s: any, mult: number, suffix: string, opts: any) { 375 opts = opts || {}; 376 var n = parseFloat(s); 377 if (isNaN(n) && typeof s === 'string') { 378 return s; 379 } 380 if (opts.round) n = Math.round(n); 381 if (!n) return suffix ? '0 ' + suffix : '0'; 382 if (isNaN(n) || !isFinite(n)) return '-'; 383 var a = Math.abs(n); 384 if (a >= 1) { 385 var number = Math.floor(Math.log(a) / Math.log(mult)); 386 a /= Math.pow(mult, Math.floor(number)); 387 if (fmtUnits[number]) { 388 suffix = fmtUnits[number] + suffix; 389 } 390 } 391 var r = a.toFixed(5); 392 if (a < 1e-5) { 393 r = a.toString(); 394 } 395 var neg = n < 0 ? '-' : ''; 396 return neg + (+r) + suffix; 397 } 398 399 bosunApp.filter('nfmt', function() { 400 return function(s: any) { 401 return nfmt(s, 1000, '', {}); 402 } 403 }); 404 405 bosunApp.filter('bytes', function() { 406 return function(s: any) { 407 return nfmt(s, 1024, 'B', { round: true }); 408 } 409 }); 410 411 bosunApp.filter('bits', function() { 412 return function(s: any) { 413 return nfmt(s, 1024, 'b', { round: true }); 414 } 415 }); 416 417 418 bosunApp.directive('elastic', [ 419 '$timeout', 420 function($timeout) { 421 return { 422 restrict: 'A', 423 link: function($scope, element) { 424 $scope.initialHeight = $scope.initialHeight || element[0].style.height; 425 var resize = function() { 426 element[0].style.height = $scope.initialHeight; 427 element[0].style.height = "" + element[0].scrollHeight + "px"; 428 }; 429 element.on("input change", resize); 430 $timeout(resize, 0); 431 } 432 }; 433 } 434 ]); 435 436 bosunApp.directive('tsBar', ['$window', 'nfmtFilter', function($window: ng.IWindowService, fmtfilter: any) { 437 var margin = { 438 top: 20, 439 right: 20, 440 bottom: 0, 441 left: 200, 442 }; 443 return { 444 scope: { 445 data: '=', 446 height: '=', 447 }, 448 link: (scope: any, elem: any, attrs: any) => { 449 var svgHeight = +scope.height || 150; 450 var height = svgHeight - margin.top - margin.bottom; 451 var svgWidth: number; 452 var width: number; 453 var xScale = d3.scale.linear(); 454 var yScale = d3.scale.ordinal() 455 var top = d3.select(elem[0]) 456 .append('svg') 457 .attr('height', svgHeight) 458 .attr('width', '100%'); 459 var svg = top 460 .append('g') 461 //.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); 462 var xAxis = d3.svg.axis() 463 .scale(xScale) 464 .orient("top") 465 var yAxis = d3.svg.axis() 466 .scale(yScale) 467 .orient("left") 468 scope.$watch('data', update); 469 var w = angular.element($window); 470 scope.$watch(() => { 471 return w.width(); 472 }, resize, true); 473 w.bind('resize', () => { 474 scope.$apply(); 475 }); 476 function resize() { 477 if (!scope.data) { 478 return; 479 } 480 svgWidth = elem.width(); 481 if (svgWidth <= 0) { 482 return; 483 } 484 margin.left = d3.max(scope.data, (d: any) => { return d.name.length * 8 }) 485 width = svgWidth - margin.left - margin.right; 486 svgHeight = scope.data.length * 15; 487 height = svgHeight - margin.top - margin.bottom; 488 xScale.range([0, width]); 489 yScale.rangeRoundBands([0, height], .1); 490 yAxis.scale(yScale); 491 svg.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); 492 svg.attr('width', svgWidth); 493 svg.attr('height', height); 494 top.attr('height', svgHeight); 495 xAxis.ticks(width / 60); 496 draw(); 497 } 498 function update(v: any) { 499 if (!angular.isArray(v) || v.length == 0) { 500 return; 501 } 502 resize(); 503 } 504 function draw() { 505 if (!scope.data) { 506 return; 507 } 508 yScale.domain(scope.data.map((d: any) => { return d.name })); 509 xScale.domain([0, d3.max(scope.data, (d: any) => { return d.Value })]); 510 svg.selectAll('g.axis').remove(); 511 //X axis 512 svg.append("g") 513 .attr("class", "x axis") 514 .call(xAxis) 515 svg.append("g") 516 .attr("class", "y axis") 517 .call(yAxis) 518 .selectAll("text") 519 .style("text-anchor", "end") 520 var bars = svg.selectAll(".bar").data(scope.data); 521 bars.enter() 522 .append("rect") 523 .attr("class", "bar") 524 .attr("y", function(d) { return yScale(d.name); }) 525 .attr("height", yScale.rangeBand()) 526 .attr('width', (d: any) => { return xScale(d.Value); }) 527 }; 528 }, 529 }; 530 }]); 531 532 bosunApp.directive('tsGraph', ['$window', 'nfmtFilter', function($window: ng.IWindowService, fmtfilter: any) { 533 var margin = { 534 top: 10, 535 right: 10, 536 bottom: 30, 537 left: 80, 538 }; 539 return { 540 scope: { 541 data: '=', 542 annotations: '=', 543 height: '=', 544 generator: '=', 545 brushStart: '=bstart', 546 brushEnd: '=bend', 547 enableBrush: '@', 548 max: '=', 549 min: '=', 550 normalize: '=', 551 annotation: '=', 552 annotateEnabled: '=', 553 showAnnotations: '=', 554 }, 555 template: '<div class="row"></div>' + // chartElemt 556 '<div class="row col-lg-12"></div>' + // timeElem 557 '<div class"row">' + // legendAnnContainer 558 '<div class="col-lg-6"></div>' + // legendElem 559 '<div class="col-lg-6"></div>' + // annElem 560 '</div>', 561 link: (scope: any, elem: any, attrs: any, $compile: any) => { 562 var chartElem = d3.select(elem.children()[0]); 563 var timeElem = d3.select(elem.children()[1]); 564 var legendAnnContainer = angular.element(elem.children()[2]); 565 var legendElem = d3.select(legendAnnContainer.children()[0]); 566 if (scope.annotateEnabled) { 567 var annElem = d3.select(legendAnnContainer.children()[1]); 568 } 569 var valueIdx = 1; 570 if (scope.normalize) { 571 valueIdx = 2; 572 } 573 var svgHeight = +scope.height || 150; 574 var height = svgHeight - margin.top - margin.bottom; 575 var svgWidth: number; 576 var width: number; 577 var yScale = d3.scale.linear().range([height, 0]); 578 var xScale = d3.time.scale.utc(); 579 var xAxis = d3.svg.axis() 580 .orient('bottom'); 581 var yAxis = d3.svg.axis() 582 .scale(yScale) 583 .orient('left') 584 .ticks(Math.min(10, height / 20)) 585 .tickFormat(fmtfilter); 586 var line: any; 587 switch (scope.generator) { 588 case 'area': 589 line = d3.svg.area(); 590 break; 591 default: 592 line = d3.svg.line(); 593 } 594 var brush = d3.svg.brush() 595 .x(xScale) 596 .on('brush', brushed) 597 .on('brushend', annotateBrushed); 598 var top = chartElem 599 .append('svg') 600 .attr('height', svgHeight) 601 .attr('width', '100%'); 602 var svg = top 603 .append('g') 604 .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); 605 var defs = svg.append('defs') 606 .append('clipPath') 607 .attr('id', 'clip') 608 .append('rect') 609 .attr('height', height); 610 var chart = svg.append('g') 611 .attr('pointer-events', 'all') 612 .attr('clip-path', 'url(#clip)'); 613 svg.append('g') 614 .attr('class', 'x axis') 615 .attr('transform', 'translate(0,' + height + ')'); 616 svg.append('g') 617 .attr('class', 'y axis'); 618 var paths = chart.append('g'); 619 chart.append('g') 620 .attr('class', 'x brush'); 621 if (scope.annotateEnabled) { 622 var ann = chart.append('g'); 623 } 624 top.append('rect') 625 .style('opacity', 0) 626 .attr('x', 0) 627 .attr('y', 0) 628 .attr('height', height) 629 .attr('width', margin.left) 630 .style('cursor', 'pointer') 631 .on('click', yaxisToggle); 632 var xloc = timeElem.append('div').attr("class", "col-lg-6"); 633 xloc.style('float', 'left'); 634 var brushText = timeElem.append('div').attr("class", "col-lg-6").append('p').attr("class", "text-right") 635 var legend = legendElem; 636 var aLegend = annElem; 637 638 var color = d3.scale.ordinal().range([ 639 '#e41a1c', 640 '#377eb8', 641 '#4daf4a', 642 '#984ea3', 643 '#ff7f00', 644 '#a65628', 645 '#f781bf', 646 '#999999', 647 ]); 648 var annColor = d3.scale.ordinal().range([ 649 '#e41a1c', 650 '#377eb8', 651 '#4daf4a', 652 '#984ea3', 653 '#ff7f00', 654 '#a65628', 655 '#f781bf', 656 '#999999', 657 ]); 658 var mousex = 0; 659 var mousey = 0; 660 var oldx = 0; 661 var hover = svg.append('g') 662 .attr('class', 'hover') 663 .style('pointer-events', 'none') 664 .style('display', 'none'); 665 var hoverPoint = hover.append('svg:circle') 666 .attr('r', 5); 667 var hoverRect = hover.append('svg:rect') 668 .attr('fill', 'white'); 669 var hoverText = hover.append('svg:text') 670 .style('font-size', '12px'); 671 var focus = svg.append('g') 672 .attr('class', 'focus') 673 .style('pointer-events', 'none'); 674 focus.append('line'); 675 var yaxisZero = false; 676 function yaxisToggle() { 677 yaxisZero = !yaxisZero; 678 draw(); 679 } 680 681 var drawAnnLegend = () => { 682 if (scope.annotation) { 683 aLegend.html('') 684 var a = scope.annotation; 685 //var table = aLegend.append('table').attr("class", "table table-condensed") 686 var table = aLegend.append("div") 687 var row = table.append("div").attr("class", "row") 688 row.append("div").attr("class", "col-lg-2").text("CreationUser") 689 row.append("div").attr("class", "col-lg-10").text(a.CreationUser) 690 row = table.append("div").attr("class", "row") 691 row.append("div").attr("class", "col-lg-2").text("Owner") 692 row.append("div").attr("class", "col-lg-10").text(a.Owner) 693 row = table.append("div").attr("class", "row") 694 row.append("div").attr("class", "col-lg-2").text("Url") 695 row.append("div").attr("class", "col-lg-10").append('a') 696 .attr("xlink:href", a.Url).text(a.Url).on("click", (d) => { 697 window.open(a.Url, "_blank"); 698 }); 699 row = table.append("div").attr("class", "row") 700 row.append("div").attr("class", "col-lg-2").text("Category") 701 row.append("div").attr("class", "col-lg-10").text(a.Category) 702 row = table.append("div").attr("class", "row") 703 row.append("div").attr("class", "col-lg-2").text("Host") 704 row.append("div").attr("class", "col-lg-10").text(a.Host) 705 row = table.append("div").attr("class", "row") 706 row.append("div").attr("class", "col-lg-2").text("Message") 707 row.append("div").attr("class", "col-lg-10").text(a.Message) 708 }// 709 }; 710 711 var drawLegend = _.throttle((normalizeIdx: any) => { 712 var names = legend.selectAll('.series') 713 .data(scope.data, (d) => { return d.Name; }); 714 names.enter() 715 .append('div') 716 .attr('class', 'series'); 717 names.exit() 718 .remove(); 719 720 721 var xi = xScale.invert(mousex); 722 xloc.text('Time: ' + fmtTime(xi)); 723 var t = xi.getTime() / 1000; 724 var minDist = width + height; 725 var minName: string, minColor: string; 726 var minX: number, minY: number; 727 728 names 729 .each(function(d: any) { 730 var idx = bisect(d.Data, t); 731 if (idx >= d.Data.length) { 732 idx = d.Data.length - 1; 733 } 734 var e = d3.select(this); 735 var pt = d.Data[idx]; 736 if (pt) { 737 e.attr('title', pt[normalizeIdx]); 738 e.text(d.Name + ': ' + fmtfilter(pt[1])); 739 var ptx = xScale(pt[0] * 1000); 740 var pty = yScale(pt[normalizeIdx]); 741 var ptd = Math.sqrt( 742 Math.pow(ptx - mousex, 2) + 743 Math.pow(pty - mousey, 2) 744 ); 745 if (ptd < minDist) { 746 minDist = ptd; 747 minX = ptx; 748 minY = pty; 749 minName = d.Name + ': ' + pt[1]; 750 minColor = color(d.Name); 751 } 752 } 753 }) 754 .style('color', (d: any) => { return color(d.Name); }); 755 hover 756 .attr('transform', 'translate(' + minX + ',' + minY + ')'); 757 hoverPoint.style('fill', minColor); 758 hoverText 759 .text(minName) 760 .style('fill', minColor); 761 var isRight = minX > width / 2; 762 var isBottom = minY > height / 2; 763 hoverText 764 .attr('x', isRight ? -5 : 5) 765 .attr('y', isBottom ? -8 : 15) 766 .attr('text-anchor', isRight ? 'end' : 'start'); 767 var node: any = hoverText.node(); 768 var bb = node.getBBox(); 769 hoverRect 770 .attr('x', bb.x - 1) 771 .attr('y', bb.y - 1) 772 .attr('height', bb.height + 2) 773 .attr('width', bb.width + 2); 774 var x = mousex; 775 if (x > width) { 776 x = 0; 777 } 778 focus.select('line') 779 .attr('x1', x) 780 .attr('x2', x) 781 .attr('y1', 0) 782 .attr('y2', height); 783 if (extentStart) { 784 var s = extentStart; 785 if (extentEnd != extentStart) { 786 s += ' - ' + extentEnd; 787 s += ' (' + extentDiff + ')' 788 } 789 brushText.text(s); 790 } 791 }, 50); 792 793 scope.$watchCollection('[data, annotations, showAnnotations]', update); 794 var showAnnotations = (show: boolean) => { 795 if (show) { 796 ann.attr("visibility", "visible"); 797 return; 798 } 799 ann.attr("visibility", "hidden"); 800 aLegend.html(''); 801 } 802 var w = angular.element($window); 803 scope.$watch(() => { 804 return w.width(); 805 }, resize, true); 806 w.bind('resize', () => { 807 scope.$apply(); 808 }); 809 function resize() { 810 svgWidth = elem.width(); 811 if (svgWidth <= 0) { 812 return; 813 } 814 width = svgWidth - margin.left - margin.right; 815 xScale.range([0, width]); 816 xAxis.scale(xScale); 817 if (!mousex) { 818 mousex = width + 1; 819 } 820 svg.attr('width', svgWidth); 821 defs.attr('width', width); 822 xAxis.ticks(width / 60); 823 draw(); 824 } 825 var oldx = 0; 826 var bisect = d3.bisector((d) => { return d[0]; }).left; 827 var bisectA = d3.bisector((d) => { return moment(d.StartDate).unix(); }).left; 828 function update(v: any) { 829 if (!angular.isArray(v) || v.length == 0) { 830 return; 831 } 832 d3.selectAll(".x.brush").call(brush.clear()); 833 if (scope.annotateEnabled) { 834 showAnnotations(scope.showAnnotations); 835 } 836 resize(); 837 } 838 function draw() { 839 if (!scope.data) { 840 return; 841 } 842 if (scope.normalize) { 843 valueIdx = 2; 844 } 845 function mousemove() { 846 var pt = d3.mouse(this); 847 mousex = pt[0]; 848 mousey = pt[1]; 849 drawLegend(valueIdx); 850 } 851 scope.data.map((data: any, i: any) => { 852 var max = d3.max(data.Data, (d: any) => { return d[1]; }); 853 data.Data.map((d: any, j: any) => { 854 d.push(d[1] / max * 100 || 0) 855 }); 856 }); 857 line.y((d: any) => { return yScale(d[valueIdx]); }); 858 line.x((d: any) => { return xScale(d[0] * 1000); }); 859 var xdomain = [ 860 d3.min(scope.data, (d: any) => { return d3.min(d.Data, (c: any) => { return c[0]; }); }) * 1000, 861 d3.max(scope.data, (d: any) => { return d3.max(d.Data, (c: any) => { return c[0]; }); }) * 1000, 862 ]; 863 if (!oldx) { 864 oldx = xdomain[1]; 865 } 866 xScale.domain(xdomain); 867 var ymin = d3.min(scope.data, (d: any) => { return d3.min(d.Data, (c: any) => { return c[1]; }); }); 868 var ymax = d3.max(scope.data, (d: any) => { return d3.max(d.Data, (c: any) => { return c[valueIdx]; }); }); 869 var diff = (ymax - ymin) / 50; 870 if (!diff) { 871 diff = 1; 872 } 873 ymin -= diff; 874 ymax += diff; 875 if (yaxisZero) { 876 if (ymin > 0) { 877 ymin = 0; 878 } else if (ymax < 0) { 879 ymax = 0; 880 } 881 } 882 var ydomain = [ymin, ymax]; 883 if (angular.isNumber(scope.min)) { 884 ydomain[0] = +scope.min; 885 } 886 if (angular.isNumber(scope.max)) { 887 ydomain[valueIdx] = +scope.max; 888 } 889 yScale.domain(ydomain); 890 if (scope.generator == 'area') { 891 line.y0(yScale(0)); 892 } 893 svg.select('.x.axis') 894 .transition() 895 .call(xAxis); 896 svg.select('.y.axis') 897 .transition() 898 .call(yAxis); 899 svg.append('text') 900 .attr("class", "ylabel") 901 .attr("transform", "rotate(-90)") 902 .attr("y", -margin.left) 903 .attr("x", - (height / 2)) 904 .attr("dy", "1em") 905 .text(_.uniq(scope.data.map(v => { return v.Unit })).join("; ")); 906 907 if (scope.annotateEnabled) { 908 var rowId = {}; // annotation Id -> rowId 909 var rowEndDate = {}; // rowId -> EndDate 910 var maxRow = 0; 911 for (var i = 0; i < scope.annotations.length; i++) { 912 if (i == 0) { 913 rowId[scope.annotations[i].Id] = 0; 914 rowEndDate[0] = scope.annotations[0].EndDate; 915 continue; 916 } 917 for (var row = 0; row <= maxRow + 1; row++) { 918 if (row == maxRow + 1) { 919 rowId[scope.annotations[i].Id] = row; 920 rowEndDate[row] = scope.annotations[i].EndDate; 921 maxRow += 1 922 break; 923 } 924 if (rowEndDate[row] < scope.annotations[i].StartDate) { 925 rowId[scope.annotations[i].Id] = row; 926 rowEndDate[row] = scope.annotations[i].EndDate; 927 break; 928 } 929 } 930 } 931 var annotations = ann.selectAll('.annotation') 932 .data(scope.annotations, (d) => { return d.Id; }); 933 annotations.enter() 934 .append("svg:a") 935 .append('rect') 936 .attr('visilibity', () => { 937 if (scope.showAnnotations) { 938 return "visible"; 939 } 940 return "hidden"; 941 }) 942 .attr("y", (d) => { return rowId[d.Id] * ((height * .05) + 2) }) 943 .attr("height", height * .05) 944 .attr("class", "annotation") 945 .attr("stroke", (d) => { return annColor(d.Id) }) 946 .attr("stroke-opacity", .5) 947 .attr("fill", (d) => { return annColor(d.Id) }) 948 .attr("fill-opacity", 0.1) 949 .attr("stroke-width", 1) 950 .attr("x", (d: any) => { return xScale(moment(d.StartDate).utc().unix() * 1000); }) 951 .attr("width", (d: any) => { 952 var startT = moment(d.StartDate).utc().unix() * 1000 953 var endT = moment(d.EndDate).utc().unix() * 1000 954 var calcWidth = xScale(endT) - xScale(startT) 955 // Never render boxes with less than 8 pixels are they are difficult to click 956 if (calcWidth < 8) { 957 return 8; 958 } 959 return calcWidth; 960 }) 961 .on("mouseenter", (ann) => { 962 if (!scope.showAnnotations) { 963 return; 964 } 965 if (ann) { 966 scope.annotation = ann; 967 drawAnnLegend(); 968 } 969 scope.$apply(); 970 }) 971 .on("click", () => { 972 if (!scope.showAnnotations) { 973 return; 974 } 975 angular.element('#modalShower').trigger('click'); 976 }); 977 annotations.exit().remove(); 978 } 979 var queries = paths.selectAll('.line') 980 .data(scope.data, (d) => { return d.Name; }); 981 switch (scope.generator) { 982 case 'area': 983 queries.enter() 984 .append('path') 985 .attr('stroke', (d: any) => { return color(d.Name); }) 986 .attr('class', 'line') 987 .style('fill', (d: any) => { return color(d.Name); }); 988 break; 989 default: 990 queries.enter() 991 .append('path') 992 .attr('stroke', (d: any) => { return color(d.Name); }) 993 .attr('class', 'line'); 994 } 995 queries.exit() 996 .remove(); 997 998 queries 999 .attr('d', (d: any) => { return line(d.Data); }) 1000 .attr('transform', null) 1001 .transition() 1002 .ease('linear') 1003 .attr('transform', 'translate(' + (xScale(oldx) - xScale(xdomain[1])) + ')'); 1004 chart.select('.x.brush') 1005 .call(brush) 1006 .selectAll('rect') 1007 .attr('height', height) 1008 .on('mouseover', () => { 1009 hover.style('display', 'block'); 1010 }) 1011 .on('mouseout', () => { 1012 hover.style('display', 'none'); 1013 }) 1014 .on('mousemove', mousemove); 1015 chart.select('.x.brush .extent') 1016 .style('stroke', '#fff') 1017 .style('fill-opacity', '.125') 1018 .style('shape-rendering', 'crispEdges'); 1019 oldx = xdomain[1]; 1020 drawLegend(valueIdx); 1021 }; 1022 var extentStart: string; 1023 var extentEnd: string; 1024 var extentDiff: string; 1025 function brushed() { 1026 var e: any; 1027 e = d3.event.sourceEvent; 1028 if (e.shiftKey) { 1029 return; 1030 } 1031 var extent = brush.extent(); 1032 extentStart = datefmt(extent[0]); 1033 extentEnd = datefmt(extent[1]); 1034 extentDiff = fmtDuration(moment(extent[1]).diff(moment(extent[0]))); 1035 drawLegend(valueIdx); 1036 if (scope.enableBrush && extentEnd != extentStart) { 1037 scope.brushStart = extentStart; 1038 scope.brushEnd = extentEnd; 1039 scope.$apply(); 1040 } 1041 } 1042 1043 function annotateBrushed() { 1044 if (!scope.annotateEnabled) { 1045 return; 1046 } 1047 var e: any 1048 e = d3.event.sourceEvent; 1049 if (!e.shiftKey) { 1050 return; 1051 } 1052 var extent = brush.extent(); 1053 scope.annotation = new Annotation(); 1054 scope.annotation.StartDate = moment(extent[0]).utc().format(timeFormat); 1055 scope.annotation.EndDate = moment(extent[1]).utc().format(timeFormat); 1056 scope.$apply(); // This logs a console type error, but also works .. odd. 1057 angular.element('#modalShower').trigger('click'); 1058 } 1059 1060 var mfmt = 'YYYY/MM/DD-HH:mm:ss'; 1061 function datefmt(d: any) { 1062 return moment(d).utc().format(mfmt); 1063 } 1064 }, 1065 }; 1066 }]);