计算甘特图总持续时间

时间:2013-12-27 04:02:20

标签: javascript html duration gantt-chart

我正在创建一个类似甘特图(真正配置),需要计算总持续时间并验证可配置的持续时间。目标是用户可以在不知道日期的情况下构建甘特图,而是通过了解任务以及它们(松散地)相关的方式。在上一步中,用户添加任务并选择start&结束这些任务的步骤。步骤有固定的顺序。实际日期未知(或相关),但稍后将映射到步骤。

我见过的大多数甘特工具都依赖于知道开始/结束日期而不做计算。

如何计算总持续时间并确认持续时间无效?显然在某些情况下无法计算总数:如果活动之间存在未使用的步骤。当2个任务共享相同的开始和结束日期但具有不同的值时,将发生简单的无效持续时间。当2个或更多活动具有不同的开始/结束步骤和重叠时,会发生更复杂的问题。

我不是在寻找一个完整的解决方案(无论如何,它可能对我当前的代码没什么用处),但更多的是一般算法或方法。我认为递归解决方案是有意义的,但因为我用JS / jQuery / DOM做这个,我关注的是递归解决方案的性能,必须反复查找元素。我应该从结束还是开始计算?我应该按照每个步骤的开始/结束,直到我不再进一步或重新评估在中途通过哪个步骤添加到总持续时间?

这是当前标记的图片: screenshot

1 个答案:

答案 0 :(得分:1)

我会试着解释一下我做了什么。

我想跟随你必须了解一些要求。 这个交互式/可配置的甘特图/时间表被用作模板来估计生产时间表。

共有3件:

  • 步骤
  • 活动
  • 活动的持续时间,具体取决于应用计划的项目类型。

由于这是用于估算的模板,因此最初没有日期 - 只有任意持续时间与映射到步骤的活动相关联。但是,最终步骤会从导入的报告(或手动输入)映射到日期。

有3页用户以递增方式建立日程表:

  • 添加/编辑步骤:步骤是使用排序顺序值(推断)
  • 创建的行
  • 添加/编辑活动:一个矩阵,其中步骤为列,活动为行。每个交叉点都是一个复选框必须为每个活动选择开始和结束步骤。
  • 添加/编辑持续时间:选择项目类型,并为每个活动添加持续时间。

<强>类

  • Step [Name,StepOrder,..]
  • 活动[名称,StartStepID,StartStepOrder,EndStepID,EndStepOrder,..]
  • ActivityDuration:活动[持续时间,项目类型,..]

在MVC控制器/存储库中:

        // start by ordering the durations by the order of the steps
        var sortedActivities = durations
            .OrderBy(x => x.StartStepOrder)
            .ThenBy(x => x.EndStepOrder);

        // create func to get the path name from the old + new activity
        var getActivityPath = new Func<string, string, string>(
            (prevPath, activityID) =>
            {
                return string.IsNullOrEmpty(prevPath)
                    ? string.Format("{0}", activityID)
                    : string.Format("{0}.{1}", prevPath, activityID);
            });

        // create the recursive func we'll call to do all the work
        Action<List<ActivityDuration>, string, long?, IEnumerable<ActivityDuration>> buildPaths = null;
        buildPaths = (activities, path, startStepID, starts) =>
        {
            // activities will be null until we are joining gapped paths, 
            // so grab the activities with the provided startStepID
            if (starts == null)
                starts = activities.Where(x => x.StartStepID == startStepID);

            // each activity represents a new branch in the path
            foreach (var activity in starts)
            {
                var newPath = getActivityPath(path, activity.Id.ToString());

                // add the new path and it's ordered collection 
                // of activities to the collection
                if (string.IsNullOrEmpty(path))
                {
                    paths.Add(newPath, new ActivityDuration[] { activity });
                }
                else
                {
                    paths.Add(newPath, paths[path].Concat(new ActivityDuration[] { activity }));
                }

                // do this recursively providing the EndStepID as the new Start
                buildPaths(activities, newPath, activity.EndStepID, null);
            }

            // if there were any new branches, remove the previous 
            // path from the collection
            if (starts.Any() && !string.IsNullOrEmpty(path))
            {
                paths.Remove(path);
            }
        };


        // since the activities are in step order, the first activity's 
        // StartStepID will be where all paths start.
        var firstStepID = sortedActivities.FirstOrDefault().StartStepID;

        // call the recursive function starting with the first step
        buildPaths(sortedActivities.ToList(), null, firstStepID, null);

        // handle gaps in the paths after all the first connected ones have been pathed.
        //   :: ie - step 1,2 & 4,5 are mapped, but not step 3
        // these would be appended to the longest path with a step order < start step of the gapped activity's start step (!!!) 
        //   :: ie - the path should be 1-2,2-4,4-5)

        // because the list of paths can grow, we need to keep track of the count
        // and loop until there are no more paths added
        var beforeCount = paths.Count;
        var afterCount = beforeCount + 1;
        while (beforeCount < afterCount)
        {
            foreach (var path in paths.ToArray())
            {
                var lastActivity = path.Value.Last();
                // check for activities that start after the last one in each path .. 
                // .. that don't start on another activity's end step (because that would be a part of a path already)
                var gapped = sortedActivities
                    .Where(x => x.StartStepOrder > lastActivity.EndStepOrder)
                    .Where(thisAct =>
                        !sortedActivities
                            .Select(otherAct => otherAct.EndStepID)
                            .Contains(thisAct.StartStepID)
                    );

                beforeCount = paths.Count;
                // for each new gapped path, build paths as if it was specified by the previous activity
                buildPaths(sortedActivities.ToList(), path.Key, null, gapped);
                afterCount = paths.Count;
            }
        }

        // build up an object that can be returned to 
        // JS with summations and ordering already done.
        rValue = new ActivityPaths()
        {
            Paths = paths
                .Select(x => new ActivityPath()
                {
                    Path = x.Key,
                    ActivityDurations = x.Value,
                    TotalDuration = x.Value.Sum(y => y.Duration)
                })
                .OrderByDescending(x => x.TotalDuration)
        };

这种设计无疑存在一些缺点,但用例允许这样做。特别:   - 活动不能直接具有多个相关步骤 - 换句话说 - 2个步骤不能具有相同的步骤顺序。   - 如果2个路径具有相同的总持续时间,则只有一个路径将显示为关键路径。

由于映射到步骤的日期最终用于计算从给定时间点返回/前进到路径的末尾,这是可以的。提供所有日期后,如果需要,可以计算更准确的关键路径。

返回整个路径集,以便可以在javascript中实现某些验证。第一条路径将是关键的“一条”,此路径在UI中突出显示,同时显示关键路径的总持续时间: duration grid with critical path