resources/perf.webkit.org/public/v3/components/chart-pane-base.js (332 lines of code) (raw):

class ChartPaneBase extends ComponentBase { constructor(name) { super(name); this._errorMessage = null; this._platformId = null; this._metricId = null; this._platform = null; this._metric = null; this._disableSampling = false; this._showOutliers = false; this._openRepository = null; this._overviewChart = null; this._mainChart = null; this._mainChartStatus = null; this._commitLogViewer = null; this._tasksForAnnotations = null; this._detectedAnnotations = null; this._renderAnnotationsLazily = new LazilyEvaluatedFunction(this._renderAnnotations.bind(this)); } configure(platformId, metricId) { var result = ChartStyles.resolveConfiguration(platformId, metricId); this._errorMessage = result.error; this._platformId = platformId; this._metricId = metricId; this._platform = result.platform; this._metric = result.metric; this._overviewChart = null; this._mainChart = null; this._mainChartStatus = null; this._commitLogViewer = this.content().querySelector('commit-log-viewer').component(); if (result.error) return; var formatter = result.metric.makeFormatter(4); this._overviewChart = new InteractiveTimeSeriesChart(this._createSourceList(false), ChartStyles.overviewChartOptions(formatter)); this._overviewChart.listenToAction('selectionChange', this._overviewSelectionDidChange.bind(this)); this.renderReplace(this.content().querySelector('.chart-pane-overview'), this._overviewChart); this._mainChart = new InteractiveTimeSeriesChart(this._createSourceList(true), ChartStyles.mainChartOptions(formatter)); this._mainChart.listenToAction('dataChange', () => this._didFetchData()); this._mainChart.listenToAction('indicatorChange', this._indicatorDidChange.bind(this)); this._mainChart.listenToAction('selectionChange', this._mainSelectionDidChange.bind(this)); this._mainChart.listenToAction('zoom', this._mainSelectionDidZoom.bind(this)); this._mainChart.listenToAction('annotationClick', this._didClickAnnotation.bind(this)); this.renderReplace(this.content().querySelector('.chart-pane-main'), this._mainChart); this._revisionRange = new ChartRevisionRange(this._mainChart); this._mainChartStatus = new ChartPaneStatusView(result.metric, this._mainChart); this._mainChartStatus.setCurrentRepository(this._openRepository); this._mainChartStatus.listenToAction('openRepository', this.openNewRepository.bind(this)); this.renderReplace(this.content().querySelector('.chart-pane-details'), this._mainChartStatus); this.content().querySelector('.chart-pane').addEventListener('keydown', this._keyup.bind(this)); this.fetchAnalysisTasks(false); } isSamplingEnabled() { return !this._disableSampling; } setSamplingEnabled(enabled) { this._disableSampling = !enabled; this._updateSourceList(); } isShowingOutliers() { return this._showOutliers; } setShowOutliers(show) { this._showOutliers = !!show; this._updateSourceList(); } _createSourceList(isMainChart) { return ChartStyles.createSourceList(this._platform, this._metric, this._disableSampling, this._showOutliers, isMainChart); } _updateSourceList() { this._mainChart.setSourceList(this._createSourceList(true)); this._overviewChart.setSourceList(this._createSourceList(false)); } fetchAnalysisTasks(noCache) { // FIXME: we need to update the annotation bars when the change type of tasks change. var self = this; AnalysisTask.fetchByPlatformAndMetric(this._platformId, this._metricId, noCache).then(function (tasks) { self._tasksForAnnotations = tasks; self.enqueueToRender(); }); } // FIXME: We should have a mechanism to get notified whenever the set of annotations change. didUpdateAnnotations() { this._tasksForAnnotations = [...this._tasksForAnnotations]; this.enqueueToRender(); } platformId() { return this._platformId; } metricId() { return this._metricId; } setOverviewDomain(startTime, endTime) { if (this._overviewChart) this._overviewChart.setDomain(startTime, endTime); } setMainDomain(startTime, endTime) { if (this._mainChart) this._mainChart.setDomain(startTime, endTime); } setMainSelection(selection) { if (this._mainChart) this._mainChart.setSelection(selection); } setOpenRepository(repository) { this._openRepository = repository; if (this._mainChartStatus) this._mainChartStatus.setCurrentRepository(repository); this._updateCommitLogViewer(); } _overviewSelectionDidChange(domain, didEndDrag) { } _mainSelectionDidChange(selection, didEndDrag) { this._updateCommitLogViewer(); } _mainSelectionDidZoom(selection) { this._overviewChart.setSelection(selection, this); this._mainChart.setSelection(null); this.enqueueToRender(); } _indicatorDidChange(indicatorID, isLocked) { this._updateCommitLogViewer(); } _didFetchData() { this._updateCommitLogViewer(); } _updateCommitLogViewer() { if (!this._revisionRange) return; const range = this._revisionRange.rangeForRepository(this._openRepository); this._commitLogViewer.view(this._openRepository, range.from, range.to); this.enqueueToRender(); } _didClickAnnotation(annotation) { if (annotation.task) this._openAnalysisTask(annotation); else { const newSelection = [annotation.startTime, annotation.endTime]; this._mainChart.setSelection(newSelection); this._overviewChart.setSelection(newSelection, this); this.enqueueToRender(); } } _openAnalysisTask(annotation) { var router = this.router(); console.assert(router); window.open(router.url(`analysis/task/${annotation.task.id()}`), '_blank'); } router() { return null; } openNewRepository(repository) { this.content().querySelector('.chart-pane').focus(); this.setOpenRepository(repository); } _keyup(event) { switch (event.keyCode) { case 37: // Left if (!this._mainChart.moveLockedIndicatorWithNotification(false)) return; break; case 39: // Right if (!this._mainChart.moveLockedIndicatorWithNotification(true)) return; break; case 38: // Up if (!this._moveOpenRepository(false)) return; break; case 40: // Down if (!this._moveOpenRepository(true)) return; break; default: return; } this.enqueueToRender(); event.preventDefault(); event.stopPropagation(); } _moveOpenRepository(forward) { const openRepository = this._openRepository; if (!openRepository) return false; const revisionList = this._revisionRange.revisionList(); if (!revisionList) return false; const currentIndex = revisionList.findIndex((info) => info.repository == openRepository); console.assert(currentIndex >= 0); const newIndex = currentIndex + (forward ? 1 : -1); if (newIndex < 0 || newIndex >= revisionList.length) return false; this.openNewRepository(revisionList[newIndex].repository); return true; } render() { Instrumentation.startMeasuringTime('ChartPane', 'render'); super.render(); if (this._overviewChart) this._overviewChart.enqueueToRender(); if (this._mainChart) { this._mainChart.enqueueToRender(); this._renderAnnotationsLazily.evaluate(this._tasksForAnnotations, this._detectedAnnotations); } if (this._errorMessage) { this.renderReplace(this.content().querySelector('.chart-pane-main'), this._errorMessage); return; } if (this._mainChartStatus) this._mainChartStatus.enqueueToRender(); var body = this.content().querySelector('.chart-pane-body'); if (this._openRepository) body.classList.add('has-second-sidebar'); else body.classList.remove('has-second-sidebar'); Instrumentation.endMeasuringTime('ChartPane', 'render'); } _renderAnnotations(taskForAnnotations, detectedAnnotations) { let annotations = (taskForAnnotations || []).map((task) => { return { task, fillStyle: ChartStyles.annotationFillStyleForTask(task), startTime: task.startTime(), endTime: task.endTime(), label: task.label() }; }); annotations = annotations.concat(detectedAnnotations || []); this._mainChart.setAnnotations(annotations); } static htmlTemplate() { return ` <section class="chart-pane" tabindex="0"> ${this.paneHeaderTemplate()} <div class="chart-pane-body"> <div class="chart-pane-main"></div> <div class="chart-pane-sidebar"> <div class="chart-pane-overview"></div> <div class="chart-pane-details"></div> </div> <div class="chart-pane-second-sidebar"> <commit-log-viewer></commit-log-viewer> </div> </div> </section> ${this.paneFooterTemplate()} `; } static paneHeaderTemplate() { return ''; } static paneFooterTemplate() { return ''; } static cssTemplate() { return Toolbar.cssTemplate() + ` .chart-pane { padding: 0rem; height: 18rem; outline: none; } .chart-pane:focus .chart-pane-header { background: rgba(204, 153, 51, 0.1); } .chart-pane-body { position: relative; width: 100%; height: 100%; } .chart-pane-main { padding-right: 20rem; height: 100%; margin: 0; vertical-align: middle; text-align: center; } .has-second-sidebar .chart-pane-main { padding-right: 40rem; } .chart-pane-main > * { width: 100%; height: 100%; } .chart-pane-sidebar, .chart-pane-second-sidebar { position: absolute; right: 0; top: 0; width: 0; border-left: solid 1px #ccc; height: 100%; } :not(.has-second-sidebar) > .chart-pane-second-sidebar { border-left: 0; } .chart-pane-sidebar { width: 20rem; } .has-second-sidebar .chart-pane-sidebar { right: 20rem; } .has-second-sidebar .chart-pane-second-sidebar { width: 20rem; } .chart-pane-overview { width: 100%; height: 5rem; border-bottom: solid 1px #ccc; } .chart-pane-overview > * { display: block; width: 100%; height: 100%; } .chart-pane-details { position: relative; display: block; height: calc(100% - 5.5rem - 2px); overflow-y: scroll; padding-top: 0.5rem; } `; } }