Search code examples
recursionelmgantt-chart

automatic gantt line numbering with elm


I am stuck with a functionality that i have already done in python long time ago.

I draw a gantt chart in a specific way that i can't reproduce with elm.

here is my code : https://ellie-app.com/8sYLsxTZHk5a1

The problem is in the "calcTaskPosition" function where i try to set the row of the task.

calcTaskPosition : Int -> Task -> List(Task)
calcTaskPosition row task =
    let
        precs = List.concatMap (calcTaskPosition (row+1)) (taskPrecs task)
    in
        { task  | col        = (Maybe.withDefault -1 <|
                                    List.maximum <|
                                        List.map (\t -> t.col) precs) + 1
                --, row        = row
        }
        :: precs

In my example, tasks lines are set by the initTask function. I wish to get the same task order whithout having to set explicit line position in the initTask function.


Solution

  • The first clue is when you look at svg you will notice that your "task1" is actually rendered twice. This is easier to see if you uncomment the line --, row = row in the snippet you posted.

    In elm (and other functional languages) your tasks will not be manipulated in-place, but instead your tasks will be copied when you mutate them. So it is not really useful to keep col and row values in the model (for now).
    Also, working with task ids makes more sense than directly linking objects.


    With this in mind, I would create two different task records: One for keeping it in the model (Task in my example) and one for rendering it (I called it DrawableTask).
    And then you need a transformation function like

    toDrawableListOfTasks : List Task -> List DrawableTask

    that will be called in the view.

    The transformation function essentially uses your tasksNotInTaskPrecs where you select all tasks that can be immediately drawn (because their precs list is empty). I generalized it and called it allDependenciesMet instead and use it on every iteration to select the tasks that can be drawn.

    All tasks that can be drawn will be added to a temporary list (in my case a dictionary for fast look-up of already entered tasks) and then the next iteration starts with all tasks that were not yet drawn. When no tasks are left, you can return the list and the rendering pass will traverse the list once again.

    order : Temp -> List Task -> List DrawableTask
    order temp todo =
        case List.partition (allDependenciesMet temp) todo of
            ( [], [] ) ->
                -- We are done and can return the list
                Dict.values temp
                    |> List.sortBy .row
    
            ( [], _ ) ->
                Debug.todo "An invalid list of tasks was passed"
    
            ( drawableTasks, nextTodo ) ->
                let
                    nextTemp =
                        List.indexedMap (toDrawable temp) drawableTasks
                            |> List.map (\t -> ( t.id, t ))
                            |> Dict.fromList
                            |> Dict.union temp
                in
                order nextTemp nextTodo
    

    I'm not sure if this is understandable, but you should be able to follow https://ellie-app.com/8tdzrgLfBfya1