Published
Edited
Dec 23, 2020
1 fork
Importers
3 stars
Insert cell
Insert cell
Insert cell
Insert cell
chart()
Insert cell
function chart () {
if (gd) {
env.structData = JSON.parse(localStorage.getItem('myMove'))
} else {
env.structData = data;}
const elem = d3.create("svg")
.attr('class', 'timelines-chart')
.attr("viewBox", [0, 0,1000, 600])
.style("overflow", "visible");
env.svg = elem.append("svg")
.attr("border", env.border)
.attr("width", env.width)
.attr("height", env.height);
env.svg.call(zoom);

var borderPath = env.svg.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("height", env.height)
.attr("width", env.width)
.style("fill", "none");
var grad = env.svg.append('defs')
.append('linearGradient')
.attr('id', 'grad')
.attr('x1', '0%')
.attr('x2', '0%')
.attr('y1', '0%')
.attr('y2', '100%');

grad.selectAll('stop')
.data(env.groupBkgGradient)
.enter()
.append('stop')
.style('stop-color', function (d) { return d; })
.attr('offset', function (d, i) {
return 100 * (i / (env.groupBkgGradient.length - 1)) + '%';
})

env.svg.transition().duration(100).attr('width', env.width).attr('height', env.height);

env.graph = env.svg.append('g')

env.g = env.svg.append("g").attr("transform", "translate(" + env.margin.left + "," + env.margin.top + ")");

env.g.append('rect').attr("class", "temp")
buildDomStructure();
convertdata(data)
parseData(env.structData);
renderAxises();
draw();
env.clip = env.svg.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", env.graphW)
.attr("height", 1600);
return elem.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
html`<style>p { max-width: none; }</style>`
Insert cell
mutable gd = false
Insert cell
d3 = require("d3@6")
Insert cell
env = ({width: 1600, height: 1600, maxHeight: 1600, lineMaxHeight: 16,
maxLineHeight: 16,
minLabelFont: 16,
margin: { top: 26, right: 0, bottom: 30, left: 150 },
groupBkgGradient: ['#FAFAFA', '#E0E0E0'],
xScale: d3.scaleTime(),
yScale: d3.scalePoint(),
grpScale: d3.scaleOrdinal(),
xGrid: d3.axisTop(),
yAxis: d3.axisRight(),
xAxis: d3.axisBottom(),
grpAxis: d3.axisLeft(),
category: "group",
svg: null,
graph: null,
graphW: null,
graphH: null,
nLines: 0,
minSegmentDuration: 0,
transDuration: 700,
structData: null,
flatData: null,
g: null,
labels: [],
border:1,
lineHeight:0,
xt:null,
color:[],
colorScale:null,
clip:null,
bordercolor: "black",
group: null
});
Insert cell
parseTime = d3.timeParse("%d/%m/%Y")
Insert cell
zoom = d3.zoom()
.scaleExtent([0.5, 32])
.on("zoom", redraw);
Insert cell
colors = ["#7fc97f","#beaed4","#fdc086","#ffff99","#386cb0","#f0027f","#bf5b17","#666666"]
Insert cell
colorsBU = [ "#95A5A6","#85C1E9","#F4D03F", "#D2B4DE","#EDBB99", "#73C6B6","#EDBB99", "#D5DBDB", "#FEF9E7", "#EBDEF0"]
Insert cell
function invertOrdinal(val, cmpFunc) {
cmpFunc = cmpFunc || function (a, b) {
return (a >= b);
};

const scDomain = this.domain();
let scRange = this.range();

if (scRange.length === 2 && scDomain.length !== 2) {
// Special case, interpolate range vals
scRange = d3.range(scRange[0], scRange[1], (scRange[1] - scRange[0]) / scDomain.length);
}

const bias = scRange[0];
for (let i = 0, len = scRange.length; i < len; i++) {
if (cmpFunc(scRange[i] + bias, val)) {
return scDomain[Math.round(i * scDomain.length / scRange.length)];
}
}

return this.domain()[this.domain().length - 1];
}
Insert cell
function convertdata() {
for (var t = 0; t < env.structData.length; t++) {
env.structData[t].start = new Date(env.structData[t].start)
env.structData[t].finish = new Date(env.structData[t].finish)
env.structData[t].clas = "x" + (t + 1)
}
}
Insert cell
function onlyUnique(value, index, self) {
return self.indexOf(value) === index;
}

Insert cell
function getColor() {
env.structData.forEach(function (item) {
env.color.push(item.label)
})
env.color = env.color.filter((v, i, a) => a.indexOf(v) === i);
return env.color
}
Insert cell
function getlabels() {
env.labels= [];
env.structData.forEach(function (item) {
env.labels.push(item.sub)
})
env.labels = env.labels.filter((v, i, a) => a.indexOf(v) === i);
env.labels.sort()
return env.labels
}
Insert cell
function getLines() {
env.nLines = 0
env.flatData.forEach(function (item) {
env.nLines += item.count
})
return env.nLines
}
Insert cell
function resolveOverlaps() {
for (var t = 0; t < env.structData.length; t++) {
env.structData[t].line = null
env.structData[t].sub = null

}

env.flatData = []

env.structData.sort(function (a, b) {
return d3.descending(a.start, b.start);
});

var keys = env.structData.map(function (d) { return d[env.category]; })
.reduce(function (p, v) { return p.indexOf(v) == -1 ? p.concat(v) : p; }, [])
.filter(function (d) { return (typeof d !== "undefined") ? d !== null : false });

keys.forEach(function (key) {
var items = env.structData.filter(function (d) { return d[env.category] === key; });
var i, line, count = [], lines = [];

var top = d3.max(env.structData, function (d) { return d.line }) || 0;
items.forEach(function (item) {
for (i = 0, line = 0; i < lines.length; i++ , line++) {
if (item.finish <= lines[i]) { break; }
}
item.line = line + top + 1;
item.sub = item[env.category] + '+&+' + item.line
lines[line] = item.start;
count.push(item.line);
})

count = count.filter(onlyUnique)
env.flatData.push({ category: items[0][env.category], count: count.length })
});
env.flatData.sort(function (a, b) {
return d3.ascending(a.category, b.category);
});
}
Insert cell
function update_group(){
//console.log(env.structData)
env.group = env.g.selectAll(".rectangle")
.data(env.structData)
.join(function (group) {
var denter = group.append("g").attr("class", "rectangle").append("svg").attr("class", function (d) { return "svgholder " + d.clas; });
denter.append("rect").attr("class", function (d) {
//console.log(d)
return "rectband " + d.clas; });
denter.append("text").attr("class", function (d) { return "interval " + d.clas; });
denter.append("rect").attr("class", function (d) { return "lefthand clasl " + d.clas; });
denter.append("rect").attr("class", function (d) { return "righthand clasr " + d.clas; });

return denter;
},
// function (update) {
// redraw()
// return update
);
return env.group;
}
Insert cell
function draw(event, d) {
// console.log("in")
if (event) {
var t = event.transform
env.xt = t.rescaleX(env.xScale);
} else {
env.xt = env.xScale;
}
env.color = getColor()
env.colorScale = d3.scaleOrdinal(colors).domain(env.color)
env.lineHeight = env.graphH / env.nLines * 0.8;

env.svg.select('.x-grid').call(env.xGrid.scale(env.xt));
const tr = env.svg.transition()
.duration(750);
update_group()
// env.group.enter();
// env.group = update_group()
// console.log(d3.selectAll(".rectangle"))
env.g.selectAll(".rectangle")
.attr("width", function (d) {
// console.log(d)
return env.xt(d.finish) - env.xt(d.start) })
.attr("height", env.lineHeight)
.attr("fill", function (d, i) { return env.colorScale(d.label); })
.attr("transform", function (d) {
// console.log(d)
return "translate(" + env.xt(d.start) + "," + (env.yScale(d.sub) - env.lineHeight / 2) + ")";
})
.attr("id", function (d) { return d.clas; })
// .attr("x", function (d) { return env.xt(d.start) })
// .attr("y", function (d) { return (env.yScale(d.sub) - env.lineHeight / 2) })
.call(d3.drag()
.subject(dragsubject)
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));


env.group.selectAll(".svgholder")
.attr("width", function (d) { return env.xt(d.finish) - env.xt(d.start) })
.attr("height", env.lineHeight);
// .attr("x", 6)
// .attr("y", 9);

env.group.selectAll(".rectband")
.attr("width", function (d) { return env.xt(d.finish) - env.xt(d.start) })
.attr("height", env.lineHeight)
.style("opacity", .5) // set the element opacity
.style("stroke", "black");
// .attr("x", function (d) { return env.xt(d.start) })
// .attr("y", function (d) { return (env.yScale(d.sub) - env.lineHeight / 2) });

env.group.selectAll(".interval")
.attr("x", 6)
.attr("y", 9)
.style("pointer-events", "none")
.style("fill", "black")
.attr("font-size", 11)
.attr("font-family", "Cambria")
.text(function (d) { return (d.description); });


env.group.selectAll(".lefthand")
.attr("idx", function (d) { return d.id; })
.attr("start", function (d) { return d.start; })
.attr("height", env.lineHeight)
.attr("id", "dragleft")
.attr("width", 2)
.attr("fill", "#4A235A")
.attr("fill-opacity", 1)
.attr("cursor", "ew-resize")
// .attr("x", function (d) { return env.xt(d.start) })
// .attr("y", function (d) { return (env.yScale(d.sub) - env.lineHeight / 2) })
.call(d3.drag()
.subject(dragsubjectl)
.on("start", dragstartedl)
.on("drag", draggedl)
.on("end", dragendedl));

env.group.selectAll(".righthand")
.attr("x", function (d) { return env.xt(d.finish) - env.xt(d.start) - 2 })
.attr("idx", function (d) { return d.id; })
.attr("start", function (d) { return d.start; })
.attr("height", env.lineHeight)
.attr("id", "dragright")
.attr("width", 2)
.attr("fill", "#4A235A")
.attr("fill-opacity", 1)
.attr("cursor", "ew-resize")
.call(d3.drag()
.subject(dragsubjectr)
.on("start", dragstartedr)
.on("drag", draggedr)
.on("end", dragendedr));
}
Insert cell
function redraw(event, d) {
env.svg.select('.x-grid').call(env.xGrid.scale(env.xt));

env.color = getColor()
env.colorScale = d3.scaleOrdinal(colors).domain(env.color)
env.lineHeight = env.graphH / env.nLines * 0.8;

if (event) {
var t = event.transform
env.xt = t.rescaleX(env.xScale);
} else {
env.xt = env.xScale;
}
console.log( d3.selectAll(".rectangle"))
d3.selectAll(".rectangle")
.data(env.structData)
.attr("width", function (d) { return env.xt(d.finish) - env.xt(d.start) })
.attr("height", env.lineHeight)
.attr("fill", function (d, i) { return env.colorScale(d.label); })
.attr("transform", function (d) {return "translate(" + env.xt(d.start) + "," + (env.yScale(d.sub) - env.lineHeight / 2) + ")";});
d3.selectAll(".svgholder")
.attr("height", env.lineHeight)
.attr("width", function (d) { return env.xt(d.finish) - env.xt(d.start) });
d3.selectAll(".rectband")
.data(env.structData)
.attr("width", function (d) { return (env.xt(d.finish) - env.xt(d.start)) })
.attr("height", env.lineHeight)
.attr("fill", function (d, i) { return env.colorScale(d.label); })
.attr("x", 0)
.attr("y", 0);

d3.selectAll(".interval")
.attr("x", 6)
.attr("y", 9)
.style("pointer-events", "none")
.style("fill", "black")
.attr("font-size", 11)
.attr("font-family", "Cambria")
.text(function (d) { return (d.description); });


d3.selectAll(".lefthand")
.attr("width", 2)
.attr("height", env.lineHeight)
.attr("fill", "#4A235A");

d3.selectAll(".righthand")
.attr("x", function (d) { return env.xt(d.finish) - env.xt(d.start) - 2 })
// .attr("y", function (d) { return env.yScale(d.sub) - env.lineHeight / 2 })
.attr("width", 2)
.attr("height", env.lineHeight)
.attr("fill", "#4A235A");
}

Insert cell
function dragsubject(event, d) {

return {
//x: d3.select(this).attr("xstart"),
//y: d3.select(this).attr("ystart")
}
};
Insert cell
function dragstarted(event, d) {
var current = d3.select(this);
d.x = event.x
}
Insert cell
function dragged(event, d) {

var clas, end;
if (typeof env.xt === 'undefined') {
env.xt = env.xScale
}
// d.x = event.x
// d.y = event.y
d3.select(this).attr("transform", function (d) {
return "translate(" + [event.x, event.y] + ")"
})
}
Insert cell
function dragended(event, d) {
if(d.x !== event.x) {
var t = d3.select(this);
var clas = t.attr("id");

for (var i = 0; i < env.structData.length; i++) {
if (env.structData[i].clas === clas) {
var delta = env.xt(env.structData[i].finish) - env.xt(env.structData[i].start);
env.structData[i].finish = env.xt.invert(event.x + delta);
env.structData[i].start = env.xt.invert(event.x);
console.log("start " + env.xt.invert(event.x))
env.structData[i][env.category] = env.yScale.invert(event.y).split('+&+')[0]
env.structData[i].ystart = event.y;
env.structData[i].xstart = event.x;
console.log("in")
}
}

for (var t = 0; t < env.structData.length; t++) {
env.structData[t].line = 0
}

resolveOverlaps()

env.structData.sort(function (a, b) {
return d3.ascending(a.order, b.order);
});

env.labels = getlabels()
env.nLines = getLines();
env.graphH = d3.min([env.nLines * env.maxLineHeight, env.maxHeight - env.margin.top - env.margin.bottom]);
env.clip.attr("height", env.graphH);
renderAxises()
event = null
redraw();
// d.x = env.xt(event.x); d.y = env.xt(event.y); // update the datum.
}
}
Insert cell
function dragsubjectl(event, d) {

return {
x: d3.select(this).attr("x"),
y: d3.select(this).attr("y")
}
};
Insert cell
function dragstartedl(event, d) {
}
Insert cell
function draggedl(event, d) {
var clas, end, start, ypos;
if (typeof env.xt === undefined) {
console.log("in")
env.xt = env.xScale
}
console.log("out")
// d.x = event.x
// d.y = event.y
// d3.select(this).attr("x", event.x)
var clas = d3.select(this).attr("class");

for (var i = 0; i < env.structData.length; i++) {
if ("lefthand clasl " + env.structData[i].clas === clas) {
end = env.xt(d.finish)
start = env.xt(d.start)
console.log("start " + d.start)
console.log("end " + end)
clas = env.structData[i].clas
ypos = env.yScale(d.sub) - (env.lineHeight / 2)
};
}
d3.select(".temp")
.attr("width", (end - start) - event.x)
.attr("x", start + event.x)
.attr("y", ypos)
.attr("height", env.lineHeight)
.attr("opacity", 0.5)
.attr("fill", "orange");
}
Insert cell
function dragendedl(event, d) {
d3.select(".temp")
.attr("width", 0)
.attr("x", 0)
.attr("y", 0)
.attr("height", 0)
.attr("fill", "none");



var clas = d3.select(this).attr("class");

for (var i = 0; i < env.structData.length; i++) {
if ("lefthand clasl " + env.structData[i].clas === clas) {
env.structData[i].start = env.xt.invert(env.xt(env.structData[i].start) + event.x)
var sub = env.structData[i].sub
}
};
//
var subitems = env.structData.filter(function (d) { return d.sub === sub; });
subitems.sort(function (a, b) {
return d3.descending(a.start, b.start);
});

if (subitems.length > 1) {
var i, line, count = false, lines = [];
subitems.forEach(function (item) {
for (i = 0, line = 0; i < lines.length; i++ , line++) {
if (item.finish >= lines[i]) {
count = true
break;
}
}
lines[line] = item.start;
})
}
if (count) { resolveOverlaps(); }


env.structData.sort(function (a, b) {
return d3.ascending(a.order, b.order);
});

env.labels = getlabels()
env.nLines = getLines();
env.graphH = d3.min([env.nLines * env.maxLineHeight, env.maxHeight - env.margin.top - env.margin.bottom]);
renderAxises()
d3.event = null
redraw();
d.x = event.x; d.y = event.y; // update the datum.
}
Insert cell
function dragsubjectr(event, d) {

return {
x: d3.select(this).attr("x"),
y: d3.select(this).attr("y")
}
};
Insert cell
function dragstartedr(event, d) {
var current = d3.select(this);
}
Insert cell

function draggedr(event, d) {
var clas, ypos, start, end;
if (typeof env.xt === 'undefined') {
env.xt = env.xScale
}

d3.select(this).attr("x", event.x)

var clas = d3.select(this).attr("class");

for (var i = 0; i < env.structData.length; i++) {
if ("righthand clasr " + env.structData[i].clas === clas) {
start = env.xt(d.start)
end = env.xt(d.finsh)
ypos = env.yScale(d.sub) - (env.lineHeight / 2)
clas = env.structData[i].clas
};
}
d3.select(".temp")
.attr("width", event.x)
.attr("x", start)
.attr("y", ypos)
.attr("height", env.lineHeight)
.attr("opacity", 0.5)
.attr("fill", "orange");
}
Insert cell
function dragendedr(event, d) {
d3.select(".temp")
.attr("width", 0)
.attr("x", 0)
.attr("y", 0)
.attr("height", 0)
.attr("fill", "none");



var clas = d3.select(this).attr("class");

if (typeof env.xt === 'undefined') {
env.xt = env.xScale
}


for (var i = 0; i < env.structData.length; i++) {
if ("righthand clasr " + env.structData[i].clas === clas) {
env.structData[i].finish = env.xt.invert(env.xt(env.structData[i].start) + event.x)
var sub = env.structData[i].sub
}
};

//
var subitems = env.structData.filter(function (d) { return d.sub === sub; });
subitems.sort(function (a, b) {
return d3.descending(a.start, b.start);
});
if (subitems.length > 1) {
var i, line, count = false, lines = [];
subitems.forEach(function (item) {
for (i = 0, line = 0; i < lines.length; i++ , line++) {
if (item.finish >= lines[i]) {
count = true
break;
}
}
lines[line] = item.start;
})
}
if (count) { resolveOverlaps(); }


env.structData.sort(function (a, b) {
return d3.ascending(a.order, b.order);
});
env.labels = getlabels()
env.nLines = getLines();
env.graphH = d3.min([env.nLines * env.maxLineHeight, env.maxHeight - env.margin.top - env.margin.bottom]);
renderAxises()
event = null
redraw();
}
Insert cell
function parseData(data) {
convertdata()
resolveOverlaps()
for (var t = 0; t < env.structData.length; t++) {
env.structData[t].order = t + 1
}
env.structData.sort(function (a, b) {
return d3.ascending(a.order, b.order);
});

env.labels = getlabels()
env.nLines = getLines();
env.graphW = env.width - env.margin.left - env.margin.right;
env.graphH = d3.min([env.nLines * env.maxLineHeight, env.maxHeight - env.margin.top - env.margin.bottom]);
env.g.attr("clip-path", "url(#clip)");
}
Insert cell

function buildDomStructure() {

env.graphW = env.width - env.margin.left - env.margin.right;

env.xScale.range([0, env.graphW])

env.svg.attr("width", env.width);

var axises = env.svg.append('g');

env.graph.attr("transform", "translate(" + env.margin.left + "," + env.margin.top + ")");

env.g.attr("transform", "translate(" + env.margin.left + "," + env.margin.top + ")");

axises.attr("class", "axises")
.attr("transform", "translate(" + env.margin.left + "," + env.margin.top + ")");

axises.append("g")
.attr("class", "x-axis");

axises.append("g")
.attr("class", "x-grid");

axises.append("g")
.attr("class", "y-axis")
.attr("transform", "translate(" + env.graphW + ", 0)");

axises.append("g")
.attr("class", "grp-axis");

env.xAxis.scale(env.xScale)
.ticks(Math.round(env.graphW * 0.011));

env.xGrid.scale(env.xScale)
.tickFormat("")
.ticks(env.xAxis.ticks()[0]);

env.yAxis.scale(env.yScale)
.tickSize(0);

env.grpAxis.scale(env.grpScale)
.tickSize(0);

}

Insert cell
function renderAxises() {

env.height = env.graphH + env.margin.top + env.margin.bottom
env.svg.transition().duration(100).attr('width', env.width).attr('height', env.height);

env.g.attr("transform", "translate(" + env.margin.left + "," + env.margin.top + ")");

var fontVerticalMargin = 0.6;

var fontSize = Math.min(12, env.graphH / env.flatData.length * fontVerticalMargin * Math.sqrt(2));
env.yScale
.domain(env.labels)
.range([env.graphH / env.labels.length * 0.5, env.graphH * (1 - 0.5 / env.labels.length)]);

env.yScale.invert = invertOrdinal;
env.grpScale.invert = invertOrdinal;

env.xGrid
.scale(env.xScale)
.tickSize(env.graphH)
// .ticks(d3.timeWeek, 1)
.tickFormat(d3.timeFormat("%b %d"));
env.xScale
.domain([d3.min(env.structData, function (d) { return d.start; }), d3.max(env.structData, function (d) { return d.finish; })])
.range([0, env.graphW]);


env.svg.select('g.x-grid')
.attr('transform', 'translate(0,' + env.graphH + ')')
.transition().duration(100)
.call(env.xGrid);


var groups = env.graph.selectAll('rect.series-group, g.series-group ').data(env.flatData, function (d) {
return d.category;
});

env.yAxis.tickValues(""); //labels

env.svg.select('g.y-axis')
.transition().duration(100)
.attr('transform', 'translate(' + env.graphW + ', 0)')
.style('font-size', fontSize + 'px')
.call(env.yAxis);

var minHeight = d3.min(env.grpScale.range(), function (d, i) {
return i > 0 ? d - env.grpScale.range()[i - 1] : d * 2;
});

env.grpAxis.tickFormat(function (d) { return d })
.ticks(0);

env.grpScale.domain(env.flatData.map(function (d) {
return d.category
}));

var cntLines = 0;
env.grpScale.range(env.flatData.map(function (d) {
var pos = (cntLines + d.count / 2) / env.nLines * env.graphH;
cntLines += d.count;
return pos;
}));

env.svg.select('g.grp-axis')
.transition()
.duration(100)
.style('font-size', fontSize + 'px')
.call(env.grpAxis);

groups.exit().transition().duration(500).style('stroke-opacity', 0).style('fill-opacity', 0).remove();

var newGroups = groups.enter()
.append('rect')
.attr('class', 'series-group')
.attr('x', 0)
.attr('y', 0)
.attr('height', 0)
.style('fill', 'url(#grad)');

newGroups.append('title').text('click-drag to pan or mouse wheel to zoom in/out');

groups = groups.merge(newGroups);

groups.transition().duration(500)
.attr('width', env.graphW)
.attr('height', function (d) { return env.graphH * d.count / env.nLines; })
.attr('y', function (d) { return env.grpScale(d.category) - env.graphH * d.count / env.nLines / 2; })


}
Insert cell
Insert cell
data = d3.csvParse(await FileAttachment("office move4@2.csv").text())
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more