Search in sources :

Example 26 with WBSNode

use of teamdash.wbs.WBSNode in project processdash by dtuma.

the class ProjectChangeListFactory method addNodeChange.

private void addNodeChange(TreeNodeChange<Integer, WBSNodeContent> tnc, Map<Integer, ProjectWbsNodeChange> nodeChanges, WBSModel wbs, Object changeType) {
    Integer parentID = tnc.getParentID();
    Integer nodeID = tnc.getNodeID();
    WBSNode parent = wbs.getNodeMap().get(parentID);
    WBSNode node = wbs.getNodeMap().get(nodeID);
    if (node == null || parent == null)
        // shouldn't happen
        return;
    // determine the effective author of this change. If it's different
    // than the current author, use a different ID to store the change in
    // the result map (so changes by different people don't get merged)
    String effAuthor = getAuthorOfNodeChange(node, changeType, author);
    int targetID = parentID;
    if (!effAuthor.equals(author))
        targetID |= effAuthor.hashCode() << 16;
    ProjectWbsNodeChange result = nodeChanges.get(targetID);
    if (result != null) {
        result.addChild(node, changeType);
    } else {
        result = new ProjectWbsNodeChange(parent, node, changeType, indivTimeAttrs, memberZeroAttrs, teamMemberNames, effAuthor, timestamp, wbsNodeComparator);
        nodeChanges.put(targetID, result);
    }
}
Also used : WBSNode(teamdash.wbs.WBSNode)

Example 27 with WBSNode

use of teamdash.wbs.WBSNode in project processdash by dtuma.

the class TeamTimeColumn method multiplyValuesUnder.

@Override
protected void multiplyValuesUnder(WBSNode topNode, double newTopDownValue, double oldTopDownValue, double ratio) {
    // our goal with this method is to scale the team time across subtasks
    // while still observing minimum time constraints.
    // find a list of the tasks under this node that need multiplying.
    // see if any of those nodes have minimum times set.
    Map<WBSNode, Double> weights = new HashMap();
    Map<WBSNode, Double> minTimes = new HashMap();
    double totalWeight = 0;
    for (WBSNode child : wbsModel.getDescendants(topNode)) {
        // do not make any changes to nodes that are hidden.
        if (child.isHidden())
            continue;
        // get the top-down time estimate for this node.
        double filt = getFilteredAmount(child);
        double val = child.getNumericAttribute(topDownAttrName) - filt;
        // look for a minimum time setting on this node.
        double minTime = WorkflowMinTimeColumn.getMinTimeAt(child);
        if (minTime > 0) {
            minTimes.put(child, minTime);
            // members would be very complex. Don't attempt it for now.
            if (filt > 0) {
                minTimes.clear();
                break;
            }
            // if the current node's time is the result of a previous "min
            // time" adjustment, allocate the node a weight based on the
            // time that was originally replaced by the minimum.
            double replacedTime = WorkflowMinTimeColumn.getReplacedTimeAt(child);
            if (!Double.isNaN(replacedTime))
                val = replacedTime;
        }
        // only scale nodes that have top-down values or minimum times
        if (!(val > 0 || minTime > 0))
            continue;
        // store the weight of this node in our map.
        weights.put(child, val);
        totalWeight += val;
        // if we previously saw a parent of this node, its top-down time
        // must have been a top-down-bottom-up mismatch. remove it from our
        // data structures and don't try to scale it.
        WBSNode parent = wbsModel.getParent(child);
        Double parentWeight = weights.remove(parent);
        if (parentWeight != null)
            totalWeight -= parentWeight;
    }
    // if we didn't find any min times, fall back to standard scaling logic.
    if (minTimes.isEmpty()) {
        super.multiplyValuesUnder(topNode, newTopDownValue, oldTopDownValue, ratio);
        return;
    }
    // we need to scale values, while respecting min times. Calculate the
    // weight-based time allocation for each node, and see if any of the
    // nodes will need a min time adjustment.
    Map<WBSNode, Double> minTimesToUse = new HashMap();
    Map<WBSNode, Double> fixedTimeWgtOverrides = new HashMap();
    double timeToSpread = newTopDownValue;
    double weightToSpread = totalWeight;
    while (true) {
        boolean madeChangeToMinTimesDuringThisPass = false;
        for (Entry<WBSNode, Double> e : weights.entrySet()) {
            WBSNode leaf = e.getKey();
            if (minTimesToUse.containsKey(leaf))
                continue;
            Double leafMinTime = minTimes.get(leaf);
            if (leafMinTime == null)
                continue;
            double leafWeight = e.getValue();
            if (leafWeight == 0)
                // track which leaves have a fixed time (e.g. zero %)
                fixedTimeWgtOverrides.put(leaf, null);
            double leafTime = timeToSpread * leafWeight / weightToSpread;
            if (leafTime < leafMinTime) {
                minTimesToUse.put(leaf, leafMinTime);
                timeToSpread -= leafMinTime;
                weightToSpread -= leafWeight;
                madeChangeToMinTimesDuringThisPass = true;
            }
        }
        if (madeChangeToMinTimesDuringThisPass == false)
            break;
    }
    // (and unexpectedly) flip down to zero.
    if (timeToSpread <= 0 || weightToSpread <= 0) {
        timeToSpread = newTopDownValue;
        weightToSpread = totalWeight;
        for (Entry<WBSNode, Double> e : fixedTimeWgtOverrides.entrySet()) {
            Double fixedTime = minTimes.remove(e.getKey());
            if (fixedTime == null)
                fixedTime = 1.0;
            double fixedWeight = fixedTime * totalWeight;
            weightToSpread += fixedWeight;
            e.setValue(fixedWeight);
        }
        minTimesToUse.clear();
    }
    // Subdivide the time over the leaf tasks, based on what we've found.
    for (Entry<WBSNode, Double> e : weights.entrySet()) {
        WBSNode leaf = e.getKey();
        Double weightOverride = fixedTimeWgtOverrides.get(leaf);
        double leafWeight = weightOverride != null ? weightOverride : e.getValue();
        double leafTime = timeToSpread * leafWeight / weightToSpread;
        Double leafMinTime = minTimesToUse.get(leaf);
        if (leafMinTime != null) {
            WorkflowMinTimeColumn.storeReplacedTimeAt(leaf, leafTime);
            leafTime = leafMinTime;
        } else if (weightOverride != null) {
            WorkflowMinTimeColumn.storeReplacedTimeAt(leaf, 0);
        } else {
            WorkflowMinTimeColumn.storeReplacedTimeAt(leaf, Double.NaN);
        }
        userChangingValue(leaf, leafTime);
        leaf.setNumericAttribute(topDownAttrName, leafTime + getFilteredAmount(leaf));
    }
}
Also used : ConcurrentHashMap(java.util.concurrent.ConcurrentHashMap) HashMap(java.util.HashMap) WBSNode(teamdash.wbs.WBSNode)

Example 28 with WBSNode

use of teamdash.wbs.WBSNode in project processdash by dtuma.

the class TopDownBottomUpColumn method filterChildren.

protected int filterChildren(WBSNode[] children) {
    int len = children.length;
    if (pruner == null)
        return len;
    int left = 0;
    int right = len - 1;
    while (true) {
        // find the leftmost child that needs to be pruned.
        while (left < len && !shouldPrune(children[left])) left++;
        if (left >= right)
            break;
        // find the rightmost child that doesn't need pruning.
        while (right > left && shouldPrune(children[right])) right--;
        if (left < right) {
            WBSNode temp = children[left];
            children[left] = children[right];
            children[right] = temp;
            left++;
            right--;
        }
    }
    return left;
}
Also used : WBSNode(teamdash.wbs.WBSNode)

Example 29 with WBSNode

use of teamdash.wbs.WBSNode in project processdash by dtuma.

the class TopDownBottomUpColumn method maybeMultiplyValues.

protected void maybeMultiplyValues(WBSNode node, double newValue, double oldValue) {
    if (topDownBottomUpMismatch(node))
        return;
    double ratio = newValue / oldValue;
    if (!Double.isNaN(ratio) && !Double.isInfinite(ratio) && ratio != 0) {
        multiplyValuesUnder(node, newValue, oldValue, ratio);
    } else {
        WBSNode delegate = getSingleLeafForNode(node, oldValue != 0);
        if (delegate != null) {
            // We have found a single leaf where the change should be made.
            // Go ahead and set its bottom up value. (This will eventually
            // be set by the recalculation logic, but this line allows the
            // getValueAt to return as a non-error value in the meantime.)
            double f = getFilteredAmount(delegate);
            delegate.setNumericAttribute(bottomUpAttrName, newValue + f);
            if (delegate != node) {
                // if the delegate is different from the target node, make
                // the change on the delegate. (If they are the same, these
                // lines are unnecessary because they will be performed by
                // the setValueAt logic after this method returns.)
                userChangingValue(delegate, newValue);
                delegate.setNumericAttribute(topDownAttrName, newValue + f);
            }
        }
    }
}
Also used : WBSNode(teamdash.wbs.WBSNode)

Example 30 with WBSNode

use of teamdash.wbs.WBSNode in project processdash by dtuma.

the class TeamActualTimeColumn method recalculate.

/** Recalculate data for a single node in the WBS.
     * 
     * With a single pass over the WBS, this method calculates actual time
     * for each team member and for the entire team; actual earned value,
     * completion date, percent complete, and percent spent.
     * 
     * @param node the node to recalculate
     * @param actualTime a result array, having one entry for each member of the
     *     team project.  This method should calculate (for each team member)
     *     the total actual time for this node and all children, and store the
     *     resulting value in corresponding field of this array.
     * @param earnedValue a single-entry result array.  This method should
     *     calculate the team earned value (for this node and all children,
     *     in hours), and return the result in the single field of this array.
     * @param completionDate a single-entry result array.  This method should
     *     calculate the actual completion date of this node and all children,
     *     and return the result in the single field of this array.
     */
private void recalculate(WBSNode node, double[] actualTime, TimeCalculator[] timeCalc, double[] earnedValue, long[] completionDate) {
    // get the list of children underneath this node
    WBSNode[] children = wbsModel.getChildren(node);
    boolean isLeaf = (children.length == 0);
    // load the actual node time for each individual into our working array
    for (int i = 0; i < teamSize; i++) actualTime[i] = nanToZero(node.getNumericAttribute(nodeTimeAttrs[i]));
    earnedValue[0] = 0;
    completionDate[0] = COMPL_DATE_NA;
    if (isLeaf) {
        int milestone = MilestoneColumn.getMilestoneID(node);
        // accumulate EV and completion date information for this leaf
        for (int i = 0; i < teamSize; i++) {
            // decide whether data from this team member should be included
            // in team sums
            boolean rollupMember = rollupEveryone || matchesTeamFilter[i];
            // retrieve the planned time for one team member.
            double memberPlanTime = nanToZero(node.getNumericAttribute(planTimeAttrs[i]));
            boolean assignedWithZero = (node.getAttribute(assignedWithZeroAttrs[i]) != null);
            if (memberPlanTime > 0 || assignedWithZero) {
                // if this team member is assigned to this leaf task, get
                // their actual completion date for the task.
                Date memberCompletionDate = (Date) node.getAttribute(completionDateAttrs[i]);
                // keep track of the max completion date so far.
                if (rollupMember)
                    completionDate[0] = mergeCompletionDate(completionDate[0], memberCompletionDate);
                // team has earned the value associated with the task.
                if (memberCompletionDate != null) {
                    if (rollupMember)
                        earnedValue[0] += memberPlanTime;
                    timeCalc[i].addCompletedTask(memberPlanTime, actualTime[i], milestone);
                } else {
                    // See if subtask data is present for this task
                    List<ActualSubtaskData> subtaskData = (List) node.getAttribute(subtaskDataAttrs[i]);
                    if (subtaskData != null && !subtaskData.isEmpty()) {
                        // if subtask data is present, handle it.
                        // if the time estimate in the personal dashboard is
                        // out of sync with the WBS, the subtask times will
                        // add up to a different value than memberPlanTime.
                        // calculate this ratio so we can adjust EV.
                        double subtaskPlanTotal = 0;
                        for (ActualSubtaskData subtask : subtaskData) subtaskPlanTotal += subtask.getPlanTime();
                        double ratio = memberPlanTime / subtaskPlanTotal;
                        if (Double.isInfinite(ratio) || Double.isNaN(ratio))
                            ratio = 0;
                        // record each subtask as an independent task.
                        for (ActualSubtaskData subtask : subtaskData) {
                            if (subtask.getCompletionDate() != null) {
                                // this subtask was completed
                                if (rollupMember)
                                    earnedValue[0] += subtask.getPlanTime() * ratio;
                                timeCalc[i].addCompletedTask(subtask.getPlanTime(), subtask.getActualTime(), milestone);
                            } else {
                                // this subtask is remaining
                                timeCalc[i].addRemainingTask(subtask.getPlanTime(), subtask.getActualTime(), milestone);
                            }
                        }
                    } else {
                        // there is no subtask data for this node. Just
                        // record a plain remaining task.
                        timeCalc[i].addRemainingTask(memberPlanTime, actualTime[i], milestone);
                    }
                }
            }
        }
    } else {
        double[] childTime = new double[teamSize];
        double[] childEarnedValue = new double[1];
        long[] childCompletionDate = new long[1];
        for (int i = 0; i < children.length; i++) {
            // ask our child to compute its time data
            recalculate(children[i], childTime, timeCalc, childEarnedValue, childCompletionDate);
            // if the child isn't hidden, add its values to our totals
            if (!children[i].isHidden()) {
                // accumulate time from that child into our total
                for (int j = 0; j < teamSize; j++) actualTime[j] += childTime[j];
                // accumulate EV related data from our children
                earnedValue[0] += childEarnedValue[0];
                completionDate[0] = Math.max(completionDate[0], childCompletionDate[0]);
            }
        }
    }
    double totalActualTime = 0;
    for (int i = 0; i < teamSize; i++) {
        // add up the actual time for all included team members
        if (rollupEveryone || matchesTeamFilter[i])
            totalActualTime += actualTime[i];
        // also store the total time per individual for this node
        node.setNumericAttribute(actTimeAttrs[i], actualTime[i]);
    }
    // store the actual time for the entire team for this node.
    node.setNumericAttribute(ACT_TIME_ATTR_NAME, totalActualTime);
    // retrieve the total plan time for this node from the TeamTimeColumn.
    double totalPlanTime = nanToZero(NumericDataValue.parse(dataModel.getValueAt(node, teamPlanTimeColumnNum)));
    // calculate and store the percent spent
    double percentSpent = totalActualTime / totalPlanTime;
    node.setNumericAttribute(PercentSpentColumn.RESULT_ATTR, percentSpent);
    // calculate and store the completion date and percent complete
    Date cd = null;
    double percentComplete = earnedValue[0] / totalPlanTime;
    if (completionDate[0] != COMPL_DATE_NA && completionDate[0] != INCOMPLETE) {
        cd = new Date(completionDate[0]);
        percentComplete = 1.0;
    }
    node.setAttribute(TeamCompletionDateColumn.ATTR_NAME, cd);
    node.setNumericAttribute(PercentCompleteColumn.RESULT_ATTR, percentComplete);
}
Also used : ArrayList(java.util.ArrayList) List(java.util.List) TeamMemberList(teamdash.team.TeamMemberList) ActualSubtaskData(teamdash.wbs.WBSSynchronizer.ActualSubtaskData) WBSNode(teamdash.wbs.WBSNode) Date(java.util.Date)

Aggregations

WBSNode (teamdash.wbs.WBSNode)40 ArrayList (java.util.ArrayList)6 HashMap (java.util.HashMap)3 HashSet (java.util.HashSet)2 ConcurrentHashMap (java.util.concurrent.ConcurrentHashMap)2 Change (net.sourceforge.processdash.ui.lib.autocomplete.AssignedToEditList.Change)2 WBSNodeContent (teamdash.wbs.AbstractWBSModelMerger.WBSNodeContent)2 DataTableModel (teamdash.wbs.DataTableModel)2 NumericDataValue (teamdash.wbs.NumericDataValue)2 Color (java.awt.Color)1 Date (java.util.Date)1 Iterator (java.util.Iterator)1 List (java.util.List)1 Random (java.util.Random)1 Matcher (java.util.regex.Matcher)1 AssignedToEditList (net.sourceforge.processdash.ui.lib.autocomplete.AssignedToEditList)1 HSSFRow (org.apache.poi.hssf.usermodel.HSSFRow)1 BlameCaretPos (teamdash.hist.BlameCaretPos)1 BlameModelData (teamdash.hist.BlameModelData)1 BlameNodeData (teamdash.hist.BlameNodeData)1