2 class ChartPane extends ComponentBase {
3 constructor(chartsPage, platformId, metricId)
7 this._chartsPage = chartsPage;
8 this._platformId = platformId;
9 this._metricId = metricId;
11 var result = ChartsPage.createChartSourceList(platformId, metricId);
12 this._errorMessage = result.error;
13 this._platform = result.platform;
14 this._metric = result.metric;
16 this._overviewChart = null;
17 this._mainChart = null;
18 this._mainChartStatus = null;
19 this._mainSelection = null;
20 this._mainChartIndicatorWasLocked = false;
22 this._revisions = null;
24 this._paneOpenedByClick = null;
26 this._commitLogViewer = this.content().querySelector('commit-log-viewer').component();
27 this.content().querySelector('close-button').component().setCallback(chartsPage.closePane.bind(chartsPage, this));
32 var formatter = result.metric.makeFormatter(4);
35 var overviewOptions = ChartsPage.overviewChartOptions(formatter);
36 overviewOptions.selection.onchange = function (domain, didEndDrag) {
37 self._chartsPage.setMainDomainFromOverviewSelection(domain, self, didEndDrag);
40 this._overviewChart = new InteractiveTimeSeriesChart(result.sourceList, overviewOptions);
41 this.renderReplace(this.content().querySelector('.chart-pane-overview'), this._overviewChart);
43 var mainOptions = ChartsPage.mainChartOptions(formatter);
44 mainOptions.indicator.onchange = this._indicatorDidChange.bind(this);
45 mainOptions.selection.onchange = this._mainSelectionDidChange.bind(this);
46 mainOptions.selection.onzoom = this._mainSelectionDidZoom.bind(this);
47 mainOptions.annotations.onclick = this._openAnalysisTask.bind(this);
48 mainOptions.ondata = this._didFetchData.bind(this);
49 this._mainChart = new InteractiveTimeSeriesChart(result.sourceList, mainOptions);
50 this.renderReplace(this.content().querySelector('.chart-pane-main'), this._mainChart);
52 this._mainChartStatus = new ChartPaneStatusView(result.metric, this._mainChart, chartsPage.router(), this._openCommitViewer.bind(this));
53 this.renderReplace(this.content().querySelector('.chart-pane-details'), this._mainChartStatus);
55 this.content().querySelector('.chart-pane').addEventListener('keyup', this._keyup.bind(this));
56 this._fetchAnalysisTasks();
62 AnalysisTask.fetchByPlatformAndMetric(this._platformId, this._metricId).then(function (tasks) {
63 self._mainChart.setAnnotations(tasks.map(function (task) {
64 var fillStyle = '#fc6';
65 switch (task.changeType()) {
81 startTime: task.startTime(),
82 endTime: task.endTime(),
90 platformId() { return this._platformId; }
91 metricId() { return this._metricId; }
95 var selection = this._mainChart ? this._mainChart.currentSelection() : null;
96 var point = this._mainChart ? this._mainChart.currentPoint() : null;
100 selection || (point && this._mainChartIndicatorWasLocked ? point.id : null),
104 updateFromSerializedState(state, isOpen)
106 if (!this._mainChart)
109 var selectionOrIndicatedPoint = state[2];
110 if (selectionOrIndicatedPoint instanceof Array)
111 this._mainChart.setSelection([parseFloat(selectionOrIndicatedPoint[0]), parseFloat(selectionOrIndicatedPoint[1])]);
112 else if (typeof(selectionOrIndicatedPoint) == 'number') {
113 this._mainChart.setIndicator(selectionOrIndicatedPoint, true);
114 this._mainChartIndicatorWasLocked = true;
116 this._mainChart.setIndicator(null, false);
119 setOverviewDomain(startTime, endTime)
121 if (this._overviewChart)
122 this._overviewChart.setDomain(startTime, endTime);
125 setOverviewSelection(selection)
127 if (this._overviewChart)
128 this._overviewChart.setSelection(selection);
131 setMainDomain(startTime, endTime)
134 this._mainChart.setDomain(startTime, endTime);
137 _mainSelectionDidChange(selection, didEndDrag)
139 this._chartsPage.mainChartSelectionDidChange(this, didEndDrag);
143 _mainSelectionDidZoom(selection)
145 this._overviewChart.setSelection(selection, this);
146 this._mainChart.setSelection(null);
147 this._chartsPage.setMainDomainFromZoom(selection, this);
151 _openAnalysisTask(annotation)
153 window.open(this._chartsPage.router().url(`analysis/task/${annotation.task.id()}`), '_blank');
156 _indicatorDidChange(indicatorID, isLocked)
158 this._chartsPage.mainChartIndicatorDidChange(this, isLocked || this._mainChartIndicatorWasLocked);
159 this._mainChartIndicatorWasLocked = isLocked;
160 this._mainChartStatus.updateRevisionListWithNotification();
164 _openCommitViewer(repository, from, to)
167 this._commitLogViewer.view(repository, from, to).then(function () {
168 self._mainChartStatus.setCurrentRepository(self._commitLogViewer.currentRepository());
175 this._mainChartStatus.updateRevisionListWithNotification();
181 switch (event.keyCode) {
183 if (!this._mainChart.moveLockedIndicatorWithNotification(false))
187 if (!this._mainChart.moveLockedIndicatorWithNotification(true))
191 if (!this._mainChartStatus.moveRepositoryWithNotification(false))
194 if (!this._mainChartStatus.moveRepositoryWithNotification(true))
200 event.preventDefault();
201 event.stopPropagation();
206 Instrumentation.startMeasuringTime('ChartPane', 'render');
210 if (this._platform && this._metric) {
211 var metric = this._metric;
212 var platform = this._platform;
214 this.renderReplace(this.content().querySelector('.chart-pane-title'),
215 metric.fullName() + ' on ' + platform.name());
218 if (this._errorMessage) {
219 this.renderReplace(this.content().querySelector('.chart-pane-main'), this._errorMessage);
223 if (this._mainChartStatus) {
224 this._mainChartStatus.render();
225 this._renderActionToolbar(this._mainChartStatus.analyzeData());
228 var body = this.content().querySelector('.chart-pane-body');
229 if (this._commitLogViewer.currentRepository()) {
230 body.classList.add('has-second-sidebar');
231 this._commitLogViewer.render();
233 body.classList.remove('has-second-sidebar');
235 Instrumentation.endMeasuringTime('ChartPane', 'render');
238 _renderActionToolbar(analyzeData)
241 var platform = this._platform;
242 var metric = this._metric;
244 var element = ComponentBase.createElement;
245 var link = ComponentBase.createLink;
248 if (this._chartsPage.canBreakdown(platform, metric)) {
249 actions.push(element('li', link('Breakdown', function () {
250 self._chartsPage.insertBreakdownPanesAfter(platform, metric, self);
254 var platformPane = this.content().querySelector('.chart-pane-alternative-platforms');
255 var alternativePlatforms = this._chartsPage.alternatePlatforms(platform, metric);
256 if (alternativePlatforms.length) {
257 this.renderReplace(platformPane, Platform.sortByName(alternativePlatforms).map(function (platform) {
258 return element('li', link(platform.label(), function () {
259 self._chartsPage.insertPaneAfter(platform, metric, self);
263 actions.push(element('li', {class: this._paneOpenedByClick == platformPane ? 'selected' : ''},
264 this._makeAnchorToOpenPane(platformPane, 'Other Platforms', true)));
266 platformPane.style.display = 'none';
269 var analyzePane = this.content().querySelector('.chart-pane-analyze-pane');
271 actions.push(element('li', {class: this._paneOpenedByClick == analyzePane ? 'selected' : ''},
272 this._makeAnchorToOpenPane(analyzePane, 'Analyze', false)));
274 var router = this._chartsPage.router();
275 analyzePane.onsubmit = function (event) {
276 event.preventDefault();
277 var newWindow = window.open(router.url('analysis/task/create'), '_blank');
279 var name = analyzePane.querySelector('input').value;
280 AnalysisTask.create(name, analyzeData.startPointId, analyzeData.endPointId).then(function (data) {
281 newWindow.location.href = router.url('analysis/task/' + data['taskId']);
282 // FIXME: Refetch the list of analysis tasks.
283 }, function (error) {
284 newWindow.location.href = router.url('analysis/task/create', {error: error});
288 analyzePane.style.display = 'none';
289 analyzePane.onsubmit = function (event) { event.preventDefault(); }
292 this._paneOpenedByClick = null;
293 this.renderReplace(this.content().querySelector('.chart-pane-action-buttons'), actions);
296 _makeAnchorToOpenPane(pane, label, shouldRespondToHover)
299 var ignoreMouseLeave = false;
301 var setPaneVisibility = function (pane, shouldShow) {
302 var anchor = pane.anchor;
304 var width = anchor.offsetParent.offsetWidth;
305 pane.style.top = anchor.offsetTop + anchor.offsetHeight + 'px';
306 pane.style.right = (width - anchor.offsetLeft - anchor.offsetWidth) + 'px';
308 pane.style.display = shouldShow ? null : 'none';
309 anchor.parentNode.className = shouldShow ? 'selected' : '';
310 if (self._paneOpenedByClick == pane && !shouldShow)
311 self._paneOpenedByClick = null;
316 onclick: function (event) {
317 event.preventDefault();
318 var shouldShowPane = pane.style.display == 'none';
319 if (shouldShowPane) {
320 if (self._paneOpenedByClick)
321 setPaneVisibility(self._paneOpenedByClick, false);
322 self._paneOpenedByClick = pane;
324 setPaneVisibility(pane, shouldShowPane);
327 if (shouldRespondToHover) {
328 var mouseIsInAnchor = false;
329 var mouseIsInPane = false;
331 attributes.onmouseenter = function () {
332 if (self._paneOpenedByClick)
334 mouseIsInAnchor = true;
335 setPaneVisibility(pane, true);
337 attributes.onmouseleave = function () {
338 setTimeout(function () {
340 setPaneVisibility(pane, false);
342 mouseIsInAnchor = false;
345 pane.onmouseleave = function () {
346 setTimeout(function () {
347 if (!mouseIsInAnchor)
348 setPaneVisibility(pane, false);
350 mouseIsInPane = false;
352 pane.onmouseenter = function () {
353 mouseIsInPane = true;
357 var anchor = ComponentBase.createElement('a', attributes, label);
358 pane.anchor = anchor;
362 static htmlTemplate()
365 <section class="chart-pane" tabindex="0">
366 <header class="chart-pane-header">
367 <h2 class="chart-pane-title">-</h2>
368 <nav class="chart-pane-actions">
370 <li class="close"><close-button></close-button></li>
372 <ul class="chart-pane-action-buttons buttoned-toolbar"></ul>
373 <ul class="chart-pane-alternative-platforms" style="display:none"></ul>
374 <form class="chart-pane-analyze-pane" style="display:none">
375 <input type="text" required>
376 <button>Create</button>
380 <div class="chart-pane-body">
381 <div class="chart-pane-main"></div>
382 <div class="chart-pane-sidebar">
383 <div class="chart-pane-overview"></div>
384 <div class="chart-pane-details"></div>
386 <div class="chart-pane-second-sidebar">
387 <commit-log-viewer></commit-log-viewer>
396 return Toolbar.cssTemplate() + `
402 border: solid 1px #ccc;
403 border-radius: 0.5rem;
407 .chart-pane:focus .chart-pane-header {
408 background: rgba(204, 153, 51, 0.1);
418 border-bottom: solid 1px #ccc;
424 padding-left: 1.5rem;
426 font-weight: inherit;
429 .chart-pane-actions {
433 justify-content: space-between;
441 .chart-pane-actions ul {
450 .chart-pane-actions .chart-pane-action-buttons {
455 .chart-pane-actions .chart-pane-alternative-platforms,
456 .chart-pane-analyze-pane {
460 border: solid 1px #ccc;
461 border-radius: 0.2rem;
463 background: rgba(255, 255, 255, 0.8);
464 -webkit-backdrop-filter: blur(0.5rem);
468 margin-right: -0.2rem;
471 .chart-pane-alternative-platforms li {
474 .chart-pane-alternative-platforms li a {
476 text-decoration: none;
479 padding: 0.2rem 0.5rem;
482 .chart-pane-alternative-platforms a:hover,
483 .chart-pane-analyze-pane input:focus {
484 background: rgba(204, 153, 51, 0.1);
487 .chart-pane-analyze-pane {
491 .chart-pane-analyze-pane input {
495 border: solid 1px #ccc;
501 height: calc(100% - 2rem);
505 padding-right: 20rem;
508 vertical-align: middle;
512 .has-second-sidebar .chart-pane-main {
513 padding-right: 40rem;
516 .chart-pane-main > * {
522 .chart-pane-second-sidebar {
527 border-left: solid 1px #ccc;
531 :not(.has-second-sidebar) > .chart-pane-second-sidebar {
535 .chart-pane-sidebar {
539 .has-second-sidebar .chart-pane-sidebar {
543 .has-second-sidebar .chart-pane-second-sidebar {
547 .chart-pane-overview {
550 border-bottom: solid 1px #ccc;
553 .chart-pane-overview > * {
559 .chart-pane-details {
562 height: calc(100% - 5.5rem - 2px);