2012-12-04 47 views
24

从数据库中,我拉着Div的时间轴的某种起点和某个终点。其中一些重叠,其中一些可以彼此相邻。 from Database垂直对齐的div,但保持水平位置不变

最后,我想他们一起滑动,以便它尽可能紧凑这样的: what we want to accomplish

我怀疑如何处理这个难题:通过服务器端(PHP)脚本或一些javascript浮动脚本thingy。 或当然一个完全不同的方法

有人可以推动我在正确的方向吗?

编辑:: 重要的是,因为它是一个时间轴,div的水平位置保持不变。因此,所有浮动的div向左或inline-block的他们没有选择:)

我的数据库设置:

id | name | start | end 
1 | a | 2  | 7 
2 | b | 5  | 10 
etc 
+2

它称为甘特图 - 如果你谷歌,你会看到一些例子在php \ JS \ CSS等 – 2012-12-04 02:21:41

+0

不知道有这类图表的名字!谢谢你,虽然甘特图/项目图看起来是一样的,但它有根本的不同:​​你只在一行上有一个项目...... – stUrb

+0

块的颜色取决于什么? –

回答

2

你的问题听起来很天真,但它实际上包含了一些复杂的元素,如果需要以最优化的方式解决。

什么,我可能会做,以生成显示器的快速回答 -

  1. 使用所提供的功能,增加了行号到表
  2. 使用你的PHP代码来生成一个DIV容器 风格=“显示:块”的每一行

  3. 行产生适当大小DIV(端开始*刻度) 风格=内部“显示:内联块;浮动:左;显示:相对的”和(编辑:)添加透明的DIV元素来补偿你需要的空白区域。 (即从0开始,并从端部到下一个DIV的开始)

  4. 添加DIV元素

use mySchema; drop procedure if exists tileItems;

DELIMITER $$ 
CREATE PROCEDURE tileItems() 
BEGIN 
DECLARE p_id, p_start, p_end, p_row int; 
DECLARE done INT DEFAULT FALSE; 
DECLARE cur1 CURSOR FOR SELECT id, start, end FROM tasks order by start, id; 
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; 

drop temporary table if exists tiles; 

create temporary table tiles (
row int(11) NOT NULL, 
id int(11) NOT NULL, 
end int(11) NOT NULL 
); 
-- row field will indicates the row number the task should apear 
OPEN cur1; 
next_task: LOOP 

FETCH cur1 into p_id, p_start, p_end; 
    IF (done) THEN 
    LEAVE next_task; 
    END IF; 
select min(row) from (select row, max(end) me from tiles t2 group by row) t1 
    where me < p_start 
    into p_row; 


-- take care of row numbering 
IF (p_row IS NULL) then 
    select max(row) from tiles 
    into p_row; 
    IF (p_row IS NULL) then 
     SET p_row = 0; 
    END IF; 
    SET p_row=p_row+1; 
END IF; 

insert into tiles (id, row, end) 
    values (p_id,p_row,p_end); 

END LOOP; 

-- CLOSE cur1; 
-- here is your output, on the PHP/.Net code you should loop on the row 
select tasks.*, tiles.row from tasks 
inner join tiles 
on tasks.id = tiles.id 
order by tiles.row, tasks.start; 


END $$ 
DELIMITER ; 

这里内部名称字段是我使用的表检查它 -

CREATE TABLE `tasks` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 
    `name` varchar(50) COLLATE utf8_unicode_ci NOT NULL, 
    `start` int(11) NOT NULL, 
    `end` int(11) NOT NULL, 
    PRIMARY KEY (`id`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=11 ; 


INSERT INTO `tasks` (`id`, `name`, `start`, `end`) VALUES 
(1, 'A', 2, 6), 
(2, 'B', 5, 7), 
(3, 'C', 8, 10), 
(4, 'D', 1, 5), 
(5, 'E', 6, 7); 

有关优化的几句话(我的一个favo urite科目:) - 在这个代码中没有优化,这意味着任务将被分配到第一个可用的行。 为了尽量减少线路及其可能的数量(但还需要一段时间),以创建一个使用Heuristic的方法来解决这个问题的功能。

输出:

id name start end row 
4 D 1 5 1 
5 E 6 7 1 
3 C 8 10 1 
1 A 2 6 2 
2 B 5 7 3 
+0

,我发现在我的代码中的错误,我的工作就可以了 –

+0

固定。我也添加了输出示例 –

0

你试图显示他们inline与CSS?

div.inline { 
    display: inline-block; 
} 

你的div的类别是inline

+1

问题是他们不得不水平地呆在同一个地方。 – stUrb

+3

很酷的问题......你应该添加这个*非常重要的*你对上面的问题做出的评论。 –

0

这看起来像一个良好的情况下使用:

div.hours { 
    position: absolute; 
    top: 100px; 
    left: 155px; 
    width: 100px; 
} 

当然,更换100像素,155px,100px的与你的价值观。

或者,您可以尝试使用位于相对和左/宽度值位置的块内块的标签。

+0

O.K.这听起来很公平,但后来我需要某种算法将一些条放在一行上,而其他条则放在新条上。那算法就是我卡住的地方。 – stUrb

0

有没有简单的方法来做到这一点,你需要使用position: absolute并计算其中的事件会在页面可见,例如:一天全天有10px height10px width,我们需要在day 1添加事件,那么我们检查line 1 is empty或有事件,如果它是空的,那么put the event there,否则go to the next line and repeat直到它被分配在一行中。

0

你需要看看topological sorting算法,以及自己或者实现它们(在PHP或JavaScript),或试图找到一个合适的现成的实现。

CSS属性和表格布局都不会自动完美地解决这个问题。

1

试试这个:

function collides($block1,$block2) { 
    return (($block1['start'] >= $block2['start']) and ($block1['start'] < $block2['end'])) or 
     (($block1['end'] > $block2['start']) and ($block1['end'] <= $block2['end'])) or 
     (($block1['start'] <= $block2['start']) and ($block1['end'] => $block2['end'])); 
} 

function FitsInRow ($row,$block) { 
    $fits=true; 
    foreach ($row as $block1) 
     if (collides($block,$block1)) 
      $fits=false; 
    return $fits; 
} 

$rows=array(); 

// $blocks like that: 
$blocks[0]['start']=0; 
$blocks[0]['end']=10; 
$blocks[0]['name']='A'; 
$blocks[0]['color']='#F00'; 

$blocks[1]['start']=5; 
$blocks[1]['end']=20; 
$blocks[1]['name']='B'; 
$blocks[1]['color']='#0F0'; 
//etc 


foreach ($blocks as $block) { 
    $i=0; 
    while (isset($rows[$i]) && !FitsInRow($block,$rows[$i])) 
     $i++; 
    $rows[$i][]=$block; 
} 

echo '<div class="block_outer" style="height: '.(count($rows)*20).'px;">'; 
foreach ($rows as $nr=>$row) 
    foreach ($row as $block) 
     echo '<div class="block" style="left:'.$block['start'].'px; width:'.($block['end']-$block['start']).'px; top:'.($nr*20).'px; background-color:'.$block['color'].';">'.$block['name'].'</div>'; 
echo '</div>'; 

用下面的CSS:

.block { 
    position:absolute; 
    height:16px; 
    /* ... */ 
} 
.block_outer { 
    position:relative; 
    width:100%; 
    overflow:auto; 
} 

我没有测试过,虽然。

编辑:我已经更改了名称,以便它符合上述数据库设置。

另一编辑:现在我已经添加了外部div,因此内部div的绝对位置不会中断页面​​布局。

+0

谢谢!这是我正在寻找的!一个干净利落的服务器端解决方案! – stUrb

5

看看我的fiddle here。我认为它可以满足你需要的无数个块。块数据取自HTML表格。

JS:

var data = [], 
    rows = [], 
    chart = $('.wrapper-inner'); 


function DataItem(id, name, start, end){ 
    this.id = id; 
    this.name = name; 
    this.start = start; 
    this.end = end; 
} 

$('.data tr').each(function() { 
    var $this = $(this), 
     item = new DataItem($this.find('td:eq(0)').text(), 
          $this.find('td:eq(1)').text(), 
          $this.find('td:eq(2)').text(), 
          $this.find('td:eq(3)').text()); 
     data.push(item); 
}); 

function addRow(){ 
    var row = { 
     el : $('<div class="row"></div>').appendTo(chart), 
     positions: [] 
    }; 

    rows.push(row); 
} 

function checkRow(rowId, item){   
    var isRowAvailible = true; 

    for (var i = 0; i < +item.end - +item.start; i++){ 
     if (rows[rowId].positions[+item.start + i]){ 
      isRowAvailible = false; 
      break; 
     } 
    } 

    return isRowAvailible; 
} 

function markRowPositions(rowId, item){ 


    for (var i = 0; i < item.end - item.start; i++){ 
     rows[rowId].positions[+item.start + i] = true; 
    } 

} 

function addItems(){ 
    for (var i = 0; i < data.length; i++){ 
     (function(i){ 
      setTimeout(function() {addItem(data[i])}, 100 * i); 
     })(i) 
    } 
} 

function addItem(item){ 
    var rowToAdd = false, 
     itemEl = $('<div class="item"></div>'); 

    for (var i = 0; i < rows.length; i++){ 
     if (checkRow(i, item)){ 
      rowToAdd = i; 
      break;  
     } 
    } 

    if (rowToAdd === false){ 
     addRow(); 
     rowToAdd = rows.length - 1; 
    } 

    rows[ rowToAdd ].el.append(itemEl); 

     console.log(itemEl.css('opacity')) 

    itemEl.css({ 
     'left': item.start * 30, 
     'opacity' : 1, 
     'width': ((item.end - item.start) * 30) - 2 
    }); 


    markRowPositions(rowToAdd, item); 



} 

addItems(); 
+2

+1为你把这个 –

+0

感谢队友的工作量:)这是不难实际上因为我喜欢它,我认为它的工作原理非常酷。 –

13
<!DOCTYPE html> 
<html> 
<!-- 
    Created using jsbin.com 
    Source can be edited via http://jsbin.com/udofoq/26/edit 
--> 
<head> 
<script src="//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script> 
<meta charset=utf-8 /> 
<title>JS Bin</title> 
<style id="jsbin-css">div.blue { 
    background-color: #a4dcdf; 
} 

div.orange { 
    background-color: #fd9226; 
} 

div.green { 
    background-color: #88b37e; 
} 

div.yellow { 
    background-color: #d8d03f; 
} 

div.red { 
    background-color: #c16558; 
} 
div.grey { 
    background-color: #cdcdcd; 
} 
div.hours1{ 
    top: 0px; 
    left: 10px; 
    width: 100px;//(110-10) 
} 
div.hours2{ 
    top: 30px; 
    left: 80px; 
    width: 50px; 
} 
div.hours3{ 
    top: 60px; 
    left: 120px; 
    width: 50px; 
} 

div.hours4{ 
    top: 90px; 
    left: 5px; 
    width: 70px; 
} 

div.hours5{ 
    top: 120px; 
    left: 110px; 
    width: 30px; 
} 
div.hours6{ 
    top: 150px; 
    left: 130px; 
    width: 70px; 
} 
div.hours { 
    position: absolute; 
    height:20px; 
    color: white; 
    text-align:center; 
    border:white; 
    -webkit-box-shadow: 3px 3px 6px 2px rgba(00, 00, 00, .2); 
    box-shadow: 3px 3px 6px 2px rgba(00, 00, 00, .2); 
    font: bold 18px Arial, Helvetica, Geneva, sans-serif; 
    line-height:20px;  
} 

button{ 
    position:static; 
    margin-top:200px; 
} 
.collapse, 
.overlap1, 
.overlap2, 
.overlap3, 
reset{ 
    float:left; 
} 
</style></head> 
<body> 
    <div class="hours hours1 orange">A</div> 
<div class="hours hours2 yellow">B</div> 
<div class="hours hours3 blue">C</div> 
<div class="hours hours4 green">D</div> 
<div class="hours hours5 red">E</div> 
<div class="hours hours6 grey">F</div> 
<button class="collapse">collapse</button> 
<button class="overlap1">sort</button> 
<button class="reset">reset</button> 

<script> 
data1 = [ 
    [1, 10, 110], 
    [2, 80, 130], 
    [3, 120, 170], 
    [4, 5, 70], 
    [5, 110, 140], 
    [6, 130, 180] 
]; 

//just added for console output not needed 
var divider=""; 
for (var i = 0; i < 80; i++) { 
    divider += "_"; 
} 

console.log(divider); 
console.log("ORIGINAL ARRAY DATA1:", data1); 


//add a column to keep track of the row, to start set it to row 1 
data1 = $.each(data1, function(index, value) { 
    value[3] = 0; 
}); 

console.log(divider); 
console.log("ORIGINAL dataA WITH ADDED COLUMN:", data1); 

function timelinesort(dataA){ 

//make a new Array to store the elements in with their new row number 
var dataB = dataA.slice(0, 1); 

console.log(divider); 
console.log("INITIALIZED dataB WITH FIRST ELEMENT FROM dataA:", dataB); 


//initialize the counter 
var counter = 0; 

console.log(divider); 
console.log("INITIALIZED ROUNDS COUNTER:", counter); 


dataA = $.map(dataA, function(value1, index1) { 

//increment counter with 1 
counter++; 

console.log(divider); 
console.log("INCREMENTED ROUNDS COUNTER:", counter); 


    dataA = $.map(dataA, function(value2, index2) { 


    //exclude comparing an element with itself 
    if(value2 != dataB[0]) { 
     //check to see if elements overlap 
     if(value2[2] >= dataB[0][1] && value2[1] <= dataB[0][2]) { 
     console.log(divider); 
     console.log("Round " + counter + " from dataA: [" + value2 + "] overlaps with " + dataB[0] + " incrementing row counter with 1"); 
     //increment the value in column 3 (row counter) of the array 
     value2[3]++; 
     console.log(divider); 
     console.log("Now the dataA has changed to this:", dataA); 
     console.log("Meanwhile data1 has changed to this:", data1); 
     } else { 
     //if no overlap occurs check if the element is not already in the dataB array and if not check if it doesn't overlap with the existing elements 
     if($.inArray(value2, dataB) == -1) { 
      $.each(dataB, function(index3, value3) { 
      if(value3[2] >= value2[1] && value3[1] <= value2[2]) { 
       console.log(divider); 
       console.log("Round " + counter + " from dataA: [" + value2 + "] overlaps with " + value3 + " incrementing row counter with 1"); 
       dataB.pop(); 
       //increment the value in column 3 (row counter) of the array 
       value2[3]++; 
      } else { 
       //if no overlap occurs add the value to dataB 
       dataB.push(value2); 
       console.log(divider); 
       console.log("Added [" + value2 + "] to dataB and now dataB has changed to this: ", dataB); 
      } 
      }); 
     } else { 
      dataB.push(value2); 
      console.log("Added [" + value2 + "] to dataB and now dataB has changed to this: ", dataB); 
     } 
     } 
    } 
    return [value2]; 
    }); 
    dataA = jQuery.grep(dataA, function(item) { 
    return jQuery.inArray(item, dataB) < 0; 
    }); 
    if(dataA.length >= 1) { 
    dataB.unshift(dataA[0]); 
    dataB = dataB.splice(0, 1); 
    } else { 
    dataA = []; 
    } 

}); 

} 
//run the function 
timelinesort(data1); 

console.log(divider); 
console.log("Finally the data1 has changed to this:", data1); 


$(".collapse").click(function() { 
    $.each(data1, function(index, value) { 
    $("div.hours" + (index + 1)).animate({ 
     "top": 0 
    }, "slow"); 
    }); 

}); 

$(".overlap1").click(function() { 
    $.each(data1, function(index, value) { 
    console.log("div.hours" + (index + 1) + ":" + (value[3]) * 26); 
    $("div.hours" + (index + 1)).animate({ 
     "top": (value[3]) * 26 
    }, "slow"); 
    }); 

}); 

$(".reset").click(function() { 
    $.each(data1, function(index, value) { 
    $("div.hours" + (index + 1)).removeAttr('style'); 
    }); 

}); 
</script> 
</body> 
</html> 

我所做的就是崩溃的所有行到第一排,然后检查哪些与该行的原件重叠,如果是增加了重叠的那些行号是什么,然后转到下一行并重复该过程,直到所有元素整齐堆叠。

你仍然必须清理javascript/jquery的东西,并把它放在一个不错的功能左右。但作为概念验证它似乎工作

工作示例:


http://jsbin.com/udofoq/26/watch

http://jsfiddle.net/stofke/7VP5U/

+0

手动将块添加到html在作者用例中并不方便。恕我直言。 –

+0

你的意思是在CSS?这只是使用jQuery来将元素放置在应该去的地方。或者用PHP做。这并不是最难的部分。这很容易。它与我们用于排序的循环只是相同的,但除了顶部之外,您还可以设置左侧和宽度。这是额外的3线。我要补充的是,当我有一些时间留给 – Stofke

+0

我将开始把音乐时我的小提琴从现在开始;) –

2

这是最接近我能得到。它接收一个json_string,解析它并生成结果。这是完全动态的。元素按照它们在json数组中的顺序渐变。

Working Demo

希望这HEPS:

HTML:

<div id="divHolder"> 
    </div> 
    <input type="button" onclick="loadChart(json_data1);" value="Load Data 1" /> 
    <input type="button" onclick="loadChart(json_data2);" value="Load Data 2" /> 
    <input type="button" onclick="loadChart(json_data3);" value="Load Data 3" /> 

JS:

var json_data1 = '[{"id":1,"name":"A","start":2,"end":7},{"id":2,"name":"B","start":6,"end":9},{"id":3,"name":"C","start":8,"end":12},{"id":4,"name":"D","start":0,"end":5},{"id":5,"name":"E","start":7,"end":9}]'; 

var json_data2 = '[{"id":1,"name":"A","start":5,"end":7},{"id":2,"name":"B","start":6,"end":9},{"id":3,"name":"C","start":2,"end":6},{"id":4,"name":"D","start":2,"end":12},{"id":5,"name":"E","start":1,"end":9}, {"id":6,"name":"F","start":7,"end":11}, {"id":7,"name":"G","start":8,"end":12}, {"id":8,"name":"H","start":2,"end":4} ]'; 

var json_data3 = '[{"id":1,"name":"A","start":8,"end":12},{"id":2,"name":"B","start":4,"end":10},{"id":3,"name":"C","start":2,"end":4},{"id":4,"name":"D","start":0,"end":7},{"id":5,"name":"E","start":7,"end":11}, {"id":6,"name":"F","start":5,"end":7}]'; 

function loadChart(json_data){ 
    var data = JSON.parse(json_data); 
    var divHolder = $('#divHolder'); 
    var maxWidth = $(document).width() - 200; 
    var maxHeight = $(document).height(); 
    $(divHolder).empty(); 
    var maxEnd = 0; 
    var minStart = 0; 
    var widthUnit = 0; 
    $(data).each(function(){ 
     if(this.end > maxEnd) 
      maxEnd = this.end; 
     if(this.start < minStart) 
      minStart = this.start; 
    }); 
    widthUnit = maxWidth/(maxEnd - minStart) ; 
    var maxItemUnit = maxEnd; 
    var rows = new Array(); 
    $(data).each(function(){ 
     var added = false; 
     var currentObj = this; 
     var i; 
     for(i=0;i<rows.length;i++){ 
      var toAdd = true; 
      //if(widthSum> maxItemUnit    
      var widthSum = 0; 
      $(rows[i]).each(function(){ 
       widthSum += this.end - this.start; 
       if(this.end < currentObj.start || this.start > currentObj.end){ 
        if((widthSum + currentObj.end - currentObj.start) < maxItemUnit) 
         toAdd = true; 
       }        
       else{ 
        toAdd=false; 
        return false; 
       } 
      }); 
      if(toAdd){ 
       rows[i].push(currentObj); 
       added = true; 
       break; 
      } 
     } 
     if(!added){ 
      rows[i] = new Array(); 
      rows[i].push(currentObj); 
     } 
    }); 

    $(rows).each(function(){ 
     var current = this; 
     var divMain = $('<div></div>').css('display','block').css('width', maxWidth).css('height', 50) 
     $(current).each(function(){ 
      var div = $('<div></div>'); 
      div.addClass('item').html('<span class="item-content">'+this.name+'</span>'); 
      var diff = this.end - this.start; 
      div.attr('id', this.id).css('opacity','0').css('left', widthUnit * this.start).width(widthUnit * diff); 
      if(diff < (maxItemUnit/3)) 
       div.addClass('small'); 
      else if (diff < (maxItemUnit/2)) 
       div.addClass('medium'); 
      else 
       div.addClass('large'); 
      divMain.append(div); 
     }); 
     $(divHolder).append(divMain); 
    }); 
    var delayVar = 0; 
    $(data).each(function(){ 
     $('#'+this.id).fadeTo(delayVar, 1); 
     delayVar += 300; 
    }); 
} 

CSS:

.item{ 
    display:inline-block; 
    position:absolute; 
    color: #ffffff; 
    border: 1px solid white; 
} 
.item-content{ 
    margin-left: 50%; 
} 
.small{ 
    background-color:#E66C3D; 
} 
.medium{ 
    background-color:#DDD634; 
} 
.large{ 
    background-color:#3EC043; 
}