OLD | NEW |
1 <!DOCTYPE html> | 1 <!DOCTYPE html> |
2 <!-- | 2 <!-- |
3 Copyright (c) 2014 The Chromium Authors. All rights reserved. | 3 Copyright (c) 2014 The Chromium Authors. All rights reserved. |
4 Use of this source code is governed by a BSD-style license that can be | 4 Use of this source code is governed by a BSD-style license that can be |
5 found in the LICENSE file. | 5 found in the LICENSE file. |
6 --> | 6 --> |
7 | 7 |
8 <link rel="import" href="/tracing/base/iteration_helpers.html"> | 8 <link rel="import" href="/tracing/base/iteration_helpers.html"> |
| 9 <link rel="import" href="/tracing/base/raf.html"> |
9 <link rel="import" href="/tracing/base/range.html"> | 10 <link rel="import" href="/tracing/base/range.html"> |
10 <link rel="import" href="/tracing/ui/base/chart_base.html"> | 11 <link rel="import" href="/tracing/ui/base/chart_base.html"> |
11 <link rel="import" href="/tracing/ui/base/mouse_tracker.html"> | 12 <link rel="import" href="/tracing/ui/base/mouse_tracker.html"> |
12 | 13 |
13 <style> | 14 <style> |
14 * /deep/ .chart-base-2d.updating-brushing-state #brushes > * { | 15 * /deep/ .chart-base-2d.updating-brushing-state #brushes > * { |
15 fill: rgb(103, 199, 165) | 16 fill: rgb(103, 199, 165) |
16 } | 17 } |
17 | 18 |
18 * /deep/ .chart-base-2d #brushes { | 19 * /deep/ .chart-base-2d #brushes { |
(...skipping 12 matching lines...) Expand all Loading... |
31 __proto__: ChartBase.prototype, | 32 __proto__: ChartBase.prototype, |
32 | 33 |
33 decorate: function() { | 34 decorate: function() { |
34 ChartBase.prototype.decorate.call(this); | 35 ChartBase.prototype.decorate.call(this); |
35 Polymer.dom(this).classList.add('chart-base-2d'); | 36 Polymer.dom(this).classList.add('chart-base-2d'); |
36 this.xScale_ = d3.scale.linear(); | 37 this.xScale_ = d3.scale.linear(); |
37 this.yScale_ = d3.scale.linear(); | 38 this.yScale_ = d3.scale.linear(); |
38 this.isYLogScale_ = false; | 39 this.isYLogScale_ = false; |
39 this.yLogScaleMin_ = undefined; | 40 this.yLogScaleMin_ = undefined; |
40 this.dataRange_ = new tr.b.Range(); | 41 this.dataRange_ = new tr.b.Range(); |
41 | 42 this.hideXAxis_ = false; |
| 43 this.hideYAxis_ = false; |
42 this.data_ = []; | 44 this.data_ = []; |
43 this.seriesKeys_ = []; | |
44 this.leftMargin_ = 50; | |
45 | 45 |
46 d3.select(this.chartAreaElement) | 46 d3.select(this.chartAreaElement) |
47 .append('g') | 47 .append('g') |
48 .attr('id', 'brushes'); | 48 .attr('id', 'brushes'); |
49 d3.select(this.chartAreaElement) | 49 d3.select(this.chartAreaElement) |
50 .append('g') | 50 .append('g') |
51 .attr('id', 'series'); | 51 .attr('id', 'series'); |
52 | 52 |
53 this.addEventListener('mousedown', this.onMouseDown_.bind(this)); | 53 this.addEventListener('mousedown', this.onMouseDown_.bind(this)); |
54 }, | 54 }, |
55 | 55 |
| 56 get hideXAxis() { |
| 57 return this.hideXAxis_; |
| 58 }, |
| 59 |
| 60 set hideXAxis(h) { |
| 61 this.hideXAxis_ = h; |
| 62 this.updateContents_(); |
| 63 }, |
| 64 |
| 65 get hideYAxis() { |
| 66 return this.hideYAxis_; |
| 67 }, |
| 68 |
| 69 set hideYAxis(h) { |
| 70 this.hideYAxis_ = h; |
| 71 this.updateContents_(); |
| 72 }, |
| 73 |
56 get data() { | 74 get data() { |
57 return this.data_; | 75 return this.data_; |
58 }, | 76 }, |
59 | 77 |
60 /** | 78 /** |
61 * Sets the data array for the object | 79 * Sets the data array for the object |
62 * | 80 * |
63 * @param {Array} data The data. Each element must be an object, with at | 81 * @param {Array} data The data. Each element must be an object, with at |
64 * least an x property. All other properties become series names in the | 82 * least an x property. All other properties become series names in the |
65 * chart. The data can be sparse (i.e. every x value does not have to | 83 * chart. The data can be sparse (i.e. every x value does not have to |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
102 leftIndex = index; | 120 leftIndex = index; |
103 rightIndex = Math.min(index + 1, data.length - 1); | 121 rightIndex = Math.min(index + 1, data.length - 1); |
104 } | 122 } |
105 var leftWidth = this.getXForDatum_(data[index], index) - | 123 var leftWidth = this.getXForDatum_(data[index], index) - |
106 this.getXForDatum_(data[leftIndex], leftIndex); | 124 this.getXForDatum_(data[leftIndex], leftIndex); |
107 var rightWidth = this.getXForDatum_(data[rightIndex], rightIndex) - | 125 var rightWidth = this.getXForDatum_(data[rightIndex], rightIndex) - |
108 this.getXForDatum_(data[index], index); | 126 this.getXForDatum_(data[index], index); |
109 return leftWidth * 0.5 + rightWidth * 0.5; | 127 return leftWidth * 0.5 + rightWidth * 0.5; |
110 }, | 128 }, |
111 | 129 |
112 getLegendKeys_: function() { | |
113 if (this.seriesKeys_ && | |
114 this.seriesKeys_.length > 1) | |
115 return this.seriesKeys_.slice(); | |
116 return []; | |
117 }, | |
118 | |
119 updateSeriesKeys_: function() { | 130 updateSeriesKeys_: function() { |
120 // Accumulate the keys on each data point. | 131 // Don't clear seriesByKey_; the caller might have put state in it using |
121 var keySet = {}; | 132 // customizeLegendTargets, customizeOptionalSeries, or |
| 133 // customizeEnabledSeries before setting data. |
122 this.data_.forEach(function(datum) { | 134 this.data_.forEach(function(datum) { |
123 Object.keys(datum).forEach(function(key) { | 135 Object.keys(datum).forEach(function(key) { |
124 if (this.isDatumFieldSeries_(key)) | 136 if (this.isDatumFieldSeries_(key)) |
125 keySet[key] = true; | 137 this.getDataSeries(key); |
126 }, this); | 138 }, this); |
127 }, this); | 139 }, this); |
128 this.seriesKeys_ = Object.keys(keySet); | |
129 }, | 140 }, |
130 | 141 |
131 isDatumFieldSeries_: function(fieldName) { | 142 isDatumFieldSeries_: function(fieldName) { |
132 throw new Error('Not implemented'); | 143 throw new Error('Not implemented'); |
133 }, | 144 }, |
134 | 145 |
135 getXForDatum_: function(datum, index) { | 146 getXForDatum_: function(datum, index) { |
136 throw new Error('Not implemented'); | 147 throw new Error('Not implemented'); |
137 }, | 148 }, |
138 | 149 |
139 updateScales_: function() { | 150 updateScales_: function() { |
140 if (this.data_.length === 0) | 151 if (this.data_.length === 0) |
141 return; | 152 return; |
142 | 153 |
143 var width = this.chartAreaSize.width; | 154 var width = this.chartAreaSize.width; |
144 var height = this.chartAreaSize.height; | 155 var height = this.chartAreaSize.height; |
145 | 156 |
146 // X. | 157 // X. |
147 this.xScale_.range([0, width]); | 158 this.xScale_.range([0, width]); |
148 this.xScale_.domain(d3.extent(this.data_, this.getXForDatum_.bind(this))); | 159 this.xScale_.domain(d3.extent(this.data_, this.getXForDatum_.bind(this))); |
149 | 160 |
150 // Y. | 161 // Y. |
151 var yRange = new tr.b.Range(); | 162 var yRange = new tr.b.Range(); |
152 var keySet = new Set(this.seriesKeys_); | 163 for (var i = 0; i < this.data_.length; i++) { |
153 for (var i = 0; i < this.data_.length; i++) | 164 for (var key in this.data_[i]) { |
154 for (var key in this.data_[i]) | 165 if (!isNaN(Math.max(this.data_[i][key]))) |
155 if (keySet.has(key)) | |
156 yRange.addValue(this.data_[i][key]); | 166 yRange.addValue(this.data_[i][key]); |
| 167 } |
| 168 } |
157 | 169 |
158 this.yScale_.range([height, 0]); | 170 this.yScale_.range([height, 0]); |
159 this.yScale_.domain([yRange.min, yRange.max]); | 171 this.yScale_.domain([yRange.min, yRange.max]); |
160 }, | 172 }, |
161 | 173 |
162 updateBrushContents_: function(brushSel) { | 174 updateBrushContents_: function(brushSel) { |
163 brushSel.selectAll('*').remove(); | 175 brushSel.selectAll('*').remove(); |
164 }, | 176 }, |
165 | 177 |
166 updateXAxis_: function(xAxis) { | 178 updateXAxis_: function(xAxis) { |
167 xAxis.selectAll('*').remove(); | 179 xAxis.selectAll('*').remove(); |
168 xAxis[0][0].style.opacity = 0; | 180 xAxis[0][0].style.opacity = 0; |
| 181 if (this.hideXAxis) |
| 182 return; |
| 183 |
169 xAxis.attr('transform', 'translate(0,' + this.chartAreaSize.height + ')') | 184 xAxis.attr('transform', 'translate(0,' + this.chartAreaSize.height + ')') |
170 .call(d3.svg.axis() | 185 .call(d3.svg.axis() |
171 .scale(this.xScale_) | 186 .scale(this.xScale_) |
172 .orient('bottom')); | 187 .orient('bottom')); |
173 window.requestAnimationFrame(function() { | 188 tr.b.requestAnimationFrame(function() { |
174 var previousRight = undefined; | 189 var previousRight = undefined; |
175 xAxis.selectAll('.tick')[0].forEach(function(tick) { | 190 xAxis.selectAll('.tick')[0].forEach(function(tick) { |
176 var currentLeft = tick.transform.baseVal[0].matrix.e; | 191 var currentLeft = tick.transform.baseVal[0].matrix.e; |
177 if ((previousRight === undefined) || | 192 if ((previousRight === undefined) || |
178 (currentLeft > (previousRight + 3))) { | 193 (currentLeft > (previousRight + 3))) { |
179 var currentWidth = tick.getBBox().width; | 194 var currentWidth = tick.getBBox().width; |
180 previousRight = currentLeft + currentWidth; | 195 previousRight = currentLeft + currentWidth; |
181 } else { | 196 } else { |
182 tick.style.opacity = 0; | 197 tick.style.opacity = 0; |
183 } | 198 } |
184 }); | 199 }); |
185 xAxis[0][0].style.opacity = 1; | 200 xAxis[0][0].style.opacity = 1; |
186 }); | 201 }, this); |
187 }, | |
188 | |
189 getMargin_: function() { | |
190 var margin = ChartBase.prototype.getMargin_.call(this); | |
191 margin.left = this.leftMargin_; | |
192 return margin; | |
193 }, | 202 }, |
194 | 203 |
195 updateDataRange_: function() { | 204 updateDataRange_: function() { |
196 var dataBySeriesKey = this.getDataBySeriesKey_(); | 205 var dataBySeriesKey = this.getDataBySeriesKey_(); |
197 this.dataRange_.reset(); | 206 this.dataRange_.reset(); |
198 tr.b.iterItems(dataBySeriesKey, function(series, values) { | 207 tr.b.iterItems(dataBySeriesKey, function(series, values) { |
199 for (var i = 0; i < values.length; i++) { | 208 for (var i = 0; i < values.length; i++) { |
200 this.dataRange_.addValue(values[i][series]); | 209 this.dataRange_.addValue(values[i][series]); |
201 } | 210 } |
202 }, this); | 211 }, this); |
203 | 212 |
204 // Choose the closest power of 10, rounded down, as the smallest tick | 213 // Choose the closest power of 10, rounded down, as the smallest tick |
205 // to display. | 214 // to display. |
206 this.yLogScaleMin_ = undefined; | 215 this.yLogScaleMin_ = undefined; |
207 if (this.dataRange_.min !== undefined) { | 216 if (this.dataRange_.min !== undefined) { |
208 var minValue = this.dataRange_.min; | 217 var minValue = this.dataRange_.min; |
209 if (minValue == 0) | 218 if (minValue == 0) |
210 minValue = 1; | 219 minValue = 1; |
211 | 220 |
212 var onePowerLess = Math.floor( | 221 var onePowerLess = Math.floor( |
213 Math.log(minValue) / Math.log(10)) - 1; | 222 Math.log(minValue) / Math.log(10)) - 1; |
214 this.yLogScaleMin_ = Math.pow(10, onePowerLess); | 223 this.yLogScaleMin_ = Math.pow(10, onePowerLess); |
215 } | 224 } |
216 }, | 225 }, |
217 | 226 |
218 updateYAxis_: function(yAxis) { | 227 updateYAxis_: function(yAxis) { |
219 yAxis.selectAll('*').remove(); | 228 yAxis.selectAll('*').remove(); |
220 yAxis[0][0].style.opacity = 0; | 229 yAxis[0][0].style.opacity = 0; |
| 230 if (this.hideYAxis) |
| 231 return; |
221 | 232 |
222 var axisModifier = d3.svg.axis() | 233 var axisModifier = d3.svg.axis() |
223 .scale(this.yScale_) | 234 .scale(this.yScale_) |
224 .orient('left'); | 235 .orient('left'); |
225 | 236 |
226 if (this.isYLogScale_) { | 237 if (this.isYLogScale_) { |
227 if (this.yLogScaleMin_ === undefined) | 238 if (this.yLogScaleMin_ === undefined) |
228 return; | 239 return; |
229 var minValue = this.dataRange_.min; | 240 var minValue = this.dataRange_.min; |
230 if (minValue == 0) | 241 if (minValue == 0) |
(...skipping 10 matching lines...) Expand all Loading... |
241 | 252 |
242 axisModifier = axisModifier | 253 axisModifier = axisModifier |
243 .tickValues(tickValues) | 254 .tickValues(tickValues) |
244 .tickFormat(function(d) { | 255 .tickFormat(function(d) { |
245 return d; | 256 return d; |
246 }); | 257 }); |
247 } | 258 } |
248 | 259 |
249 yAxis.call(axisModifier); | 260 yAxis.call(axisModifier); |
250 | 261 |
251 window.requestAnimationFrame(function() { | 262 tr.b.requestAnimationFrame(function() { |
252 var previousTop = undefined; | 263 var previousTop = undefined; |
253 var leftMargin = 0; | 264 var leftMargin = 0; |
254 yAxis.selectAll('.tick')[0].forEach(function(tick) { | 265 yAxis.selectAll('.tick')[0].forEach(function(tick) { |
255 var bbox = tick.getBBox(); | 266 var bbox = tick.getBBox(); |
256 leftMargin = Math.max(leftMargin, bbox.width); | 267 leftMargin = Math.max(leftMargin, bbox.width); |
257 var currentTop = tick.transform.baseVal[0].matrix.f; | 268 var currentTop = tick.transform.baseVal[0].matrix.f; |
258 var currentBottom = currentTop + bbox.height; | 269 var currentBottom = currentTop + bbox.height; |
259 if ((previousTop === undefined) || | 270 if ((previousTop === undefined) || |
260 (previousTop > (currentBottom + 3))) { | 271 (previousTop > (currentBottom + 3))) { |
261 previousTop = currentTop; | 272 previousTop = currentTop; |
262 } else { | 273 } else { |
263 tick.style.opacity = 0; | 274 tick.style.opacity = 0; |
264 } | 275 } |
265 }); | 276 }); |
266 if (leftMargin > this.leftMargin_) { | 277 |
267 this.leftMargin_ = leftMargin; | 278 leftMargin = parseInt(Math.ceil(leftMargin)); |
| 279 if (leftMargin > this.margin.left) { |
| 280 this.margin.left = leftMargin; |
268 this.updateContents_(); | 281 this.updateContents_(); |
269 } else { | 282 } else { |
270 yAxis[0][0].style.opacity = 1; | 283 yAxis[0][0].style.opacity = 1; |
271 } | 284 } |
272 }.bind(this)); | 285 }, this); |
273 }, | 286 }, |
274 | 287 |
275 updateContents_: function() { | 288 updateContents_: function() { |
276 ChartBase.prototype.updateContents_.call(this); | 289 ChartBase.prototype.updateContents_.call(this); |
277 var chartAreaSel = d3.select(this.chartAreaElement); | 290 var chartAreaSel = d3.select(this.chartAreaElement); |
278 this.updateXAxis_(chartAreaSel.select('.x.axis')); | 291 this.updateXAxis_(chartAreaSel.select('.x.axis')); |
279 this.updateYAxis_(chartAreaSel.select('.y.axis')); | 292 this.updateYAxis_(chartAreaSel.select('.y.axis')); |
280 this.updateBrushContents_(chartAreaSel.select('#brushes')); | 293 this.updateBrushContents_(chartAreaSel.select('#brushes')); |
281 this.updateDataContents_(chartAreaSel.select('#series')); | 294 this.updateDataContents_(chartAreaSel.select('#series')); |
282 }, | 295 }, |
283 | 296 |
284 updateDataContents_: function(seriesSel) { | 297 updateDataContents_: function(seriesSel) { |
285 throw new Error('Not implemented'); | 298 throw new Error('Not implemented'); |
286 }, | 299 }, |
287 | 300 |
288 /** | 301 /** |
289 * Returns a map of series key to the data for that series. | 302 * Returns a map of series key to the data for that series. |
290 * | 303 * |
291 * Example: | 304 * Example: |
292 * // returns {y: [{x: 1, y: 1}, {x: 3, y: 3}], z: [{x: 2, z: 2}]} | 305 * // returns {y: [{x: 1, y: 1}, {x: 3, y: 3}], z: [{x: 2, z: 2}]} |
293 * this.data_ = [{x: 1, y: 1}, {x: 2, z: 2}, {x: 3, y: 3}]; | 306 * this.data_ = [{x: 1, y: 1}, {x: 2, z: 2}, {x: 3, y: 3}]; |
294 * this.getDataBySeriesKey_(); | 307 * this.getDataBySeriesKey_(); |
295 * @return {Object} A map of series data by series key. | 308 * @return {Object} A map of series data by series key. |
296 */ | 309 */ |
297 getDataBySeriesKey_: function() { | 310 getDataBySeriesKey_: function() { |
298 var dataBySeriesKey = {}; | 311 var dataBySeriesKey = {}; |
299 this.seriesKeys_.forEach(function(seriesKey) { | 312 for (var [key, series] of this.seriesByKey_) { |
300 dataBySeriesKey[seriesKey] = []; | 313 dataBySeriesKey[key] = []; |
301 }); | 314 } |
302 | 315 |
303 this.data_.forEach(function(multiSeriesDatum, index) { | 316 this.data_.forEach(function(multiSeriesDatum, index) { |
304 var x = this.getXForDatum_(multiSeriesDatum, index); | 317 var x = this.getXForDatum_(multiSeriesDatum, index); |
305 | 318 |
306 d3.keys(multiSeriesDatum).forEach(function(seriesKey) { | 319 d3.keys(multiSeriesDatum).forEach(function(seriesKey) { |
307 // Skip 'x' - it's not a series | 320 // Skip 'x' - it's not a series |
308 if (seriesKey === 'x') | 321 if (seriesKey === 'x') |
309 return; | 322 return; |
310 | 323 |
311 if (multiSeriesDatum[seriesKey] === undefined) | 324 if (multiSeriesDatum[seriesKey] === undefined) |
312 return; | 325 return; |
313 | 326 |
314 if (!this.isDatumFieldSeries_(seriesKey)) | 327 if (!this.isDatumFieldSeries_(seriesKey)) |
315 return; | 328 return; |
316 | 329 |
317 var singleSeriesDatum = {x: x}; | 330 var singleSeriesDatum = {x: x}; |
318 singleSeriesDatum[seriesKey] = multiSeriesDatum[seriesKey]; | 331 singleSeriesDatum[seriesKey] = multiSeriesDatum[seriesKey]; |
319 dataBySeriesKey[seriesKey].push(singleSeriesDatum); | 332 dataBySeriesKey[seriesKey].push(singleSeriesDatum); |
320 }, this); | 333 }, this); |
321 }, this); | 334 }, this); |
322 | 335 |
323 return dataBySeriesKey; | 336 return dataBySeriesKey; |
324 }, | 337 }, |
325 | 338 |
326 getDataPointAtClientPoint_: function(clientX, clientY) { | 339 getDataPointAtClientPoint_: function(clientX, clientY) { |
327 var rect = this.getBoundingClientRect(); | 340 var rect = this.getBoundingClientRect(); |
328 var margin = this.margin; | 341 var x = clientX - rect.left - this.margin.left; |
329 var x = clientX - rect.left - margin.left; | 342 var y = clientY - rect.top - this.margin.top; |
330 var y = clientY - rect.top - margin.top; | |
331 x = this.xScale_.invert(x); | 343 x = this.xScale_.invert(x); |
332 y = this.yScale_.invert(y); | 344 y = this.yScale_.invert(y); |
333 x = tr.b.clamp(x, this.xScale_.domain()[0], this.xScale_.domain()[1]); | 345 x = tr.b.clamp(x, this.xScale_.domain()[0], this.xScale_.domain()[1]); |
334 y = tr.b.clamp(y, this.yScale_.domain()[0], this.yScale_.domain()[1]); | 346 y = tr.b.clamp(y, this.yScale_.domain()[0], this.yScale_.domain()[1]); |
335 return {x: x, y: y}; | 347 return {x: x, y: y}; |
336 }, | 348 }, |
337 | 349 |
338 prepareDataEvent_: function(mouseEvent, dataEvent) { | 350 prepareDataEvent_: function(mouseEvent, dataEvent) { |
339 var dataPoint = this.getDataPointAtClientPoint_( | 351 var dataPoint = this.getDataPointAtClientPoint_( |
340 mouseEvent.clientX, mouseEvent.clientY); | 352 mouseEvent.clientX, mouseEvent.clientY); |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
375 this.dispatchEvent(dataEvent); | 387 this.dispatchEvent(dataEvent); |
376 Polymer.dom(this).classList.remove('updating-brushing-state'); | 388 Polymer.dom(this).classList.remove('updating-brushing-state'); |
377 } | 389 } |
378 }; | 390 }; |
379 | 391 |
380 return { | 392 return { |
381 ChartBase2D: ChartBase2D | 393 ChartBase2D: ChartBase2D |
382 }; | 394 }; |
383 }); | 395 }); |
384 </script> | 396 </script> |
OLD | NEW |