Add dashboard
This commit is contained in:
247
assets/javascripts/timeknots.js
Normal file
247
assets/javascripts/timeknots.js
Normal file
@@ -0,0 +1,247 @@
|
||||
var TimeKnots = {
|
||||
draw: function(id, events, options) {
|
||||
// remove any exisitng elements
|
||||
d3.select(id).select("div").html("");
|
||||
|
||||
var TODAY = Date.now();
|
||||
var cfg = {
|
||||
width: 600,
|
||||
height: 200,
|
||||
radius: 10,
|
||||
lineWidth: 4,
|
||||
color: "#999",
|
||||
background: "#FFF",
|
||||
dateFormat: "%Y/%m/%d %H:%M:%S",
|
||||
horizontalLayout: true,
|
||||
showLabels: false,
|
||||
labelFormat: "%Y/%m/%d %H:%M:%S",
|
||||
addNow: false,
|
||||
seriesColor: d3.scale.category20(),
|
||||
dateDimension: true
|
||||
};
|
||||
|
||||
//default configuration overrid
|
||||
if (options != undefined) {
|
||||
for (var i in options) {
|
||||
cfg[i] = options[i];
|
||||
}
|
||||
}
|
||||
if (cfg.addNow != false) {
|
||||
events.push({
|
||||
date: new Date(),
|
||||
name: cfg.addNowLabel || "Today"
|
||||
});
|
||||
}
|
||||
//sort events ascending
|
||||
events.sort(function(a,b){
|
||||
return new Date(a.date) - new Date(b.date);
|
||||
});
|
||||
var svg = d3.select(id).select("div").append('svg').attr("width", cfg.width).attr("height", cfg.height);
|
||||
//Calculate times in terms of timestamps
|
||||
if (!cfg.dateDimension) {
|
||||
var timestamps = events.map(function(d) {
|
||||
return d.value
|
||||
}); //new Date(d.date).getTime()});
|
||||
var maxValue = d3.max(timestamps);
|
||||
var minValue = d3.min(timestamps);
|
||||
} else {
|
||||
var timestamps = events.map(function(d) {
|
||||
return Date.parse(d.date);
|
||||
}); //new Date(d.date).getTime()});
|
||||
var maxValue = d3.max(timestamps);
|
||||
var minValue = d3.min(timestamps);
|
||||
}
|
||||
var margin = (d3.max(events.map(function(d) {
|
||||
return d.radius
|
||||
})) || cfg.radius) * 1.5 + cfg.lineWidth;
|
||||
var step = (cfg.horizontalLayout) ? ((cfg.width - 2 * margin) / (maxValue - minValue)) : ((cfg.height - 2 * margin) / (maxValue - minValue));
|
||||
var series = [];
|
||||
if (maxValue == minValue) {
|
||||
step = 0;
|
||||
if (cfg.horizontalLayout) {
|
||||
margin = cfg.width / 2
|
||||
} else {
|
||||
margin = cfg.height / 2
|
||||
}
|
||||
}
|
||||
linePrevious = {
|
||||
x1: null,
|
||||
x2: null,
|
||||
y1: null,
|
||||
y2: null
|
||||
}
|
||||
|
||||
TIMELINE_LEFT_MARGIN = 130;
|
||||
TEXT_LEFT_MARGIN = 110;
|
||||
DATE_LEFT_MARGIN = 100;
|
||||
|
||||
svg.selectAll("line")
|
||||
.data(events).enter().append("line")
|
||||
.attr("class", "timeline-line")
|
||||
.attr("x1", function(d) {
|
||||
var ret;
|
||||
if (cfg.horizontalLayout) {
|
||||
var datum = (cfg.dateDimension) ? new Date(d.date).getTime() : d.value;
|
||||
ret = Math.floor(step * (datum - minValue) + margin) - TIMELINE_LEFT_MARGIN
|
||||
} else {
|
||||
ret = Math.floor(cfg.width / 2) - TIMELINE_LEFT_MARGIN
|
||||
}
|
||||
linePrevious.x1 = ret
|
||||
return ret
|
||||
})
|
||||
.attr("x2", function(d) {
|
||||
if (linePrevious.x1 != null) {
|
||||
return linePrevious.x1
|
||||
}
|
||||
if (cfg.horizontalLayout) {
|
||||
var datum = (cfg.dateDimension) ? new Date(d.date).getTime() : d.value;
|
||||
ret = Math.floor(step * (datum - minValue))
|
||||
}
|
||||
return Math.floor(cfg.width / 2) - TIMELINE_LEFT_MARGIN
|
||||
})
|
||||
.attr("y1", function(d) {
|
||||
var ret;
|
||||
if (cfg.horizontalLayout) {
|
||||
ret = Math.floor(cfg.height / 2)
|
||||
} else {
|
||||
var datum = (cfg.dateDimension) ? new Date(d.date).getTime() : d.value;
|
||||
ret = Math.floor(step * (datum - minValue)) + margin
|
||||
}
|
||||
linePrevious.y1 = ret
|
||||
return ret
|
||||
})
|
||||
.attr("y2", function(d) {
|
||||
if (linePrevious.y1 != null) {
|
||||
return linePrevious.y1
|
||||
}
|
||||
if (cfg.horizontalLayout) {
|
||||
return Math.floor(cfg.height / 2)
|
||||
}
|
||||
var datum = (cfg.dateDimension) ? new Date(d.date).getTime() : d.value;
|
||||
return Math.floor(step * (datum - minValue))
|
||||
})
|
||||
.style("stroke", function(d) {
|
||||
if (d.color != undefined) {
|
||||
return d.color
|
||||
}
|
||||
if (d.series != undefined) {
|
||||
if (series.indexOf(d.series) < 0) {
|
||||
series.push(d.series);
|
||||
}
|
||||
return cfg.seriesColor(series.indexOf(d.series));
|
||||
}
|
||||
return cfg.color
|
||||
})
|
||||
.style("stroke-width", cfg.lineWidth);
|
||||
|
||||
svg.selectAll("circle")
|
||||
.data(events).enter()
|
||||
.append("circle")
|
||||
.attr("class", "timeline-event")
|
||||
.attr("r", function(d) {
|
||||
if (d.radius != undefined) {
|
||||
return d.radius
|
||||
}
|
||||
return cfg.radius
|
||||
})
|
||||
.style("stroke", function(d) {
|
||||
if (d.color != undefined) {
|
||||
return d.color
|
||||
}
|
||||
if (d.series != undefined) {
|
||||
if (series.indexOf(d.series) < 0) {
|
||||
series.push(d.series);
|
||||
}
|
||||
console.log(d.series, series, series.indexOf(d.series));
|
||||
return cfg.seriesColor(series.indexOf(d.series));
|
||||
}
|
||||
return cfg.color
|
||||
})
|
||||
.style("stroke-width", function(d) {
|
||||
if (d.lineWidth != undefined) {
|
||||
return d.lineWidth
|
||||
}
|
||||
return cfg.lineWidth
|
||||
})
|
||||
.style("fill", function(d) {
|
||||
if (d.background != undefined) {
|
||||
return d.background
|
||||
}
|
||||
return cfg.background
|
||||
})
|
||||
.attr("cy", function(d) {
|
||||
if (cfg.horizontalLayout) {
|
||||
return Math.floor(cfg.height / 2)
|
||||
}
|
||||
var datum = (cfg.dateDimension) ? new Date(d.date).getTime() : d.value;
|
||||
return Math.floor(step * (datum - minValue) + margin)
|
||||
})
|
||||
.attr("cx", function(d) {
|
||||
if (cfg.horizontalLayout) {
|
||||
var datum = (cfg.dateDimension) ? new Date(d.date).getTime() : d.value;
|
||||
var x = Math.floor(step * (datum - minValue) + margin);
|
||||
return x;
|
||||
}
|
||||
return Math.floor(cfg.width / 2) - TIMELINE_LEFT_MARGIN
|
||||
});
|
||||
|
||||
svg.selectAll("text")
|
||||
.data(events).enter()
|
||||
.append('text').attr("class", "event-description")
|
||||
.html(function(d){
|
||||
return d.name;
|
||||
})
|
||||
.attr("x", function(d) {
|
||||
return Math.floor(cfg.width / 2) - TEXT_LEFT_MARGIN
|
||||
})
|
||||
.attr("y", function(d) {
|
||||
var datum = (cfg.dateDimension) ? new Date(d.date).getTime() : d.value;
|
||||
var y = Math.floor(step * (datum - minValue) + margin);
|
||||
return TimeKnots.checkAndFixCollision(svg, ".event-description", y, this);
|
||||
})
|
||||
.attr("text-anchor", "center")
|
||||
.style("opacity", function(d) {
|
||||
if (d.opacity != undefined) {
|
||||
return d.opacity
|
||||
}
|
||||
return 1
|
||||
});
|
||||
|
||||
svg.selectAll("p")
|
||||
.data(events).enter()
|
||||
.append('text').attr("class", "event-description-date")
|
||||
.html(function(d){
|
||||
if (moment(d.date).isSame(TODAY, 'day') && d.name !== "TODAY") {
|
||||
return "TODAY";
|
||||
}
|
||||
return moment(new Date(d.date)).format("MMM D");
|
||||
})
|
||||
.attr("x", function(d) {
|
||||
return Math.floor(cfg.width / 2) + DATE_LEFT_MARGIN
|
||||
})
|
||||
.attr("y", function(d) {
|
||||
var datum = (cfg.dateDimension) ? new Date(d.date).getTime() : d.value;
|
||||
var y = Math.floor(step * (datum - minValue) + margin);
|
||||
return TimeKnots.checkAndFixCollision(svg, ".event-description-date", y, this);
|
||||
})
|
||||
.attr("text-anchor", "right")
|
||||
.style("opacity", function(d) {
|
||||
if (d.opacity != undefined) {
|
||||
return d.opacity
|
||||
}
|
||||
return 1
|
||||
});
|
||||
},
|
||||
checkAndFixCollision: function(svg, id, y, that) {
|
||||
svg.selectAll(id).each(function() {
|
||||
if (this != that) {
|
||||
if (this.getAttribute('y') != null && (this.getAttribute('y') >= (y-10) && this.getAttribute('y') <= (y+10))) {
|
||||
y += 15 - 0.05*(this.getAttribute('y') - y);
|
||||
return y;
|
||||
}
|
||||
}
|
||||
});
|
||||
return y;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user