Avi Drissman | 4e1b7bc3 | 2022-09-15 14:03:50 | [diff] [blame] | 1 | // Copyright 2013 The Chromium Authors |
[email protected] | 03bf84a | 2013-03-23 22:11:48 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | // |
| 6 | // This file contains helper methods to draw the stats timeline graphs. |
| 7 | // Each graph represents a series of stats report for a PeerConnection, |
| 8 | // e.g. 1234-0-ssrc-abcd123-bytesSent is the graph for the series of bytesSent |
| 9 | // for ssrc-abcd123 of PeerConnection 0 in process 1234. |
| 10 | // The graphs are drawn as CANVAS, grouped per report type per PeerConnection. |
| 11 | // Each group has an expand/collapse button and is collapsed initially. |
| 12 | // |
| 13 | |
dpapad | 303c9281 | 2023-10-31 02:08:35 | [diff] [blame] | 14 | import {$} from 'chrome://resources/js/util.js'; |
rbpotter | 5796b00 | 2021-03-10 18:49:21 | [diff] [blame] | 15 | |
| 16 | import {TimelineDataSeries} from './data_series.js'; |
| 17 | import {peerConnectionDataStore} from './dump_creator.js'; |
Philipp Hancke | f91ac40 | 2022-10-17 11:05:06 | [diff] [blame] | 18 | import {generateStatsLabel} from './stats_helper.js'; |
rbpotter | 5796b00 | 2021-03-10 18:49:21 | [diff] [blame] | 19 | import {TimelineGraphView} from './timeline_graph_view.js'; |
[email protected] | 03bf84a | 2013-03-23 22:11:48 | [diff] [blame] | 20 | |
Philipp Hancke | 0635d314 | 2021-03-16 20:02:09 | [diff] [blame] | 21 | const STATS_GRAPH_CONTAINER_HEADING_CLASS = 'stats-graph-container-heading'; |
[email protected] | 2746df9 | 2013-04-26 23:42:32 | [diff] [blame] | 22 | |
Philipp Hancke | ba4adef | 2023-12-20 14:43:13 | [diff] [blame] | 23 | function isReportBlocklisted(report) { |
Philipp Hancke | b7e04abd | 2022-10-17 09:35:52 | [diff] [blame] | 24 | // Codec stats reflect what has been negotiated. They don't contain |
| 25 | // information that is useful in graphs. |
Dan Beam | 26d7374 | 2020-01-16 05:59:21 | [diff] [blame] | 26 | if (report.type === 'codec') { |
Henrik Boström | b6d732af | 2019-04-23 11:32:29 | [diff] [blame] | 27 | return true; |
| 28 | } |
| 29 | // Unused data channels can stay in "connecting" indefinitely and their |
| 30 | // counters stay zero. |
Dan Beam | 26d7374 | 2020-01-16 05:59:21 | [diff] [blame] | 31 | if (report.type === 'data-channel' && |
| 32 | readReportStat(report, 'state') === 'connecting') { |
Henrik Boström | b6d732af | 2019-04-23 11:32:29 | [diff] [blame] | 33 | return true; |
| 34 | } |
| 35 | // The same is true for transports and "new". |
Dan Beam | 26d7374 | 2020-01-16 05:59:21 | [diff] [blame] | 36 | if (report.type === 'transport' && |
| 37 | readReportStat(report, 'dtlsState') === 'new') { |
Henrik Boström | b6d732af | 2019-04-23 11:32:29 | [diff] [blame] | 38 | return true; |
| 39 | } |
| 40 | // Local and remote candidates don't change over time and there are several of |
| 41 | // them. |
Dan Beam | 26d7374 | 2020-01-16 05:59:21 | [diff] [blame] | 42 | if (report.type === 'local-candidate' || report.type === 'remote-candidate') { |
Henrik Boström | b6d732af | 2019-04-23 11:32:29 | [diff] [blame] | 43 | return true; |
| 44 | } |
| 45 | return false; |
| 46 | } |
| 47 | |
| 48 | function readReportStat(report, stat) { |
Philipp Hancke | 0635d314 | 2021-03-16 20:02:09 | [diff] [blame] | 49 | const values = report.stats.values; |
Henrik Boström | b6d732af | 2019-04-23 11:32:29 | [diff] [blame] | 50 | for (let i = 0; i < values.length; i += 2) { |
Dan Beam | 26d7374 | 2020-01-16 05:59:21 | [diff] [blame] | 51 | if (values[i] === stat) { |
Henrik Boström | b6d732af | 2019-04-23 11:32:29 | [diff] [blame] | 52 | return values[i + 1]; |
| 53 | } |
| 54 | } |
| 55 | return undefined; |
| 56 | } |
| 57 | |
Philipp Hancke | ba4adef | 2023-12-20 14:43:13 | [diff] [blame] | 58 | function isStatBlocklisted(report, statName) { |
Henrik Boström | b6d732af | 2019-04-23 11:32:29 | [diff] [blame] | 59 | // The priority does not change over time on its own; plotting uninteresting. |
Dan Beam | 26d7374 | 2020-01-16 05:59:21 | [diff] [blame] | 60 | if (report.type === 'candidate-pair' && statName === 'priority') { |
Henrik Boström | b6d732af | 2019-04-23 11:32:29 | [diff] [blame] | 61 | return true; |
| 62 | } |
Philipp Hancke | 7132fb6e | 2023-09-25 09:56:33 | [diff] [blame] | 63 | // The mid/rid and ssrcs associated with a sender/receiver do not change |
| 64 | // over time; plotting uninteresting. |
Philipp Hancke | 9889636c | 2024-04-18 12:24:07 | [diff] [blame] | 65 | if (['inbound-rtp', 'outbound-rtp', |
| 66 | 'remote-inbound-rtp', 'remote-outbound-rtp'].includes(report.type) && |
Philipp Hancke | 7132fb6e | 2023-09-25 09:56:33 | [diff] [blame] | 67 | ['mid', 'rid', 'ssrc', 'rtxSsrc', 'fecSsrc'].includes(statName)) { |
Philipp Hancke | ccc4b863 | 2022-07-01 12:45:27 | [diff] [blame] | 68 | return true; |
| 69 | } |
Philipp Hancke | 06f9f8b | 2024-05-23 13:33:23 | [diff] [blame] | 70 | // Last packet sent/received timestamps on candidate-pair and inbound-rtp |
| 71 | // do not plot nicely. |
| 72 | if (['candidate-pair', 'inbound-rtp'].includes(report.type) && |
| 73 | ['lastPacketSentTimestamp', |
| 74 | 'lastPacketReceivedTimestamp'].includes(statName)) { |
| 75 | return true; |
| 76 | } |
Henrik Boström | b6d732af | 2019-04-23 11:32:29 | [diff] [blame] | 77 | return false; |
| 78 | } |
| 79 | |
Philipp Hancke | 0635d314 | 2021-03-16 20:02:09 | [diff] [blame] | 80 | const graphViews = {}; |
rbpotter | 5796b00 | 2021-03-10 18:49:21 | [diff] [blame] | 81 | // Export on |window| since tests access this directly from C++. |
| 82 | window.graphViews = graphViews; |
Philipp Hancke | 0635d314 | 2021-03-16 20:02:09 | [diff] [blame] | 83 | const graphElementsByPeerConnectionId = new Map(); |
[email protected] | 03bf84a | 2013-03-23 22:11:48 | [diff] [blame] | 84 | |
Philipp Hancke | ba4adef | 2023-12-20 14:43:13 | [diff] [blame] | 85 | // Returns number parsed from |value|, or NaN. |
[email protected] | 78d221d | 2013-06-05 07:31:33 | [diff] [blame] | 86 | function getNumberFromValue(name, value) { |
Henrik Boström | b6d732af | 2019-04-23 11:32:29 | [diff] [blame] | 87 | if (isNaN(value)) { |
| 88 | return NaN; |
| 89 | } |
[email protected] | 78d221d | 2013-06-05 07:31:33 | [diff] [blame] | 90 | return parseFloat(value); |
| 91 | } |
| 92 | |
[email protected] | 2bc26523 | 2013-05-08 20:24:13 | [diff] [blame] | 93 | // Adds the stats report |report| to the timeline graph for the given |
| 94 | // |peerConnectionElement|. |
rbpotter | 5796b00 | 2021-03-10 18:49:21 | [diff] [blame] | 95 | export function drawSingleReport( |
Philipp Hancke | ba4adef | 2023-12-20 14:43:13 | [diff] [blame] | 96 | peerConnectionElement, report) { |
Philipp Hancke | 0635d314 | 2021-03-16 20:02:09 | [diff] [blame] | 97 | const reportType = report.type; |
| 98 | const reportId = report.id; |
| 99 | const stats = report.stats; |
Dan Beam | bdd7d82 | 2019-01-05 00:59:42 | [diff] [blame] | 100 | if (!stats || !stats.values) { |
[email protected] | 03bf84a | 2013-03-23 22:11:48 | [diff] [blame] | 101 | return; |
Dan Beam | bdd7d82 | 2019-01-05 00:59:42 | [diff] [blame] | 102 | } |
Henrik Boström | b6d732af | 2019-04-23 11:32:29 | [diff] [blame] | 103 | |
| 104 | const childrenBefore = peerConnectionElement.hasChildNodes() ? |
| 105 | Array.from(peerConnectionElement.childNodes) : |
| 106 | []; |
[email protected] | 2746df9 | 2013-04-26 23:42:32 | [diff] [blame] | 107 | |
Philipp Hancke | 0635d314 | 2021-03-16 20:02:09 | [diff] [blame] | 108 | for (let i = 0; i < stats.values.length - 1; i = i + 2) { |
| 109 | const rawLabel = stats.values[i]; |
Philipp Hancke | 0635d314 | 2021-03-16 20:02:09 | [diff] [blame] | 110 | const rawDataSeriesId = reportId + '-' + rawLabel; |
| 111 | const rawValue = getNumberFromValue(rawLabel, stats.values[i + 1]); |
[email protected] | 78d221d | 2013-06-05 07:31:33 | [diff] [blame] | 112 | if (isNaN(rawValue)) { |
| 113 | // We do not draw non-numerical values, but still want to record it in the |
| 114 | // data series. |
Nasko Oskov | 9303a066 | 2018-10-15 18:03:44 | [diff] [blame] | 115 | addDataSeriesPoints( |
Philipp Hancke | ec3c954 | 2022-07-15 09:19:21 | [diff] [blame] | 116 | peerConnectionElement, reportType, rawDataSeriesId, rawLabel, |
| 117 | [stats.timestamp], [stats.values[i + 1]]); |
[email protected] | 78d221d | 2013-06-05 07:31:33 | [diff] [blame] | 118 | continue; |
| 119 | } |
Philipp Hancke | 0635d314 | 2021-03-16 20:02:09 | [diff] [blame] | 120 | let finalDataSeriesId = rawDataSeriesId; |
| 121 | let finalLabel = rawLabel; |
| 122 | let finalValue = rawValue; |
[email protected] | 03bf84a | 2013-03-23 22:11:48 | [diff] [blame] | 123 | |
| 124 | // Updates the final dataSeries to draw. |
Nasko Oskov | 9303a066 | 2018-10-15 18:03:44 | [diff] [blame] | 125 | addDataSeriesPoints( |
Philipp Hancke | ec3c954 | 2022-07-15 09:19:21 | [diff] [blame] | 126 | peerConnectionElement, reportType, finalDataSeriesId, finalLabel, |
| 127 | [stats.timestamp], [finalValue]); |
[email protected] | 03bf84a | 2013-03-23 22:11:48 | [diff] [blame] | 128 | |
Philipp Hancke | ba4adef | 2023-12-20 14:43:13 | [diff] [blame] | 129 | if (isReportBlocklisted(report) || isStatBlocklisted(report, rawLabel)) { |
| 130 | // We do not want to draw certain reports but still want to |
Philipp Hancke | 3168f20 | 2019-06-13 12:54:41 | [diff] [blame] | 131 | // record them in the data series. |
| 132 | continue; |
| 133 | } |
| 134 | |
[email protected] | 03bf84a | 2013-03-23 22:11:48 | [diff] [blame] | 135 | // Updates the graph. |
Philipp Hancke | ba4adef | 2023-12-20 14:43:13 | [diff] [blame] | 136 | const graphType = finalLabel; |
Philipp Hancke | 0635d314 | 2021-03-16 20:02:09 | [diff] [blame] | 137 | const graphViewId = |
[email protected] | 3e7fd92 | 2013-05-15 00:03:03 | [diff] [blame] | 138 | peerConnectionElement.id + '-' + reportId + '-' + graphType; |
[email protected] | 03bf84a | 2013-03-23 22:11:48 | [diff] [blame] | 139 | |
| 140 | if (!graphViews[graphViewId]) { |
Nasko Oskov | 9303a066 | 2018-10-15 18:03:44 | [diff] [blame] | 141 | graphViews[graphViewId] = |
| 142 | createStatsGraphView(peerConnectionElement, report, graphType); |
Philipp Hancke | f212a84 | 2022-08-19 08:03:03 | [diff] [blame] | 143 | const searchParameters = new URLSearchParams(window.location.search); |
| 144 | if (searchParameters.has('statsInterval')) { |
| 145 | const statsInterval = Math.max( |
| 146 | parseInt(searchParameters.get('statsInterval'), 10), |
| 147 | 100); |
| 148 | if (isFinite(statsInterval)) { |
| 149 | graphViews[graphViewId].setScale(statsInterval); |
| 150 | } |
| 151 | } |
Philipp Hancke | 0635d314 | 2021-03-16 20:02:09 | [diff] [blame] | 152 | const date = new Date(stats.timestamp); |
[email protected] | 03bf84a | 2013-03-23 22:11:48 | [diff] [blame] | 153 | graphViews[graphViewId].setDateRange(date, date); |
| 154 | } |
Henrik Boström | 23dec44 | 2023-07-25 12:13:35 | [diff] [blame] | 155 | // Ensures the stats graph title is up-to-date. |
| 156 | ensureStatsGraphContainer(peerConnectionElement, report); |
[email protected] | 03bf84a | 2013-03-23 22:11:48 | [diff] [blame] | 157 | // Adds the new dataSeries to the graphView. We have to do it here to cover |
| 158 | // both the simple and compound graph cases. |
Philipp Hancke | 0635d314 | 2021-03-16 20:02:09 | [diff] [blame] | 159 | const dataSeries = |
[email protected] | 4d41b80 | 2013-05-15 18:38:23 | [diff] [blame] | 160 | peerConnectionDataStore[peerConnectionElement.id].getDataSeries( |
| 161 | finalDataSeriesId); |
Dan Beam | bdd7d82 | 2019-01-05 00:59:42 | [diff] [blame] | 162 | if (!graphViews[graphViewId].hasDataSeries(dataSeries)) { |
[email protected] | 4d41b80 | 2013-05-15 18:38:23 | [diff] [blame] | 163 | graphViews[graphViewId].addDataSeries(dataSeries); |
Dan Beam | bdd7d82 | 2019-01-05 00:59:42 | [diff] [blame] | 164 | } |
[email protected] | f339c2f | 2013-05-08 22:18:46 | [diff] [blame] | 165 | graphViews[graphViewId].updateEndDate(); |
[email protected] | 03bf84a | 2013-03-23 22:11:48 | [diff] [blame] | 166 | } |
[email protected] | 3fdde8a | 2023-07-07 12:45:13 | [diff] [blame] | 167 | // Add a synthetic data series for the timestamp. |
| 168 | addDataSeriesPoints( |
| 169 | peerConnectionElement, reportType, reportId + '-timestamp', |
| 170 | reportId + '-timestamp', [stats.timestamp], [stats.timestamp]); |
Henrik Boström | b6d732af | 2019-04-23 11:32:29 | [diff] [blame] | 171 | |
| 172 | const childrenAfter = peerConnectionElement.hasChildNodes() ? |
| 173 | Array.from(peerConnectionElement.childNodes) : |
| 174 | []; |
| 175 | for (let i = 0; i < childrenAfter.length; ++i) { |
| 176 | if (!childrenBefore.includes(childrenAfter[i])) { |
| 177 | let graphElements = |
| 178 | graphElementsByPeerConnectionId.get(peerConnectionElement.id); |
| 179 | if (!graphElements) { |
| 180 | graphElements = []; |
| 181 | graphElementsByPeerConnectionId.set( |
| 182 | peerConnectionElement.id, graphElements); |
| 183 | } |
| 184 | graphElements.push(childrenAfter[i]); |
| 185 | } |
| 186 | } |
| 187 | } |
| 188 | |
rbpotter | 5796b00 | 2021-03-10 18:49:21 | [diff] [blame] | 189 | export function removeStatsReportGraphs(peerConnectionElement) { |
Henrik Boström | b6d732af | 2019-04-23 11:32:29 | [diff] [blame] | 190 | const graphElements = |
| 191 | graphElementsByPeerConnectionId.get(peerConnectionElement.id); |
| 192 | if (graphElements) { |
| 193 | for (let i = 0; i < graphElements.length; ++i) { |
| 194 | peerConnectionElement.removeChild(graphElements[i]); |
| 195 | } |
| 196 | graphElementsByPeerConnectionId.delete(peerConnectionElement.id); |
| 197 | } |
| 198 | Object.keys(graphViews).forEach(key => { |
| 199 | if (key.startsWith(peerConnectionElement.id)) { |
| 200 | delete graphViews[key]; |
| 201 | } |
| 202 | }); |
[email protected] | 03bf84a | 2013-03-23 22:11:48 | [diff] [blame] | 203 | } |
| 204 | |
| 205 | // Makes sure the TimelineDataSeries with id |dataSeriesId| is created, |
[email protected] | ca00194 | 2014-02-18 18:10:54 | [diff] [blame] | 206 | // and adds the new data points to it. |times| is the list of timestamps for |
| 207 | // each data point, and |values| is the list of the data point values. |
| 208 | function addDataSeriesPoints( |
Philipp Hancke | ec3c954 | 2022-07-15 09:19:21 | [diff] [blame] | 209 | peerConnectionElement, reportType, dataSeriesId, label, times, values) { |
Philipp Hancke | 0635d314 | 2021-03-16 20:02:09 | [diff] [blame] | 210 | let dataSeries = |
Nasko Oskov | 9303a066 | 2018-10-15 18:03:44 | [diff] [blame] | 211 | peerConnectionDataStore[peerConnectionElement.id].getDataSeries( |
| 212 | dataSeriesId); |
[email protected] | 4d41b80 | 2013-05-15 18:38:23 | [diff] [blame] | 213 | if (!dataSeries) { |
Philipp Hancke | ec3c954 | 2022-07-15 09:19:21 | [diff] [blame] | 214 | dataSeries = new TimelineDataSeries(reportType); |
[email protected] | 4d41b80 | 2013-05-15 18:38:23 | [diff] [blame] | 215 | peerConnectionDataStore[peerConnectionElement.id].setDataSeries( |
| 216 | dataSeriesId, dataSeries); |
[email protected] | 03bf84a | 2013-03-23 22:11:48 | [diff] [blame] | 217 | } |
Philipp Hancke | 0635d314 | 2021-03-16 20:02:09 | [diff] [blame] | 218 | for (let i = 0; i < times.length; ++i) { |
[email protected] | ca00194 | 2014-02-18 18:10:54 | [diff] [blame] | 219 | dataSeries.addPoint(times[i], values[i]); |
Dan Beam | bdd7d82 | 2019-01-05 00:59:42 | [diff] [blame] | 220 | } |
[email protected] | ca00194 | 2014-02-18 18:10:54 | [diff] [blame] | 221 | } |
| 222 | |
[email protected] | 35d9ad2 | 2023-02-13 08:38:37 | [diff] [blame] | 223 | // Ensures a div container to the stats graph for a peerConnectionElement is |
| 224 | // created as a child of the |peerConnectionElement|. |
| 225 | function ensureStatsGraphTopContainer(peerConnectionElement) { |
| 226 | const containerId = peerConnectionElement.id + '-graph-container'; |
| 227 | let container = document.getElementById(containerId); |
| 228 | if (!container) { |
| 229 | container = document.createElement('div'); |
| 230 | container.id = containerId; |
| 231 | container.className = 'stats-graph-container'; |
| 232 | const label = document.createElement('label'); |
| 233 | label.innerText = 'Filter statistics graphs by type including '; |
| 234 | container.appendChild(label); |
| 235 | const input = document.createElement('input'); |
| 236 | input.placeholder = 'separate multiple values by `,`'; |
| 237 | input.size = 25; |
| 238 | input.oninput = (e) => filterStats(e, container); |
| 239 | container.appendChild(input); |
| 240 | |
| 241 | peerConnectionElement.appendChild(container); |
| 242 | } |
| 243 | return container; |
| 244 | } |
| 245 | |
| 246 | // Ensures a div container to the stats graph for a single set of data is |
| 247 | // created as a child of the |peerConnectionElement|'s graph container. |
| 248 | function ensureStatsGraphContainer(peerConnectionElement, report) { |
| 249 | const topContainer = ensureStatsGraphTopContainer(peerConnectionElement); |
Philipp Hancke | 0635d314 | 2021-03-16 20:02:09 | [diff] [blame] | 250 | const containerId = peerConnectionElement.id + '-' + report.type + '-' + |
Nasko Oskov | 9303a066 | 2018-10-15 18:03:44 | [diff] [blame] | 251 | report.id + '-graph-container'; |
Rebekah Potter | 8148f85 | 2022-11-29 18:04:37 | [diff] [blame] | 252 | // Disable getElementById restriction here, since |containerId| is not always |
| 253 | // a valid selector. |
| 254 | // eslint-disable-next-line no-restricted-properties |
| 255 | let container = document.getElementById(containerId); |
[email protected] | 03bf84a | 2013-03-23 22:11:48 | [diff] [blame] | 256 | if (!container) { |
[email protected] | 2746df9 | 2013-04-26 23:42:32 | [diff] [blame] | 257 | container = document.createElement('details'); |
[email protected] | 03bf84a | 2013-03-23 22:11:48 | [diff] [blame] | 258 | container.id = containerId; |
[email protected] | 2746df9 | 2013-04-26 23:42:32 | [diff] [blame] | 259 | container.className = 'stats-graph-container'; |
[email protected] | 35d9ad2 | 2023-02-13 08:38:37 | [diff] [blame] | 260 | container.attributes['data-statsType'] = report.type; |
[email protected] | 03bf84a | 2013-03-23 22:11:48 | [diff] [blame] | 261 | |
| 262 | peerConnectionElement.appendChild(container); |
Jun Kokatsu | 7eaf471 | 2020-06-01 23:32:07 | [diff] [blame] | 263 | container.appendChild($('summary-span-template').content.cloneNode(true)); |
[email protected] | 2746df9 | 2013-04-26 23:42:32 | [diff] [blame] | 264 | container.firstChild.firstChild.className = |
| 265 | STATS_GRAPH_CONTAINER_HEADING_CLASS; |
Philipp Hancke | 07263c65 | 2023-06-19 10:02:09 | [diff] [blame] | 266 | topContainer.appendChild(container); |
[email protected] | 03bf84a | 2013-03-23 22:11:48 | [diff] [blame] | 267 | } |
Philipp Hancke | 07263c65 | 2023-06-19 10:02:09 | [diff] [blame] | 268 | // Update the label all the time to account for new information. |
Philipp Hancke | ba4adef | 2023-12-20 14:43:13 | [diff] [blame] | 269 | container.firstChild.firstChild.textContent = 'Stats graphs for ' + |
Philipp Hancke | 07263c65 | 2023-06-19 10:02:09 | [diff] [blame] | 270 | generateStatsLabel(report); |
[email protected] | 03bf84a | 2013-03-23 22:11:48 | [diff] [blame] | 271 | return container; |
| 272 | } |
| 273 | |
| 274 | // Creates the container elements holding a timeline graph |
| 275 | // and the TimelineGraphView object. |
Nasko Oskov | 9303a066 | 2018-10-15 18:03:44 | [diff] [blame] | 276 | function createStatsGraphView(peerConnectionElement, report, statsName) { |
Philipp Hancke | 0635d314 | 2021-03-16 20:02:09 | [diff] [blame] | 277 | const topContainer = |
[email protected] | 35d9ad2 | 2023-02-13 08:38:37 | [diff] [blame] | 278 | ensureStatsGraphContainer(peerConnectionElement, report); |
[email protected] | 03bf84a | 2013-03-23 22:11:48 | [diff] [blame] | 279 | |
Philipp Hancke | 0635d314 | 2021-03-16 20:02:09 | [diff] [blame] | 280 | const graphViewId = |
[email protected] | 4d41b80 | 2013-05-15 18:38:23 | [diff] [blame] | 281 | peerConnectionElement.id + '-' + report.id + '-' + statsName; |
Philipp Hancke | 0635d314 | 2021-03-16 20:02:09 | [diff] [blame] | 282 | const divId = graphViewId + '-div'; |
| 283 | const canvasId = graphViewId + '-canvas'; |
| 284 | const container = document.createElement('div'); |
[email protected] | 03bf84a | 2013-03-23 22:11:48 | [diff] [blame] | 285 | container.className = 'stats-graph-sub-container'; |
| 286 | |
| 287 | topContainer.appendChild(container); |
Jun Kokatsu | 7eaf471 | 2020-06-01 23:32:07 | [diff] [blame] | 288 | const canvasDiv = $('container-template').content.cloneNode(true); |
| 289 | canvasDiv.querySelectorAll('div')[0].textContent = statsName; |
| 290 | canvasDiv.querySelectorAll('div')[1].id = divId; |
| 291 | canvasDiv.querySelector('canvas').id = canvasId; |
| 292 | container.appendChild(canvasDiv); |
[email protected] | 03bf84a | 2013-03-23 22:11:48 | [diff] [blame] | 293 | return new TimelineGraphView(divId, canvasId); |
| 294 | } |
[email protected] | 35d9ad2 | 2023-02-13 08:38:37 | [diff] [blame] | 295 | |
| 296 | /** |
| 297 | * Apply a filter to the stats graphs |
| 298 | * @param event InputEvent from the filter input field. |
| 299 | * @param container stats table container element. |
| 300 | * @private |
| 301 | */ |
| 302 | function filterStats(event, container) { |
| 303 | const filter = event.target.value; |
| 304 | const filters = filter.split(','); |
| 305 | container.childNodes.forEach(node => { |
| 306 | if (node.nodeName !== 'DETAILS') { |
| 307 | return; |
| 308 | } |
| 309 | const statsType = node.attributes['data-statsType']; |
| 310 | if (!filter || filters.includes(statsType) || |
| 311 | filters.find(f => statsType.includes(f))) { |
| 312 | node.style.display = 'block'; |
| 313 | } else { |
| 314 | node.style.display = 'none'; |
| 315 | } |
| 316 | }); |
| 317 | } |