Last active
March 27, 2026 15:01
-
-
Save vasturiano/ded69192b8269a78d2d97e24211e64e0 to your computer and use it in GitHub Desktop.
Revisions
-
vasturiano revised this gist
Oct 22, 2017 . 2 changed files with 1 addition and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,3 +1,2 @@ height: 700 scrolling: yes This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,5 +1,5 @@ <head> <script src="//unpkg.com/timelines-chart@2"></script> <script src="random-data.js"></script> </head> -
vasturiano revised this gist
Sep 19, 2017 . 5 changed files with 58 additions and 68 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,19 +1,15 @@ <head> <script src="//unpkg.com/timelines-chart@2/dist/timelines-chart.min.js"></script> <script src="random-data.js"></script> </head> <body> <div id="myPlot"></div> <script> TimelinesChart() .data(getRandomData(true)) .zQualitative(true) (document.getElementById('myPlot')); </script> </body> This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,55 +0,0 @@ LoadingSorry, something went wrong. Reload?Sorry, we cannot display this file.Sorry, this file is invalid so it cannot be displayed.This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,49 @@ function getRandomData(ordinal = false) { const NGROUPS = 6, MAXLINES = 15, MAXSEGMENTS = 20, MAXCATEGORIES = 20, MINTIME = new Date(2013,2,21); const nCategories = Math.ceil(Math.random()*MAXCATEGORIES), categoryLabels = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z']; return [...Array(NGROUPS).keys()].map(i => ({ group: 'group' + (i+1), data: getGroupData() })); // function getGroupData() { return [...Array(Math.ceil(Math.random()*MAXLINES)).keys()].map(i => ({ label: 'label' + (i+1), data: getSegmentsData() })); // function getSegmentsData() { const nSegments = Math.ceil(Math.random()*MAXSEGMENTS), segMaxLength = Math.round(((new Date())-MINTIME)/nSegments); let runLength = MINTIME; return [...Array(nSegments).keys()].map(i => { const tDivide = [Math.random(), Math.random()].sort(), start = new Date(runLength.getTime() + tDivide[0]*segMaxLength), end = new Date(runLength.getTime() + tDivide[1]*segMaxLength); runLength = new Date(runLength.getTime() + segMaxLength); return { timeRange: [start, end], val: ordinal ? categoryLabels[Math.ceil(Math.random()*nCategories)] : Math.random() //labelVal: is optional - only displayed in the labels }; }); } } } LoadingSorry, something went wrong. Reload?Sorry, we cannot display this file.Sorry, this file is invalid so it cannot be displayed. -
vasturiano revised this gist
Apr 20, 2017 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,3 +1,3 @@ license: mit height: 700 scrolling: yes -
vasturiano revised this gist
Oct 15, 2016 . 2 changed files with 4 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,6 +1,7 @@ A parallel timelines layout (swimlanes) for representing state of time-series over time. Using the [timelines-chart](https://github.com/vasturiano/timelines-chart) D3 component. Each timeline segment can be assigned a value from a color scale, either continuous (heatmap mode) or ordinal (for categorical representation). Time-series can be grouped into logical groups, represented as distinct sections. Explore the data by zooming (drag) or using the timeline brush at the bottom. Example is populated with randomly generated data. This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -3,7 +3,7 @@ function getMockupData() { var NGROUPS = 6; var MAXLINES = 15; var MAXSEGMENTS = 20; var MINTIME = new Date(new Date() - 3*365*24*60*60*1000); function getGroupData() { -
vasturiano revised this gist
Oct 15, 2016 . 2 changed files with 0 additions and 0 deletions.There are no files selected for viewing
LoadingSorry, something went wrong. Reload?Sorry, we cannot display this file.Sorry, this file is invalid so it cannot be displayed.LoadingSorry, something went wrong. Reload?Sorry, we cannot display this file.Sorry, this file is invalid so it cannot be displayed. -
vasturiano revised this gist
Oct 11, 2016 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -3,7 +3,7 @@ function getMockupData() { var NGROUPS = 6; var MAXLINES = 15; var MAXSEGMENTS = 20; var MINTIME = new Date(new Date() - 3*365*24*60*60*1000); function getGroupData() { -
vasturiano revised this gist
Oct 11, 2016 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -3,7 +3,7 @@ function getMockupData() { var NGROUPS = 6; var MAXLINES = 15; var MAXSEGMENTS = 20; var MINTIME = new Date(new Date() - 365*24*60*60*1000); function getGroupData() { -
vasturiano revised this gist
Oct 11, 2016 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -3,7 +3,7 @@ function getMockupData() { var NGROUPS = 6; var MAXLINES = 15; var MAXSEGMENTS = 20; var MINTIME = (new Date()) - 365*24*60*60*1000; function getGroupData() { -
vasturiano revised this gist
Oct 11, 2016 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,6 +1,6 @@ A parallel timelines layout (swimlanes) for representing state of time-series over time. Using the [timelines-chart](https://github.com/vasturiano/timelines-chart) D3 component. Each timeline segment can be assigned a value from a color scale, either continuous (heatmap mode) or ordinal (for categorical representation). Time-series can be grouped into logical groups, represented as distinct sections. Explore the data by zooming (drag) or using the timeline brush at the bottom. Example is populated with randomly generated data. -
vasturiano revised this gist
Oct 11, 2016 . 1 changed file with 1 addition and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -2,5 +2,6 @@ A parallel timelines layout (swimlanes) for representing state of time-series ov Each timeline segment can be assigned a value on a color scale, either continuous (heatmap mode) or ordinal (for categorical representation). Time-series can be grouped into logical groups, represented as distinct sections. Explore the data by zooming (drag) or using the timeline brush at the bottom. Example is populated with randomly generated data. -
vasturiano revised this gist
Oct 11, 2016 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,4 +1,4 @@ A parallel timelines layout (swimlanes) for representing state of time-series over time. Using the [timelines-chart](https://github.com/vasturiano/timelines-chart) D3 component. Each timeline segment can be assigned a value on a color scale, either continuous (heatmap mode) or ordinal (for categorical representation). Time-series can be grouped into logical groups, represented as distinct sections. Explore the data by zooming (drag) or using the timeline brush at the bottom. -
vasturiano revised this gist
Oct 10, 2016 . No changes.There are no files selected for viewing
-
vasturiano revised this gist
Oct 10, 2016 . 1 changed file with 1 addition and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,6 +1,4 @@ A parallel timelines layout (swimlanes) for representing state of time-series over time. Using the [timelines-chart](https://github.com/vasturiano/timelines-chart) D3 reusable chart. Each timeline segment can be assigned a value on a color scale, either continuous (heatmap mode) or ordinal (for categorical representation). Time-series can be grouped into logical groups, represented as distinct sections. Explore the data by zooming (drag) or using the timeline brush at the bottom. -
vasturiano revised this gist
Oct 10, 2016 . 2 changed files with 4 additions and 4 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,6 +1,6 @@ A parallel timelines layout (swimlanes) for representing state of time-series over time. Using the [timelines-chart](https://github.com/vasturiano/timelines-chart) D3 reusable chart. Each timeline segment can be assigned a value on a color scale, either continuous (heatmap mode) or ordinal (for categorical representation). Time-series can be grouped into logical groups, represented as distinct sections. Explore the data by zooming (drag) or using the timeline brush at the bottom. This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,10 +1,10 @@ <head> <script src="//unpkg.com/timelines-chart@^1.0/dist/timelines-chart.min.js"></script> <script src="mockup-data.js"></script> <script> var myData = getMockupData(), myPlot = TimelinesChart() .width(window.innerWidth) .zScaleLabel("My Scale Units"); -
vasturiano revised this gist
Oct 10, 2016 . 1 changed file with 3 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -4,9 +4,9 @@ <script> var myData = getMockupData(), myPlot = StackedTimelinesChart() .width(window.innerWidth) .zScaleLabel("My Scale Units"); document.addEventListener("DOMContentLoaded", function() { myPlot(document.getElementById("myPlot"), myData); -
vasturiano revised this gist
Oct 10, 2016 . 7 changed files with 14 additions and 1869 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,3 +1,8 @@ A stacked timelines layout for representing state of time-series over time. Using the [stacked-timelines-chart](https://github.com/vasturiano/stacked-timelines-chart) D3 reusable chart. Each timeline segment can be assigned a value on a color scale, either continuous (heatmap mode) or ordinal (for categorical representation). Time-series can be grouped into logical groups, represented as distinct sections. Explore the data by zooming (drag) or using the timeline brush at the bottom. Example is populated with randomly generated data. This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,290 +0,0 @@ This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,29 +1,19 @@ <head> <script src="//unpkg.com/stacked-timelines-chart@^1.0/dist/stacked-timelines-chart.min.js"></script> <script src="mockup-data.js"></script> <script> var myData = getMockupData(), myPlot = StackedTimelinesChart() .width(window.innerWidth) .zScaleLabel("My Scale Units"); document.addEventListener("DOMContentLoaded", function() { myPlot(document.getElementById("myPlot"), myData); }); </script> </head> <body> <div id="myPlot"></div> </body> This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,80 +0,0 @@ This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,1256 +0,0 @@ Binary file not shown.This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,224 +0,0 @@ -
vasturiano revised this gist
Oct 6, 2016 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,3 +1,3 @@ A stacked timelines layout for representing state of time-series over time in a heatmap fashion. Time-series can be grouped into logical groups, represented as distinct sections. Explore the data by zooming (drag) or using the timeline brush at the bottom. Example is populated with randomly generated data. -
vasturiano revised this gist
Oct 6, 2016 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,3 +1,3 @@ A stacked heatmap timelines layout for representing state of time-series over time. Time-series can be grouped into logical groups, represented as distinct sections. Explore the data by zooming (drag) or using the timeline brush at the bottom. Example is populated with randomly generated data. -
vasturiano revised this gist
Aug 11, 2016 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,3 +1,3 @@ license: gpl-3.0 height: 700 scrolling: yes -
vasturiano revised this gist
Aug 11, 2016 . 1 changed file with 3 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,3 @@ license: gpl-3.0 height: 800 scrolling: yes -
vasturiano revised this gist
Jun 1, 2016 . 1 changed file with 4 additions and 4 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -120,7 +120,7 @@ d3.selection.prototype.appendOrdinalColorLegend = function(w, h, scale, label) { .attr("x", colorBinWidth*(index+.5)) .attr("y", h*0.5) .style("text-anchor", "middle") .style("dominant-baseline", "central") .style('fill', tinycolor(scale(val)).isLight()?'#333':'#DDD' ) .style('font-family', 'Sans-Serif') .textFitToBox(colorBinWidth, h*0.8); @@ -165,7 +165,7 @@ d3.selection.prototype.appendLinearColorLegend = function(w, h, scale, label) { .attr("x", w*0.5) .attr("y", h*0.5) .style("text-anchor", "middle") .style("dominant-baseline", "central") .style('fill', tinycolor(scale((scale.domain()[scale.domain().length-1] - scale.domain()[0])/2)).isLight()?'#444':'#CCC' ) .style('font-family', 'Sans-Serif') .textFitToBox(w*0.8, h*0.9); @@ -175,7 +175,7 @@ d3.selection.prototype.appendLinearColorLegend = function(w, h, scale, label) { .attr("x", w*0.02) .attr("y", h*0.5) .style("text-anchor", "start") .style("dominant-baseline", "central") .style('font', h*0.7 + 'px sans-serif') .style('fill', tinycolor(scale.range()[0]).isLight()?'#444':'#CCC' ) .style('font-family', 'Sans-Serif') @@ -186,7 +186,7 @@ d3.selection.prototype.appendLinearColorLegend = function(w, h, scale, label) { .attr("x", w*0.98) .attr("y", h*0.5) .style("text-anchor", "end") .style("dominant-baseline", "central") .style('fill', tinycolor(scale.range()[scale.range().length-1]).isLight()?'#444':'#CCC' ) .style('font-family', 'Sans-Serif') .textFitToBox(w*0.3, h*0.7); -
vasturiano revised this gist
May 16, 2016 . 2 changed files with 9 additions and 7 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -120,8 +120,8 @@ d3.selection.prototype.appendOrdinalColorLegend = function(w, h, scale, label) { .attr("x", colorBinWidth*(index+.5)) .attr("y", h*0.5) .style("text-anchor", "middle") .style("alignment-baseline", "central") .style('fill', tinycolor(scale(val)).isLight()?'#333':'#DDD' ) .style('font-family', 'Sans-Serif') .textFitToBox(colorBinWidth, h*0.8); @@ -165,7 +165,8 @@ d3.selection.prototype.appendLinearColorLegend = function(w, h, scale, label) { .attr("x", w*0.5) .attr("y", h*0.5) .style("text-anchor", "middle") .style("alignment-baseline", "central") .style('fill', tinycolor(scale((scale.domain()[scale.domain().length-1] - scale.domain()[0])/2)).isLight()?'#444':'#CCC' ) .style('font-family', 'Sans-Serif') .textFitToBox(w*0.8, h*0.9); @@ -174,9 +175,9 @@ d3.selection.prototype.appendLinearColorLegend = function(w, h, scale, label) { .attr("x", w*0.02) .attr("y", h*0.5) .style("text-anchor", "start") .style("alignment-baseline", "central") .style('font', h*0.7 + 'px sans-serif') .style('fill', tinycolor(scale.range()[0]).isLight()?'#444':'#CCC' ) .style('font-family', 'Sans-Serif') .textFitToBox(w*0.3, h*0.7); @@ -185,8 +186,8 @@ d3.selection.prototype.appendLinearColorLegend = function(w, h, scale, label) { .attr("x", w*0.98) .attr("y", h*0.5) .style("text-anchor", "end") .style("alignment-baseline", "central") .style('fill', tinycolor(scale.range()[scale.range().length-1]).isLight()?'#444':'#CCC' ) .style('font-family', 'Sans-Serif') .textFitToBox(w*0.3, h*0.7); This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -2,6 +2,7 @@ <script src="//code.jquery.com/jquery-2.2.3.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/d3-tip/0.6.7/d3-tip.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/tinycolor/1.3.0/tinycolor.min.js"></script> <script src="d3-utils.js"></script> <script src="time-overview.js"></script> -
vasturiano revised this gist
May 16, 2016 . 2 changed files with 65 additions and 11 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -96,16 +96,59 @@ d3.selection.prototype.addDropShadow = function() { return shadowId; }; d3.selection.prototype.appendOrdinalColorLegend = function(w, h, scale, label) { var legend = this; var colorBinWidth = w / scale.domain().length; scale.domain().forEach(function(val, index) { var colorG = legend.append('g'); colorG.append("rect") .attr("width", colorBinWidth) .attr("height", h) .attr("x", colorBinWidth*index) .attr("y", 0) .attr("rx", 0) .attr("ry", 0) .attr("stroke-width", 0) .attr("fill", scale(val)); colorG.append("text") .text(val) .attr("x", colorBinWidth*(index+.5)) .attr("y", h*0.5) .style("text-anchor", "middle") .attr('dy','.4em') .style('fill', '#CCC' ) .style('font-family', 'Sans-Serif') .textFitToBox(colorBinWidth, h*0.8); colorG.append('title') .text(val + ' ' + label); }); legend.append("rect") .attr("width", w) .attr("height", h) .attr("x", 0) .attr("y", 0) .attr("rx", 3) .attr("ry", 3) .attr("stroke", "black") .attr("stroke-width", 0.5) .attr("fill-opacity", 0) .style("pointer-events", 'none'); return legend; }; d3.selection.prototype.appendLinearColorLegend = function(w, h, scale, label) { var gradId = this.addGradient(scale, 0); this.append("rect") .attr("width", w) .attr("height", h) .attr("x", 0) @@ -116,7 +159,7 @@ d3.selection.prototype.appendColorLegend = function(x, y, w, h, scale, label) { .attr("stroke-width", 0.5) .style("fill", 'url(#' + gradId + ')'); this.append("text") .attr("class", "legendText") .text(label) .attr("x", w*0.5) @@ -126,7 +169,7 @@ d3.selection.prototype.appendColorLegend = function(x, y, w, h, scale, label) { .style('font-family', 'Sans-Serif') .textFitToBox(w*0.8, h*0.9); this.append("text") .text(scale.domain()[0]) .attr("x", w*0.02) .attr("y", h*0.5) @@ -137,7 +180,7 @@ d3.selection.prototype.appendColorLegend = function(x, y, w, h, scale, label) { .style('font-family', 'Sans-Serif') .textFitToBox(w*0.3, h*0.7); this.append("text") .text(scale.domain()[scale.domain().length-1]) .attr("x", w*0.98) .attr("y", h*0.5) @@ -147,7 +190,18 @@ d3.selection.prototype.appendColorLegend = function(x, y, w, h, scale, label) { .style('font-family', 'Sans-Serif') .textFitToBox(w*0.3, h*0.7); return this; }; d3.selection.prototype.appendColorLegend = function(x, y, w, h, scale, label) { var legendG = this.append("g") .attr("class", "legend"); legendG.attr("transform", "translate(" + x + "," + y + ")"); return (scale.copy().domain([1, 2]).range([1, 2])(1.5) === 1) ?legendG.appendOrdinalColorLegend(w, h, scale, label) :legendG.appendLinearColorLegend(w, h, scale, label); }; d3.selection.prototype.appendSvgThrobber = function(x, y, r, color, duration, angleFull) { This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -388,7 +388,7 @@ var StackedTimeSeriesHeatMap = function() { var normVal = env.valScale.domain()[env.valScale.domain().length-1] - env.valScale.domain()[0]; var dateFormat = d3.time.format("%Y-%m-%d %H:%M:%S"); return "<strong>" + d.labelVal + " </strong>" + env.zDataLabel + (normVal?" (<strong>" + d3.round((d.val-env.valScale.domain()[0])/normVal*100, 2) + "%</strong>)":"") + "<br>" + "<strong>From: </strong>" + dateFormat(d.timeRange[0]) + "<br>" + "<strong>To: </strong>" + dateFormat(d.timeRange[1]); }); -
vasturiano revised this gist
May 16, 2016 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -297,7 +297,7 @@ var StackedTimeSeriesHeatMap = function() { this ); env.overviewArea.init(($('<div>').appendTo(env.$elem))[0]); env.$elem.bind('zoomScent', function(event, zoomX, zoomY) { if (!env.overviewArea || !zoomX) return; -
vasturiano revised this gist
May 10, 2016 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,3 +1,3 @@ A stacked heatmap layout for representing state of time-series over time. Time-series can be grouped into logical groups, represented as distinct sections. Explore the data by zooming (drag) or using the timeline brush at the bottom. Example is populated with randomly generated data. -
vasturiano revised this gist
May 10, 2016 . 9 changed files with 1881 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1 +1,3 @@ A stacked heatmap layout for representing state of time-series over time. Time-series can be grouped into logical groups, represented as distinct sections. Explore the data by zooming (drag) or using the timeline brush at the bottom. Example is populated by randomly generated data. This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,235 @@ // D3 selections util funcs d3.selection.prototype.moveToFront = function() { return this.each(function(){ this.parentNode.appendChild(this); }); }; d3.selection.prototype.textFitToBox = function(w,h,passes) { passes = passes||3; var startSize = parseInt(this.style("font-size").split('px')[0]); var bbox = this.node().getBBox(); var newSize = Math.floor(startSize*Math.min(w/bbox.width, h/bbox.height)); if (newSize!=startSize) { this.style('font-size', newSize + 'px'); if(--passes) this.textFitToBox(w,h,passes); } return this; }; d3.selection.prototype.textAbbreviateToFit = function(maxW) { function abbreviateText(txt, maxChars) { return txt.length<=maxChars?txt:( txt.substring(0, maxChars*2/3) + '...' + txt.substring(txt.length - maxChars/3, txt.length) ); } var origTxt = this.text(); var nChars = Math.round(origTxt.length*maxW/this.node().getBBox().width*1.2); // Start above while(--nChars && maxW/this.node().getBBox().width<1){ this.text(abbreviateText(origTxt, nChars)); } return this; }; // colorScale: d3.scale.linear().domain([0, 1, 2]).range(['red', 'yellow', 'green']) // angle: 0 (left-right), 90 (down-up), ... d3.selection.prototype.addGradient = function(colorScale, angle) { angle = angle||0; // Horizontal var rad = Math.PI * angle/180; var gradId = "areaGradient" + Math.round(Math.random()*10000); var areaGradients = this.append("linearGradient") .attr("y1", Math.round(100*Math.max(0, Math.sin(rad))) + "%") .attr("y2", Math.round(100*Math.max(0, -Math.sin(rad))) + "%") .attr("x1", Math.round(100*Math.max(0, -Math.cos(rad))) + "%") .attr("x2", Math.round(100*Math.max(0, Math.cos(rad))) + "%") .attr("id", gradId); var threshVal = colorScale.domain()[0]; var normVal = colorScale.domain()[colorScale.domain().length-1] - threshVal; for (var i=0, len=colorScale.domain().length; i<len; i++) { areaGradients.append("stop") .attr("offset", (100*(colorScale.domain()[i] - threshVal)/normVal) + "%") .attr("stop-color", colorScale.range()[i]); } // Use with: .attr("fill", 'url(#<gradId>)'); return gradId; }; d3.selection.prototype.addDropShadow = function() { var shadowId = "areaGradient" + Math.round(Math.random()*10000); var filter = this.append('defs').append('filter') .attr('id', shadowId) .attr('height', '130%'); filter.append('feGaussianBlur') .attr('in', 'SourceAlpha') .attr('stdDeviation', 3); filter.append('feOffset') .attr('dx', 2) .attr('dy', 2) .attr('result', 'offsetblur'); var feMerge = filter.append('feMerge'); feMerge.append('feMergeNode'); feMerge.append('feMergeNode') .attr('in', 'SourceGraphic'); // Use with: .attr('filter', 'url(#<shadowId>)')) return shadowId; }; d3.selection.prototype.appendColorLegend = function(x, y, w, h, scale, label) { var gradId = this.addGradient(scale, 0); var legendG = this.append("g") .attr("class", "legend"); legendG.attr("transform", "translate(" + x + "," + y + ")"); legendG.append("rect") .attr("width", w) .attr("height", h) .attr("x", 0) .attr("y", 0) .attr("rx", 3) .attr("ry", 3) .attr("stroke", "black") .attr("stroke-width", 0.5) .style("fill", 'url(#' + gradId + ')'); legendG.append("text") .attr("class", "legendText") .text(label) .attr("x", w*0.5) .attr("y", h*0.5) .style("text-anchor", "middle") .attr('dy','.35em') .style('font-family', 'Sans-Serif') .textFitToBox(w*0.8, h*0.9); legendG.append("text") .text(scale.domain()[0]) .attr("x", w*0.02) .attr("y", h*0.5) .style("text-anchor", "start") .attr('dy','.4em') .style('font', h*0.7 + 'px sans-serif') .style('fill', '#CCC' ) .style('font-family', 'Sans-Serif') .textFitToBox(w*0.3, h*0.7); legendG.append("text") .text(scale.domain()[scale.domain().length-1]) .attr("x", w*0.98) .attr("y", h*0.5) .style("text-anchor", "end") .attr('dy','.4em') .style('fill', '#CCC' ) .style('font-family', 'Sans-Serif') .textFitToBox(w*0.3, h*0.7); return legendG; }; d3.selection.prototype.appendSvgThrobber = function(x, y, r, color, duration, angleFull) { function genDonutSlice(cx, cy, r, thickness, startAngle, endAngle) { startAngle = startAngle/180*Math.PI; endAngle = endAngle/180*Math.PI; var outerR=r; var innerR=r-thickness; p=[ [cx+outerR*Math.cos(startAngle), cy+outerR*Math.sin(startAngle)], [cx+outerR*Math.cos(endAngle), cy+outerR*Math.sin(endAngle)], [cx+innerR*Math.cos(endAngle), cy+innerR*Math.sin(endAngle)], [cx+innerR*Math.cos(startAngle), cy+innerR*Math.sin(startAngle)] ]; angleDiff = endAngle - startAngle; largeArc = ((angleDiff % (Math.PI * 2)) > Math.PI)?1:0; path = []; path.push("M" + p[0].join()); path.push("A" + [outerR,outerR,0,largeArc,1,p[1]].join()); path.push("L" + p[2].join()); path.push("A" + [innerR,innerR,0,largeArc,0,p[3]].join()); path.push("z"); return path.join(" "); } r = r||8; color = color||'darkblue'; duration = duration||0.7; angleFull = angleFull||120; var thickness = r/3; var path = this.append('path') .attr('d', genDonutSlice(x, y, r, thickness, 0, angleFull)) .attr('fill', color); path.append('animateTransform') .attr('attributeName', 'transform') .attr('attributeType', 'XML') .attr('type', 'rotate') .attr('from', '0 ' + x + ' ' + y) .attr('to', '360 ' + x + ' ' + y) .attr('begin', '0s') .attr('dur', duration + 's') .attr('fill', 'freeze') .attr('repeatCount', 'indefinite'); return path; }; d3.selection.prototype.appendImage = function(imgUrl, x, y, maxW, maxH, svgAlign) { svgAlign = svgAlign || "xMidYMid"; return new function(svgElem, imgUrl, x, y, maxW, maxH, svgAlign) { this.img = svgElem.append("image") .attr("xlink:href", imgUrl) .attr("x", x) .attr("y", y) .attr("width", maxW) .attr("height", maxH) .attr("preserveAspectRatio", svgAlign + " meet"); this.show = function() { this.img .attr("width", maxW) .attr("height", maxH); return this; }; this.hide = function() { this.img .attr("width", 0) .attr("height", 0); return this; }; }(this, imgUrl, x, y, maxW, maxH, svgAlign); }; This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,28 @@ <head> <script src="//code.jquery.com/jquery-2.2.3.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/d3-tip/0.6.7/d3-tip.min.js"></script> <script src="d3-utils.js"></script> <script src="time-overview.js"></script> <script src="stacked-heat-map.js"></script> <script src="mockup-data.js"></script> <link rel="stylesheet" type="text/css" href="stacked-heat-map.css"> <script> var myData = getMockupData(); $(function() { var myPlot = StackedTimeSeriesHeatMap() .width($(window).width()) .throbberImg('throbber.gif') .zScaleLabel("My Scale Units"); myPlot($('#myHeatMap'), myData); }); </script> </head> <body> <div id="myHeatMap"></div> </body> This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,55 @@ function getMockupData() { var NGROUPS = 6; var MAXLINES = 15; var MAXSEGMENTS = 20; var MINTIME = new Date(2013,2,21); function getGroupData() { function getSegmentsData() { var segData=[]; var nSegments = Math.ceil(Math.random()*MAXSEGMENTS); var segMaxLength = Math.round(((new Date())-MINTIME)/nSegments); var runLength = MINTIME; for (var i=0; i< nSegments; i++) { var tDivide = [Math.random(), Math.random()].sort(); var start = new Date(runLength.getTime() + tDivide[0]*segMaxLength); var end = new Date(runLength.getTime() + tDivide[1]*segMaxLength); runLength = new Date(runLength.getTime() + segMaxLength); segData.push({ 'timeRange': [start, end], 'val': Math.random() //'labelVal': is optional - only displayed in the labels }); } return segData; } var grpData = []; for (var i=0, nLines=Math.ceil(Math.random()*MAXLINES); i<nLines; i++) { grpData.push({ 'label': 'label' + (i+1), 'data': getSegmentsData() }); } return grpData; } var data = []; for (var i=0; i< NGROUPS; i++) { data.push({ 'group': 'group' + (i+1), 'data': getGroupData() }); } return data; } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,80 @@ .stacked-heat-map .axises { shape-rendering: crispedges; } .stacked-heat-map .axises line, .stacked-heat-map .axises path { fill: none; stroke: #808080; } .stacked-heat-map .y-axis line, .stacked-heat-map .y-axis path, .stacked-heat-map .grp-axis line, .stacked-heat-map .grp-axis path { stroke: none; } .stacked-heat-map .y-axis text, .stacked-heat-map .grp-axis text { fill: #2F4F4F; } .stacked-heat-map .x-grid line { stroke: #D3D3D3; } .stacked-heat-map rect.heatmap-group { fill-opacity: 0.6; stroke: #808080; stroke-opacity: 0.2; cursor: crosshair; } .stacked-heat-map rect.heatmap-segment { stroke: none; cursor: crosshair; } .legend .legendText { fill: #666; } .brusher .axis text { font: 11px sans-serif; } .brusher .axis path { display: none; } .brusher .axis line { fill: none; stroke: #000; shape-rendering: crispEdges; } .brusher .grid-background { fill: #C0C0C0; } .brusher .grid line, .brusher .grid path { fill: none; stroke: #fff; } .brusher .grid .minor.tick line { stroke-opacity: .5; } .brusher .brush .extent { stroke: blue; stroke-opacity: 0.6; fill: blue; fill-opacity: 0.3; shape-rendering: crispEdges; } /* * Cancel selection interaction */ .stat-noselect { -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,1256 @@ /* Stacked Time-Series Heat Map SVG Layout Exposed functions: .width(<px>) .leftMargin(<px>) .rightMargin(<px>) .topMargin(<px>) .bottomMargin(<px>) .maxHeight(<px>) .throbberImg(<image URI>) .dataDomain([<min>, <max>]) .dataScale(<d3 scale object>) .getNLines() .getTotalNLines() .zoomX([<start date>, <end date>], <force redraw (boolean). default: true>) .zoomY([<start row index, end row index], <force redraw (boolean). default: true>) .zoomYLabels([<(start) {group,label}>, <(end) {group,label}>], <force redraw (boolean). default: true>) .getVisibleStructure() .minSegmentDuration(<msecs>) .zDataLabel(<unit text on tooltips>) .zScaleLabel(<legend unit text>) .sort(<label compare function>, <group compare function>) .sortAlpha(<ascending (boolean)>) .sortChrono(<ascending (boolean)>) .replaceData(<new data>, <keep graph structure (boolean). default: false>) .enableOverview(<boolean>) .overviewDomain(<new time range for overview: [<start date>, <end date>]>) .animationsEnabled(<(boolean)>) .forceThrobber(<force throbber on (boolean>). default: false>) .axisClickURL(<URL to follow when clicking on Y axises>) .getSvg() .onZoom(<callback function for user initiated zoom>) .refresh() */ var StackedTimeSeriesHeatMap = function() { var env = { $elem: null, width : 720, // default width height : null, maxHeight : 640, // default maxHeight overviewHeight : 20, // Height of overview section in bottom lineMaxHeight : 12, minLabelFont : 2, margin : {top: 26, right: 100, bottom: 30, left: 90 }, groupBkgGradient : ['#FAFAFA', '#E0E0E0'], xScale : d3.time.scale(), yScale : d3.scale.ordinal(), grpScale : d3.scale.ordinal(), valScale : d3.scale.linear() .domain([0, 0.5, 1]) .range(["red", "yellow", "green"]) .clamp(false), zDataLabel: "", // Units of z data. Used in the tooltip descriptions zScaleLabel: "", // Units of valScale. Used in the legend label. xAxis : d3.svg.axis(), xGrid : d3.svg.axis(), yAxis : d3.svg.axis(), grpAxis : d3.svg.axis(), svg : null, graph : null, overviewArea: null, graphW : null, graphH : null, completeStructData : null, structData : null, completeFlatData : null, flatData : null, totalNLines : null, nLines : null, zoomX : [null, null], // Which time-range to show (null = min/max) zoomY : [null, null], // Which lines to show (null = min/max) [0 indexed] minSegmentDuration : 0, // ms transDuration : 700, // ms for transition duration throbber: null, throbberImg: null, throbberR: 23, forceThrobber: false, // Force the throbber to stay on enableOverview: true, axisClickURL: null, labelCmpFunction: alphaNumCmp, grpCmpFunction: alphaNumCmp, // Events callbacks onZoom: null // When user zooms in / resets zoom. Returns ([startX, endX], [startY, endY]) }; function chart($elem, data) { env.$elem = $elem; env.$elem.addClass('stacked-heat-map') .css('text-align', 'center'); env.svg = d3.select($elem[0]).append("svg"); initStatic(); drawNewData(data); return chart; } function parseData(rawData) { env.completeStructData = []; env.completeFlatData = []; env.totalNLines = 0; var dateObjs = rawData.length?rawData[0].data[0].data[0].timeRange[0] instanceof Date:false; for (var i= 0, ilen=rawData.length; i<ilen; i++) { var group = rawData[i].group; env.completeStructData.push({ group: group, lines: rawData[i].data.map(function(d) { return d.label; }) }); for (var j= 0, jlen=rawData[i].data.length; j<jlen; j++) { for (var k= 0, klen=rawData[i].data[j].data.length; k<klen; k++) { env.completeFlatData.push({ group: group, label: rawData[i].data[j].label, timeRange: (dateObjs ?rawData[i].data[j].data[k].timeRange :[new Date(rawData[i].data[j].data[k].timeRange[0]), new Date(rawData[i].data[j].data[k].timeRange[1])] ), val: rawData[i].data[j].data[k].val, labelVal: rawData[i].data[j].data[k][rawData[i].data[j].data[k].hasOwnProperty('labelVal')?'labelVal':'val'] }); } env.totalNLines++; } } } function initStatic() { buildDomStructure(); addTooltips(); addZoomSelection(); setEvents(); function buildDomStructure () { env.yScale.invert = invertOrdinal; env.grpScale.invert = invertOrdinal; env.groupGradId = env.svg.addGradient( d3.scale.linear() .domain([0, 1]) .range(env.groupBkgGradient), -90 ); env.graphW = env.width-env.margin.left-env.margin.right; env.xScale.range([0, env.graphW]) .clamp(true); env.svg.attr("width", env.width); var axises = env.svg.append('g'); env.graph = env.svg.append('g') .attr("transform", "translate(" + env.margin.left + "," + env.margin.top + ")"); axises.attr("class", "axises") .attr("transform", "translate(" + env.margin.left + "," + env.margin.top + ")"); axises.append("g") .attr("class", "x-axis") .style({ font: '12px sans-serif'}); axises.append("g") .attr("class", "x-grid"); axises.append("g") .attr("class", "y-axis") .attr("transform", "translate(" + env.graphW + ", 0)"); axises.append("g") .attr("class", "grp-axis"); env.xAxis.scale(env.xScale) .orient("bottom") .ticks(Math.round(env.graphW*0.011)); // Abbreviated month names var xAxisFormat = d3.time.format.multi([ [".%L", function(d) { return d.getMilliseconds(); }], [":%S", function(d) { return d.getSeconds(); }], ["%I:%M", function(d) { return d.getMinutes(); }], ["%I %p", function(d) { return d.getHours(); }], ["%a %d", function(d) { return d.getDay() && d.getDate() != 1; }], ["%b %d", function(d) { return d.getDate() != 1; }], ["%b", function(d) { return d.getMonth(); }], ["%Y", function() { return true; }] ]); env.xAxis.tickFormat(xAxisFormat); env.xGrid.scale(env.xScale) .orient("top") .tickFormat("") .ticks(env.xAxis.ticks()[0]); env.yAxis .scale(env.yScale) .orient("right") .tickSize(0); env.grpAxis .scale(env.grpScale) .orient("left") .tickSize(0); env.svg.appendColorLegend( (env.margin.left + env.graphW*0.05), 2, env.graphW/3, env.margin.top*.6, env.valScale, env.zScaleLabel ); if (env.enableOverview) { addOverviewArea(); } if (env.throbberImg) { env.throbber = env.svg.appendImage( env.throbberImg, env.margin.left + (env.graphW-env.throbberR)/2, env.margin.top + 5, env.throbberR, env.throbberR, 'xMidYMin' ).hide(); env.throbber.img .style('opacity', 0.85) .append('title').text('Loading data...'); } // Applies to ordinal scales (invert not supported in d3) function invertOrdinal(val, cmpFunc) { cmpFunc = cmpFunc || function(a, b) { return (a>=b); }; var bias = this.range()[0]; for (var i=0, len=this.range().length; i<len; i++) { if (cmpFunc(this.range()[i]+bias, val)) { return this.domain()[i]; } } return this.domain()[this.domain().length-1]; } function addOverviewArea() { var overviewMargins = { top: 1, right: 20, bottom: 20, left: 20 }; env.overviewArea = new TimeOverview( { margins: overviewMargins, granularityLevels: { "day": 43200 * 0.5, // 1 tick = 1 day if the total time window is within 0.5 month "week": 43200 * 5, // 1 tick = 1 week if the total time window is 5 month "month": (43200 * 12 * 1), // 1 tick = 1 month if the total time window is 1 year "year": (43200 * 12 * 20) }, width: env.width*0.8, height: env.overviewHeight + overviewMargins.top + overviewMargins.bottom, verticalLabels: false, format: xAxisFormat }, function(startTime, endTime) { env.$elem.trigger('zoom', [[startTime, endTime], null]); }, this ); env.overviewArea.init(env.$elem[0]); env.$elem.bind('zoomScent', function(event, zoomX, zoomY) { if (!env.overviewArea || !zoomX) return; // Out of overview bounds if (zoomX[0]<env.overviewArea.domainRange[0] || zoomX[1]>env.overviewArea.domainRange[1]) { env.overviewArea.update( [ new Date(Math.min(zoomX[0], env.overviewArea.domainRange[0])), new Date(Math.max(zoomX[1], env.overviewArea.domainRange[1])) ], env.zoomX ); } else { // Normal case env.overviewArea.updateSelection(zoomX); } /* var startLine = (zoomY&&zoomY[0]!=null)?zoomY[0]:0; var endLine = env.nLines?env.nLines+startLine:env.overviewArea.yDomain()[1]; */ }); } } function addTooltips() { env.groupTooltip = d3.tip() .direction('w') .offset([0, 0]) .style({ color: '#eee', background: "rgba(0,0,140,0.85)", padding: '5px', 'border-radius': '3px', font: '14px sans-serif', 'font-weight': 'bold', 'z-index': 4000 }) .html(function(d) { var leftPush = (d.hasOwnProperty("timeRange") ?env.xScale(d.timeRange[0]) :0 ); var topPush = (d.hasOwnProperty("label") ?env.grpScale(d.group)-env.yScale(d.group+"+&+"+d.label) :0 ); env.groupTooltip.offset([topPush, -leftPush]); return d.group; }); env.svg.call(env.groupTooltip); env.lineTooltip = d3.tip() .direction('e') .offset([0, 0]) .style({ color: '#eee', background: "rgba(0,0,140,0.85)", padding: '5px', 'border-radius': '3px', font: '13px sans-serif', 'font-weight': 'bold', 'z-index': 4000 }) .html(function(d) { var rightPush = (d.hasOwnProperty("timeRange")?env.xScale.range()[1]-env.xScale(d.timeRange[1]):0); env.lineTooltip.offset([0, rightPush]); return d.label; }); env.svg.call(env.lineTooltip); env.segmentTooltip = d3.tip() .direction('s') .offset([5, 0]) .style({ color: '#eee', background: "rgba(0,0,140,0.7)", padding: "5px", 'border-radius': "3px", font: '11px sans-serif', 'text-align': 'center', 'z-index': 4000 }) .html(function(d) { var normVal = env.valScale.domain()[env.valScale.domain().length-1] - env.valScale.domain()[0]; var dateFormat = d3.time.format("%Y-%m-%d %H:%M:%S"); return "<strong>" + d.labelVal + " </strong>" + env.zDataLabel + " (<strong>" + d3.round((d.val-env.valScale.domain()[0])/normVal*100, 2) + "%</strong>)<br>" + "<strong>From: </strong>" + dateFormat(d.timeRange[0]) + "<br>" + "<strong>To: </strong>" + dateFormat(d.timeRange[1]); }); env.svg.call(env.segmentTooltip); } function addZoomSelection() { env.graph.on("mousedown", function() { if (d3.select(window).on("mousemove.zoomRect")!=null) // Selection already active return; var e = this; if (d3.mouse(e)[0]<0 || d3.mouse(e)[0]>env.graphW || d3.mouse(e)[1]<0 || d3.mouse(e)[1]>env.graphH) return; env.disableHover=true; var rect = env.graph.append("rect") .style({ stroke: 'blue', 'stroke-opacity': .6, fill: 'blue', 'fill-opacity': .3 }); var startCoords = d3.mouse(e); d3.select("body").classed("stat-noselect", true); d3.select(window) .on("mousemove.zoomRect", function() { d3.event.stopPropagation(); var newCoords = [ Math.max(0, Math.min(env.graphW, d3.mouse(e)[0])), Math.max(0, Math.min(env.graphH, d3.mouse(e)[1])) ]; rect.attr("x", Math.min(startCoords[0], newCoords[0])) .attr("y", Math.min(startCoords[1], newCoords[1])) .attr("width", Math.abs(newCoords[0] - startCoords[0])) .attr("height", Math.abs(newCoords[1] - startCoords[1])); env.$elem.trigger('zoomScent', [ [startCoords[0], newCoords[0]].sort(d3.ascending).map(env.xScale.invert), [startCoords[1], newCoords[1]].sort(d3.ascending).map(function(d) { return env.yScale.domain().indexOf(env.yScale.invert(d)) + ((env.zoomY && env.zoomY[0])?env.zoomY[0]:0); }) ] ); }) .on("mouseup.zoomRect", function() { d3.select(window).on("mousemove.zoomRect", null).on("mouseup.zoomRect", null); d3.select("body").classed("stat-noselect", false); rect.remove(); env.disableHover=false; var endCoords = [ Math.max(0, Math.min(env.graphW, d3.mouse(e)[0])), Math.max(0, Math.min(env.graphH, d3.mouse(e)[1])) ]; if (startCoords[0]==endCoords[0] && startCoords[1]==endCoords[1]) return; var newDomainX = [startCoords[0], endCoords[0]].sort(d3.ascending).map(env.xScale.invert); var newDomainY = [startCoords[1], endCoords[1]].sort(d3.ascending).map(function(d) { return env.yScale.domain().indexOf(env.yScale.invert(d)) + ((env.zoomY && env.zoomY[0])?env.zoomY[0]:0); }); var changeX=((newDomainX[1] - newDomainX[0])>(60*1000)); // Zoom damper var changeY=(newDomainY[0]!=env.zoomY[0] || newDomainY[1]!=env.zoomY[1]); if (changeX || changeY) { env.$elem.trigger('zoom', [ changeX?newDomainX:null, changeY?newDomainY:null ]); } }, true); d3.event.stopPropagation(); }); env.svg.append('text') .text("Reset Zoom") .attr("x", env.margin.left + env.graphW*.99) .attr("y", env.margin.top *.8) .style("text-anchor", "end") .style({ 'font-family': 'sans-serif', fill: "blue", opacity: .6, cursor: 'pointer' }) .textFitToBox(env.graphW *.4, Math.min(13,env.margin.top *.8)) .on("mouseup" , function() { env.$elem.trigger('resetZoom'); }) .on("mouseover", function(){ d3.select(this).style('opacity', 1); }) .on("mouseout", function() { d3.select(this).style('opacity', .6); }); } function setEvents() { env.$elem.bind('zoom', function(event, zoomX, zoomY, redraw) { redraw = (redraw==null)?true:redraw; if (!zoomX && !zoomY) return; if (zoomX) env.zoomX=zoomX; if (zoomY) env.zoomY=zoomY; env.$elem.trigger('zoomScent', [zoomX, zoomY]); if (!redraw) return; draw(); if (env.onZoom) env.onZoom(env.zoomX, env.zoomY); }); env.$elem.bind('resetZoom', function() { var prevZoomX = env.zoomX; var prevZoomY = env.zoomY || [null, null]; var newZoomX = env.enableOverview ?env.overviewArea.domainRange :[ d3.min(env.flatData, function(d) { return d.timeRange[0]; }), d3.max(env.flatData, function(d) { return d.timeRange[1]; }) ]; newZoomY = [null, null]; if (prevZoomX[0]<newZoomX[0] || prevZoomX[1]>newZoomX[1] || prevZoomY[0]!=newZoomY[0] || prevZoomY[1]!=newZoomX[1]) { env.zoomX = [ new Date(Math.min(prevZoomX[0],newZoomX[0])), new Date(Math.max(prevZoomX[1],newZoomX[1])) ]; env.zoomY = newZoomY; env.$elem.trigger('zoomScent', [env.zoomX, env.zoomY]); draw(); } if (env.onZoom) env.onZoom(null, null); }); } } function drawNewData(data, keepGraphStructure) { keepGraphStructure = (keepGraphStructure==null?false:keepGraphStructure); var oldStructData = env.completeStructData; parseData(data); if (keepGraphStructure) { env.completeStructData = oldStructData; } else { env.zoomX = [ d3.min(env.completeFlatData, function(d) { return d.timeRange[0]; }), d3.max(env.completeFlatData, function(d) { return d.timeRange[1]; }) ]; env.zoomY = [null, null]; if (env.overviewArea) { env.overviewArea.update(env.zoomX, env.zoomX); //var yDomain = [0, env.totalNLines]; } } draw(); } function draw() { applyFilters(); setupHeights(); adjustXScale(); adjustYScale(); adjustGrpScale(); renderAxises(); renderGroups(); if (env.throbber) { env.throbber.show(); } renderTimelines(); if (env.throbber && !env.forceThrobber) { env.throbber.hide(); } function applyFilters() { // Flat data based on segment length env.flatData = (env.minSegmentDuration>0 ?env.completeFlatData.filter(function(d) { return (d.timeRange[1]-d.timeRange[0])>=env.minSegmentDuration; }) :env.completeFlatData ); // zoomY if (env.zoomY==null || env.zoomY==[null, null]) { env.structData = env.completeStructData; env.nLines=0; for (var i=0, len=env.structData.length; i<len; i++) { env.nLines += env.structData[i].lines.length; } return; } env.structData = []; var cntDwn = [env.zoomY[0]==null?0:env.zoomY[0]]; // Initial threshold cntDwn.push(Math.max(0, (env.zoomY[1]==null?env.totalNLines:env.zoomY[1]+1)-cntDwn[0])); // Number of lines env.nLines = cntDwn[1]; for (var i=0, len=env.completeStructData.length; i<len; i++) { var validLines = env.completeStructData[i].lines; if(env.minSegmentDuration>0) { // Use only non-filtered (due to segment length) groups/labels if (!env.flatData.some(function(d){ return d.group == env.completeStructData[i].group; })) { continue; // No data for this group } validLines = env.completeStructData[i].lines.filter( function(d) { return env.flatData.some( function (dd) { return (dd.group == env.completeStructData[i].group && dd.label == d); }) }); } if (cntDwn[0]>=validLines.length) { // Ignore whole group (before start) cntDwn[0]-=validLines.length; continue; } var groupData = { group: env.completeStructData[i].group, lines: null }; if (validLines.length-cntDwn[0]>=cntDwn[1]) { // Last (or first && last) group (partial) groupData.lines = validLines.slice(cntDwn[0],cntDwn[1]+cntDwn[0]); env.structData.push(groupData); cntDwn[1]=0; break; } if (cntDwn[0]>0) { // First group (partial) groupData.lines = validLines.slice(cntDwn[0]); cntDwn[0]=0; } else { // Middle group (full fit) groupData.lines = validLines; } env.structData.push(groupData); cntDwn[1]-=groupData.lines.length; } env.nLines-=cntDwn[1]; } function setupHeights() { env.graphH = d3.min([env.nLines*env.lineMaxHeight, env.maxHeight-env.margin.top-env.margin.bottom]); env.height = env.graphH + env.margin.top + env.margin.bottom; env.svg.transition().duration(env.transDuration) .attr("height", env.height); } function adjustXScale() { env.zoomX[0] = env.zoomX[0] || d3.min(env.flatData, function(d) { return d.timeRange[0]; }); env.zoomX[1] = env.zoomX[1] || d3.max(env.flatData, function(d) { return d.timeRange[1]; }); env.xScale.domain(env.zoomX); } function adjustYScale() { var labels = []; for (var i= 0, len=env.structData.length; i<len; i++) { labels = labels.concat(env.structData[i].lines.map(function (d) { return env.structData[i].group + "+&+" + d })); } env.yScale.domain(labels); env.yScale.rangePoints([env.graphH/labels.length*0.5, env.graphH*(1-0.5/labels.length)]); } function adjustGrpScale() { env.grpScale.domain(env.structData.map(function(d) { return d.group; })); var cntLines=0; env.grpScale.range(env.structData.map(function(d) { var pos = (cntLines+d.lines.length/2)/env.nLines*env.graphH; cntLines+=d.lines.length; return pos; })); } function renderAxises() { function reduceLabel(label, maxChars) { return label.length<=maxChars?label:( label.substring(0, maxChars*2/3) + '...' + label.substring(label.length - maxChars/3, label.length )); } // X env.svg.select('g.x-axis') .style({ 'stroke-opacity': 0, 'fill-opacity': 0 }) .attr("transform", "translate(0," + env.graphH + ")") .transition().duration(env.transDuration) .call(env.xAxis) .style({ 'stroke-opacity': 1, 'fill-opacity': 1 }); /* Angled x axis labels env.svg.select('g.x-axis').selectAll("text") .style("text-anchor", "end") .attr('transform', 'translate(-10, 3) rotate(-60)'); */ env.xGrid.tickSize(env.graphH); env.svg.select('g.x-grid') .attr("transform", "translate(0," + env.graphH + ")") .transition().duration(env.transDuration) .call(env.xGrid); // Y var fontVerticalMargin = 0.6; var labelDisplayRatio = Math.ceil(env.nLines*env.minLabelFont/Math.sqrt(2)/env.graphH/fontVerticalMargin); var tickVals = env.yScale.domain().filter(function(d, i) { return !(i % labelDisplayRatio); }); var fontSize = Math.min(12, env.graphH/tickVals.length*fontVerticalMargin*Math.sqrt(2)); var maxChars = Math.ceil(env.margin.right/(fontSize/Math.sqrt(2))); env.yAxis.tickValues(tickVals); env.yAxis.tickFormat(function(d) { return reduceLabel(d.split('+&+')[1], maxChars); }); env.svg.select('g.y-axis') .transition().duration(env.transDuration) .style({ font: fontSize + 'px sans-serif'}) .call(env.yAxis); // Grp var minHeight = d3.min(env.grpScale.range(), function (d,i) { return i>0?d-env.grpScale.range()[i-1]:d*2; }); fontSize = Math.min(14, minHeight*fontVerticalMargin*Math.sqrt(2)); maxChars = Math.floor(env.margin.left/(fontSize/Math.sqrt(2))); env.grpAxis.tickFormat(function(d) { return reduceLabel(d, maxChars); }); env.svg.select('g.grp-axis') .transition().duration(env.transDuration) .style({ font: fontSize + 'px sans-serif'}) .call(env.grpAxis); // Make Axises clickable if (env.axisClickURL) { env.svg.selectAll('g.y-axis,g.grp-axis').selectAll("text") .style("cursor", "pointer") .on("click", function(d){ var segms = d.split('+&+'); var lbl = segms[segms.length-1]; window.open(env.axisClickURL + lbl, '_blank'); }) .append('title') .text(function(d) { var segms = d.split('+&+'); var lbl = segms[segms.length-1]; return 'Open ' + lbl + ' on ' + env.axisClickURL; }); } } function renderGroups() { var groups = env.graph.selectAll('rect.heatmap-group').data(env.structData, function(d) { return d.group}); groups.exit() .transition().duration(env.transDuration) .style({ "stroke-opacity": 0, "fill-opacity": 0 }) .remove(); groups.enter() .append('rect').attr("class", "heatmap-group") .attr('width', env.graphW) .attr('x', 0) .attr('y', 0) .attr('height', 0) .style('fill', 'url(#' + env.groupGradId + ')') .on('mouseover', env.groupTooltip.show) .on('mouseout', env.groupTooltip.hide) .append('title') .text('click-drag to zoom in'); groups.transition().duration(env.transDuration) .attr('height', function (d) { return env.graphH*d.lines.length/env.nLines; }) .attr('y', function (d) { return env.grpScale(d.group)-env.graphH*d.lines.length/env.nLines/2; }); } function renderTimelines(maxElems) { if (maxElems<0) maxElems=null; var hoverEnlargeRatio = .4; var dataFilter = function(d, i) { return (maxElems==null || i<maxElems) && (env.grpScale.domain().indexOf(d.group)+1 && d.timeRange[1]>=env.xScale.domain()[0] && d.timeRange[0]<=env.xScale.domain()[1] && env.yScale.domain().indexOf(d.group+"+&+"+d.label)+1); }; env.lineHeight = env.graphH/env.nLines*0.8; var timelines = env.graph.selectAll('rect.heatmap-segment').data( env.flatData.filter(dataFilter), function(d) { return d.group + d.label + d.timeRange[0];} ); timelines.exit() .transition().duration(env.transDuration) .style({ "fill-opacity": 0 }) .remove(); var newSegments = timelines.enter() .append('rect').attr("class", "heatmap-segment") .attr('rx', 1) .attr('ry', 1) .attr('x', env.graphW/2) .attr('y', env.graphH/2) .attr('width', 0) .attr('height', 0) .style({ fill: function(d) { return env.valScale(d.val); }, 'fill-opacity': 0 }) .on('mouseover.groupTooltip', env.groupTooltip.show) .on('mouseout.groupTooltip', env.groupTooltip.hide) .on('mouseover.lineTooltip', env.lineTooltip.show) .on('mouseout.lineTooltip', env.lineTooltip.hide) .on('mouseover.segmentTooltip', env.segmentTooltip.show) .on('mouseout.segmentTooltip', env.segmentTooltip.hide); newSegments .on("mouseover", function() { if ('disableHover' in env && env.disableHover) return; var hoverEnlarge = env.lineHeight*hoverEnlargeRatio; d3.select(this) .moveToFront() .transition().duration(70) .attr('x', function (d) { return env.xScale(d.timeRange[0])-hoverEnlarge/2; }) .attr('width', function (d) { return d3.max([1, env.xScale(d.timeRange[1])-env.xScale(d.timeRange[0])])+hoverEnlarge; }) .attr('y', function (d) { return env.yScale(d.group+"+&+"+d.label)-(env.lineHeight+hoverEnlarge)/2; }) .attr('height', env.lineHeight+hoverEnlarge) .style({ "fill-opacity": 1 }); }) .on("mouseout", function() { d3.select(this) .transition().duration(250) .attr('x', function (d) { return env.xScale(d.timeRange[0]); }) .attr('width', function (d) { return d3.max([1, env.xScale(d.timeRange[1])-env.xScale(d.timeRange[0])]); }) .attr('y', function (d) { return env.yScale(d.group+"+&+"+d.label)-env.lineHeight/2; }) .attr('height', env.lineHeight) .style({ "fill-opacity": .8 }); }); timelines.transition().duration(env.transDuration) .attr('x', function (d) { return env.xScale(d.timeRange[0]); }) .attr('width', function (d) { return d3.max([1, env.xScale(d.timeRange[1])-env.xScale(d.timeRange[0])]); }) .attr('y', function (d) { return env.yScale(d.group+"+&+"+d.label)-env.lineHeight/2; }) .attr('height', env.lineHeight) .style({ 'fill-opacity': .8 }); } } function y2Label(y) { function getIdxLine(grpData, idx) { return { 'group': grpData.group, 'label': grpData.lines[idx] }; } if (y==null) return y; var cntDwn = y; for (var i=0, len=env.completeStructData.length; i<len; i++) { if (env.completeStructData[i].lines.length>cntDwn) return getIdxLine(env.completeStructData[i], cntDwn); cntDwn-=env.completeStructData[i].lines.length; } // y larger than all lines, return last return getIdxLine(env.completeStructData[env.completeStructData.length-1], env.completeStructData[env.completeStructData.length-1].lines.length-1); } function label2Y(label, useIdxAfterIfNotFound) { useIdxAfterIfNotFound = useIdxAfterIfNotFound || false; var subIdxNotFound = useIdxAfterIfNotFound?0:1; if (label==null) return label; var idx=0; for (var i=0, lenI=env.completeStructData.length; i<lenI; i++) { var grpCmp = env.grpCmpFunction(label.group, env.completeStructData[i].group); if (grpCmp<0) break; if (grpCmp==0 && label.group==env.completeStructData[i].group) { for (var j=0, lenJ=env.completeStructData[i].lines.length; j<lenJ; j++) { var cmpRes = env.labelCmpFunction(label.label, env.completeStructData[i].lines[j]); if (cmpRes<0) { return idx+j-subIdxNotFound; } if (cmpRes==0 && label.label==env.completeStructData[i].lines[j]) { return idx+j; } } return idx+env.completeStructData[i].lines.length-subIdxNotFound; } idx+=env.completeStructData[i].lines.length; } return idx-subIdxNotFound; } function alphaNumCmp(a,b){ var alist = a.split(/(\d+)/), blist = b.split(/(\d+)/); (alist.length && alist[alist.length-1] == '') ? alist.pop() : null; // remove the last element if empty (blist.length && blist[blist.length-1] == '') ? blist.pop() : null; // remove the last element if empty for (var i = 0, len = Math.max(alist.length, blist.length); i < len;i++){ if (alist.length==i || blist.length==i) { // Out of bounds for one of the sides return alist.length - blist.length; } if (alist[i] != blist[i]){ // find the first non-equal part if (alist[i].match(/\d/)) // if numeric { return (+alist[i])-(+blist[i]); // compare as number } else { return (alist[i].toLowerCase() > blist[i].toLowerCase())?1:-1; // compare as string } } } return 0; } // Exposed functions chart.width = function(_) { if (!arguments.length) { return env.width } env.width = _; return chart; }; chart.leftMargin = function(_) { if (!arguments.length) { return env.margin.left } env.margin.left = _; return chart; }; chart.rightMargin = function(_) { if (!arguments.length) { return env.margin.right } env.margin.right = _; return chart; }; chart.topMargin = function(_) { if (!arguments.length) { return env.margin.top } env.margin.top = _; return chart; }; chart.bottomMargin = function(_) { if (!arguments.length) { return env.margin.bottom } env.margin.bottom = _; return chart; }; chart.maxHeight = function(_) { if (!arguments.length) { return env.maxHeight; } env.maxHeight = _; return chart; }; chart.throbberImg = function(_) { if (!arguments.length) { return env.throbberImg; } env.throbberImg = _; return chart; }; chart.dataDomain = function(_) { if (!arguments.length) { return [env.valScale.domain()[0], env.valScale.domain()[env.valScale.domain.length-1]] } var midVal = _[0] + (_[1]-_[0])/2; env.valScale.domain([_[0], midVal, _[1]]); return chart; }; chart.dataScale = function(_) { if (!arguments.length) { return env.valScale; } env.valScale = _; return chart; }; chart.getNLines = function() { return env.nLines; }; chart.getTotalNLines = function() { return env.totalNLines; }; chart.zoomX = function(_, redraw) { if (!arguments.length) { return env.zoomX; } env.zoomX = _; if (env.$elem) env.$elem.trigger('zoom', [_, null, redraw]); return chart; }; chart.zoomY = function(_, redraw) { if (!arguments.length) { return env.zoomY; } env.zoomY = _; if (env.$elem) env.$elem.trigger('zoom', [null, _, redraw]); return chart; }; chart.zoomYLabels = function(_, redraw) { if (!arguments.length) { return [y2Label(env.zoomY[0]), y2Label(env.zoomY[1])]; } return chart.zoomY([label2Y(_[0], true), label2Y(_[1], false)], redraw); }; chart.getVisibleStructure = function() { return env.structData; }; chart.minSegmentDuration = function (_) { if (!arguments.length) { return env.minSegmentDuration; } env.minSegmentDuration = _; return chart; }; chart.zDataLabel = function (_) { if (!arguments.length) { return env.zDataLabel; } env.zDataLabel = _; return chart; }; chart.zScaleLabel = function (_) { if (!arguments.length) { return env.zScaleLabel; } env.zScaleLabel = _; return chart; }; chart.sort = function(labelCmpFunction, grpCmpFunction) { if (labelCmpFunction==null) { labelCmpFunction = env.labelCmpFunction } if (grpCmpFunction==null) { grpCmpFunction = env.grpCmpFunction } env.labelCmpFunction = labelCmpFunction; env.grpCmpFunction = grpCmpFunction; env.completeStructData.sort(function(a, b) { return grpCmpFunction(a.group, b.group); }); for (var i=0, len=env.completeStructData.length;i<len;i++) { env.completeStructData[i].lines.sort(labelCmpFunction); } draw(); return chart; }; chart.sortAlpha = function(asc) { if (asc==null) { asc=true } var alphaCmp = function (a, b) { return alphaNumCmp(asc?a:b, asc?b:a); }; chart.sort(alphaCmp, alphaCmp); return chart; }; chart.sortChrono = function(asc) { if (asc==null) { asc=true } function buildIdx(accessFunction) { var idx = {}; for (var i= 0, len=env.completeFlatData.length; i<len; i++ ) { var key = accessFunction(env.completeFlatData[i]); if (idx.hasOwnProperty(key)) { continue; } var itmList = env.completeFlatData.filter(function(d) { return key == accessFunction(d); }); idx[key] = [ d3.min(itmList, function(d) { return d.timeRange[0]}), d3.max(itmList, function(d) { return d.timeRange[1]}) ]; } return idx; } var timeCmp = function (a, b) { var aT = a[1], bT=b[1]; if (!aT || !bT) return null; // One of the two vals is null if (aT[1].getTime()==bT[1].getTime()) { if (aT[0].getTime()==bT[0].getTime()) { return alphaNumCmp(a[0],b[0]); // If first and last is same, use alphaNum } return aT[0]-bT[0]; // If last is same, earliest first wins } return bT[1]-aT[1]; // latest last wins }; function getCmpFunction(accessFunction, asc) { return function(a, b) { return timeCmp(accessFunction(asc?a:b), accessFunction(asc?b:a)); } } var grpIdx = buildIdx(function(d) { return d.group; }); var lblIdx = buildIdx(function(d) { return d.label; }); var grpCmp = getCmpFunction(function(d) { return [d, grpIdx[d] || null]; }, asc); var lblCmp = getCmpFunction(function(d) { return [d, lblIdx[d] || null]; }, asc); chart.sort(lblCmp, grpCmp); return chart; }; chart.replaceData =function(newData, keepGraphStructure) { keepGraphStructure = keepGraphStructure || false; drawNewData(newData, keepGraphStructure); return chart; }; // True/False chart.enableOverview = function(_) { if (!arguments.length) { return env.enableOverview; } env.enableOverview = _; return chart; }; chart.overviewDomain = function(_) { if (!env.enableOverview) { return null; } if (!arguments.length) { return env.overviewArea.domainRange; } env.overviewArea.update(_, env.overviewArea.currentSelection); return chart; }; // True/False chart.animationsEnabled = function(_) { if (!arguments.length) { return (env.transDuration !=0); } env.transDuration = (_?700:0); return chart; }; // True/false (true = shows throbber and leaves it on permanently. false = automatic internal management) chart.forceThrobber = function(_) { if (!arguments.length) { return env.forceThrobber; } env.forceThrobber=_; if (env.forceThrobber && env.throbber) { env.throbber.show(); } return chart; }; chart.axisClickURL = function(_) { if (!arguments.length) { return env.axisClickURL; } env.axisClickURL = _; return chart; }; chart.getSvg = function() { return d3.select(env.svg.node().parentNode).html(); }; chart.onZoom = function(_) { if (!arguments.length) { return env.onZoom; } env.onZoom = _; return chart; }; chart.refresh = function() { draw(); return chart; }; return chart; } LoadingSorry, something went wrong. Reload?Sorry, we cannot display this file.Sorry, this file is invalid so it cannot be displayed.LoadingSorry, something went wrong. Reload?Sorry, we cannot display this file.Sorry, this file is invalid so it cannot be displayed.This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,224 @@ /** * Based on http://bl.ocks.org/mbostock/6232620 */ var TimeOverview = function(options, callback, context){ var timeMapper, timeTicker, brusherBucketLevelsMinutes, timeGrid, margins, width, hideIfLessThanSeconds, height, brush, xAxis, svg, groupOverview, timeUnitGrid, $this, margins, dom, labels, verticalLabels, format; $this = this; margins = options.margins; brusherBucketLevelsMinutes = options.granularityLevels; hideIfLessThanSeconds = options.hideIfLessThanSeconds; verticalLabels = (options.verticalLabels != null) ? options.verticalLabels : true; format = options.format || d3.time.format("%Y-%m-%d"); this.init = function(domElement, domainRange, currentSelection){ dom = domElement; if (domainRange && currentSelection){ this.render(domainRange, currentSelection); } }; this._afterInteraction = function(){ if (!d3.event.sourceEvent) return; var extent0, selectionPoints, boundedLeft, boundedRight, selectionPointsRounded, magneticEffect; extent0 = brush.extent(); boundedLeft = false; boundedRight = false; magneticEffect = 10 * 60 * 60 * 1000; // Magnetic effect selectionPoints = extent0; selectionPointsRounded = extent0.map(timeUnitGrid.round); if (selectionPoints[0].getTime() <= $this.domainRange[0].getTime() + magneticEffect){ selectionPoints[0] = $this.domainRange[0]; boundedLeft = true; } if (selectionPoints[1].getTime() >= $this.domainRange[1].getTime() - magneticEffect){ selectionPoints[1] = $this.domainRange[1]; boundedRight = true; } if (boundedLeft && !boundedRight){ selectionPoints[1] = selectionPointsRounded[1]; }else if (!boundedLeft && boundedRight){ selectionPoints[0] = selectionPointsRounded[0]; }else if (!boundedLeft && !boundedRight){ selectionPoints[0] = selectionPointsRounded[0]; selectionPoints[1] = selectionPointsRounded[1]; } if (selectionPoints[0] >= selectionPoints[1]) { selectionPoints[0] = timeUnitGrid.floor(extent0[0]); selectionPoints[1] = timeUnitGrid.ceil(extent0[1]); } // Apply magnetic feedback d3.select(this).transition() .call(brush.extent(selectionPoints)); callback.call(context, selectionPoints[0], selectionPoints[1]); }; this._duringInteraction = function(){ if (!d3.event.sourceEvent) return; var extent0, selectionPoints; extent0 = brush.extent(); // Magnetic effect selectionPoints = extent0.map(timeUnitGrid.round); if (selectionPoints[0] >= selectionPoints[1]) { selectionPoints[0] = timeUnitGrid.floor(extent0[0]); selectionPoints[1] = timeUnitGrid.ceil(extent0[1]); } // Apply magnetic feedback d3.select(this).transition() .call(brush.extent(selectionPoints)); }; this.render = function(domainRange, currentSelection){ var timeWindow; this.domainRange = domainRange; this.currentSelection = currentSelection; timeWindow = domainRange[1] - domainRange[0]; if (timeWindow < hideIfLessThanSeconds * 1000){ return false; } if (timeWindow < (brusherBucketLevelsMinutes.day * 60 * 1000)){ timeMapper = d3.time.day; timeTicker = d3.time.days; timeGrid = d3.time.hours; timeUnitGrid = d3.time.hour; }else if (timeWindow < (brusherBucketLevelsMinutes.week * 60 * 1000)){ timeMapper = d3.time.week; timeTicker = d3.time.weeks; timeGrid = d3.time.days; timeUnitGrid = d3.time.day; }else if (timeWindow < (brusherBucketLevelsMinutes.month * 60 * 1000)){ timeMapper = d3.time.month; timeTicker = d3.time.months; timeGrid = d3.time.weeks; timeUnitGrid = d3.time.week; }else{ timeMapper = d3.time.year; timeTicker = d3.time.years; timeGrid = d3.time.months; timeUnitGrid = d3.time.month; } width = options.width; height = options.height - margins.top - margins.bottom; xAxis = d3 .time .scale .utc() .domain(domainRange) .range([0, width]); brush = d3.svg.brush() .x(xAxis) .extent(currentSelection) //.on("brush", brushing) .on("brushend", $this._afterInteraction); svg = d3.select(dom) .append("svg") .attr("class", "brusher") .attr("width", width + margins.left + margins.right) .attr("height", height + margins.top + margins.bottom) .append("g") .attr("transform", "translate(" + margins.left + "," + margins.top + ")"); svg.append("rect") .attr("class", "grid-background") .attr("width", width) .attr("height", height); svg.append("g") .attr("class", "x grid") .attr("transform", "translate(0," + height + ")") .call(d3.svg.axis() .scale(xAxis) .orient("bottom") .ticks(timeGrid) .tickSize(-height) .tickFormat("")) .selectAll(".tick") .classed("minor", function(d) { return d.getHours(); }); svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(d3.svg.axis() .scale(xAxis) .orient("bottom") .ticks(timeTicker) .tickFormat(format) .tickPadding(0)) .selectAll("text") .attr("x", 6) .style("text-anchor", null); groupOverview = svg.append("g") .attr("class", "brush") .call(brush); groupOverview.selectAll("rect") .attr("height", height); labels = svg.selectAll("text") .style("text-anchor", "end"); if (verticalLabels){ labels .attr("dx", "-1.2em") .attr("dy", ".15em") .attr('transform', 'rotate(-65)'); } return true; }; this.update = function(domainRange, currentSelection){ if (this.domainRange == domainRange){ return this.updateSelection(currentSelection); }else{ d3.select(dom) .select(".brusher") .remove(); return this.render(domainRange, currentSelection); } }; this.updateSelection = function(currentSelection){ if (this.currentSelection != currentSelection){ groupOverview .call(brush.extent(currentSelection)); return true; } return false; }; }; -
vasturiano created this gist
May 10, 2016 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1 @@ -