Experimental support for gantt diagrams

This commit is contained in:
knsv 2015-02-20 19:06:15 +01:00
parent 1b016bd412
commit 2877501ff5
11 changed files with 8742 additions and 160 deletions

4239
dist/mermaid.full.js vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

4239
dist/mermaid.slim.js vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

15
src/diagrams/gantt/d3.js vendored Normal file
View File

@ -0,0 +1,15 @@
/* global window */
var d3;
if (require) {
try {
d3 = require("d3");
} catch (e) {}
}
if (!d3) {
d3 = window.d3;
}
module.exports = d3;

View File

@ -1,6 +1,7 @@
/**
* Created by knut on 15-01-14.
*/
var moment = require('moment');
var dateFormat = '';
var title = '';
@ -29,7 +30,7 @@ exports.setTitle = function(txt){
title = txt;
};
exports.gettitle = function(){
exports.getTitle = function(){
return title;
};
@ -48,12 +49,22 @@ exports.findTaskById = function(id) {
};
exports.getTasks=function(){
var i;
for(i=10000;i<tasks.length;i++){
tasks[i].startTime = moment(tasks[i].startTime).format('YYYY-MM-DD');
tasks[i].endTime = moment(tasks[i].endTime).format('YYYY-MM-DD');
}
return tasks;
};
var getStartDate = function(prevTime, dateFormat, str){
var moment = require('moment');
//console.log('Deciding start date:'+str);
//console.log('with dateformat:'+dateFormat);
str = str.trim();
// Test for after
var re = /^after\s+([\d\w\-]+)/;
var afterStatement = re.exec(str.trim());
@ -70,6 +81,9 @@ var getStartDate = function(prevTime, dateFormat, str){
// Check for actual date set
if(moment(str,dateFormat,true).isValid()){
return moment(str,dateFormat).toDate();
}else{
console.log('Invalid date:'+str);
console.log('With date format:'+dateFormat);
}
// Default date - now
@ -77,8 +91,8 @@ var getStartDate = function(prevTime, dateFormat, str){
};
var getEndDate = function(prevTime, dateFormat, str){
var moment = require('moment');
str = str.trim();
// Check for actual date
if(moment(str,dateFormat,true).isValid()){
return moment(str,dateFormat).toDate();
@ -127,7 +141,16 @@ var parseId = function(idStr){
// length
var compileData = function(prevTask, dataStr){
var data = dataStr.split(',');
var ds;
if(dataStr.substr(0,1) === ':'){
ds = dataStr.substr(1,dataStr.length);
}
else{
ds=dataStr;
}
var data = ds.split(',');
var task = {};
var df = exports.getDateFormat();
@ -150,6 +173,7 @@ var compileData = function(prevTask, dataStr){
default:
}
return task;
};
@ -159,7 +183,9 @@ exports.addTask = function(descr,data){
var newTask = {
section:currentSection,
description:descr
type:currentSection,
description:descr,
task:descr
};
var taskInfo = compileData(lastTask, data);
newTask.startTime = taskInfo.startTime;

View File

@ -2,38 +2,51 @@ var gantt = require('./parser/gantt').parser;
gantt.yy = require('./ganttDb');
var d3 = require('./d3');
var conf = {
titleTopMargin:25,
barHeight:20,
barGap:4,
topPadding:75,
sidePadding:75
};
module.exports.setConf = function(cnf){
var keys = Object.keys(cnf);
keys.forEach(function(key){
conf[key] = cnf[key];
});
};
var w;
module.exports.draw = function (text, id) {
gantt.yy.clear();
gantt.parse(text);
console.log(gantt.yy.getTasks());
var w = 1200;
var h = 400;
var elem = document.getElementById(id);
w = elem.offsetWidth;
if(typeof w === 'undefined'){
w = 800;
}
var svg = d3.select('#'+id)
.attr("width", w)
.attr("height", h);
/*var svg = d3.selectAll(".svg")
//.selectAll("svg")
.append("svg")
.attr("width", w)
.attr("height", h)
.attr("class", "svg");*/
var taskArray = gantt.yy.getTasks();
// Set height based on number of tasks
var h = taskArray.length*(conf.barHeight+conf.barGap)+2*conf.topPadding;
elem.style.height = h+'px';
var svg = d3.select('#'+id);
// http://codepen.io/anon/pen/azLvWR
var taskArray = gantt.yy.getTasks();
var dateFormat = d3.time.format("%Y-%m-%d");
// Set timescale
var timeScale = d3.time.scale()
.domain([d3.min(taskArray, function (d) {
return dateFormat.parse(d.startTime);
return d.startTime;
}),
d3.max(taskArray, function (d) {
return dateFormat.parse(d.endTime);
return d.endTime;
})])
.range([0, w - 150]);
@ -53,7 +66,7 @@ module.exports.draw = function (text, id) {
var title = svg.append("text")
.text(gantt.yy.getTitle())
.attr("x", w / 2)
.attr("y", 25)
.attr("y", conf.titleTopMargin)
.attr("text-anchor", "middle")
.attr("font-size", 18)
.attr("fill", "#009FFC");
@ -61,10 +74,10 @@ module.exports.draw = function (text, id) {
function makeGant(tasks, pageWidth, pageHeight) {
var barHeight = 20;
var gap = barHeight + 4;
var topPadding = 75;
var sidePadding = 75;
var barHeight = conf.barHeight;
var gap = barHeight + conf.barGap;
var topPadding = conf.topPadding;
var sidePadding = conf.sidePadding;
var colorScale = d3.scale.linear()
.domain([0, categories.length])
@ -96,7 +109,7 @@ module.exports.draw = function (text, id) {
.attr("stroke", "none")
.attr("fill", function (d) {
for (var i = 0; i < categories.length; i++) {
if (d.type == categories[i]) {
if (d.type === categories[i]) {
return d3.rgb(theColorScale(i));
}
}
@ -114,19 +127,19 @@ module.exports.draw = function (text, id) {
.attr("rx", 3)
.attr("ry", 3)
.attr("x", function (d) {
return timeScale(dateFormat.parse(d.startTime)) + theSidePad;
return timeScale(d.startTime) + theSidePad;
})
.attr("y", function (d, i) {
return i * theGap + theTopPad;
})
.attr("width", function (d) {
return (timeScale(dateFormat.parse(d.endTime)) - timeScale(dateFormat.parse(d.startTime)));
return (timeScale(d.endTime) - timeScale(d.startTime));
})
.attr("height", theBarHeight)
.attr("stroke", "none")
.attr("fill", function (d) {
for (var i = 0; i < categories.length; i++) {
if (d.type == categories[i]) {
if (d.type === categories[i]) {
return d3.rgb(theColorScale(i));
}
}
@ -137,80 +150,44 @@ module.exports.draw = function (text, id) {
.text(function (d) {
return d.task;
})
.attr("font-size", 11)
.attr("x", function (d) {
return (timeScale(dateFormat.parse(d.endTime)) - timeScale(dateFormat.parse(d.startTime))) / 2 + timeScale(dateFormat.parse(d.startTime)) + theSidePad;
var startX = timeScale(d.startTime),
endX = timeScale(d.endTime),
textWidth = this.getBBox().width;
// Check id text width > width of rectangle
if(textWidth>(endX-startX)){
if (endX + textWidth > w){
return startX + theSidePad;
}else {
return endX + theSidePad;
}
}else{
return (endX - startX) / 2 + startX + theSidePad;
}
})
.attr("y", function (d, i) {
return i * theGap + 14 + theTopPad;
})
.attr("font-size", 11)
.attr("text-anchor", "middle")
//.attr("text-anchor", "middle")
.attr("text-height", theBarHeight)
.attr("fill", "#fff");
rectText.on('mouseover', function (e) {
// console.log(this.x.animVal.getItem(this));
var tag = "";
if (typeof d3.select(this).data()[0].details !== 'undefined') {
tag = "Task: " + d3.select(this).data()[0].task + "<br/>" +
"Type: " + d3.select(this).data()[0].type + "<br/>" +
"Starts: " + d3.select(this).data()[0].startTime + "<br/>" +
"Ends: " + d3.select(this).data()[0].endTime + "<br/>" +
"Details: " + d3.select(this).data()[0].details;
} else {
tag = "Task: " + d3.select(this).data()[0].task + "<br/>" +
"Type: " + d3.select(this).data()[0].type + "<br/>" +
"Starts: " + d3.select(this).data()[0].startTime + "<br/>" +
"Ends: " + d3.select(this).data()[0].endTime;
}
var output = document.getElementById("tag");
var x = this.x.animVal.getItem(this) + "px";
var y = this.y.animVal.getItem(this) + 25 + "px";
output.innerHTML = tag;
output.style.top = y;
output.style.left = x;
output.style.display = "block";
}).on('mouseout', function () {
var output = document.getElementById("tag");
output.style.display = "none";
});
innerRects.on('mouseover', function (e) {
//console.log(this);
var tag = "";
if (typeof d3.select(this).data()[0].details !== 'undefined') {
tag = "Task: " + d3.select(this).data()[0].task + "<br/>" +
"Type: " + d3.select(this).data()[0].type + "<br/>" +
"Starts: " + d3.select(this).data()[0].startTime + "<br/>" +
"Ends: " + d3.select(this).data()[0].endTime + "<br/>" +
"Details: " + d3.select(this).data()[0].details;
} else {
tag = "Task: " + d3.select(this).data()[0].task + "<br/>" +
"Type: " + d3.select(this).data()[0].type + "<br/>" +
"Starts: " + d3.select(this).data()[0].startTime + "<br/>" +
"Ends: " + d3.select(this).data()[0].endTime;
}
var output = document.getElementById("tag");
var x = (this.x.animVal.value + this.width.animVal.value / 2) + "px";
var y = this.y.animVal.value + 25 + "px";
output.innerHTML = tag;
output.style.top = y;
output.style.left = x;
output.style.display = "block";
}).on('mouseout', function () {
var output = document.getElementById("tag");
output.style.display = "none";
});
.attr("class",function (d) {
var startX = timeScale(d.startTime),
endX = timeScale(d.endTime),
textWidth = this.getBBox().width;
// Check id text width > width of rectangle
if(textWidth>(endX-startX)) {
if (endX + textWidth > w){
return 'taskTextOutsideLeft';
}else {
return 'taskTextOutsideRight';
}
}else{
return 'taskText';
}
});
}
@ -220,7 +197,7 @@ module.exports.draw = function (text, id) {
var xAxis = d3.svg.axis()
.scale(timeScale)
.orient('bottom')
.ticks(d3.time.weeks, 5)
//.ticks(d3.time.days, 5)
.tickSize(-h + theTopPad + 20, 0, 0)
.tickFormat(d3.time.format('%d %b'));
@ -269,7 +246,7 @@ module.exports.draw = function (text, id) {
.attr("text-height", 14)
.attr("fill", function (d) {
for (var i = 0; i < categories.length; i++) {
if (d[0] == categories[i]) {
if (d[0] === categories[i]) {
// console.log("true!");
return d3.rgb(theColorScale(i)).darker();
}
@ -294,7 +271,9 @@ module.exports.draw = function (text, id) {
function getCounts(arr) {
var i = arr.length, // var to loop over
obj = {}; // obj to store results
while (i) obj[arr[--i]] = (obj[arr[i]] || 0) + 1; // count occurrences
while (i){
obj[arr[--i]] = (obj[arr[i]] || 0) + 1; // count occurrences
}
return obj;
}

View File

@ -11,6 +11,9 @@ var dotParser = require('./diagrams/flowchart/parser/dot');
var sequenceParser = require('./diagrams/sequenceDiagram/parser/sequenceDiagram');
var sequenceDb = require('./diagrams/sequenceDiagram/sequenceDb');
var infoDb = require('./diagrams/example/exampleDb');
var gantt = require('./diagrams/gantt/ganttRenderer');
var ganttParser = require('./diagrams/gantt/parser/gantt');
var ganttDb = require('./diagrams/gantt/ganttDb');
/**
* Function that parses a mermaid diagram defintion. If parsing fails the parseError callback is called and an error is
@ -38,6 +41,10 @@ var parse = function(text){
parser = infoParser;
parser.parser.yy = infoDb;
break;
case 'gantt':
parser = ganttParser;
parser.parser.yy = ganttDb;
break;
}
try{
@ -115,7 +122,10 @@ var init = function (sequenceConfig) {
break;
case 'sequenceDiagram':
seq.draw(txt,id);
// TODO - Get styles for sequence diagram
utils.cloneCssStyles(element.firstChild, []);
break;
case 'gantt':
gantt.draw(txt,id);
utils.cloneCssStyles(element.firstChild, []);
break;
case 'info':

View File

@ -27,6 +27,11 @@ module.exports.detectType = function(text,a){
return "info";
}
if(text.match(/^\s*gantt/)) {
//console.log('Detected info syntax');
return "gantt";
}
return "graph";
};

132
test/gantt.html Normal file
View File

@ -0,0 +1,132 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="../dist/mermaid.full.js"></script>
<link rel="stylesheet" href="seq.css"/>
<script>
var mermaid_config = {
startOnLoad:true
}
//mermaid.sequenceConfig = '{"diagramMarginX":50,"diagramMarginY":10,"actorMargin":50,"width":150,"height":45,"boxMargin":10,"boxTextMargin":5,"noteMargin":10,"messageMargin":35, "mirrorActors":false}';
//mermaid.sequenceConfig = JSON.parse('{"diagramMarginX":50,"diagramMarginY":10,"actorMargin":50,"width":150,"height":165,"boxMargin":10,"boxTextMargin":5,"noteMargin":10,"messageMargin":35}');
</script>
<script>
function apa(){
console.log('CLICKED');
}
</script>
<style>
* {
margin: 0;
padding: 0;
}
body {
background: #fff;
font-family: 'Open-Sans',sans-serif;
}
#container{
margin: 0 auto;
position: relative;
width:800px;
overflow: visible;
}
.svg {
width:800px;
height:400px;
overflow: visible;
position:absolute;
}
.grid .tick {
stroke: lightgrey;
opacity: 0.3;
shape-rendering: crispEdges;
}
.grid path {
stroke-width: 0;
}
#tag {
color: white;
background: #FA283D;
width: 150px;
position: absolute;
display: none;
padding:3px 6px;
margin-left: -80px;
font-size: 11px;
}
#tag:before {
border: solid transparent;
content: ' ';
height: 0;
left: 50%;
margin-left: -5px;
position: absolute;
width: 0;
border-width: 10px;
border-bottom-color: #FA283D;
top: -20px;
}
.taskText {
fill:white;
text-anchor:middle;
}
.taskTextOutsideRight {
fill:black;
text-anchor:start;
}
.taskTextOutsideLeft {
fill:black;
text-anchor:end;
}
</style>
</head>
<body>
<h1>No line breaks</h1>
<div class="mermaid">
gantt
dateFormat YYYY-MM-DD
title Adding GANTT diagram functionality to mermaid
section Design
Design jison grammar :des1, 2014-01-01, 2014-01-04
Create example text :des2, 2014-01-01, 3d
Bounce gantt example with users :des3, after des2, 5d
section Implementation
update build script :2014-01-02,24h
Implement parser and jison :after des1, 2d
Create tests for parser :3d
Create renderer :5d
Create tests for renderer :2d
Add to mermaid core bore tore gore bore lore :1d
section Documentation
Describe gantt syntax :a1, 2014-01-10, 3d
Add gantt diagram to demo page :after a1 , 20h
Add another diagram to demo page :after a1 , 48h
</div>
<div class="mermaid">
gantt
dateFormat YYYY-MM-DD
title A small test
section Documentation
Describe gantt syntax :a1, 2014-01-01, 30d
Add gantt diagram to demo page :after a1 , 20d
Add another diagram to demo page :after a1 , 48d
</div>
</body>
</html>

View File

@ -6,7 +6,8 @@
<script src="mermaid.full.js"></script>
<script>
var mermaid_config = {
startOnLoad:true
startOnLoad:true,
htmlLabels:false
}
mermaid.startOnLoad=true;
</script>