(function() { var margin = { top: 20, right: 20, bottom: 30, left: 50 }, width = 960 - margin.left - margin.right, height = 500 - margin.top - margin.bottom; var x = d3.time.scale() .range([0, width]); var y = d3.scale.linear() .range([height, 0]); var xAxis = d3.svg.axis() .scale(x) .orient("bottom"); var yAxis = d3.svg.axis() .scale(y) .orient("left"); var line = d3.svg.line() .x(function(d) { return x(d.ts); }) .y(function(d) { return y(d.data); }); var svg = d3.select("#timeseries") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); var data = getRandomData(1000); x.domain(d3.extent(data, function(d) { return d.ts; })); y.domain(d3.extent(data, function(d) { return d.data; })); svg.append("text") .attr("class", "log") .attr("dx", 12) .attr("dy", 12) .text("data:" + data.length + " downsampled:" + 0); svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(xAxis); svg.append("g") .attr("class", "y axis") .call(yAxis); svg.append("path") .datum(data) .attr("class", "line") .attr("d", line); resize(); function getRandomValue(min, max) { return Math.random() * (max - min) + min; } function getRandomData(count) { count = count || 0; for (var data = [], day = 24 * 3600 * 1000, date = new Date() * 1, lognormal = d3.random.logNormal(1, .5), i = 0; i < count; i++) { date = date + day; data.push({ "ts": date, "data": lognormal(), }); } return data; } function largestTriangleThreeBucket(data, threshold, xProperty, yProperty) { /** * This method is adapted from the * "Largest Triangle Three Bucket" algorithm by Sveinn Steinarsson * In his 2013 Masters Thesis - "Downsampling Time Series for Visual Representation" * http://skemman.is/handle/1946/15343 * * The MIT License * * Copyright (c) 2013 by Sveinn Steinarsson * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * -------------------------------------------------------------------------------------------------------- */ yProperty = yProperty || 0; xProperty = xProperty || 1; var m = Math.floor, y = Math.abs, f = data.length; if (threshold >= f || 0 === threshold) { return data; } var n = [], t = 0, p = (f - 2) / (threshold - 2), c = 0, v, u, w; n[t++] = data[c]; for (var e = 0; e < threshold - 2; e++) { for (var g = 0, h = 0, a = m((e + 1) * p) + 1, d = m((e + 2) * p) + 1, d = d < f ? d : f, k = d - a; a < d; a++) { g += +data[a][xProperty], h += +data[a][yProperty]; } for (var g = g / k, h = h / k, a = m((e + 0) * p) + 1, d = m((e + 1) * p) + 1, k = +data[c][xProperty], x = +data[c][yProperty], c = -1; a < d; a++) { "undefined" != typeof data[a] && (u = .5 * y((k - g) * (data[a][yProperty] - x) - (k - data[a][xProperty]) * (h - x)), u > c && (c = u, v = data[a], w = a)); } n[t++] = v; c = w; } n[t++] = data[f - 1]; return n; }; function resize() { // get the container dimensions var container = d3.select('#timeseries'); width = parseInt(container.style('width'), 10); width = width - margin.left - margin.right; height = parseInt(container.style('height'), 10); height = height - margin.top - margin.bottom; // set the new ranges and axes x.range([0, width]); y.range([height, 0]).nice(); // define this number of axis ticks relative to the pixel length of the axis xAxis.ticks(Math.max(width / 100, 2)); yAxis.ticks(Math.max(height / 50, 2)); d3.select(".x.axis") .attr("transform", "translate(0," + height + ")") .call(xAxis); d3.select(".y.axis") .call(yAxis); // calculate the downsample data using half the width as the threshold var downsampled = largestTriangleThreeBucket(data, width / 2, "ts", "data"); // redraw the path with the downsampled data d3.select("path.line") .datum(downsampled) .attr("d", line); // output the lengths d3.select(".log").text("data:" + data.length + " downsampled:" + downsampled.length); } d3.select(window).on('resize', resize); })();