d3-timelines
Version: 
A d3 v4 version of timeline. Can display single bar timelines, timelines with icons, and timelines that pan.
903 lines (783 loc) • 28.1 kB
JavaScript
(function (global, factory) {
	typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-axis'), require('d3-array'), require('d3-time-format'), require('d3-time'), require('d3-scale'), require('d3-selection'), require('d3-zoom')) :
	typeof define === 'function' && define.amd ? define(['exports', 'd3-axis', 'd3-array', 'd3-time-format', 'd3-time', 'd3-scale', 'd3-selection', 'd3-zoom'], factory) :
	(factory((global.d3 = global.d3 || {}),global.d3,global.d3,global.d3,global.d3,global.d3,global.d3,global.d3));
}(this, function (exports,d3Axis,d3Array,d3TimeFormat,d3Time,d3Scale,d3Selection,d3Zoom) { 'use strict';
	var timelines = function() {
			var DISPLAY_TYPES = ["circle", "rect"];
			var hover = function () {},
					mouseover = function () {},
					mouseout = function () {},
					click = function () {},
					scroll = function () {},
					labelFunction = function(label) { return label; },
					labelFloat = 0,  // floats up this many pixels
					navigateLeft = function () {},
					navigateRight = function () {},
					orient = "bottom",
					width = null,
					height = null,
					rowSeparatorsColor = null,
					backgroundColor = null,
					tickFormat = {
						format: d3TimeFormat.timeFormat("%I %p"),
						tickTime: d3Time.timeHour,
						tickInterval: 1,
						tickSize: 6,
						tickValues: null
					},
					allowZoom = true,
					axisBgColor = "white",
					chartData = {},
					colorCycle = d3Scale.scaleOrdinal(d3Scale.schemeCategory20),
					colorPropertyName = null,
					display = "rect",
					beginning = 0,
					labelMargin = 0,
					ending = 0,
					margin = {left: 30, right:30, top: 30, bottom:30},
					maxZoom = 5,
					stacked = false,
					rotateTicks = false,
					timeIsRelative = false,
					timeIsLinear = false,
					fullLengthBackgrounds = false,
					itemHeight = 20,
					itemMargin = 5,
					navMargin = 60,
					showTimeAxis = true,
					showAxisTop = false,
					showTodayLine = false,
					timeAxisTick = false,
					timeAxisTickFormat = {stroke: "stroke-dasharray", spacing: "4 10"},
					showTodayFormat = {marginTop: 25, marginBottom: 0, width: 1, color: colorCycle},
					showBorderLine = false,
					showBorderFormat = {marginTop: 25, marginBottom: 0, width: 1, color: colorCycle},
					showBorderLineClass = "timeline-border-line",
					showAxisHeaderBackground = false,
					showAxisNav = false,
					showAxisCalendarYear = false,
					xAxisClass = 'timeline-xAxis'
				;
			var appendTimeAxis = function(g, xAxis, yPosition) {
				if(showAxisHeaderBackground){ appendAxisHeaderBackground(g, 0, 0); }
				if(showAxisNav){ appendTimeAxisNav(g); }
				var axis = g.append("g")
					.attr("class", xAxisClass)
					.attr("transform", "translate(" + 0 + "," + yPosition + ")")
					.call(xAxis);
				return axis;
			};
			var appendTimeAxisCalendarYear = function (nav) {
				var calendarLabel = beginning.getFullYear();
				if (beginning.getFullYear() != ending.getFullYear()) {
					calendarLabel = beginning.getFullYear() + "-" + ending.getFullYear();
				}
				nav.append("text")
					.attr("transform", "translate(" + 20 + ", 0)")
					.attr("x", 0)
					.attr("y", 14)
					.attr("class", "calendarYear")
					.text(calendarLabel)
				;
			};
			var appendTimeAxisNav = function (g) {
				var timelineBlocks = 6;
				var leftNavMargin = (margin.left - navMargin);
				var incrementValue = (width - margin.left)/timelineBlocks;
				var rightNavMargin = (width - margin.right - incrementValue + navMargin);
				var nav = g.append('g')
						.attr("class", "axis")
						.attr("transform", "translate(0, 20)")
					;
				if(showAxisCalendarYear) { appendTimeAxisCalendarYear(nav); }
				nav.append("text")
					.attr("transform", "translate(" + leftNavMargin + ", 0)")
					.attr("x", 0)
					.attr("y", 14)
					.attr("class", "chevron")
					.text("<")
					.on("click", function () {
						return navigateLeft(beginning, chartData);
					})
				;
				nav.append("text")
					.attr("transform", "translate(" + rightNavMargin + ", 0)")
					.attr("x", 0)
					.attr("y", 14)
					.attr("class", "chevron")
					.text(">")
					.on("click", function () {
						return navigateRight(ending, chartData);
					})
				;
			};
			var appendAxisHeaderBackground = function (g, xAxis, yAxis) {
				g.insert("rect")
					.attr("class", "row-green-bar")
					.attr("x", xAxis)
					.attr("width", width)
					.attr("y", yAxis)
					.attr("height", itemHeight)
					.attr("fill", axisBgColor);
			};
			var appendTimeAxisTick = function(g, xAxis, maxStack) {
				g.append("g")
					.attr("class", "axis")
					.attr("transform", "translate(" + 0 + "," + (margin.top + (itemHeight + itemMargin) * maxStack) + ")")
					.attr(timeAxisTickFormat.stroke, timeAxisTickFormat.spacing)
					.call(xAxis.tickFormat("").tickSize(-(margin.top + (itemHeight + itemMargin) * (maxStack - 1) + 3), 0, 0));
			};
			var appendBackgroundBar = function (yAxisMapping, index, g, data, datum) {
				var greenbarYAxis = ((itemHeight + itemMargin) * yAxisMapping[index]) + margin.top;
				g.selectAll("svg")
					.data(data).enter()
					.insert("rect", ":first-child")
					.attr("class", "row-green-bar")
					.attr("x", fullLengthBackgrounds ? 0 : margin.left)
					.attr("width", fullLengthBackgrounds ? width : (width - margin.right - margin.left))
					.attr("y", greenbarYAxis)
					.attr("height", itemHeight)
					.attr("fill", backgroundColor instanceof Function ? backgroundColor(datum, index) : backgroundColor)
				;
			};
			var appendLabel = function (gParent, yAxisMapping, index, hasLabel, datum) {
				var fullItemHeight    = itemHeight + itemMargin;
				var rowsDown          = margin.top + (fullItemHeight/2) + fullItemHeight * (yAxisMapping[index] || 1);
				gParent.append("text")
					.attr("class", "timeline-label")
					.attr("transform", "translate(" + labelMargin + "," + rowsDown + ")")
					.text(hasLabel ? labelFunction(datum.label) : datum.id)
					.on("click", function (d, i) {
						console.log("label click!");
						var point = d3Selection.mouse(this);
						gParent.append("rect")
							.attr("id", "clickpoint")
							.attr("x", point[0])
							.attr("width", 10)
							.attr("height", itemHeight);
						click(d, index, datum, point, xScale.invert(point[0]));
					});
			};
			/*###########################
			####    START timelines    ###
			#############################*/
			function timelines (gParent) {
				var gParentSize = gParent.node().getBoundingClientRect(); // the svg size
				var gParentItem = d3Selection.select(gParent.node()); // the svg
				var g = gParent.append("g").attr("class", "container");
				var yAxisMapping = {},
					maxStack = 1,
					minTime = 0,
					maxTime = 0;
				setWidth();
				// check if the user wants relative time
				// if so, substract the first timestamp from each subsequent timestamps
				if(timeIsRelative){
					g.each(function (d, i) {
						var originTime = 0;
						d.forEach(function (datum, index) {
							datum.times.forEach(function (time, j) {
								if(index === 0 && j === 0){
									originTime = time.starting_time;               //Store the timestamp that will serve as origin
									time.starting_time = 0;                        //Set tahe origin
									time.ending_time = time.ending_time - originTime;     //Store the relative time (millis)
								}else{
									time.starting_time = time.starting_time - originTime;
									time.ending_time = time.ending_time - originTime;
								}
							});
						});
					});
				}
				// check how many stacks we're gonna need
				// do this here so that we can draw the axis before the graph
				if (stacked || ending === 0 || beginning === 0) {
					g.each(function (d, i) {
						d.forEach(function (datum, index) {
							// create y mapping for stacked graph
							if (stacked && Object.keys(yAxisMapping).indexOf(index) == -1) {
								yAxisMapping[index] = maxStack;
								maxStack++;
							}
							// figure out beginning and ending times if they are unspecified
							datum.times.forEach(function (time, i) {
								if(beginning === 0)
									if (time.starting_time < minTime || (minTime === 0 && timeIsRelative === false))
										minTime = time.starting_time;
								if(ending === 0)
									if (time.ending_time > maxTime)
										maxTime = time.ending_time;
							});
						});
					});
					if (ending === 0) {
						ending = maxTime;
					}
					if (beginning === 0) {
						beginning = minTime;
					}
				}
				var scaleFactor = (1/(ending - beginning)) * (width - margin.left - margin.right);
				function formatDays(d) {
						var days = Math.floor(d / 86400),
								hours = Math.floor((d - (days * 86400)) / 3600),
								minutes = Math.floor((d - (days * 86400) - (hours * 3600)) / 60),
								seconds = d - (days * 86400) - (hours * 3600) - (minutes * 60);
						var output = '';
						if (seconds) {
							output = seconds + 's';
						}
						if (minutes) {
								output = minutes + 'm ' + output;
						}
						if (hours) {
								output = hours + 'h ' + output;
						}
						if (days) {
								output = days + 'd ' + output;
						}
						return output;
				};
				var xScale;
				var xAxis;
				if (orient == "bottom") {
					xAxis = d3Axis.axisBottom();
				} else if (orient == "top") {
					xAxis = d3Axis.axisTop();
				}
				if (timeIsLinear) {
					xScale = d3Scale.scaleLinear()
						.domain([beginning, ending])
						.range([margin.left, width - margin.right]);
					xAxis.scale(xScale)
						.tickFormat(formatDays)
						.tickValues(d3Array.range(0, ending, 86400));
				} else {
						xScale = d3Scale.scaleTime()
							.domain([beginning, ending])
							.range([margin.left, width - margin.right]);
						xAxis.scale(xScale)
							.tickFormat(tickFormat.format)
							.tickSize(tickFormat.tickSize);
				}
				if (tickFormat.tickValues !== null) {
					xAxis.tickValues(tickFormat.tickValues);
				} else {
					xAxis.tickArguments(tickFormat.numTicks || [tickFormat.tickTime, tickFormat.tickInterval]);
				}
				// append a view for zoom/pan support
				var view = g.append("g")
					.attr("class", "view");
				// draw the chart
				g.each(function(d, i) {
					chartData = d;
					d.forEach( function(datum, index){
						var data = datum.times;
						data.forEach(function(d) { d.name = datum.name });
						var hasLabel = (typeof(datum.label) != "undefined");
						// issue warning about using id per data set. Ids should be individual to data elements
						if (typeof(datum.id) != "undefined") {
							console.warn("d3Timeline Warning: Ids per dataset is deprecated in favor of a 'class' key. Ids are now per data element.");
						}
						if (backgroundColor) { appendBackgroundBar(yAxisMapping, index, g, data, datum); }
						view.selectAll("svg")
							.data(data).enter()
							.append(function(d, i) {
										return document.createElementNS(d3Selection.namespaces.svg, "display" in d? d.display:display);
							})
							.attr("x", getXPos)
							.attr("y", getStackPosition)
							.attr("width", function (d, i) {
								return (d.ending_time - d.starting_time) * scaleFactor;
							})
							.attr("cy", function(d, i) {
									return getStackPosition(d, i) + itemHeight/2;
							})
							.attr("cx", getXPos)
							.attr("r", itemHeight / 2)
							.attr("height", itemHeight)
							.style("fill", function(d, i){
								var dColorPropName;
								if (d.color) return d.color;
								if( colorPropertyName ){
									dColorPropName = d[colorPropertyName];
									if ( dColorPropName ) {
										return colorCycle( dColorPropName );
									} else {
										return colorCycle( datum[colorPropertyName] );
									}
								}
								return colorCycle(index);
							})
							.on("mousemove", function (d, i) {
								hover(d, index, datum, i);
							})
							.on("mouseover", function (d, i) {
								mouseover(d, i, datum, i);
							})
							.on("mouseout", function (d, i) {
								mouseout(d, i, datum, i);
							})
							.on("click", function (d, i) {
								var point = d3Selection.mouse(this);
								var selectedRect = d3Selection.select(this).node();
								var selectorLabel = "text#" + selectedRect.id + '.textnumbers';
								var selectedLabel = d3Selection.select(selectorLabel).node();
								click(d, index, datum, selectedLabel, selectedRect, xScale.invert(point[0]));
							})
							.attr("class", function (d, i) {
								return datum.class ? "timelineSeries_"+datum.class : "timelineSeries_"+index;
							})
							.attr("id", function(d, i) {
								// use deprecated id field
								if (datum.id && !d.id) {
									return 'timelineItem_'+datum.id;
								}
								return d.id ? d.id : "timelineItem_"+index+"_"+i;
							})
						;
						// appends the labels to the boxes - DAY/HOUR LABEL
						view.selectAll("svg")
							.data(data).enter()
							.append("text")
							.attr("class", "textlabels")
							.attr("id", function(d) { return d.id })
							.attr("x", function(d, i) { return getXTextPos(d, i, d.label, '.textlabels')})
							.attr("y", (getStackTextPosition() - labelFloat))
							.text(function(d) {
								return d.label;
							})
							.on("click", function(d, i){
								// when clicking on the label, call the click for the rectangle with the same id
								var point = d3Selection.mouse(this);
								var id = this.id;
								var labelSelector = "text#" + id + ".textnumbers";
								var selectedLabel = d3Selection.select(labelSelector).node();
								var selector = "rect#" + id;
								var selectedRect = d3Selection.select(selector).node();
								click(d, index, datum, selectedLabel, selectedRect, xScale.invert(point[0]));
							})
						;
						// appends the NUMBER LABEL
						view.selectAll("svg").data(data).enter()
							.filter(function(d) { return d.labelNumber !== undefined; })
							.append("text")
							.attr("class", "textnumbers")
							.attr("id", function(d) { return d.id })
							.attr("x", function(d, i) { return getXTextPos(d, i, d.labelNumber, '.textnumbers')})
							.attr("y", getStackTextPosition)
							.text(function(d) {
								return d.labelNumber;
							})
							.on("click", function(d, i){
								// when clicking on the label, call the click for the rectangle with the same id
								var point = d3Selection.mouse(this);
								var id = this.id;
								var selectedLabel = d3Selection.select(this).node();
								var selector = "rect#" + id;
								var selectedRect = d3Selection.select(selector).node();
								click(d, index, datum, selectedLabel, selectedRect, xScale.invert(point[0]));
							})
						;
						if (rowSeparatorsColor) {
							var lineYAxis = ( itemHeight + itemMargin / 2 + margin.top + (itemHeight + itemMargin) * yAxisMapping[index]);
							gParent.append("svg:line")
								.attr("class", "row-separator")
								.attr("x1", 0 + margin.left)
								.attr("x2", width - margin.right)
								.attr("y1", lineYAxis)
								.attr("y2", lineYAxis)
								.attr("stroke-width", 1)
								.attr("stroke", rowSeparatorsColor);
						}
						// add the label
						if (hasLabel) { appendLabel(gParent, yAxisMapping, index, hasLabel, datum); }
						if (typeof(datum.icon) !== "undefined") {
							gParent.append("image")
								.attr("class", "timeline-label")
								.attr("transform", "translate("+ 0 +","+ (margin.top + (itemHeight + itemMargin) * yAxisMapping[index])+")")
								.attr("xlink:href", datum.icon)
								.attr("width", margin.left)
								.attr("height", itemHeight);
						}
						function getStackPosition(d, i) {
							if (stacked) {
								return margin.top + (itemHeight + itemMargin) * yAxisMapping[index];
							}
							return margin.top;
						}
						function getStackTextPosition(d, i) {
							if (stacked) {
								return margin.top + (itemHeight + itemMargin) * yAxisMapping[index] + itemHeight * 0.75;
							}
							return margin.top + itemHeight * 0.75;
						}
					});
				});
				var belowLastItem = (margin.top + (itemHeight + itemMargin) * maxStack);
				var aboveFirstItem = margin.top;
				var timeAxisYPosition = showAxisTop ? aboveFirstItem : belowLastItem;
				var gX;
				if (showTimeAxis) { gX = appendTimeAxis(g, xAxis, timeAxisYPosition); }
				if (timeAxisTick) { appendTimeAxisTick(g, xAxis, maxStack); }
				if (width > gParentSize.width) { // only if the scrolling should be allowed
					var move = function() {
						g.select(".view")
						.attr("transform", "translate(" + d3Selection.event.transform.x + ",0)"
															 + "scale(" + d3Selection.event.transform.k + " 1)");
						g.selectAll(".timeline-xAxis")
							.attr("transform", function(d) {
								 return "translate(" + d3Selection.event.transform.x + ", " + timeAxisYPosition + ")"
											+ "scale(" + d3Selection.event.transform.k + " 1)";
							});
						var new_xScale = d3Selection.event.transform.rescaleX(xScale);
						g.selectAll('.timeline-xAxis').call(function(d) { xAxis.scale(new_xScale); });
						var xpos = -d3Selection.event.transform.x;
						scroll(xpos, xScale);
					};
				};
				var zoom = d3Zoom.zoom()
					.scaleExtent([0, maxZoom]) // max zoom defaults to 5
					.translateExtent([[0, 0], [width, 0]]) // [x0, y0], [x1, y1] don't allow translating y-axis
					.on("zoom", move);
				gParent
					.classed("scrollable", true)
					.call(zoom);
				if (! allowZoom) {
					g.on("wheel", function() {
						d3Selection.event.preventDefault();
						d3Selection.event.stopImmediatePropagation();
					});
					g.on("dblclick.zoom", function() {
						d3Selection.event.preventDefault();
						d3Selection.event.stopImmediatePropagation();
					});
				}
				if (rotateTicks) {
					g.selectAll(".tick text")
						.attr("transform", function(d) {
							return "rotate(" + rotateTicks + ")translate("
															 + (this.getBBox().width / 2 + 10) + "," // TODO: change this 10
															 + this.getBBox().height / 2 + ")";
						});
				}
				// use the size of the elements added to the timeline to set the height
				//var gSize = g._groups[0][0].getBoundingClientRect();
				var gSize = g.node().getBoundingClientRect();
				setHeight();
				if (showBorderLine) {
					g.each(function (d, i) {
						d.forEach(function (datum) {
							var times = datum.times;
							times.forEach(function (time) {
								appendLine(xScale(time.starting_time), showBorderFormat, showBorderLineClass);
								appendLine(xScale(time.ending_time), showBorderFormat, showBorderLineClass);
							});
						});
					});
				}
				if (showTodayLine) {
					var todayLine = xScale(new Date());
					appendLine(todayLine, showTodayFormat);
				}
				function getXPos(d, i) {
					return margin.left + (d.starting_time - beginning) * scaleFactor;
				}
				function getTextWidth(text, font) {
						// re-use canvas object for better performance
						var canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement("canvas"));
						var context = canvas.getContext("2d");
						context.font = font;
						var metrics = context.measureText(text);
						return metrics.width;
				}
				function getXTextPos(d, i, text, style) {
					var width = 0;
					if (d.ending_time) {
						width = (((d.ending_time - d.starting_time) / 2) * scaleFactor);
					}
					if (text && style) {
						// get the style data for the class selector pass in
						var textl = getComputedStyle(document.querySelector(style));
						// create a fontsize fontfamily string - 12pt Graphik
						var fontInfo = textl.fontSize + ' ' + textl.fontFamily;
						// calculate the width of the text in that fontsize
						var tl = getTextWidth(text, fontInfo);
						// subtract half of the text length from the xPosition to keep the text centered
						var textLength = tl / 2;
						var xPosition = margin.left + ((d.starting_time - beginning) * scaleFactor) + width - textLength;
						return xPosition;
					} else {
						return margin.left + (d.starting_time - beginning) * scaleFactor + 5;
					}
				}
				function setHeight() {
					if (!height && !gParentSize.height) {
						if (itemHeight) {
							// set height based off of item height
							height = gSize.height + gSize.top - gParentSize.top;
							// set bounding rectangle height
							d3Selection.select(gParent).node().attr("height", height);
							//select(view).node().attr("height", height);
						} else {
							throw "height of the timeline is not set";
						}
					} else {
						if (!height) {
							height = gParentSize.height;
						} else {
							gParentItem.node().attr("height", height);
							//view.node().attr("height", height);
						}
					}
				}
				function setWidth() {
					if (!width && !gParentSize.width) {
						try {
							width = gParentItem.node().attr("width");
							if (!width) {
								throw "width of the timeline is not set. As of Firefox 27, timeline().with(x) needs to be explicitly set in order to render";
							}
						} catch (err) {
							console.log( err );
						}
					} else if (!width && gParentSize.width) {
						try {
							width = gParentSize.width;
						} catch (err) {
							console.log( err );
						}
					}
					// if both are set, do nothing
				}
				function appendLine(lineScale, lineFormat, lineClass) {
					lineClass = lineClass || "timeline-line";
					view.append("svg:line")
						.attr("x1", lineScale)
						.attr("y1", lineFormat.marginTop)
						.attr("x2", lineScale)
						.attr("y2", height - lineFormat.marginBottom)
						.attr("class", lineClass)
						.style("stroke", lineFormat.color)//"rgb(6,120,155)"
						.style("stroke-width", lineFormat.width);
				}
			}
			// SETTINGS
			timelines.margin = function (p) {
				if (!arguments.length) return margin;
				margin = p;
				return timelines;
			};
			timelines.orient = function (orientation) {
				if (!arguments.length) return orient;
				orient = orientation;
				return timelines;
			};
			timelines.itemHeight = function (h) {
				if (!arguments.length) return itemHeight;
				itemHeight = h;
				return timelines;
			};
			timelines.itemMargin = function (h) {
				if (!arguments.length) return itemMargin;
				itemMargin = h;
				return timelines;
			};
			timelines.navMargin = function (h) {
				if (!arguments.length) return navMargin;
				navMargin = h;
				return timelines;
			};
			timelines.height = function (h) {
				if (!arguments.length) return height;
				height = h;
				return timelines;
			};
			timelines.width = function (w) {
				if (!arguments.length) return width;
				width = w;
				return timelines;
			};
			timelines.display = function (displayType) {
				if (!arguments.length || (DISPLAY_TYPES.indexOf(displayType) == -1)) return display;
				display = displayType;
				return timelines;
			};
			timelines.labelFormat = function(f) {
				if (!arguments.length) return labelFunction;
				labelFunction = f;
				return timelines;
			};
			timelines.tickFormat = function (format) {
				if (!arguments.length) return tickFormat;
				tickFormat = format;
				return timelines;
			};
			timelines.allowZoom = function (zoomSetting) {
				if (!arguments.length) return allowZoom;
				allowZoom = zoomSetting;
				return timelines;
			};
			timelines.maxZoom = function (max) {
				if (!arguments.length) return maxZoom;
				maxZoom = max;
				return timelines;
			};
			timelines.hover = function (hoverFunc) {
				if (!arguments.length) return hover;
				hover = hoverFunc;
				return timelines;
			};
			timelines.mouseover = function (mouseoverFunc) {
				if (!arguments.length) return mouseover;
				mouseover = mouseoverFunc;
				return timelines;
			};
			timelines.mouseout = function (mouseoutFunc) {
				if (!arguments.length) return mouseout;
				mouseout = mouseoutFunc;
				return timelines;
			};
			timelines.click = function (clickFunc) {
				if (!arguments.length) return click;
				click = clickFunc;
				return timelines;
			};
			timelines.scroll = function (scrollFunc) {
				if (!arguments.length) return scroll;
				scroll = scrollFunc;
				return timelines;
			};
			timelines.colors = function (colorFormat) {
				if (!arguments.length) return colorCycle;
				colorCycle = colorFormat;
				return timelines;
			};
			timelines.beginning = function (b) {
				if (!arguments.length) return beginning;
				beginning = b;
				return timelines;
			};
			timelines.ending = function (e) {
				if (!arguments.length) return ending;
				ending = e;
				return timelines;
			};
			timelines.labelMargin = function (m) {
				if (!arguments.length) return labelMargin;
				labelMargin = m;
				return timelines;
			};
			timelines.labelFloat = function (f) {
				if (!arguments.length) return labelFloat;
				labelFloat = f;
				return timelines;
			};
			timelines.rotateTicks = function (degrees) {
				if (!arguments.length) return rotateTicks;
				rotateTicks = degrees;
				return timelines;
			};
			timelines.stack = function () {
				stacked = !stacked;
				return timelines;
			};
			timelines.relativeTime = function() {
				timeIsRelative = !timeIsRelative;
				return timelines;
			};
			timelines.linearTime = function() {
				timeIsLinear = !timeIsLinear;
				return timelines;
			};
			timelines.showBorderLine = function () {
				showBorderLine = !showBorderLine;
				return timelines;
			};
			timelines.showBorderFormat = function(borderFormat) {
				if (!arguments.length) return showBorderFormat;
				showBorderFormat = borderFormat;
				return timelines;
			};
			// CSS class for the lines added by showBorder
			timelines.showBorderLineClass = function(borderClass) {
				if (!arguments.length) return showBorderLineClass;
				showBorderLineClass = borderClass;
				return timelines;
			};
			timelines.showToday = function () {
				showTodayLine = !showTodayLine;
				return timelines;
			};
			timelines.showTodayFormat = function(todayFormat) {
				if (!arguments.length) return showTodayFormat;
				showTodayFormat = todayFormat;
				return timelines;
			};
			timelines.colorProperty = function(colorProp) {
				if (!arguments.length) return colorPropertyName;
				colorPropertyName = colorProp;
				return timelines;
			};
			timelines.rowSeparators = function (color) {
				if (!arguments.length) return rowSeparatorsColor;
				rowSeparatorsColor = color;
				return timelines;
			};
			timelines.background = function (color) {
				if (!arguments.length) return backgroundColor;
				backgroundColor = color;
				return timelines;
			};
			timelines.showTimeAxis = function () {
				showTimeAxis = !showTimeAxis;
				return timelines;
			};
			timelines.showAxisTop = function () {
				showAxisTop = !showAxisTop;
				return timelines;
			};
			timelines.showAxisCalendarYear = function () {
				showAxisCalendarYear = !showAxisCalendarYear;
				return timelines;
			};
			timelines.showTimeAxisTick = function () {
				timeAxisTick = !timeAxisTick;
				return timelines;
			};
			timelines.fullLengthBackgrounds = function () {
				fullLengthBackgrounds = !fullLengthBackgrounds;
				return timelines;
			};
			timelines.showTimeAxisTickFormat = function(format) {
				if (!arguments.length) return timeAxisTickFormat;
				timeAxisTickFormat = format;
				return timelines;
			};
			timelines.showAxisHeaderBackground = function(bgColor) {
				showAxisHeaderBackground = !showAxisHeaderBackground;
				if(bgColor) { (axisBgColor = bgColor); }
				return timelines;
			};
			// CSS class for the x-axis
			timelines.xAxisClass = function (axisClass) {
				if (!arguments.length) return xAxisClass;
				xAxisClass = axisClass;
				return timelines;
			};
			timelines.navigate = function (navigateBackwards, navigateForwards) {
				if (!arguments.length) return [navigateLeft, navigateRight];
				navigateLeft = navigateBackwards;
				navigateRight = navigateForwards;
				showAxisNav = !showAxisNav;
				return timelines;
			};
			timelines.version = function() {
				return "1.0.0";
			};
			return timelines;
	};
	exports.timelines = timelines;
	Object.defineProperty(exports, '__esModule', { value: true });
}));