1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-01-18 18:51:12 +01:00

Implement day view event clustering so that overlapping events can share latitude

Summary: Ref T4393, Implement day view event clustering so that overlapping events can share latitude.

Test Plan: Open day view in Calendar search query on a day with overlapping events. Overlapping events should be displayed side by side when possible, and should be layed out as a staircase when overlapping times.

Reviewers: #blessed_reviewers, epriestley

Reviewed By: #blessed_reviewers, epriestley

Subscribers: Korvin, epriestley

Maniphest Tasks: T4393

Differential Revision: https://secure.phabricator.com/D12711
This commit is contained in:
lkassianik 2015-05-04 21:23:18 -07:00
parent c5a734073d
commit 0aea2cb1c0
3 changed files with 104 additions and 22 deletions

View file

@ -387,6 +387,7 @@ final class PhabricatorCalendarEventSearchEngine
foreach ($statuses as $status) {
$event = new AphrontCalendarDayEventView();
$event->setEventID($status->getID());
$event->setEpochRange($status->getDateFrom(), $status->getDateTo());
$event->setName($status->getName());

View file

@ -2,11 +2,21 @@
final class AphrontCalendarDayEventView extends AphrontView {
private $eventID;
private $epochStart;
private $epochEnd;
private $name;
private $uri;
public function setEventID($event_id) {
$this->eventID = $event_id;
return $this;
}
public function getEventID() {
return $this->eventID;
}
public function setName($name) {
$this->name = $name;
return $this;

View file

@ -27,57 +27,74 @@ final class PHUICalendarDayView extends AphrontView {
$header_text = $day_of_week.', '.$header_text;
$day_box->setHeaderText($header_text);
$hours = $this->getHoursOfDay();
$hourly_events = array();
$rows = array();
// sort events into buckets by their start time
// pretend no events overlap
foreach ($hours as $hour) {
// time slot
$cell_time = phutil_tag(
'td',
array('class' => 'phui-calendar-day-hour'),
$hour->format('g A'));
$events = array();
$hour_start = $hour->format('U');
$hour_end = id(clone $hour)->modify('+1 hour')->format('U');
foreach ($this->events as $event) {
// check if start date is in hour slot
if ($event->getEpochStart() >= $hour_start
&& $event->getEpochStart() < $hour_end) {
$events[] = $event;
}
}
$count_events = count($events);
$event_boxes = array();
$n = 0;
// draw all events that start in this hour
// all times as epochs
foreach ($events as $event) {
$event_start = $event->getEpochStart();
$event_end = $event->getEpochEnd();
$offset = (($n / $count_events) * 100).'%';
$width = ((1 / $count_events) * 100).'%';
$top = ((($event_start - $hour_start) / ($hour_end - $hour_start))
* 100).'%';
$height = ((($event_end - $event_start) / ($hour_end - $hour_start))
* 100).'%';
$event_boxes[] = $this->drawEvent(
$event,
$offset,
$width,
$top,
$height);
$hourly_events[$event->getEventID()] = array(
'hour' => $hour,
'event' => $event,
'offset' => '0',
'width' => '100%',
'top' => $top,
'height' => $height,
);
$n++;
}
}
// events starting in time slot
$clusters = $this->findClusters();
foreach ($clusters as $cluster) {
$hourly_events = $this->updateEventsFromCluster(
$cluster,
$hourly_events);
}
// actually construct table
foreach ($hours as $hour) {
$drawn_hourly_events = array();
$cell_time = phutil_tag(
'td',
array('class' => 'phui-calendar-day-hour'),
$hour->format('g A'));
foreach ($hourly_events as $hourly_event) {
if ($hourly_event['hour'] == $hour) {
$drawn_hourly_events[] = $this->drawEvent(
$hourly_event['event'],
$hourly_event['offset'],
$hourly_event['width'],
$hourly_event['top'],
$hourly_event['height']);
}
}
$cell_event = phutil_tag(
'td',
array('class' => 'phui-calendar-day-events'),
$event_boxes);
$drawn_hourly_events);
$row = phutil_tag(
'tr',
@ -100,6 +117,25 @@ final class PHUICalendarDayView extends AphrontView {
}
private function updateEventsFromCluster($cluster, $hourly_events) {
$cluster_size = count($cluster);
$n = 0;
foreach ($cluster as $cluster_member) {
$event_id = $cluster_member->getEventID();
$offset = (($n / $cluster_size) * 100).'%';
$width = ((1 / $cluster_size) * 100).'%';
if (isset($hourly_events[$event_id])) {
$hourly_events[$event_id]['offset'] = $offset;
$hourly_events[$event_id]['width'] = $width;
}
$n++;
}
return $hourly_events;
}
private function drawEvent(
AphrontCalendarDayEventView $event,
$offset,
@ -171,4 +207,39 @@ final class PHUICalendarDayView extends AphrontView {
return $date;
}
private function findClusters() {
$events = msort($this->events, 'getEpochStart');
$clusters = array();
foreach ($events as $event) {
$destination_cluster_key = null;
$event_start = $event->getEpochStart();
$event_end = $event->getEpochEnd();
foreach ($clusters as $key => $cluster) {
foreach ($cluster as $clustered_event) {
$compare_event_start = $clustered_event->getEpochStart();
$compare_event_end = $clustered_event->getEpochEnd();
if ($event_start < $compare_event_end
&& $event_end > $compare_event_start) {
$destination_cluster_key = $key;
break;
}
}
}
if ($destination_cluster_key !== null) {
$clusters[$destination_cluster_key][] = $event;
} else {
$next_cluster = array();
$next_cluster[] = $event;
$clusters[] = $next_cluster;
}
}
return $clusters;
}
}