Repository: denisemauldin/d3-timeline
Branch: master
Commit: 2d821ec5b12c
Files: 17
Total size: 107.2 KB
Directory structure:
gitextract_dkg6lrp9/
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── dist/
│ └── d3-timelines.js
├── examples/
│ ├── days.html
│ ├── days_scrollable.html
│ ├── example.html
│ ├── scrollable.html
│ ├── sp500.csv
│ ├── timeline.html
│ └── timelineStacked.html
├── index.js
├── package.json
├── rollup.config.js
├── src/
│ └── timelines.js
└── test/
└── timeline-test.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
.DS_Store
build/
node_modules
npm-debug.log
*.swp
================================================
FILE: .npmignore
================================================
build/*.zip
test/
================================================
FILE: LICENSE
================================================
Copyright 2017, Denise Mauldin
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the author nor the names of contributors may be used to
endorse or promote products derived from this software without specific prior
written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: README.md
================================================
# d3-timeline
A d3 v4 version of d3-timeline
Get something that looks like

for a dataset that looks like
```js
var testData = [
{label: "person a", times: [
{"starting_time": 1355752800000, "ending_time": 1355759900000},
{"starting_time": 1355767900000, "ending_time": 1355774400000}]},
{label: "person b", times: [
{"starting_time": 1355759910000, "ending_time": 1355761900000}]},
{label: "person c", times: [
{"starting_time": 1355761910000, "ending_time": 1355763910000}]}
];
```
with a call that looks like
```js
var chart = d3.timelines();
var svg = d3.select("#timeline1").append("svg").attr("width", 500)
.datum(testData).call(chart);
```
Works with circles. In case the rectangular edges are too pointy.

Combine rectangles and circles to your liking

by adding a ``display`` key to the data:
```js
var rectAndCircleTestData = [
{times: [{"starting_time": 1355752800000, "display": "circle"},
{"starting_time": 1355767900000, "ending_time": 1355774400000}]},
{times: [{"starting_time": 1355759910000, "display":"circle"}, ]},
{times: [{"starting_time": 1355761910000, "ending_time": 1355763910000}]}
];
```
Make a pseudo-gantt chart thingy

with icons

For your *really* long charts, it supports scrolling. It can even do things on hover, click, and scroll for when someone accidentally interacts with your chart.
You can also specify an optional `class` key in the data dictionary. This will label each timeline rectangle item within the visualization with the following id property: "timelineItem_"+class. For example, this data
```js
var testData = [
{class: "pA", label: "person a", times: [
{"starting_time": 1355752800000, "ending_time": 1355759900000},
{"starting_time": 1355767900000, "ending_time": 1355774400000}]},
{class: "pB", label: "person b", times: [
{"starting_time": 1355759910000, "ending_time": 1355761900000}]},
{class: "pC", label: "person c", times: [
{"starting_time": 1355761910000, "ending_time": 1355763910000}]}
];
```
would generate `<rect>` with the following classes: `timelineItem_pA`,`timelineItem_pB`,`timelineItem_pC`. This means that you can dynamically change the visual properties of each timeline item using JQuery like so: `$(".timelineSeries_pA").css("fill","blue");`.
If no custom class is provided, the class attribute will be generated sequentially in the order they have been provided in. e.g.: `timelineSeries_0`.
Also optional is an `id` field per data element.
```js
var testData = [
{label: "person a", times: [
{"starting_time": 1355752800000, "ending_time": 1355759900000, "id": "A1"},
{"starting_time": 1355767900000, "ending_time": 1355774400000, "id": "A2"}]}
];
```
This generates `<rect>`s with `A1` and `A2` as ids. If no id is provided, the id attribute will be generated sequentially in the order they have been provided in. e.g.: `timelineItem_0_0`.
Look at the [examples](examples/example.html) for more details.
## Data formats
The simplest data format only requires `starting_time` and `ending_time` for each series of data.
```js
[
{times: [
{"starting_time": 1355752800000, "ending_time": 1355759900000},
{"starting_time": 1355767900000, "ending_time": 1355774400000}]},
{times: [
{"starting_time": 1355759910000, "ending_time": 1355761900000}]}
];
```
`label` can be added if you want names by each series of data. In order for this to properly show up, the timeline needs to be called with .stack()
```js
[
{label: "person a", times: [
{"starting_time": 1355752800000, "ending_time": 1355759900000},
{"starting_time": 1355767900000, "ending_time": 1355774400000}]},
{label: "person b", times: [
{"starting_time": 1355759910000, "ending_time": 1355761900000}]}
];
```
`icon` can be added if you want icons by each series of data. In order for this to properly show up, the timeline needs to be called with .stack(). Icons and labels can also be mixed in together.
```js
[
{icon: "path/to/img.png", times: [
{"starting_time": 1355752800000, "ending_time": 1355759900000},
{"starting_time": 1355767900000, "ending_time": 1355774400000}]},
{label: "person b", times: [
{"starting_time": 1355759910000, "ending_time": 1355761900000}]}
];
```
### 'times' elements array
Each item in the times array must have `starting_time` and `ending_time`. You could also specify optional `color` or `label` elements within a times item, as well as a [property mapped to a color](#colorpropertypropertyname).
```js
[
{label: "person a", times: [{"color":"green", "label":"Weeee", "starting_time": 1355752800000, "ending_time": 1355759900000}, {"color":"blue", "label":"Weeee", "starting_time": 1355767900000, "ending_time": 1355774400000}]},
{label: "person b", times: [{"color":"pink", "label":"Weeee", "starting_time": 1355759910000, "ending_time": 1355761900000}, ]},
{label: "person c", times: [{"color":"yellow", "label":"Weeee", "starting_time": 1355761910000, "ending_time": 1355763910000}]}
];
```
## Method Calls
All methods that take in arguments return the current settings if no argument is passed.
### .width(width)
sets the width of the timeline. If the width of the timeline is longer than the width of the svg object, the timeline will automatically scroll. The width of the timeline will default to the width of the svg if width is not set.
### .height(height)
sets the height of the timeline. The height of the timeline will be automatically calculated from the height of each item if height is not set on the timeline or the svg.
### .itemHeight(height)
sets the height of the data series in the timeline. Defaults to 20px.
### .itemMargin(height)
sets the margin between the data series in the timeline. Defaults to 5px.
### .margin({left: , right: , top: , bottom: })
sets the margin of the entire timeline inside of the svg. Defaults to 30px all around.
### .display("circle" | "rect")
Displays the data series as either circles or rectangles. Defaults to "rect".
### .labelFormat(callback)
registers a function to be called when the text for the label needs to
be generated. Useful if your label looks like this:
```
{
en: "my label",
fr: "mon étiquette"
}
```
The callback function is passed the whatever the datum.label returns, so
in this case it would be the object above. So the labelFormat might
look something like this:
```js
.labelFormat(function(label){ return label[currentLocale];})
```
### .tickFormat({format: , tickTime: , tickInterval: , tickSize: , numTicks: , tickValues})
sets the formatting of the ticks in the timeline. Defaults to
```js
{
format: d3.time.format("%I %p"),
tickTime: d3.time.hours,
tickInterval: 1,
tickSize: 6
}
```
Tick interval/values can be set with:
- ``tickTime`` and ``tickInterval``
- ``numTicks`` and ``tickInterval``
- ``tickValues``
### .rotateTicks(degrees)
sets the degree of rotation of the tickmarks. Defaults to no rotation (0 degrees).
### .orient("bottom" | "top")
sets the placement of the axis. Defaults to bottom.
### .colors(callback)
sets the d3 color scale the data series in the timeline. Defaults to `d3.scale.category20()`.
### .colorProperty(propertyName)
sets the data item property name that maps your data items to your color scale. For example if you set your chart's `colors()` and `colorsProperty()` as follows:
```js
var colorScale = d3.scale.ordinal().range(['#6b0000','#ef9b0f','#ffee00'])
.domain(['apple','orange','lemon']);
var chart = d3.timelines()
.colors( colorScale )
.colorProperty('fruit');
```
And pass this dataset:
```js
var testData = [
{label: "fruit 1", fruit: "orange", times: [
{"starting_time": 1355759910000, "ending_time": 1355761900000}]},
{label: "fruit 2", fruit: "apple", times: [
{"starting_time": 1355752800000, "ending_time": 1355759900000},
{"starting_time": 1355767900000, "ending_time": 1355774400000}]},
{label: "fruit3", fruit: "lemon", times: [
{"starting_time": 1355761910000, "ending_time": 1355763910000}]}
];
```
Your chart's bar colors will be determined based on the value of the fruit property:

You can also set the color property for a specific time object:
```js
var testData = [
{label: "fruit 2", fruit: "apple", times: [
{fruit: "orange", "starting_time": 1355752800000, "ending_time": 1355759900000},
{"starting_time": 1355767900000, "ending_time": 1355774400000},
{fruit: "lemon", "starting_time": 1355774400000, "ending_time": 1355775500000}]}
];
```
Properties set in the time object will override the property set for the series:

### .beginning(date)
sets the time that the timeline should start. If `beginning` and `ending` are not set, the timeline will calculate it based off of the smallest and largest times.
### .ending(date)
sets the time that the timeline should end. If `beginning` and `ending` are not set, the timeline will calculate it based off of the smallest and largest times.
### .stack()
Takes in no arguments. Toggles the stacking/unstacking of data series in the timeline. Needs to be true in order for icons and labels to show up properly.
### .relativeTime()
Takes in no arguments. Toggles the calculation and use of relative timestamps. The origin of the timeline will be set to 0 and the starting_time of the first data dictionary in the data array will be subtracted from every subsequent timestamp.
### .showToday()
Takes in no arguments. Toggles a vertical line showing the current Date.now() time. Uses showTodayFormat for the line formatting.
### .showTodayFormat({marginTop: , marginBottom: , width: , color: })
Sets the formatting of the showToday line. Color cycle can also be of the format `rgb(x, y, z)`.
### .showBorderLine()
Takes in no arguments. Toggles a vertical line showing the borders of one specific timeline. Uses showBorderFormat for the line formatting.
### .showBorderFormat({marginTop: , marginBottom:, width: , color: })
Sets the formatting of the showBorder line. Color cycle can also be of the format `rgb(x, y, z)`.
### .showTimeAxis()
Takes in no arguments. Toggles the visibility of the time axis.
### .showTimeAxisTick()
Takes in no arguments. Shows tick marks along the X axis according to the arguments for `showTimeAxisTickFormat`. Useful for datasets with a lot of stacked elements.

### .showTimeAxisTickFormat(format)
Format for `showTimeAxisTick`. Defaults to ```{stroke: "stroke-dasharray", spacing: "4 10"}```.
Defaults to
```js
{
marginTop: 25,
marginBottom: 0,
width: 1,
color: colorCycle
}
```
### .rowSeparators(color)
Sets the display of horizontal lines betweens rows.
### .background(color)
Sets the background of the rows. Useful for creating a continuous effect when there are gaps in your data.
### .hover(callback)
takes in a callback called on mousemove of the timeline data. Example
```js
d3.timelines()
.hover(function (d, i, datum) {
// d is the current rendering object
// i is the index during d3 rendering
// datum is the data object
});
```
### .xAxisClass
Default is timeline-xAxis, but can be used to set any class.
### .allowZoom
Default is true. Takes a boolean. If set to false, does not allow zooming.
### .mouseover(callback)
takes in a callback called on mouseover of the timeline data. Example
```js
d3.timelines()
.mouseover(function (d, i, datum) {
// d is the current rendering object
// i is the index during d3 rendering
// datum is the data object
});
```
### .mouseout(callback)
takes in a callback called on mouseout of the timeline data. Example
```js
d3.timelines()
.mouseout(function (d, i, datum) {
// d is the current rendering object
// i is the index during d3 rendering
// datum is the data object
});
```
### .click(callback)
takes in a callback called on click of the timeline data. Example
```js
d3.timelines()
.click(function (d, i, datum) {
// d is the current rendering object
// i is the index during d3 rendering
// datum is the data object
});
```
### .scroll(callback)
takes in a callback called on scroll of the timeline data. Example
```js
d3.timelines()
.scroll(function (x, scale) {
// x is the current position of the scroll
// scale is the scale of the axis used
});
```
## Build
To build:
- `npm install`
- `npm test`
- copy the build/d3-timelines.js to dist/d3-timelines.js
## Previous work
Inspired by jiahuang - https://github.com/jiahuang/d3-timeline
## License
MIT
================================================
FILE: dist/d3-timelines.js
================================================
(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 });
}));
================================================
FILE: examples/days.html
================================================
<!doctype html>
<html>
<head>
<script src="https://code.jquery.com/jquery-latest.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script>
<script src="../dist/d3-timelines.js"></script>
<style type="text/css">
.timeline-xAxis path,
.timeline-xAxis line {
shape-rendering: crispEdges;
}
.timeline-xAxis text {
display: none;
}
.textlabels {
font-size: 12pt;
font-family: Helvetica;
}
.textnumbers {
font-size: 18pt;
font-family: Helvetica;
}
.timeline-label {
font-family: sans-serif;
font-size: 12px;
}
.coloredDiv {
height:20px; width:20px; float:left;
}
</style>
<script type="text/javascript">
window.onload = function() {
formatDay = function(d) {
var days = Math.floor(d / 86400) +1,
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;
}
if (output === '') {
output = 'Start';
}
return output;
};
// beginning has to be > 0
/* var testData = [
// {name: "Hydrocortisone", times: [{"starting_time": 1, "ending_time": 691201, "concentration": "10.0 uM"}]}, // 0-8 days (8)
// {name: "SCF", times: [{"starting_time": 691201, "ending_time": 1382401, "concentration": "100.0 ng/mL"}]}, // 8-16 days (8)
{name: "IL3", times: [
{"starting_time": 1382401, "ending_time": 2073601, "concentration": "5.0 ng/mL"}, // 16-24 days (8)
{"starting_time": 2764801, "ending_time": 3024001, "concentration": "5.0 ng/mL"}, // 32-35 days, (3) after Epo
]}, // 8 days, 3 days
{name: "Epo", times: [{"starting_time": 2073601, "ending_time": 2764801, "concentration": "3.0 Ul/ml"}]}, // 24-32 days (8)
// {name: "Epo Feeder layer", times: [{"starting_time": 3024001, "ending_time": 3369601, "concentration": "3.0 Ul/ml"}]}, // 35-39 days (4)
// {name: "Epo chemical Feeder layer", times: [{"starting_time": 3369601, "ending_time": 3715201, "concentration": "3.0 Ul/ml"}]} // 39-43 days (4)
];
*/
var testData = [
{times:[
{"id": "day1", "label": "DAY", "labelNumber": "1", "starting_time": 1, "ending_time": 86400},
{"id": "day2", "label": "DAY", "labelNumber": "2", "starting_time": 86400, "ending_time": 86400*2},
{"id": "day3", "label": "DAY", "labelNumber": "3", "starting_time": 86400*2, "ending_time": 86400*3},
{"id": "day4", "label": "DAY", "labelNumber": "4", "starting_time": 86400*3, "ending_time": 86400*4},
{"id": "day5", "label": "DAY", "labelNumber": "5", "starting_time": 86400*4, "ending_time": 86400*5}
]},
];
var width = 400;
// d3 v3
// var colorScale = d3.scale.ordinal().range(['#ffffff']);
var colorScale = d3.scaleOrdinal().range(['#ffffff']);
function buildTable() {
var tbody = d3.select("#assays > tbody")
var rows = tbody.selectAll('tr').data(timepoints, function(d) { return d.id });
rows.enter()
.append("tr")
.attr("id", (d) => d.id)
.html(function(d) {
var html = "<td><select id=\"assay_" + d.id + "\" ><option value=\"dnase\">DNAseSeq</option><option value=\"rnase\">RNASeq</option></select></td>\
<td><input id=\"duration_" + d.id + "\" type=\"text\" value=\""+ formatDay(Math.round(d.duration)) + "\"></input></td>\
<td><input id=\"replication_" + d.id + "\" type=\"text\"></input></td>";
return html;
})
.attr("id", function(d) { return d.id });
rows.exit().remove();
}
function secondFormat(num) {
var h = Math.floor( num / 3600 );
var m = Math.floor((num - h * 3600) / 60 );
var s = num - (h * 3600 + m * 60);
return ( h < 10 ? "0" + h : h ) + ":" + ( m < 10 ? "0" + m : m ) + ":" + ( s < 10 ? "0" + s : s );
}
var timepoints = [];
function timelineHover() {
var chart = d3.timelines()
.linearTime()
.itemHeight(60)
.labelFloat(25)
.itemMargin(0)
.colors(colorScale)
.showBorderLine()
.showBorderFormat({marginTop: 30, marginBottom: 0, width: 3, color: 'black'})
//.showTimeAxisTick()
//.showTimeAxisTickFormat({stroke: "4px" })
.margin({left:10, right:30, top:30, bottom:0})
.hover(function (d, i, datum) {
// d is the current rendering object
// i is the index during d3 rendering
// datum is the id object
var div = $('#hoverRes');
//var colors = chart.colors();
//div.find('.coloredDiv').css('background-color', colors(i));
div.find('#name').text(datum.name);
})
.click(function (d, i, datum, labelElement, rectElement, duration) {
var ele = d3.select(rectElement);
var labelEle = d3.select(labelElement);
rectElement.duration = duration;
if (timepoints.includes(rectElement)) {
ele.style("fill", "#ffffff");
labelEle.style("fill", "#000000");
var index = timepoints.indexOf(rectElement);
timepoints.splice(index, 1);
} else {
timepoints.push(rectElement);
labelEle.style("fill", "#ffffff");
ele.style("fill", "#e2ae79");
}
buildTable();
})
.rotateTicks(45);
// v3 returns [Array(1)] => [svg]
// v4 returns
var svg = d3.select("#timeline").append("svg").attr("width", width)
.datum(testData).call(chart);
}
timelineHover();
/*
function createTable() {
var table = $("<tbody></tbody>");
for (var i = 0; i < testData.length; i++ ){
var step = testData[i];
var times = testData[i]['times'];
for (var j = 0; j < times.length; j++ ) {
var $line = $("<tr></tr>");
$line.append($("<td></td>").html(step.name));
$line.append($("<td></td>").html(times[j].concentration));
$line.append($("<td></td>").html(formatDay(times[j].starting_time)));
$line.append($("<td></td>").html(formatDay(times[j].ending_time - times[j].starting_time)));
table.append($line);
}
}
return table;
}
$("#treatments").append(createTable());
*/
}
</script>
</head>
<body>
<div>
<h3>Differentiation</h3>
<div id="protocol">Erythroid - J.Namath Nature 2010</div>
<div id="days">11 Days</div>
<div id="timeline"></div>
<div id="hoverRes">
<div class="coloredDiv"></div>
<div id="name"></div>
</div>
</div>
<div>
</div>
<div>
<h3>Assays</h3>
<table id="assays">
<thead>
<tr>
<th>Assay Type</th>
<th>Timepoint</th>
<th>Replication</th>
</tr>
</thead>
<tbody>
</tbody>
</div>
</body>
</html>
================================================
FILE: examples/days_scrollable.html
================================================
<!doctype html>
<html>
<head>
<script src="https://code.jquery.com/jquery-latest.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script>
<script src="../dist/d3-timelines.js"></script>
<style type="text/css">
.timeline-xAxis path,
.timeline-xAxis line {
shape-rendering: crispEdges;
}
.timeline-xAxis text {
display: none;
}
.textlabels {
font-size: 12pt;
font-family: Helvetica;
}
.textnumbers {
font-size: 18pt;
font-family: Helvetica;
}
.timeline-label {
font-family: sans-serif;
font-size: 12px;
}
.coloredDiv {
height:20px; width:20px; float:left;
}
</style>
<script type="text/javascript">
window.onload = function() {
var select = document.getElementById("selectNumber");
var options = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"];
for (var i = 0; i < options.length; i++) {
var opt = options[i];
var el = document.createElement("option");
el.textContent = opt;
el.value = opt;
select.appendChild(el);
}
var formatDay = function(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;
}
if (output === '') {
output = 'Start';
}
return output;
};
// beginning has to be > 0
/* var testData = [
// {name: "Hydrocortisone", times: [{"starting_time": 1, "ending_time": 691201, "concentration": "10.0 uM"}]}, // 0-8 days (8)
// {name: "SCF", times: [{"starting_time": 691201, "ending_time": 1382401, "concentration": "100.0 ng/mL"}]}, // 8-16 days (8)
{name: "IL3", times: [
{"starting_time": 1382401, "ending_time": 2073601, "concentration": "5.0 ng/mL"}, // 16-24 days (8)
{"starting_time": 2764801, "ending_time": 3024001, "concentration": "5.0 ng/mL"}, // 32-35 days, (3) after Epo
]}, // 8 days, 3 days
{name: "Epo", times: [{"starting_time": 2073601, "ending_time": 2764801, "concentration": "3.0 Ul/ml"}]}, // 24-32 days (8)
// {name: "Epo Feeder layer", times: [{"starting_time": 3024001, "ending_time": 3369601, "concentration": "3.0 Ul/ml"}]}, // 35-39 days (4)
// {name: "Epo chemical Feeder layer", times: [{"starting_time": 3369601, "ending_time": 3715201, "concentration": "3.0 Ul/ml"}]} // 39-43 days (4)
];
*/
var marginLeft = 10;
var marginRight = 10;
var testData = [];
window.constructTimes = function(days) {
var width = 400;
var newWidth = 400;
var times = [];
for (var i = 1; i <= days; i++) {
times.push({"id": "day" + i, "label": "DAY", "labelNumber": i, "starting_time": (i * 86400) + 1, "ending_time": ((i + 1) * 86400)})
}
testData = [
{times:times},
];
var boxWidth = 47;
var dataLength = testData[0].times.length;
var viewWidth = 400 / dataLength;
var sf = boxWidth/ viewWidth;
var newWidth = width;
if (sf > 1) {
newWidth = Math.round(width * sf) + marginLeft + marginRight;
} else {
width = (boxWidth * dataLength) + marginLeft + marginRight;
newWidth = width;
}
return width, newWidth;
}
var width, newWidth = constructTimes(10);
// d3 v3
// var colorScale = d3.scale.ordinal().range(['#ffffff']);
var colorScale = d3.scaleOrdinal().range(['#ffffff']);
function buildTable() {
var tbody = d3.select("#assays > tbody")
var rows = tbody.selectAll('tr').data(timepoints, function(d) { return d.id });
rows.enter()
.append("tr")
.attr("id", (d) => d.id)
.html(function(d) {
var html = "<td><select id=\"assay_" + d.id + "\" ><option value=\"dnase\">DNAseSeq</option><option value=\"rnase\">RNASeq</option></select></td>\
<td><input id=\"duration_" + d.id + "\" type=\"text\" value=\""+ formatDay(Math.round(d.duration)) + "\"></input></td>\
<td><input id=\"replication_" + d.id + "\" type=\"text\"></input></td>";
return html;
})
.attr("id", function(d) { return d.id });
rows.exit().remove();
}
function secondFormat(num) {
var h = Math.floor( num / 3600 );
var m = Math.floor((num - h * 3600) / 60 );
var s = num - (h * 3600 + m * 60);
return ( h < 10 ? "0" + h : h ) + ":" + ( m < 10 ? "0" + m : m ) + ":" + ( s < 10 ? "0" + s : s );
}
var timepoints = [];
function timelineHover(width, newWidth) {
var chart = d3.timelines()
.linearTime()
.allowZoom(false)
.itemHeight(60)
.labelFloat(25)
.itemMargin(0)
.width(newWidth)
.colors(colorScale)
.showBorderLine()
.showBorderFormat({marginTop: 30, marginBottom: 0, width: 3, color: 'black'})
//.showTimeAxisTick()
//.showTimeAxisTickFormat({stroke: "4px" })
.margin({left: marginLeft, right:marginRight, top:30, bottom:0})
.hover(function (d, i, datum) {
// d is the current rendering object
// i is the index during d3 rendering
// datum is the id object
var div = $('#hoverRes');
//var colors = chart.colors();
//div.find('.coloredDiv').css('background-color', colors(i));
div.find('#name').text(datum.name);
})
.click(function (d, i, datum, labelElement, rectElement, duration) {
var ele = d3.select(rectElement);
var labelEle = d3.select(labelElement);
rectElement.duration = duration;
if (timepoints.includes(rectElement)) {
ele.style("fill", "#ffffff");
labelEle.style("fill", "#000000");
var index = timepoints.indexOf(rectElement);
timepoints.splice(index, 1);
} else {
timepoints.push(rectElement);
labelEle.style("fill", "#ffffff");
ele.style("fill", "#e2ae79");
}
buildTable();
})
.rotateTicks(45);
// v3 returns [Array(1)] => [svg]
// v4 returns
var svg = d3.select("#timeline").append("svg").attr("width", width)
.datum(testData).call(chart);
}
timelineHover(width, newWidth);
/*
function createTable() {
var table = $("<tbody></tbody>");
for (var i = 0; i < testData.length; i++ ){
var step = testData[i];
var times = testData[i]['times'];
for (var j = 0; j < times.length; j++ ) {
var $line = $("<tr></tr>");
$line.append($("<td></td>").html(step.name));
$line.append($("<td></td>").html(times[j].concentration));
$line.append($("<td></td>").html(formatDay(times[j].starting_time)));
$line.append($("<td></td>").html(formatDay(times[j].ending_time - times[j].starting_time)));
table.append($line);
}
}
return table;
}
$("#treatments").append(createTable());
*/
window.updateTimeline = function(number) {
d3.select("#timeline").select('svg').remove();
timepoints = [];
var uwidth, unewWidth = constructTimes(number);
timelineHover(uwidth, unewWidth);
}
}
</script>
</head>
<body>
<div>
<h3>Differentiation</h3>
<div id="protocol">Erythroid - J.Namath Nature 2010</div>
<div id="days">11 Days</div>
<div id="timeline"></div>
<div>
<select id="selectNumber" onChange="window.updateTimeline(this.value);"></select>
</div>
</div>
<div>
</div>
<div>
<h3>Assays</h3>
<table id="assays">
<thead>
<tr>
<th>Assay Type</th>
<th>Timepoint</th>
<th>Replication</th>
</tr>
</thead>
<tbody>
</tbody>
</div>
</body>
</html>
================================================
FILE: examples/example.html
================================================
<!doctype html>
<html>
<head>
<script src="https://code.jquery.com/jquery-latest.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script>
<script src="../dist/d3-timelines.js"></script>
<style type="text/css">
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.axis text {
font-family: sans-serif;
font-size: 10px;
}
.timeline-label {
font-family: sans-serif;
font-size: 12px;
}
#timeline2 .axis {
transform: translate(0px,40px);
-ms-transform: translate(0px,40px); /* IE 9 */
-webkit-transform: translate(0px,40px); /* Safari and Chrome */
-o-transform: translate(0px,40px); /* Opera */
-moz-transform: translate(0px,40px); /* Firefox */
}
.coloredDiv {
height:20px; width:20px; float:left;
}
</style>
<script type="text/javascript">
window.onload = function() {
var testData = [
{times: [{"starting_time": 1355752800000, "ending_time": 1355759900000}, {"starting_time": 1355767900000, "ending_time": 1355774400000}]},
{times: [{"starting_time": 1355759910000, "ending_time": 1355761900000}, ]},
{times: [{"starting_time": 1355761910000, "ending_time": 1355763910000}]}
];
var rectAndCircleTestData = [
{times: [{"starting_time": 1355752800000,
"display": "circle"}, {"starting_time": 1355767900000, "ending_time": 1355774400000}]},
{times: [{"starting_time": 1355759910000,
"display":"circle"}, ]},
{times: [{"starting_time": 1355761910000, "ending_time": 1355763910000}]}
];
var labelTestData = [
{label: "person a", times: [{"starting_time": 1355752800000, "ending_time": 1355759900000}, {"starting_time": 1355767900000, "ending_time": 1355774400000}]},
{label: "person b", times: [{"starting_time": 1355759910000, "ending_time": 1355761900000}, ]},
{label: "person c", times: [{"starting_time": 1355761910000, "ending_time": 1355763910000}]}
];
var iconTestData = [
{class:"jackie", icon: "jackie.png", times: [
{"starting_time": 1355752800000, "ending_time": 1355759900000},
{"starting_time": 1355767900000, "ending_time": 1355774400000}]},
{class:"troll", icon: "troll.png", times: [
{"starting_time": 1355759910000, "ending_time": 1355761900000,
"display":"circle"}, ]},
{class:"wat", icon: "wat.png", times: [
{"starting_time": 1355761910000, "ending_time": 1355763910000}]}
];
var labelColorTestData = [
{label: "person a", times: [{"color":"green", "label":"Weeee", "starting_time": 1355752800000, "ending_time": 1355759900000}, {"color":"blue", "label":"Weeee", "starting_time": 1355767900000, "ending_time": 1355774400000}]},
{label: "person b", times: [{"color":"pink", "label":"Weeee", "starting_time": 1355759910000, "ending_time": 1355761900000}, ]},
{label: "person c", times: [{"color":"yellow", "label":"Weeee", "starting_time": 1355761910000, "ending_time": 1355763910000}]}
];
var testDataWithColor = [
{label: "fruit 1", fruit: "orange", times: [
{"starting_time": 1355759910000, "ending_time": 1355761900000}]},
{label: "fruit 2", fruit: "apple", times: [
{"starting_time": 1355752800000, "ending_time": 1355759900000},
{"starting_time": 1355767900000, "ending_time": 1355774400000}]},
{label: "fruit3", fruit: "lemon", times: [
{"starting_time": 1355761910000, "ending_time": 1355763910000}]}
];
var testDataWithColorPerTime = [
{label: "fruit 2", fruit: "apple", times: [
{fruit: "orange", "starting_time": 1355752800000, "ending_time": 1355759900000},
{"starting_time": 1355767900000, "ending_time": 1355774400000},
{fruit: "lemon", "starting_time": 1355774400000, "ending_time": 1355775500000}]}
];
var testDataRelative = [
{times: [{"starting_time": 1355752800000, "ending_time": 1355759900000}, {"starting_time": 1355767900000, "ending_time": 1355774400000}]},
{times: [{"starting_time": 1355759910000, "ending_time": 1355761900000}]},
{times: [{"starting_time": 1355761910000, "ending_time": 1355763910000}]}
];
var width = 500;
function timelineRect() {
var chart = d3.timelines();
var svg = d3.select("#timeline1").append("svg").attr("width", width)
.datum(testData).call(chart);
}
function timelineRectNoAxis() {
var chart = d3.timelines().showTimeAxis();
var svg = d3.select("#timeline1_noaxis").append("svg").attr("width", width)
.datum(testData).call(chart);
}
function timelineCircle() {
var chart = d3.timelines()
.tickFormat( //
{format: d3.timeFormat("%I %p"),
tickTime: d3.timeHours,
tickInterval: 1,
tickSize: 30})
.display("circle"); // toggle between rectangles and circles
var svg = d3.select("#timeline2").append("svg").attr("width", width)
.datum(testData).call(chart);
}
function timelineRectAndCircle() {
var chart = d3.timelines();
var svg = d3.select("#timeline2_combine").append("svg").attr("width", width)
.datum(rectAndCircleTestData).call(chart);
}
// the stacked, hover, scrollable
function timelineHover() {
var chart = d3.timelines()
.width(width*4)
.stack()
.margin({left:70, right:30, top:0, bottom:0})
.hover(function (d, i, datum) {
// d is the current rendering object
// i is the index during d3 rendering
// datum is the id object
var div = $('#hoverRes');
var colors = chart.colors();
div.find('.coloredDiv').css('background-color', colors(i))
div.find('#name').text(datum.label);
})
.click(function (d, i, datum) {
alert(datum.label);
})
.scroll(function (x, scale) {
$("#scrolled_date").text(scale.invert(x) + " to " + scale.invert(x+width));
});
var svg = d3.select("#timeline3").append("svg").attr("width", width)
.datum(labelTestData).call(chart);
}
function timelineStackedIcons() {
var chart = d3.timelines()
.beginning(1355752800000) // we can optionally add beginning and ending times to speed up rendering a little
.ending(1355774400000)
.showTimeAxisTick() // toggles tick marks
.stack() // toggles graph stacking
.margin({left:70, right:30, top:0, bottom:0})
;
var svg = d3.select("#timeline5").append("svg").attr("width", width)
.datum(iconTestData).call(chart);
}
function timelineLabelColor() {
var chart = d3.timelines()
.beginning(1355752800000) // we can optionally add beginning and ending times to speed up rendering a little
.ending(1355774400000)
.stack() // toggles graph stacking
.margin({left:70, right:30, top:0, bottom:0})
;
var svg = d3.select("#timeline6").append("svg").attr("width", width)
.datum(labelColorTestData).call(chart);
}
function timelineRotatedTicks() {
var chart = d3.timelines()
.rotateTicks(45);
var svg = d3.select("#timeline7").append("svg").attr("width", width)
.datum(testData).call(chart);
}
function timelineRectColors() {
var colorScale = d3.scaleOrdinal().range(['#6b0000','#ef9b0f','#ffee00'])
.domain(['apple','orange','lemon']);
var chart = d3.timelines()
.colors( colorScale )
.colorProperty('fruit');
var svg = d3.select("#timelineColors").append("svg").attr("width", width)
.datum(testDataWithColor).call(chart);
}
function timelineRectColorsPerTime() {
var colorScale = d3.scaleOrdinal().range(['#6b0000','#ef9b0f','#ffee00'])
.domain(['apple','orange','lemon']);
var chart = d3.timelines()
.colors( colorScale )
.colorProperty('fruit');
var svg = d3.select("#timelineColorsPerTime").append("svg").attr("width", width)
.datum(testDataWithColorPerTime).call(chart);
}
function timelineRelativeTime() {
//This solution is for relative time is from
//http://stackoverflow.com/questions/11286872/how-do-i-make-a-custom-axis-formatter-for-hours-minutes-in-d3-js
var chart = d3.timelines()
.relativeTime()
.tickFormat({
format: function(d) { return d3.timeFormat("%H:%M")(d) },
tickTime: d3.timeMinutes,
tickInterval: 30,
tickSize: 15,
});
var svg = d3.select("#timelineRelativeTime").append("svg").attr("width", width)
.datum(testDataRelative).call(chart);
}
function timelineAxisTop() {
var chart = d3.timelines().showAxisTop().stack();
var svg = d3.select("#timelineAxisTop").append("svg").attr("width", width)
.datum(testData).call(chart);
}
function timelineBgndTick() {
var chart = d3.timelines().stack().showTimeAxisTick().background('grey');
var svg = d3.select("#timelineBgndTick").append("svg").attr("width", width)
.datum(testData).call(chart);
}
function timelineBgnd() {
var chart = d3.timelines().stack().background('grey');
var svg = d3.select("#timelineBgnd").append("svg").attr("width", width)
.datum(testData).call(chart);
}
function timelineComplex() {
var chart = d3.timelines();
chart.stack();
chart.showTimeAxisTick();
// chart.showAxisTop();
// chart.showToday();
// chart.itemHeight(50);
chart.margin({left: 250, right: 0, top: 20, bottom: 0});
chart.itemMargin(0);
chart.labelMargin(25);
var backgroundColor = "#FCFCFD";
var altBackgroundColor = "red";
chart.background(function (datum, i) {
var odd = (i % 2) === 0;
return odd ? altBackgroundColor : backgroundColor;
});
chart.fullLengthBackgrounds();
var svg = d3.select("#timelineComplex").append("svg").attr("width", width)
.datum(labelTestData).call(chart);
}
timelineRect();
timelineRectNoAxis();
timelineCircle();
timelineRectAndCircle();
timelineHover();
timelineStackedIcons();
timelineLabelColor();
timelineRotatedTicks();
timelineRectColors();
timelineRectColorsPerTime();
timelineRelativeTime();
timelineAxisTop();
timelineBgndTick();
timelineBgnd();
timelineComplex();
}
</script>
</head>
<body>
<div>
<h3>A simple timeline</h3>
<div id="timeline1"></div>
</div>
<div>
<h3>A simple timeline without Axis</h3>
<div id="timeline1_noaxis"></div>
</div>
<div>
<h3>It works with circles too</h3>
<div id="timeline2"></div>
</div>
<div>
<h3>Combine circles and rectangles</h3>
<div id="timeline2_combine"></div>
</div>
<div>
<h3>A stacked timeline with hover, click, and scroll events</h3>
<div id="timeline3"></div>
<div id="hoverRes">
<div class="coloredDiv"></div>
<div id="name"></div>
<div id="scrolled_date"></div>
</div>
</div>
<div>
<h3>We can also use icons</h3>
<div id="timeline5"></div>
</div>
<div>
<h3>We can change colors and put labels</h3>
<div id="timeline6"></div>
</div>
<div>
<h3>We can also rotate tick mark labels</h3>
<div id="timeline7"></div>
</div>
<div>
<h3>A timeline with colors mapped from the data</h3>
<div id="timelineColors"></div>
</div>
<div>
<h3>A timeline with colors mapped from the data for individual time objects</h3>
<div id="timelineColorsPerTime"></div>
</div>
<div>
<h3>A timeline with relative timepoints</h3>
<div width="100%" id="timelineRelativeTime"></div>
</div>
<div>
<h3>A stacked timeline with axis on top</h3>
<div width="100%" id="timelineAxisTop"></div>
</div>
<div>
<h3>A stacked timeline with backgrounds</h3>
<div width="100%" id="timelineBgnd"></div>
</div>
<div>
<h3>A stacked timeline with backgrounds and ticks</h3>
<div width="100%" id="timelineBgndTick"></div>
</div>
<div>
<h3>A complex timeline</h3>
<div width="100%" id="timelineComplex"></div>
</div>
</body>
</html>
================================================
FILE: examples/scrollable.html
================================================
<!doctype html>
<html>
<head>
<script src="https://code.jquery.com/jquery-latest.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script>
<script src="../dist/d3-timelines.js"></script>
<style type="text/css">
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.axis text {
font-family: sans-serif;
font-size: 10px;
}
.timeline-label {
font-family: sans-serif;
font-size: 12px;
}
.coloredDiv {
height:20px; width:20px; float:left;
}
</style>
<script type="text/javascript">
window.onload = function() {
var labelTestData = [
{label: "person a", times: [{"starting_time": 1355752800000, "ending_time": 1355759900000}, {"starting_time": 1355767900000, "ending_time": 1355774400000}]},
{label: "person b", times: [{"starting_time": 1355759910000, "ending_time": 1355761900000}, ]},
{label: "person c", times: [{"starting_time": 1355761910000, "ending_time": 1355763910000}]}
];
var width = 500;
// the stacked, hover, scrollable
function timelineHover() {
var chart = d3.timelines()
.width(width*4)
.stack()
.margin({left:70, right:30, top:0, bottom:0})
.hover(function (d, i, datum) {
// d is the current rendering object
// i is the index during d3 rendering
// datum is the id object
var div = $('#hoverRes');
var colors = chart.colors();
div.find('.coloredDiv').css('background-color', colors(i))
div.find('#name').text(datum.label);
})
.click(function (d, i, datum) {
alert(datum.label);
})
.scroll(function (x, scale) {
$("#scrolled_date").text(scale.invert(x) + " to " + scale.invert(x+width));
});
var svg = d3.select("#timeline3").append("svg").attr("width", width)
.datum(labelTestData).call(chart);
}
timelineHover();
}
</script>
</head>
<body>
<div>
<h3>A stacked timeline with hover, click, and scroll events</h3>
<div id="timeline3"></div>
<div id="hoverRes">
<div class="coloredDiv"></div>
<div id="name"></div>
<div id="scrolled_date"></div>
</div>
</div>
</body>
</html>
================================================
FILE: examples/sp500.csv
================================================
date,price
Jan 2000,1394.46
Feb 2000,1366.42
Mar 2000,1498.58
Apr 2000,1452.43
May 2000,1420.6
Jun 2000,1454.6
Jul 2000,1430.83
Aug 2000,1517.68
Sep 2000,1436.51
Oct 2000,1429.4
Nov 2000,1314.95
Dec 2000,1320.28
Jan 2001,1366.01
Feb 2001,1239.94
Mar 2001,1160.33
Apr 2001,1249.46
May 2001,1255.82
Jun 2001,1224.38
Jul 2001,1211.23
Aug 2001,1133.58
Sep 2001,1040.94
Oct 2001,1059.78
Nov 2001,1139.45
Dec 2001,1148.08
Jan 2002,1130.2
Feb 2002,1106.73
Mar 2002,1147.39
Apr 2002,1076.92
May 2002,1067.14
Jun 2002,989.82
Jul 2002,911.62
Aug 2002,916.07
Sep 2002,815.28
Oct 2002,885.76
Nov 2002,936.31
Dec 2002,879.82
Jan 2003,855.7
Feb 2003,841.15
Mar 2003,848.18
Apr 2003,916.92
May 2003,963.59
Jun 2003,974.5
Jul 2003,990.31
Aug 2003,1008.01
Sep 2003,995.97
Oct 2003,1050.71
Nov 2003,1058.2
Dec 2003,1111.92
Jan 2004,1131.13
Feb 2004,1144.94
Mar 2004,1126.21
Apr 2004,1107.3
May 2004,1120.68
Jun 2004,1140.84
Jul 2004,1101.72
Aug 2004,1104.24
Sep 2004,1114.58
Oct 2004,1130.2
Nov 2004,1173.82
Dec 2004,1211.92
Jan 2005,1181.27
Feb 2005,1203.6
Mar 2005,1180.59
Apr 2005,1156.85
May 2005,1191.5
Jun 2005,1191.33
Jul 2005,1234.18
Aug 2005,1220.33
Sep 2005,1228.81
Oct 2005,1207.01
Nov 2005,1249.48
Dec 2005,1248.29
Jan 2006,1280.08
Feb 2006,1280.66
Mar 2006,1294.87
Apr 2006,1310.61
May 2006,1270.09
Jun 2006,1270.2
Jul 2006,1276.66
Aug 2006,1303.82
Sep 2006,1335.85
Oct 2006,1377.94
Nov 2006,1400.63
Dec 2006,1418.3
Jan 2007,1438.24
Feb 2007,1406.82
Mar 2007,1420.86
Apr 2007,1482.37
May 2007,1530.62
Jun 2007,1503.35
Jul 2007,1455.27
Aug 2007,1473.99
Sep 2007,1526.75
Oct 2007,1549.38
Nov 2007,1481.14
Dec 2007,1468.36
Jan 2008,1378.55
Feb 2008,1330.63
Mar 2008,1322.7
Apr 2008,1385.59
May 2008,1400.38
Jun 2008,1280
Jul 2008,1267.38
Aug 2008,1282.83
Sep 2008,1166.36
Oct 2008,968.75
Nov 2008,896.24
Dec 2008,903.25
Jan 2009,825.88
Feb 2009,735.09
Mar 2009,797.87
Apr 2009,872.81
May 2009,919.14
Jun 2009,919.32
Jul 2009,987.48
Aug 2009,1020.62
Sep 2009,1057.08
Oct 2009,1036.19
Nov 2009,1095.63
Dec 2009,1115.1
Jan 2010,1073.87
Feb 2010,1104.49
Mar 2010,1140.45
================================================
FILE: examples/timeline.html
================================================
<!doctype html>
<html>
<head>
<script src="https://code.jquery.com/jquery-latest.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script>
<script src="../dist/d3-timelines.js"></script>
<style type="text/css">
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.axis text {
font-family: sans-serif;
font-size: 10px;
}
.timeline-label {
font-family: sans-serif;
font-size: 12px;
}
#timeline2 .axis {
transform: translate(0px,40px);
-ms-transform: translate(0px,40px); /* IE 9 */
-webkit-transform: translate(0px,40px); /* Safari and Chrome */
-o-transform: translate(0px,40px); /* Opera */
-moz-transform: translate(0px,40px); /* Firefox */
}
.coloredDiv {
height:20px; width:20px; float:left;
}
</style>
<script type="text/javascript">
window.onload = function() {
var testData = [
{name: "step1", times: [{"starting_time": 0, "ending_time": 135}]},
{name: "step2", times: [{"starting_time": 120, "ending_time": 160}, ]},
{name: "step3", times: [{"starting_time": 160, "ending_time": 175}]}
];
var width = 500;
function timelineHover() {
var chart = d3.timelines()
.relativeTime()
.tickFormat({
format: function(d) { return d3.timeFormat("%M")(d) },
tickTime: d3.timeMinutes,
tickInterval: 15,
tickSize: 15,
})
.margin({left:70, right:30, top:0, bottom:0})
.hover(function (d, i, datum) {
// d is the current rendering object
// i is the index during d3 rendering
// datum is the id object
var div = $('#hoverRes');
var colors = chart.colors();
div.find('.coloredDiv').css('background-color', colors(i))
div.find('#name').text(datum.name);
})
.click(function (d, i, datum, selectedLabel, selectedRect, xVal) {
console.log("timelineHover", datum.name);
console.log("point", xVal)
$("#assays").append("<div>\
<label>Assay Type:</label><select><option value=\"dnase\">DNAseSeq</option><option value=\"rnase\">RNASeq</option></select> \
<label>Assay Timepoint:</label><span>"+ Math.round(xVal) + "</span> \
</div>");
});
var svg = d3.select("#timeline").append("svg").attr("width", width)
.datum(testData).call(chart);
}
timelineHover();
}
</script>
</head>
<body>
<div>
<h3>A stacked timeline with hover, click, and scroll events</h3>
<div id="timeline"></div>
<div id="hoverRes">
<div class="coloredDiv"></div>
<div id="name"></div>
</div>
</div>
<div>
</div>
<div id="assays">
<form>
</form>
</div>
</body>
</html>
================================================
FILE: examples/timelineStacked.html
================================================
<!doctype html>
<html>
<head>
<script src="https://code.jquery.com/jquery-latest.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script>
<script src="../dist/d3-timelines.js"></script>
<style type="text/css">
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.axis text {
font-family: sans-serif;
font-size: 10px;
}
.timeline-label {
font-family: sans-serif;
font-size: 12px;
}
#timeline2 .axis {
transform: translate(0px,40px);
-ms-transform: translate(0px,40px); /* IE 9 */
-webkit-transform: translate(0px,40px); /* Safari and Chrome */
-o-transform: translate(0px,40px); /* Opera */
-moz-transform: translate(0px,40px); /* Firefox */
}
.coloredDiv {
height:20px; width:20px; float:left;
}
</style>
<script type="text/javascript">
window.onload = function() {
var labelTestData = [
{label: "step1", times: [{"starting_time": 0, "ending_time": 135}]},
{label: "step2", times: [{"starting_time": 120, "ending_time": 160}, ]},
{label: "step3", times: [{"starting_time": 160, "ending_time": 175}]}
];
var testData = [
{name: "step1", times: [{"starting_time": 0, "ending_time": 135}]},
{name: "step2", times: [{"starting_time": 120, "ending_time": 160}, ]},
{name: "step3", times: [{"starting_time": 160, "ending_time": 175}]}
];
var width = 500;
function timelineStackedHover() {
var chart = d3.timelines()
.relativeTime()
.tickFormat({
format: function(d) { return d3.timeFormat("%M")(d) },
tickTime: d3.timeMinutes,
tickInterval: 15,
tickSize: 15,
})
.stack()
.margin({left:70, right:30, top:0, bottom:0})
.hover(function (d, i, datum) {
// d is the current rendering object
// i is the index during d3 rendering
// datum is the id object
var div = $('#hoverRes');
var colors = chart.colors();
div.find('.coloredDiv').css('background-color', colors(i))
div.find('#name').text(datum.label);
})
.click(function (d, i, datum) {
console.log("timeStackedHover", datum.label);
});
var svg = d3.select("#timeline3").append("svg").attr("width", width)
.datum(labelTestData).call(chart);
}
function timelineHover() {
var chart = d3.timelines()
.relativeTime()
.tickFormat({
format: function(d) { return d3.timeFormat("%M")(d) },
tickTime: d3.timeMinutes,
tickInterval: 15,
tickSize: 15,
})
.margin({left:70, right:30, top:0, bottom:0})
.hover(function (d, i, datum) {
// d is the current rendering object
// i is the index during d3 rendering
// datum is the id object
var div = $('#hoverRes1');
var colors = chart.colors();
div.find('.coloredDiv1').css('background-color', colors(i))
div.find('#name1').text(datum.name);
})
.click(function (d, i, datum, point) {
console.log("timelineHover", datum.name);
console.log("point", point)
});
var svg = d3.select("#timeline1").append("svg").attr("width", width)
.datum(testData).call(chart);
}
timelineHover();
timelineStackedHover();
}
</script>
</head>
<body>
<div>
<h3>A stacked timeline with hover, click, and scroll events</h3>
<div id="timeline3"></div>
<div id="hoverRes">
<div class="coloredDiv"></div>
<div id="name"></div>
</div>
</div>
<div>
<h3>A timeline with hover, click, and scroll events</h3>
<div id="timeline1"></div>
<div id="hoverRes1">
<div class="coloredDiv1"></div>
<div id="name1"></div>
</div>
</div>
</body>
</html>
================================================
FILE: index.js
================================================
export { default as timelines } from "./src/timelines";
================================================
FILE: package.json
================================================
{
"name": "d3-timelines",
"version": "1.3.1",
"description": "A d3 v4 version of timeline. Can display single bar timelines, timelines with icons, and timelines that pan.",
"keywords": [
"d3",
"d3-module",
"d3-timelines"
],
"license": "BSD-3-Clause",
"main": "build/d3-timelines.js",
"jsnext:main": "index",
"homepage": "https://github.com/denisemauldin/d3-timeline",
"repository": {
"type": "git",
"url": "https://github.com/denisemauldin/d3-timeline.git"
},
"scripts": {
"pretest": "rm -rf build && mkdir build && rollup -c -f umd -n d3 -o build/d3-timelines.js -- index.js",
"test": "tape 'test/**/*-test.js'",
"prepare": "npm run test && uglifyjs build/d3-timelines.js -c -m -o build/d3-timelines.min.js",
"postpublish": "zip -j build/d3-timelines.zip -- LICENSE README.md build/d3-timelines.js build/d3-timelines.min.js"
},
"peerDependencies": {
"d3-array": "^1.2.0",
"d3-axis": "^1.0.7",
"d3-scale": "^1.0.6",
"d3-selection": "^1.1.0",
"d3-time": "^1.0.6",
"d3-time-format": "^2.0.5",
"d3-zoom": "^1.2.0"
},
"devDependencies": {
"@types/d3": "^4.5",
"rollup": "0.27",
"rollup-plugin-node-resolve": "^3.0.0",
"tape": "4",
"uglify-js": "2"
},
"dependencies": {
"d3": "^4.9.1",
"d3-array": "^1.2.0",
"d3-axis": "^1.0.7",
"d3-scale": "^1.0.6",
"d3-selection": "^1.1.0",
"d3-time": "^1.0.6",
"d3-time-format": "^2.0.5",
"d3-zoom": "^1.2.0"
}
}
================================================
FILE: rollup.config.js
================================================
import nodeResolve from 'rollup-plugin-node-resolve';
let pkg = require("./package.json");
let external = Object.keys(pkg.peerDependencies);
export default {
entry: 'index.js',
dest: 'bundle.js',
format: 'umd',
moduleName: 'd3-timeline',
external: external,
globals: {
"d3-axis": "d3",
"d3-array": "d3",
"d3-time-format": "d3",
"d3-time": "d3",
"d3-selection": "d3",
"d3-scale": "d3",
"d3-zoom": "d3"
},
plugins: [nodeResolve({ jsnext: true, main: true})]
};
================================================
FILE: src/timelines.js
================================================
import { axisBottom, axisTop } from 'd3-axis';
import { range } from 'd3-array';
import { timeFormat } from 'd3-time-format';
import { timeHour } from 'd3-time';
import { scaleOrdinal, scaleTime, scaleLinear, schemeCategory20 } from 'd3-scale';
import { event, mouse, namespace, namespaces, select } from 'd3-selection';
import { zoom as d3z } from 'd3-zoom'
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: timeFormat("%I %p"),
tickTime: timeHour,
tickInterval: 1,
tickSize: 6,
tickValues: null
},
allowZoom = true,
axisBgColor = "white",
chartData = {},
colorCycle = scaleOrdinal(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',
xScale = null,
xAxis = null
;
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 = 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 = 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;
};
if (orient == "bottom") {
xAxis = axisBottom();
} else if (orient == "top") {
xAxis = axisTop();
}
if (timeIsLinear) {
xScale = scaleLinear()
.domain([beginning, ending])
.range([margin.left, width - margin.right]);
xAxis.scale(xScale)
.tickFormat(formatDays)
.tickValues(range(0, ending, 86400));
} else {
xScale = 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(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 = mouse(this);
var selectedRect = select(this).node();
var selectorLabel = "text#" + selectedRect.id + '.textnumbers';
var selectedLabel = 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 = mouse(this);
var id = this.id;
var labelSelector = "text#" + id + ".textnumbers";
var selectedLabel = select(labelSelector).node();
var selector = "rect#" + id;
var selectedRect = 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 = mouse(this);
var id = this.id;
var selectedLabel = select(this).node();
var selector = "rect#" + id;
var selectedRect = 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(" + event.transform.x + ",0)"
+ "scale(" + event.transform.k + " 1)");
g.selectAll(".timeline-xAxis")
.attr("transform", function(d) {
return "translate(" + event.transform.x + ", " + timeAxisYPosition + ")"
+ "scale(" + event.transform.k + " 1)";
});
var new_xScale = event.transform.rescaleX(xScale);
g.selectAll('.timeline-xAxis').call(function(d) { xAxis.scale(new_xScale); });
var xpos = -event.transform.x;
scroll(xpos, xScale);
};
};
if (! allowZoom) {
var zoom = d3z()
.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);
g.on("wheel", function() {
event.preventDefault();
event.stopImmediatePropagation();
});
g.on("dblclick.zoom", function() {
event.preventDefault();
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
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;
};
export default timelines;
================================================
FILE: test/timeline-test.js
================================================
var tape = require("tape"),
d3 = Object.assign({}, require("../"), require("d3")),
timelines = require("../");
tape("timeline() returns the correct version", function(test) {
test.equal(timelines.timelines().version(), '1.0.0');
test.end();
});
gitextract_dkg6lrp9/
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── dist/
│ └── d3-timelines.js
├── examples/
│ ├── days.html
│ ├── days_scrollable.html
│ ├── example.html
│ ├── scrollable.html
│ ├── sp500.csv
│ ├── timeline.html
│ └── timelineStacked.html
├── index.js
├── package.json
├── rollup.config.js
├── src/
│ └── timelines.js
└── test/
└── timeline-test.js
SYMBOL INDEX (2 symbols across 2 files)
FILE: dist/d3-timelines.js
function timelines (line 188) | function timelines (gParent) {
FILE: src/timelines.js
function timelines (line 193) | function timelines (gParent) {
Condensed preview — 17 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (126K chars).
[
{
"path": ".gitignore",
"chars": 50,
"preview": ".DS_Store\nbuild/\nnode_modules\nnpm-debug.log\n*.swp\n"
},
{
"path": ".npmignore",
"chars": 18,
"preview": "build/*.zip\ntest/\n"
},
{
"path": "LICENSE",
"chars": 1473,
"preview": "Copyright 2017, Denise Mauldin\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without "
},
{
"path": "README.md",
"chars": 12767,
"preview": "# d3-timeline\n\nA d3 v4 version of d3-timeline \n\nGet something that looks like\n\n {\n\ttypeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('"
},
{
"path": "examples/days.html",
"chars": 6849,
"preview": "<!doctype html>\n<html>\n<head>\n\t<script src=\"https://code.jquery.com/jquery-latest.min.js\"></script>\n\t<script src=\"https:"
},
{
"path": "examples/days_scrollable.html",
"chars": 7727,
"preview": "<!doctype html>\n<html>\n<head>\n\t<script src=\"https://code.jquery.com/jquery-latest.min.js\"></script>\n\t<script src=\"https:"
},
{
"path": "examples/example.html",
"chars": 12738,
"preview": "<!doctype html>\n<html>\n<head>\n <script src=\"https://code.jquery.com/jquery-latest.min.js\"></script>\n <script src=\"http"
},
{
"path": "examples/scrollable.html",
"chars": 2170,
"preview": "<!doctype html>\n<html>\n<head>\n\t<script src=\"https://code.jquery.com/jquery-latest.min.js\"></script>\n\t<script src=\"https:"
},
{
"path": "examples/sp500.csv",
"chars": 2059,
"preview": "date,price\nJan 2000,1394.46\nFeb 2000,1366.42\nMar 2000,1498.58\nApr 2000,1452.43\nMay 2000,1420.6\nJun 2000,1454.6\nJul 2000,"
},
{
"path": "examples/timeline.html",
"chars": 2952,
"preview": "<!doctype html>\n<html>\n<head>\n <script src=\"https://code.jquery.com/jquery-latest.min.js\"></script>\n <script src=\"http"
},
{
"path": "examples/timelineStacked.html",
"chars": 4094,
"preview": "<!doctype html>\n<html>\n<head>\n <script src=\"https://code.jquery.com/jquery-latest.min.js\"></script>\n <script src=\"http"
},
{
"path": "index.js",
"chars": 56,
"preview": "export { default as timelines } from \"./src/timelines\";\n"
},
{
"path": "package.json",
"chars": 1422,
"preview": "{\n\t\"name\": \"d3-timelines\",\n\t\"version\": \"1.3.1\",\n\t\"description\": \"A d3 v4 version of timeline. Can display single bar tim"
},
{
"path": "rollup.config.js",
"chars": 483,
"preview": "import nodeResolve from 'rollup-plugin-node-resolve';\n\nlet pkg = require(\"./package.json\");\nlet external = Object.keys(p"
},
{
"path": "src/timelines.js",
"chars": 26590,
"preview": "\nimport { axisBottom, axisTop } from 'd3-axis';\nimport { range } from 'd3-array';\nimport { timeFormat } from 'd3-time-fo"
},
{
"path": "test/timeline-test.js",
"chars": 255,
"preview": "var tape = require(\"tape\"),\n\td3 = Object.assign({}, require(\"../\"), require(\"d3\")),\n timelines = require(\"../\");\n\ntap"
}
]
About this extraction
This page contains the full source code of the denisemauldin/d3-timeline GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 17 files (107.2 KB), approximately 31.8k tokens, and a symbol index with 2 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.