简体   繁体   English

将碰撞元素放在一起

[英]Place colliding elements next to each other

I'm creating some kind of calendar/agenda which shows the events for a specific day. 我正在创建某种日历/日程表来显示特定日期的事件。 Each event is displayed as an HTML element in a vertical hours grid. 每个事件在垂直小时网格中显示为HTML元素。 There could be multiple ("colliding") events at the same time, and in those cases the elements should be placed next to each other, horizontally, and have equal widths. 可能同时存在多个(“碰撞”)事件,并且在这些情况下,元素应该彼此相邻放置,水平放置,并且具有相等的宽度。 Eg four colliding events get the column value 4, in this way the width of 25%. 例如,四个碰撞事件得到列值4,这样宽度为25%。

The tricky part is these colliding events. 棘手的部分是这些碰撞事件。 I thought I solved it, but some elements get the wrong number of columns. 我以为我解决了它,但是有些元素得到了错误的列数。

There might be a better way to calculate the column count and placement - I'm open to suggestions. 可能有更好的方法来计算列数和位置 - 我愿意接受建议。

Sample image for current (wrong) result: 当前(错误)结果的示例图像: 在此输入图像描述

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's 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's 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);
        }
    }
?>

Try this: 尝试这个:

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 actually test it, but I think it should leave you with a correct blocks array. 我实际上无法测试它,但我认为应该给你一个正确的块数组。

Edit 1: Doesn't seem to yield the required result, see comments below. 编辑1:似乎没有产生所需的结果,请参阅下面的评论。

Edit 2: I think this is the exact same problem: Visualization of calendar events. 编辑2:我认为这是完全相同的问题: 日历事件的可视化。 Algorithm to layout events with maximum width . 布局具有最大宽度的事件的算法 Someone solved it with C#, but it should be relatively easy to port that answer to PHP to solve your problem. 有人用C#解决了它,但将PHP的答案移植到解决你的问题应该相对容易。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM