Here's the basic outline I would use.
In the controller, use these variable members:
// Controller members
public Map<Id,Map<Id,Timecard__c[]>> projects { get; set; }
public Map<Id,SObject> records { get; set; }
Then, we load the data with a function (called in constructor is fine):
void loadData() {
projects = new Map<Id,Map<id,Timecard__c[]>>();
records = new Map<Id,SObject>();
for(Timecard__c record:[SELECT Id,Consultant__c,Project__c,TimeUsed__c FROM Timecard__c]) {
if(!projects.containskey(record.project__c)) {
records.put(record.project__c,null):
projects.put(record.project__c,new map<id,timecard__c[]>());
}
if(!projects.get(record.project__c).containskey(record.consultant__c)) {
records.put(record.project__c,null);
projects.get(record.project__c).put(record.consultant__c,new timecard__c[0]);
}
projects.get(record.project__c).get(record.consultant__c).add(record);
}
records.putAll([SELECT Id,Name FROM Consultant__c WHERE Id IN :records.keyset()]);
records.putAll([SELECT Id,Name FROM Project__c WHERE Id in :records.keyset()]);
}
From here, we can now display our information:
<apex:page controller="projectcontroller">
<ul>
<apex:repeat value="{!projects}" var="project">
<li>{!records[project]['name']}<ul>
<apex:repeat value="{!projects[project]}" var="consultant">
<li>{!records[consultant]['name']}<ul>
<apex:repeat value="{!projects[project][consultant]}" var="timecard">
<li>{!timecard.TimeUsed__c}</li>
</apex:repeat>
</ul></li>
</apex:repeat>
</ul></li>
</apex:repeat>
</ul>
</apex:page>
Feel free to modify as you need to, of course.