display-timeline.js
4.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
// render the timeline with d3
let timeline = document.querySelector('.timeline');
timeline.innerHTML = '';
let margin = {
top: 20,
right: 80,
bottom: 30,
left: 50
};
let width = 960 - margin.left - margin.right;
let height = 500 - margin.top - margin.bottom;
let svg = d3.select('.timeline').append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
const displayTimeline = function(error, data) {
var x = d3.scale.linear().range([0, width]),
y = d3.scale.linear().range([height, 0]),
timeAxis = d3.svg.axis().scale(x).orient('bottom'),
tickFormatter = d3.format(',.0f'),
bitrateAxis = d3.svg.axis()
.scale(y)
.tickFormat(function(value) {
return tickFormatter(value / 1024);
})
.orient('left'),
bandwidthLine = d3.svg.line()
.interpolate('basis')
.x(function(data) {
return x(data.time);
})
.y(function(data) {
return y(data.bandwidth);
}),
effectiveBandwidthLine = d3.svg.line()
.interpolate('basis')
.x(function(data) {
return x(data.time);
})
.y(function(data) {
return y(data.bandwidth);
});
x.domain(d3.extent(data.bandwidth, function(data) {
return data.time;
}));
y.domain([0, Math.max(d3.max(data.bandwidth, function(data) {
return data.bandwidth;
}), d3.max(data.options.playlists), d3.max(data.playlists, function(data) {
return data.bitrate;
}))]);
// time axis
svg.selectAll('.axis').remove();
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')')
.call(timeAxis);
// bitrate axis
svg.append('g')
.attr('class', 'y axis')
.call(bitrateAxis)
.append('text')
.attr('transform', 'rotate(-90)')
.attr('y', 6)
.attr('dy', '.71em')
.style('text-anchor', 'end')
.text('Bitrate (kb/s)');
// playlist bitrate lines
svg.selectAll('.line.bitrate').remove();
svg.selectAll('.line.bitrate')
.data(data.options.playlists)
.enter().append('path')
.attr('class', 'line bitrate')
.attr('d', function(playlist) {
return 'M0,' + y(playlist) + 'L' + width + ',' + y(playlist);
});
// bandwidth line
svg.selectAll('.bandwidth').remove();
svg.append('path')
.datum(data.bandwidth)
.attr('class', 'line bandwidth')
.attr('d', bandwidthLine);
svg.selectAll('.effective-bandwidth').remove();
svg.append('path')
.datum(data.effectiveBandwidth)
.attr('class', 'line effective-bandwidth')
.attr('d', effectiveBandwidthLine);
svg.append('text')
.attr('class', 'bandwidth label')
.attr('transform', 'translate(' + x(x.range()[1]) + ', ' + y(data.bandwidth.slice(-1)[0].bandwidth) + ')')
.attr('dy', '1.35em')
.text('bandwidth');
svg.append('text')
.attr('class', 'bandwidth label')
.attr('transform', 'translate(' + x(x.range()[1]) + ', ' + y(data.effectiveBandwidth.slice(-1)[0].bandwidth) + ')')
.attr('dy', '1.35em')
.text('measured');
// segment bitrate dots
svg.selectAll('.segment-bitrate').remove();
svg.selectAll('.segment-bitrate')
.data(data.playlists)
.enter().append('circle')
.attr('class', 'dot segment-bitrate')
.attr('r', 3.5)
.attr('cx', function(playlist) {
return x(playlist.time);
})
.attr('cy', function(playlist) {
return y(playlist.bitrate);
});
// highlight intervals when the buffer is empty
svg.selectAll('.buffer-empty').remove();
svg.selectAll('.buffer-empty')
.data(data.buffered.reduce(function(result, sample) {
var last = result[result.length - 1];
if (sample.buffered === 0) {
if (last && sample.time === last.end + 1) {
// add this sample to the interval we're accumulating
return result.slice(0, result.length - 1).concat({
start: last.start,
end: sample.time
});
} else {
// this sample starts a new interval
return result.concat({
start: sample.time,
end: sample.time
});
}
}
// filter out time periods where the buffer isn't empty
return result;
}, []))
.enter().append('rect')
.attr('class', 'buffer-empty')
.attr('x', function(data) {
return x(data.start);
})
.attr('width', function(data) {
return x(1 + data.end - data.start);
})
.attr('y', 0)
.attr('height', y(height));
};
export default displayTimeline;