exupero's blog
RSSApps

Uncertain Gantt

In the previous post I created a Gantt chart that explored multiple scenarios branching off each other. Often, though, the ambiguity in a Gantt chart isn't so discrete, and events can happen within a range. To implement that, let's add a some uncertainty to when tasks start or end:

(def events
  '[[plan :starts 0]
    [plan :ends [plan :starts + 3] 1]
    [design :starts [plan :starts + 2] 1]
    [design :ends [design :starts + 5] 1]
    [coding :starts [design :starts + 3] 0.5]
    [coding :ends [coding :starts + 7] 0.5]
    [testing :starts [coding :starts + 1] 0.25]
    [testing :ends [coding :ends + 1] 0.25]
    [user-testing :starts [design :starts + 2] 0.25]
    [user-testing :ends [coding :ends + 2] 0.25]])

All of the relative events now have a final parameter that indicates how far ahead or behind the given time they may happen.

Let's update absolute-time to return early and late estimates:

(defn absolute-time [[entity attribute] events]
  (some (fn [[entity2 attribute2 definition plus-or-minus]]
          (when (= [entity attribute] [entity2 attribute2])
            (if (number? definition)
              [definition definition]
              (let [[entity3 attribute3 f d] definition
                    f (eval f)
                    [early late] (absolute-time [entity3 attribute3] events)]
                [(- (f early d) plus-or-minus)
                 (+ (f late d) plus-or-minus)]))))
        events))

Here are the estimated time ranges:

(map (fn [[entity attribute]]
       [entity attribute (absolute-time [entity attribute] events)])
     events)
([plan :starts [0 0]]
 [plan :ends [2 4]]
 [design :starts [1 3]]
 [design :ends [5 9]]
 [coding :starts [3.5 6.5]]
 [coding :ends [10.0 14.0]]
 [testing :starts [4.25 7.75]]
 [testing :ends [10.75 15.25]]
 [user-testing :starts [2.75 5.25]]
 [user-testing :ends [11.75 16.25]])

Let's restructure this to draw it:

(def data
  (->> events
    (map (fn [[entity attribute]]
           [entity attribute (absolute-time [entity attribute] events)]))
    (group-by first)
    (map (fn [[entity events]]
           [entity (into {} (map #(vec (drop 1 %))) events)]))))
([plan {:starts [0 0], :ends [2 4]}]
 [design {:starts [1 3], :ends [5 9]}]
 [coding {:starts [3.5 6.5], :ends [10.0 14.0]}]
 [testing {:starts [4.25 7.75], :ends [10.75 15.25]}]
 [user-testing {:starts [2.75 5.25], :ends [11.75 16.25]}])
012345678910111213141516plandesigncodingtestinguser-testing

The lighter areas show where there's uncertainty about the task starting or ending. In the dark areas, the task is expected to be in progress, but optimistically the task could start sooner, and pessimistically it could end later. During scheduling, it can be helpful to know the estimated range of possibilities rather than a concrete shot in the dark.

In the next post we'll look at a different application of this type of visualization.