process flow

d3 visualisation of a process flow

HTML

<div id="chart"></div>

CSS

.action {
    fill: #ccc;
    stroke: #000;
    stroke-width: 1.5px;
    cursor: pointer;
}
.action.done {
    fill: #cfc;
    stroke: #0b0;
}
.action.active {
    stroke: #bb0;
    fill: #ffc;
    filter: url(#active-glow);
}
.link {
    stroke: #000;
    stroke-width: 1.5px;
}
div.tooltip {
    position: absolute;
    text-align: center;
    width: 60px;
    height: 12px;
    padding: 8px;
    font: 10px sans-serif;
    background: #ddd;
    border: solid 1px #aaa;
    border-radius: 8px;
    pointer-events: none;
}

JavaScript

var process = [
    ['submit', [], 'done', 'Application submitted'],
    ['MS approve', ['submit'], 'active', 'Application verification'],
    ['RS approve', ['submit'], 'done', 'Resource approval'],
    ['invoice', ['MS approve', 'RS approve'], 'pending', 'Invoice issued'],
    ['payment', ['invoice'], 'pending', 'Payment received'],
    ['allocate', ['payment'], 'pending', 'Allocation made'],
    ['infopack', ['payment'], 'pending', 'Welcome letter sent'],
    ['complete', ['infopack', 'allocate'], 'pending', 'Application complete']
];

var flow = processFlow();
d3.select("#chart").datum(process).call(flow);

function processFlow() {
    // TODO: sort each column to minimise vertical cross-over
    // TODO: auto-height option based on a given ygap
    // TODO: fix ygap for entire chart, offset from centre line
    var width = 400,
        height = 75,
        margins = {
            top: 20,
            right: 20,
            bottom: 20,
            left: 20
        },
        xsize = width - margins.right - margins.left,
        ysize = (height - margins.top - margins.bottom);

    function my(selection) {
        selection.each(function (data) {
            var actions = {},
                columns = [],
                deps = [],
                nodes = [],
                maxHeight = 0;

            // Convert a list of steps into columns
            data.forEach(function (d) {
                var action = {
                    id: d[0],
                    deps: d[1].map(function (d) {
                        return actions[d];
                    }),
                    status: d[2]
                };
                action.column = d3.max(action.deps, function (d) {
                    return d.column + 1;
                });
                if (action.column === undefined) {
                    action.column = 0;
                }
                actions[action.id] = action;
                if (columns[action.column] === undefined) {
                   ...