block by renecnielsen 8f1b29cbb51f4d2e725035953818c579

Nested Timelines

Full Screen

Nested timelines in d3.layout.timeline.

You enable nested support by setting the timeline.children() accessor (which by default returns null) and pass the timeline either an array of time bands with child elements or a hierarchically structured JSON object (as in this example).

Child timeline heights are relative to the top-level parent(s).

Note that with nested timelines maxBandHeight only applies to the top-level parent (in the case of a single root node like this, it is the exact same as the height setting of timeline.size()).

forked from emeeks‘s block: Nested Timelines

index.html

<html xmlns="//www.w3.org/1999/xhtml">
<head>
  <title>Nested Timelines</title>
  <meta charset="utf-8" />
    <style type="text/css">
      svg {
        height: 1100px;
        width: 1100px;
      }
      div.viz {
        height: 1000px;
        width: 1000px;
      }
      </style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.16/d3.min.js" charset="utf-8" type="text/javascript"></script>
      <script src="d3.layout.timeline.js" charset="utf-8" type="text/javascript"></script>

</head>
<body>
<div id="viz">
  <svg style="background:white;" height=1100 width=1100>
  </svg>
</div>
<footer>
</footer>
<script>

var colors = d3.scale.category10();

var timeline = d3.layout.timeline()
  .size([1000,400])
  .dateFormat(function (d) {return d})
  .childAccessor(function (d) {return d.children})
  .maxBandHeight(300)
  .padding(2);

colors = d3.scale.ordinal()
  .range(["#6798c1", "#677468", "#d7ce85", "#bf4730", "#9c5166"]);

d3.json("art_movements.json", function (json) {
  timelineBands = timeline(json);

  d3.select("svg").selectAll("g")
  .data(timelineBands)
  .enter()
  .append("g")
  .attr("class", "band")
  .on("mouseover", function (d) {d3.selectAll("text").style("opacity", function (p) {return p.label === d.label ? 1 : 0})})
  .on("mouseout", function (d) {d3.selectAll("text").style("opacity", 0)})

  d3.selectAll("g.band")
  .append("rect")
  .attr("x", function (d) {return d.start})
  .attr("y", function (d) {return d.y})
  .attr("height", function (d) {return d.dy})
  .attr("width", function (d) {return d.end - d.start})
  .style("stroke-width", function (d) {return Math.max(0,(2 - d.level))})
  .attr("rx", 3)
  .style("fill", function (d) {return colors(d.level)})
  .style("stroke", "black")

  d3.select("svg")
  .selectAll("text")
  .data(timelineBands)
  .enter()
  .append("text")
  .attr("x", function (d) {return (d.start + d.end) / 2})
  .attr("y", function (d) {return d.y + (d.dy / 2)})
  .text(function(d) {return d.label})
  .style("opacity", 0)
  .style("pointer-events", "none")
  .style("text-anchor", "middle")
  .style("font-size", "14px")

})

</script>
</body>
</html>

art_movements.json

{
	"label": "Art Movements",
	"children": [
		{"label": "Renaissance",
		"children": [
			{"label": "Italian Renaissance",
			"start": 1275,
			"end": 1602},
			{"label": "Renaissance Classicism",
			"start": 1475,
			"end": 1602},
			{"label": "Early Netherlandish Painting",
			"start": 1400,
			"end": 1500}
		]
		},
		{"label": "Renaissance to Neoclassicism",
		"children": [
			{"label": "Mannerism and Late Renaissance",
			"start": 1520,
			"end": 1600},
			{"label": "Boroque",
			"start": 1600,
			"end": 1730},
			{"label": "Dutch Golden Age Painting",
			"start": 1702,
			"end": 1730},
			{"label": "Flemish Boroque Painting",
			"start": 1585,
			"end": 1700},
			{"label": "Rococo",
			"start": 1720,
			"end": 1780},
			{"label": "Neoclassicism",
			"start": 1750,
			"end": 1830}
		]
		},
		{"label": "Romanticism",
		"children": [
			{"label": "Nazarene Movement",
			"start": 1820,
			"end": 1847},
			{"label": "The Ancients",
			"start": 1825,
			"end": 1845},
			{"label": "Purismo",
			"start": 1825,
			"end": 1845},
			{"label": "Düsseldorf school",
			"start": 1825,
			"end": 1865},
			{"label": "Hudson River school",
			"start": 1855,
			"end": 1880},
			{"label": "Luminism",
			"start": 1855,
			"end": 1875}
	]
},
		{"label": "Romanticism to Modern Art",
		"children": [
			{"label": "Norwich school",
			"start": 1803,
			"end": 1833},
			{"label": "Biedermeier",
			"start": 1815,
			"end": 1848},
			{"label": "Photography",
			"start": 1830,
			"end": 2015},
			{"label": "Realism",
			"start": 1830,
			"end": 1870},
			{"label": "Barbizon school ",
			"start": 1830,
			"end": 1870},
			{"label": "Peredvizhniki",
			"start": 1870,
			"end": 1923},
			{"label": "Hague School",
			"start": 1870,
			"end": 1900},
			{"label": "American Barbizon school",
			"start": 1853,
			"end": 1895},
			{"label": "Spanish Eclecticism",
			"start": 1845,
			"end": 1890},
			{"label": "Macchiaioli",
			"start": 1850,
			"end": 1859},
			{"label": "Pre-Raphaelite Brotherhood",
			"start": 1848,
			"end": 1854}
	]
},
		{"label": "Modern Art",
		"children": [
			{"label": "Impressionism",
			"start": 1860,
			"end": 1890,
			"children": [
				{"label": "American Impressionism",
				"start": 1880,
				"end": 1890},
				{"label": "Cos Cob Art Colony",
				"start": 1890,
				"end": 1899},
				{"label": "Heidelberg School",
				"start": 1887,
				"end": 1890}
			]},
			{"label": "Arts and Crafts",
			"start": 1880,
			"end": 1910},
			{"label": "Tonalism",
			"start": 1880,
			"end": 1920},
			{"label": "Symbolism",
			"start": 1880,
			"end": 1910,
			"children": [
				{"label": "Russian Symbolism",
				"start": 1884,
				"end": 1910},
				{"label": "Aesthetic Movement",
				"start": 1868,
				"end": 1901}
			]},
			{"label": "Post-Impressionism",
			"start": 1886,
			"end": 1905,
			"children": [
				{"label": "Les Nabis",
				"start": 1888,
				"end": 1900},
				{"label": "Cloisonnism",
				"start": 1884,
				"end": 1886},
				{"label": "Synthetism",
				"start": 1887,
				"end": 1893}
			]},
			{"label": "Neo-Impressionism",
			"start": 1886,
			"end": 1906,
			"children": [
				{"label": "Pointillism",
				"start": 1879,
				"end": 1906},
				{"label": "Divisionism",
				"start": 1880,
				"end": 1880}
			]},
			{"label": "Art Nouveau",
			"start": 1890,
			"end": 1914,
			"children": [
				{"label": "Vienna Secession",
				"start": 1897,
				"end": 1914},
				{"label": "Jugendstil",
				"start": 1890,
				"end": 1914},
				{"label": "Modernisme",
				"start": 1890,
				"end": 1910}
			]},
			{"label": "Russian avant-garde",
			"start": 1890,
			"end": 1930},
			{"label": "Art à la Rue",
			"start": 1890,
			"end": 1905},
			{"label": "Young Poland",
			"start": 1890,
			"end": 1918},
			{"label": "Mir iskusstva",
			"start": 1889,
			"end": 1900},
			{"label": "Hagenbund",
			"start": 1900,
			"end": 1930},
			{"label": "Fauvism",
			"start": 1904,
			"end": 1909},
			{"label": "Expressionism",
			"start": 1905,
			"end": 1930},
			{"label": "Die Brücke",
			"start": 1905,
			"end": 1913},
			{"label": "Der Blaue Reiter",
			"start": 1911,
			"end": 1912},
			{"label": "Bloomsbury Group",
			"start": 1905,
			"end": 1945}
			]
		},
		{"label": "Contemporary Art",
		"children": [
			{"label": "Vienna School of Fantastic Realism",
			"start": 1946,
			"end": 1947},
			{"label": "Neo-Dada",
			"start": 1950,
			"end": 1959},
			{"label": "International Typographic Style",
			"start": 1950,
			"end": 1959},
			{"label": "Soviet Nonconformist Art",
			"start": 1953,
			"end": 1986},
			{"label": "Painters Eleven",
			"start": 1954,
			"end": 1960},
			{"label": "Pop Art",
			"start": 1953,
			"end": 1957},
			{"label": "Woodlands School",
			"start": 1958,
			"end": 1962},
			{"label": "Situationism",
			"start": 1957,
			"end": 1973},
			{"label": "New realism",
			"start": 1960,
			"end": 2015},
			{"label": "Magic realism",
			"start": 1960,
			"end": 1969},
			{"label": "Minimalism",
			"start": 1960,
			"end": 2015},
			{"label": "Hard-edge painting",
			"start": 1960,
			"end": 1963},
			{"label": "Fluxus",
			"start": 1960,
			"end": 1977},
			{"label": "Happening",
			"start": 1960,
			"end": 2015}
		]}
		]

}

d3.layout.timeline.js

(function() {
d3.layout.timeline = function() {
    var timelines = [];
    var dateAccessor = function (d) {return new Date(d)};
    var processedTimelines = [];
    var startAccessor = function (d) {return d.start};
    var endAccessor = function (d) {return d.end};
    var size = [500,100];
    var timelineExtent = [-Infinity, Infinity];
    var setExtent = [];
    var displayScale = d3.scale.linear();
    var swimlanes = {root: []};
    var swimlaneNumber = 1;
    var padding = 0;
    var fixedExtent = false;
    var maximumHeight = Infinity;
    var childAccessor = function (d) {return null};
    var bandID = 1;
    var projectedHierarchy = {id: "root", values: []};

    function processTimelines(timelines, parentBand) {

        if (!Array.isArray(timelines) && Array.isArray(childAccessor(timelines))) {
            var rootnode = {id: 0, level: 0};
            for (var x in timelines) {
                if (timelines.hasOwnProperty(x)) {
                    rootnode[x] = timelines[x];
                }
            }
            rootnode.id = 0;
            rootnode.level = 0;
            rootnode.values = [];
            projectedHierarchy = rootnode;
            processTimelines(childAccessor(timelines), rootnode);
            rootnode.start = d3.min(rootnode.values, function (d) {return d.start});
            rootnode.end = d3.max(rootnode.values, function (d) {return d.end});
            processedTimelines.push(rootnode);

            return;
        }

    	timelines.forEach(function (band) {
            var projectedBand = {level: 0, id: bandID};

            if (parentBand !== undefined) {
                projectedBand.parent = parentBand;
            }
            bandID++;

            for (var x in band) {
                if (band.hasOwnProperty(x)) {
                    projectedBand[x] = band[x];
                }
            }

            if (Array.isArray(childAccessor(band))) {
                processTimelines(childAccessor(band), projectedBand);
                projectedBand.start = d3.min(projectedBand.values, function (d) {return d.start});
                projectedBand.end = d3.max(projectedBand.values, function (d) {return d.end});
            }
            else {
                projectedBand.start = dateAccessor(startAccessor(band));
                projectedBand.end = dateAccessor(endAccessor(band));
            }

    		projectedBand.lane = 0;
    		processedTimelines.push(projectedBand);
            if (parentBand) {
                if (!parentBand.values) {
                    parentBand.values = [];
                }
                parentBand.values.push(projectedBand);
            }
            if (parentBand === undefined) {
                projectedHierarchy.values.push(projectedBand);
            }
    	});
    }

    function projectTimelines() {
        if (fixedExtent === false) {
            var minStart = d3.min(processedTimelines, function (d) {return d.start});
            var maxEnd = d3.max(processedTimelines, function (d) {return d.end});
            timelineExtent = [minStart,maxEnd];
        }
        else {
            timelineExtent = [dateAccessor(setExtent[0]), dateAccessor(setExtent[1])];
        }

        displayScale.domain(timelineExtent).range([0,size[0]]);

        processedTimelines.forEach(function (band) {
            band.originalStart = band.start;
            band.originalEnd = band.end;
            band.start = displayScale(band.start);
            band.end = displayScale(band.end);
        });
    }

    function fitsIn(lane, band) {

    	if (lane.end < band.start || lane.start > band.end) {
    		return true;
    	}
    	var filteredLane = lane.filter(function (d) {return d.start <= band.end && d.end >= band.start});
    	if (filteredLane.length === 0) {
    		return true;
    	}
    	return false;
    }

    function findlane(band) {
    	//make the first array
        var swimlane = swimlanes["root"];
        if (band.parent) {
            swimlane = swimlanes[band.parent.id];
        }

        if (swimlane === undefined) {
            swimlanes[band.parent.id] = [[band]];
            swimlane = swimlanes[band.parent.id];
            swimlaneNumber++;
            return;
        }
    	var l = swimlane.length - 1;
    	var x = 0;

    	while (x <= l) {
    		if (fitsIn(swimlane[x], band)) {
    			swimlane[x].push(band);
    			return;
    		}
    		x++;
    	}
    	swimlane[x] = [band];
    	return;
    }

    function timeline(data) {
    	if (!arguments.length) return timeline;

        projectedHierarchy = {id: "root", values: []};

    	processedTimelines = [];
    	swimlanes = {root: []};

    	processTimelines(data);
        projectTimelines(data);

    	processedTimelines.forEach(function (band) {
    		findlane(band);
    	});

        for (var x in swimlanes) {
            swimlanes[x].forEach(function (lane, i) {
                var height = size[1] / swimlanes[x].length;
                height = Math.min(height, maximumHeight);
                lane.forEach(function (band) {
                    band.y = i * (height) + (padding / 2);
                    band.dy = height - padding;
                    band.lane = i;
                    band.dyp = 1 / swimlanes[x].length;
                });
            });
        }

        projectedHierarchy.values.forEach(relativePosition);

        processedTimelines.sort(function (a, b) {
                    if (a.level > b.level) {
                        return 1;
                    }
                    if (a.level < b.level) {
                        return -1;
                    }
                    return 1;
                });

    	return processedTimelines;
    }

    function relativePosition(band, i) {
        if (!band.parent) {
            band.level = 0;
        }
        else {
            band.level = band.parent.level + 1;
            var height = band.dyp * band.parent.dy;
            band.y = band.parent.y + (band.lane * height) + (padding / 2);
            band.dy = Math.max(1, height - padding);

        }
        if (band.values) {
            band.values.forEach(relativePosition);
        }
    }

    timeline.childAccessor = function (_x) {
        if (!arguments.length) return childAccessor;
           childAccessor = _x;
        return timeline;
    }

    timeline.dateFormat = function (_x) {
    	if (!arguments.length) return dateAccessor;
    	   dateAccessor = _x;
        return timeline;
    }

    timeline.bandStart = function (_x) {
    	if (!arguments.length) return startAccessor;
    	   startAccessor = _x;
        return timeline;
    }

    timeline.bandEnd = function (_x) {
	     if (!arguments.length) return endAccessor;
	     endAccessor = _x;
    	return timeline;
    }

    timeline.size = function (_x) {
	     if (!arguments.length) return size;
	     size = _x;
    	return timeline;
    }

    timeline.padding = function (_x) {
	     if (!arguments.length) return padding;
	     padding = _x;
    	return timeline;
    }

    timeline.extent = function (_x) {
	    if (!arguments.length) return timelineExtent;
	    	fixedExtent = true;
	    	setExtent = _x;
	    	if (_x.length === 0) {
	    		fixedExtent = false;
	    	}
    	return timeline;
    }

    timeline.maxBandHeight = function (_x) {
	    if (!arguments.length) return maximumHeight;
	    	maximumHeight = _x;
    	return timeline;
    }

    return timeline;
}
})();