各イベントボックスの幅が最大になるようにカレンダーイベントをレイアウトするアルゴリズム(javascriptを使用してクライアント側で開発されますが、実際には問題ではありません。アルゴリズム自体にほとんど関心があります)についてサポートが必要です。次の写真をご覧ください。
Y軸は時間です。たとえば、「テストイベント」が正午に始まり、それと交差するものがない場合は、100%の幅全体が使用されます。 「ウィークリーレビュー」は「タンブリングYMCA」と「アンナ/アメリア」と交差していますが、後者の2つは交差していないため、すべて50%を占めています。 Test3、Test4、およびTest5はすべて交差しているため、最大幅はそれぞれ33.3%です。ただし、Test3は33%固定されているため(上記を参照)、Test7は66%であり、使用可能なすべてのスペース(66%)を使用します。
これをレイアウトするアルゴリズムが必要です。
前もって感謝します
/// Pick the left and right positions of each event, such that there are no overlap.
/// Step 3 in the algorithm.
void LayoutEvents(IEnumerable<Event> events)
{
var columns = new List<List<Event>>();
DateTime? lastEventEnding = null;
foreach (var ev in events.OrderBy(ev => ev.Start).ThenBy(ev => ev.End))
{
if (ev.Start >= lastEventEnding)
{
PackEvents(columns);
columns.Clear();
lastEventEnding = null;
}
bool placed = false;
foreach (var col in columns)
{
if (!col.Last().CollidesWith(ev))
{
col.Add(ev);
placed = true;
break;
}
}
if (!placed)
{
columns.Add(new List<Event> { ev });
}
if (lastEventEnding == null || ev.End > lastEventEnding.Value)
{
lastEventEnding = ev.End;
}
}
if (columns.Count > 0)
{
PackEvents(columns);
}
}
/// Set the left and right positions for each event in the connected group.
/// Step 4 in the algorithm.
void PackEvents(List<List<Event>> columns)
{
float numColumns = columns.Count;
int iColumn = 0;
foreach (var col in columns)
{
foreach (var ev in col)
{
int colSpan = ExpandEvent(ev, iColumn, columns);
ev.Left = iColumn / numColumns;
ev.Right = (iColumn + colSpan) / numColumns;
}
iColumn++;
}
}
/// Checks how many columns the event can expand into, without colliding with
/// other events.
/// Step 5 in the algorithm.
int ExpandEvent(Event ev, int iColumn, List<List<Event>> columns)
{
int colSpan = 1;
foreach (var col in columns.Skip(iColumn + 1))
{
foreach (var ev1 in col)
{
if (ev1.CollidesWith(ev))
{
return colSpan;
}
}
colSpan++;
}
return colSpan;
}
編集:イベントがソートされていると想定するのではなく、イベントをソートするようになりました。
Edit2:十分なスペースがある場合、イベントを右に展開します。
受け入れられた回答は、5つのステップのアルゴリズムを記述しています。受け入れられた回答のコメントにリンクされている実装例は、ステップ1から4のみを実装します。ステップ5は、右端のイベントが使用可能なすべてのスペースを使用することを確認することです。 OPが提供する画像のイベント7を参照してください。
説明したアルゴリズムのステップ5を追加して、特定の実装を拡張しました。
$( document ).ready( function( ) {
var column_index = 0;
$( '#timesheet-events .daysheet-container' ).each( function() {
var block_width = $(this).width();
var columns = [];
var lastEventEnding = null;
// Create an array of all events
var events = $('.bubble_selector', this).map(function(index, o) {
o = $(o);
var top = o.offset().top;
return {
'obj': o,
'top': top,
'bottom': top + o.height()
};
}).get();
// Sort it by starting time, and then by ending time.
events = events.sort(function(e1,e2) {
if (e1.top < e2.top) return -1;
if (e1.top > e2.top) return 1;
if (e1.bottom < e2.bottom) return -1;
if (e1.bottom > e2.bottom) return 1;
return 0;
});
// Iterate over the sorted array
$(events).each(function(index, e) {
// Check if a new event group needs to be started
if (lastEventEnding !== null && e.top >= lastEventEnding) {
// The latest event is later than any of the event in the
// current group. There is no overlap. Output the current
// event group and start a new event group.
PackEvents( columns, block_width );
columns = []; // This starts new event group.
lastEventEnding = null;
}
// Try to place the event inside the existing columns
var placed = false;
for (var i = 0; i < columns.length; i++) {
var col = columns[ i ];
if (!collidesWith( col[col.length-1], e ) ) {
col.Push(e);
placed = true;
break;
}
}
// It was not possible to place the event. Add a new column
// for the current event group.
if (!placed) {
columns.Push([e]);
}
// Remember the latest event end time of the current group.
// This is later used to determine if a new groups starts.
if (lastEventEnding === null || e.bottom > lastEventEnding) {
lastEventEnding = e.bottom;
}
});
if (columns.length > 0) {
PackEvents( columns, block_width );
}
});
});
// Function does the layout for a group of events.
function PackEvents( columns, block_width )
{
var n = columns.length;
for (var i = 0; i < n; i++) {
var col = columns[ i ];
for (var j = 0; j < col.length; j++)
{
var bubble = col[j];
var colSpan = ExpandEvent(bubble, i, columns);
bubble.obj.css( 'left', (i / n)*100 + '%' );
bubble.obj.css( 'width', block_width * colSpan / n - 1 );
}
}
}
// Check if two events collide.
function collidesWith( a, b )
{
return a.bottom > b.top && a.top < b.bottom;
}
// Expand events at the far right to use up any remaining space.
// Checks how many columns the event can expand into, without
// colliding with other events. Step 5 in the algorithm.
function ExpandEvent(ev, iColumn, columns)
{
var colSpan = 1;
// To see the output without event expansion, uncomment
// the line below. Watch column 3 in the output.
//return colSpan;
for (var i = iColumn + 1; i < columns.length; i++)
{
var col = columns[i];
for (var j = 0; j < col.length; j++)
{
var ev1 = col[j];
if (collidesWith(ev, ev1))
{
return colSpan;
}
}
colSpan++;
}
return colSpan;
}
実用的なデモは http://jsbin.com/detefuveta/edit?html,js,output 右端のイベントを展開する例については、出力の列3を参照してください。
PS:これは本当に受け入れられた答えへのコメントであるべきです。残念ながら、コメントする権限がありません。