Place colliding elements next to each other

I am creating some sort of calendar / agenda that shows events for a specific day. Each event is displayed as an HTML element in a vertical clock grid. There can be several ("colliding") events at the same time, in which cases the elements must be horizontally adjacent to each other and of equal width. For example. four oncoming events get a column value of 4, thus a width of 25%.

The tricky part is these colliding events. I figured I worked it out, but some items are getting the wrong number of columns.

There might be a better way of counting the number of columns and placement - I'm open to suggestions.

Example image for the current (wrong) result: enter image description here

Relevant code:

<?php
    class Calendar {
        const ROW_HEIGHT = 24;

        public $events = array();
        public $blocks = array();


        public function calculate_blocks() {
            foreach($this->events as $event) {

                // Calculate the correct height and vertical placement
                $top = $this->time_to_pixels($event->_event_start_time);
                $bottom = $this->time_to_pixels($event->_event_end_time);
                $height = $bottom - $top;

                // Abort if there no height
                if(!$height) continue;

                $this->blocks[] = array(
                    'id' => $event->ID,
                    'columns' => 1,
                    'placement' => 0, // Column order, 0 = first
                    'css' => array(
                        'top' => $top,
                        'bottom' => $bottom, // bottom = top + height
                        'height' => $height
                    )
                );
            }

            $done = array();

            // Compare all the blocks with each other
            foreach($this->blocks as &$block) {
                foreach($this->blocks as &$sub) {

                    // Only compare two blocks once, and never compare a block with itself
                    if($block['id'] == $sub['id'] || (isset($done[$block['id']]) && in_array($sub['id'], $done[$block['id']])) || (isset($done[$sub['id']]) && in_array($block['id'], $done[$sub['id']]))) continue;
                    $done[$block['id']][] = $sub['id'];

                    // If the blocks are colliding
                    if(($sub['css']['top'] >= $block['css']['top'] && $sub['css']['top'] < $block['css']['bottom'])
                    || ($sub['css']['bottom'] >= $block['css']['top'] && $sub['css']['bottom'] < $block['css']['bottom'])
                    || ($sub['css']['top'] <= $block['css']['top'] && $sub['css']['bottom'] >= $block['css']['bottom'])) {

                        // Increase both blocks' columns and sub-block placement
                        $sub['columns'] = ++$block['columns'];
                        $sub['placement']++;
                    }
                }
            }
        }


        private function time_to_int($time) {

            // H:i:s (24-hour format)
            $hms = explode(':', $time);
            return ($hms[0] + ($hms[1] / 60) + ($hms[2] / 3600));
        }


        private function time_to_pixels($time) {
            $block = $this->time_to_int($time);

            return (int)round($block * self::ROW_HEIGHT * 2);
        }
    }
?>

      

+3


source to share


1 answer


Try the following:

public function calculate_blocks()
{
    $n          = count($events);
    $collumns   = array();
    $placements = array();

    // Set initial values.
    for ($i = 0; $i < $n; $i++)
    {
        $collumns[$i]   = 1;
        $placements[$i] = 0;
    }
    // Loop over all events.
    for ($i = 0; $i < $n; $i++)
    {
        $top1           = $this->time_to_pixels($events[$i]->_event_start_time);
        $bottom1        = $this->time_to_pixels($events[$i]->_event_end_time);

        // Check for collisions with events with higher indices.
        for ($j = $i + 1; $j < $n; $j++)
        {
            $top2     = $this->time_to_pixels($events[$k]->_event_start_time);
            $bottom2  = $this->time_to_pixels($events[$k]->_event_end_time);
            $collides = $top1 < $bottom2 && $top2 < $bottom1;

            // If there is a collision, increase the collumn count for both events and move the j'th event one place to the right.
            if ($collides)
            {
                $collumns[$i]++;
                $collumns[$j]++;
                $placements[$j]++;
            }
        }

        $this->blocks[] = array(
            'id'        => $events[$i]->ID,
            'columns'   => $collumns[$i],
            'placement' => $placements[$i],
            'css'       => array(
                'top'    => $top1,
                'bottom' => $bottom1,
                'height' => $bottom1 - $top1;
            )
        );
    }
}

      

I can't test it, but I think it should leave you with the correct block array.



Edit 1: Doesn't seem to give the desired result, see comments below.

Edit 2: I think this is the same problem: Rendering calendar events. Algorithm for composing events with maximum width . Someone solved it with C #, but it should be relatively easy to pipe this answer to PHP to solve your problem.

+1


source







All Articles