mirror of
https://github.com/CajunAvenger/cajunavenger.github.io.git
synced 2026-03-21 17:34:16 -05:00
1436 lines
48 KiB
JavaScript
1436 lines
48 KiB
JavaScript
var timelineObj = {
|
|
start:[],
|
|
start_date: null,
|
|
end: [],
|
|
end_date: null,
|
|
majorAxis: [1, 0],
|
|
plot: {
|
|
left:40, top:25, width:730, height:550, min_width:500,
|
|
"marker_width":20, "marker_height":3,
|
|
"milestone_width":60, "milestone_height":3,
|
|
"label_offset_y": -6, "label_offset_x": 25, "label_width": 150,
|
|
"bar_width": 40, "thread_offset": 10,
|
|
"axis_offset": 70, "axis_label_offset": -6, "bar_offset": 5,
|
|
"border_padding": 25, "font_scale": 100
|
|
},
|
|
calendar: ["year"],
|
|
label_pattern: "{Y}",
|
|
reverse: false,
|
|
threads: []
|
|
}
|
|
var defaults = {
|
|
"height-definer": 550,
|
|
"bar-width": 40,
|
|
"mile-width": 60,
|
|
"label-width": 150,
|
|
"label-offset": 25,
|
|
"thread-offset": 10,
|
|
"axis-offset": 70,
|
|
"axis-label-offset": -5,
|
|
"bar-offset": 5,
|
|
"font-scaler": 100
|
|
}
|
|
var editing_backup = {};
|
|
var thread_id = 0;
|
|
var event_id = 0;
|
|
var loaded = false;
|
|
var active_tab = "tab1";
|
|
var active_font = "";
|
|
var default_axis = true;
|
|
var cssCache = {};
|
|
var element_keeper = {
|
|
"year-marker": [],
|
|
"bar-1": [],
|
|
"milestone": [],
|
|
"label-1": []
|
|
}
|
|
var calendar_arrays = [
|
|
[],
|
|
["January", 31],
|
|
["February", 28, true],
|
|
["March", 31],
|
|
["April", 30],
|
|
["May", 31],
|
|
["June", 30],
|
|
["July", 31],
|
|
["August", 31],
|
|
["September", 30],
|
|
["October", 31],
|
|
["November", 30],
|
|
["December", 31]
|
|
]
|
|
var days_per_year = 365.2425;
|
|
|
|
//utility scripts
|
|
function arraymove(arr, fromIndex, toIndex) { // move an element in an array to a new index
|
|
var elem = arr[fromIndex];
|
|
arr.splice(fromIndex, 1);
|
|
arr.splice(toIndex, 0, elem);
|
|
}
|
|
|
|
// add new timeline objects
|
|
function newThread(name, color) { // add a new thread object
|
|
if(!name)
|
|
name = "Thread " + (timelineObj.threads.length+1);
|
|
if(!color)
|
|
color = "#000000"
|
|
var thread = {
|
|
id: thread_id++,
|
|
name: name,
|
|
index: timelineObj.threads.length,
|
|
color: color,
|
|
offset: 0,
|
|
events: [],
|
|
thread_offset: 0, // moves bar right (or left w/negative)
|
|
bar_offset: 0, // makes the bar wider (or thinner w/negative)
|
|
label_offset: 0, // makes labels further away (or closer w/negative)
|
|
label_width_offset: 0//makes labels wider (or thinner w/negative)
|
|
};
|
|
timelineObj.threads.push(thread);
|
|
if(thread.id != 0) {
|
|
// add the new CSS code for this thread
|
|
var pl = timelineObj.plot;
|
|
var start_left = pl.axis_offset + pl.bar_offset;
|
|
for(var i=0; i<thread.index; i++) {
|
|
var this_thread = timelineObj.threads[i];
|
|
start_left += pl.bar_width + this_thread.bar_offset;
|
|
start_left += pl.label_offset_x + this_thread.label_offset;
|
|
start_left += pl.label_width + this_thread.label_width_offset;
|
|
start_left += pl.thread_offset + this_thread.thread_offset;
|
|
}
|
|
var str = `.timeline .bar-${thread.id} {left: ${start_left}px;}`
|
|
start_left += pl.bar_width + thread.bar_offset;
|
|
start_left += pl.label_offset_x + thread.label_offset;
|
|
str += `.timeline .bar-${thread.id}-label {left: ${start_left}px; display:block;}`
|
|
str += `.timeline .timeline-milestone {width:${pl.milestone_width}px}`;
|
|
addCSSRule(str);
|
|
}
|
|
return thread;
|
|
}
|
|
function newEvent(start, end, label, thread, color) { // add a new event object
|
|
if(typeof start == "number")
|
|
start = numsToDate(start);
|
|
if(!end)
|
|
end = start;
|
|
if(typeof end == "number")
|
|
end = numsToDate(end);
|
|
if(!label)
|
|
label = "New Event";
|
|
if(start.getTime() > end.getTime())
|
|
[start, end] = [end, start];
|
|
var new_event = {
|
|
start: start,
|
|
end: end,
|
|
label: label,
|
|
label_offset_x: 0, // move label to the right (or left w/negative)
|
|
label_offset_y: 0, // move label up (or down w/negative)
|
|
id: event_id++
|
|
};
|
|
if(typeof thread == "number" || typeof thread == "string")
|
|
thread = getThread(thread);
|
|
if(color != thread.color)
|
|
new_event.color = color;
|
|
thread.events.push(new_event);
|
|
new_event.thread = thread;
|
|
var event_ele = getElement("bar-"+thread.id);
|
|
if(start.getTime() == end.getTime())
|
|
$("#"+event_ele.id).addClass("timeline-milestone");
|
|
new_event.ele_id = event_ele.id;
|
|
event_ele.onclick = function() {
|
|
console.log(new_event.label);
|
|
editEvent(this, new_event.id, thread);
|
|
}
|
|
renderEvent(new_event, event_ele);
|
|
return new_event;
|
|
}
|
|
function addCSSRule(cssCode, styleid) { // add a new CSS rule
|
|
$(`<style id="${styleid}">${cssCode}</style>`).appendTo("body");
|
|
}
|
|
|
|
// safely fetch timeline info
|
|
function getThread(ident) { // get a thread from its id or name
|
|
if(typeof ident == "number")
|
|
return timelineObj.threads[ident];
|
|
for(var thread in timelineObj.threads) {
|
|
if(timelineObj.threads[thread].name == ident)
|
|
return timelineObj.threads[thread];
|
|
}
|
|
return timelineObj.threads[0];
|
|
}
|
|
function getEvent(ident, thread) { // get an event from its id or label
|
|
if(!thread) {
|
|
// may have moved
|
|
for(let t in timelineObj.threads) {
|
|
for(let e in timelineObj.threads[t].events) {
|
|
if(timelineObj.threads[t].events[e].id == ident)
|
|
return timelineObj.threads[t].events[e];
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
for(let e in thread.events) {
|
|
if(thread.events[e].id == ident)
|
|
return thread.events[e];
|
|
}
|
|
return null;
|
|
}
|
|
function getElement(type, number) { // get element id="type-number", or create it
|
|
if(!element_keeper[type])
|
|
element_keeper[type] = [];
|
|
if(number === undefined)
|
|
number = element_keeper[type].length + 1;
|
|
var ele_name = type + "-" + String(number);
|
|
var ele = document.getElementById(ele_name);
|
|
if(ele == null) {
|
|
//create the element
|
|
ele = document.createElement("div");
|
|
ele.className = "timeline-element " + type;
|
|
if(type.match(/^bar-\d+$/))
|
|
ele.className += " timeline-bar";
|
|
ele.id = type + "-" + number;
|
|
document.getElementById("timeline").appendChild(ele);
|
|
element_keeper[type].push(ele.id);
|
|
}
|
|
return ele;
|
|
}
|
|
function yr_diff(d1, d2) { // fractional year difference between two dates
|
|
var ms_diff = Math.abs(d1.getTime() - d2.getTime());
|
|
return ms_diff / (86400000*days_per_year);
|
|
}
|
|
function yr_height() { // pixel height of yr_diff
|
|
return timelineObj.plot.height / yr_diff(timelineObj.start_date, timelineObj.end_date);
|
|
}
|
|
function findNormalOffset(type, thread) { // find the typical offset value for a type in a thread
|
|
if(typeof thread == "number")
|
|
thread = timelineObj.threads[thread];
|
|
var pl = timelineObj.plot
|
|
var norm = pl.bar_width + pl.label_offset_x + pl.label_width + pl.thread_offset;
|
|
var offs = {left:pl.axis_offset + pl.bar_offset}
|
|
for(let t in timelineObj.threads) {
|
|
if(timelineObj.threads[t] != thread) {
|
|
offs.left += norm;
|
|
offs.left += timelineObj.threads[t].bar_offset;
|
|
offs.left += timelineObj.threads[t].label_offset;
|
|
offs.left += timelineObj.threads[t].label_width_offset;
|
|
offs.left += timelineObj.threads[t].thread_offset;
|
|
}else{
|
|
//offs.left += thread.thread_offset;
|
|
if(type == "bar")
|
|
break;
|
|
offs.left += pl.bar_width + thread.bar_offset;
|
|
offs.left += pl.label_offset_x + thread.label_offset;
|
|
if(type == "label")
|
|
break;
|
|
offs.left += pl.label_width + thread.label_width_offset;
|
|
offs.left += thread.thread_offset;
|
|
break;
|
|
}
|
|
}
|
|
return offs;
|
|
}
|
|
function findCSSRule(selector, skip_cache) { // find the most relevant CSSRule for a selector
|
|
if(!skip_cache && cssCache[selector]) {
|
|
var sheet = document.styleSheets[cssCache[selector][0]];
|
|
var rules = sheet.cssRules || sheet.rules;
|
|
var rule = rules[cssCache[selector][1]];
|
|
if(rule && rule.selectorText == selector)
|
|
return rule;
|
|
}
|
|
for(var i=document.styleSheets.length-1; i>=0; i--) {
|
|
var sheet = document.styleSheets[i];
|
|
var rules = sheet.cssRules || sheet.rules;
|
|
for(var j=0; j<rules.length; j++) {
|
|
var rule = rules[j];
|
|
if(rule.selectorText == selector) {
|
|
cssCache[selector] = [i, j];
|
|
return rule;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// website scripts
|
|
function switchTab(ele) { // switch the tab when the user clicks it
|
|
if(!loaded || ele.id == active_tab)
|
|
return;
|
|
var map = {
|
|
"tab1": "#global-settings",
|
|
"tab2": "#thread-settings",
|
|
"tab3": (editing_backup.event ? "#edit-settings" : "#event-settings"),
|
|
"tab4": "#export-settings"
|
|
}
|
|
var tab = document.getElementById("live-tab");
|
|
switch(ele.id) {
|
|
case "tab1":
|
|
tab.style.left = 8;
|
|
break;
|
|
case "tab2":
|
|
tab.style.left = 84;
|
|
//show thread
|
|
break;
|
|
case "tab3":
|
|
tab.style.left = 160;
|
|
updateThreadNameDisplay();
|
|
//show event
|
|
break;
|
|
case "tab4":
|
|
tab.style.left = 238;
|
|
readyExports();
|
|
break;
|
|
}
|
|
//hide the old tab, show the new one
|
|
$(map[active_tab]).hide();
|
|
$(map[ele.id]).show();
|
|
active_tab = ele.id;
|
|
}
|
|
function documentLoaded() { // initialze jQuery and run startup sequence
|
|
// jQuery is safe
|
|
loaded = true;
|
|
// initialize our first thread
|
|
createThread();
|
|
return;
|
|
createThread(newThread("Karina", "#c444e6"));
|
|
updateThread({id:"thread-name", value:"Karina"})
|
|
|
|
// testing setup, import Karina, Lettie, and Zero
|
|
var y1 = 65, y2 = 69
|
|
timelineObj.start = [y1, 1, 1];
|
|
timelineObj.end = [y2, 1, 1];
|
|
timelineObj.start_date = numsToDate(y1, 1, 1);
|
|
timelineObj.end_date = numsToDate(y2, 1, 1)
|
|
var st = document.getElementById("startYear");
|
|
st.value = y1;
|
|
updateGlobals(st);
|
|
var et = document.getElementById("endYear");
|
|
et.value = y2;
|
|
updateGlobals(et);
|
|
var maj = document.getElementById("axis-value");
|
|
maj.value = 1;
|
|
updateGlobals(maj);
|
|
|
|
//newEvent(57.2, 57.2, "Karina is born", 0);
|
|
//newEvent(57.2, 65.5, "Living with her parents", 0);
|
|
newEvent(65.5, null, "Karina's parents die", 0, "#e26666");
|
|
newEvent(65.5, 67.3, "KXP (Festenya)", 0, "#e26666");
|
|
newEvent(67.3, null, "Okus takes over Karina", 0, "#e26666");
|
|
newEvent(67.4, 68.5, "Battle Boards", 0, "#e26666");
|
|
newEvent(68.2, null, "Karina gets Kirino's spark", 0, "c44444");
|
|
newEvent(68.2, 68.5, " ", 0, "c44444");
|
|
newEvent(69.4, null, "HVU", 0);
|
|
newEvent(69.7, null, "KXP (Tirvana and Flynn)", 0);
|
|
newEvent(71.5, 71.9, "MON", 0);
|
|
newEvent(72.3, 72.5, "CCR", 0);
|
|
newEvent(73.6, null, "The Extraction Project", 0);
|
|
newEvent(73.6, 75.1, "Karina's Explorations", 0);
|
|
newEvent(75.1, null, "Barhopping", 0);
|
|
newEvent(75.5, null, "Pentaga", 0);
|
|
newEvent(75.5, 75.9, " ", 0);
|
|
newEvent(75.8, null, "Tunnel of Love", 0);
|
|
newEvent(76.5, 76.8, "Skalor", 0);
|
|
|
|
createThread(newThread("Lettie", "#007700"));
|
|
newEvent(67.6, 67.8, "Feanav, early half", 1);
|
|
newEvent(68.3, 69, "Battle Boards", 1);
|
|
newEvent(68.5, null, "Occasion 1", 1);
|
|
//newEvent(68.6, null, "The Tragedy of Kaira Veis", 1);
|
|
newEvent(69, null, "Lettie vs Zero", 1);
|
|
newEvent(69.4, 69.7, "Feanav, latter half", 1);
|
|
newEvent(71.2, 72.9, "The Empty Throne of Feanav", 1);
|
|
|
|
createThread(newThread("Zero", "#5e547c"));
|
|
newEvent(68.2, 68.4, "Nulien", 2);
|
|
newEvent(68.6, null, "The Tragedy of Kaira Veis", 2);
|
|
newEvent(68.85, 69.2, "Return to Battle Boards", 2)
|
|
newEvent(69, null, "Lettie vs Zero", 2);
|
|
newEvent(69.2, null, "Elsewhere Invasion", 2);
|
|
newEvent(69.5, 70, "REZA", 2);
|
|
newEvent(70.2, 70.3, "NAMI", 2);
|
|
newEvent(70.4, 70.9, "Magic Champions somewhere", 2);
|
|
newEvent(72.5, 73.3, "LHIN", 2);
|
|
newEvent(73.8, null, "XAN", 2);
|
|
newEvent(74.5, null, "CARA", 2);
|
|
|
|
rebuildTimeline();
|
|
}
|
|
function updateThreadNameDisplay(thread) { // update the thread name on the evet tab
|
|
if(!thread)
|
|
thread = focusedThread();
|
|
document.getElementById("creating-thread-name").innerHTML = `${thread.index}: ${thread.name}`;
|
|
document.getElementById("editing-thread-name").innerHTML = `${thread.index}: ${thread.name}`;
|
|
}
|
|
// forms from the editor page
|
|
// general
|
|
function correctify(year, month, day) { // validate month and day inputs based on year
|
|
var mn = parseInt(month.value);
|
|
if(mn >= calendar_arrays.length) {
|
|
month.value = calendar_arrays.length-1;
|
|
mn = calendar_arrays.length-1;
|
|
}
|
|
if(!mn)
|
|
mn = 0;
|
|
if(mn < 0)
|
|
mn = 0;
|
|
if(parseInt(day.value) > calendar_arrays[mn][1]) {
|
|
// check leap year
|
|
if(calendar_arrays[mn][2]) {
|
|
var year = Math.trunc(year);
|
|
if(year%4==0 && !(year%100==0 && year%400==0)) {
|
|
day.value = calendar_arrays[mn][1] + 1;
|
|
return;
|
|
}
|
|
}
|
|
day.value = calendar_arrays[mn][1];
|
|
}
|
|
}
|
|
// main timeline
|
|
function resetDefault(id) { // reset global setting to its default
|
|
var ele = document.getElementById(id)
|
|
ele.value = defaults[id];
|
|
updateGlobals(ele);
|
|
}
|
|
function updateGlobals(inp, skip_build) { // process forms for global timeline options
|
|
var pl = timelineObj.plot;
|
|
switch(inp.id) {
|
|
case "btnApplyBoundary":
|
|
var sy = document.getElementById("startYear");
|
|
var sm = document.getElementById("startMonth");
|
|
var sd = document.getElementById("startDay");
|
|
var ey = document.getElementById("endYear");
|
|
var em = document.getElementById("endMonth");
|
|
var ed = document.getElementById("endDay");
|
|
correctify(sy.value, sm, sd);
|
|
correctify(ey.value, em, ed);
|
|
var start_ar = [parseInt(sy.value), parseInt(sm.value), parseInt(sd.value)];
|
|
var end_ar = [parseInt(ey.value), parseInt(em.value), parseInt(ed.value)];
|
|
var start_date = numsToDate(parseInt(sy.value), parseInt(sm.value), parseInt(sd.value));
|
|
var end_date = numsToDate(parseInt(ey.value), parseInt(em.value), parseInt(ed.value));
|
|
if(start_date.getTime() > end_date.getTime()) {
|
|
[start_ar, end_ar] = [end_ar, start_ar];
|
|
[start_date, end_date] = [end_date, start_date];
|
|
[sy.value, ey.value] = [ey.value, sy.value];
|
|
[sm.value, em.value] = [em.value, sm.value];
|
|
[sd.value, ed.value] = [ed.value, sd.value];
|
|
}
|
|
timelineObj.start = start_ar;
|
|
timelineObj.end = end_ar;
|
|
timelineObj.start_date = start_date;
|
|
timelineObj.end_date = end_date;
|
|
if(default_axis)
|
|
initialAxis();
|
|
break;
|
|
case "btnResetBoundary":
|
|
document.getElementById("startYear").value = timelineObj.start[0] || 0;
|
|
document.getElementById("startMonth").value = timelineObj.start[1] || 0;
|
|
document.getElementById("startDay").value = timelineObj.start[2] || 0;
|
|
document.getElementById("endYear").value = timelineObj.end[0] || 1;
|
|
document.getElementById("endMonth").value = timelineObj.end[1] || 0;
|
|
document.getElementById("endDay").value = timelineObj.end[2] || 0;
|
|
break;
|
|
case "axis-button":
|
|
let val = parseInt(document.getElementById("axis-value").value);
|
|
if(val < 1) {
|
|
val = 1;
|
|
inp.value = 1;
|
|
}
|
|
timelineObj.majorAxis = [val, document.getElementById("axis-unit").value]
|
|
break;
|
|
case "label-definer":
|
|
timelineObj.label_pattern = inp.value;
|
|
break;
|
|
case "height-definer":
|
|
var h = parseInt(inp.value);
|
|
if(h < 300) {
|
|
h = 300;
|
|
inp.value = 300;
|
|
}
|
|
timelineObj.plot.height = h;
|
|
document.getElementById("timeline").style.height = h + 2*timelineObj.plot.border_padding;
|
|
document.getElementById("axis").style.height = h;
|
|
break;
|
|
case "axis-style":
|
|
updateYearMarkers(inp.value);
|
|
break;
|
|
case "bar-width":
|
|
var w = parseInt(inp.value);
|
|
if(w < 1) {
|
|
w = 1;
|
|
inp.value = w;
|
|
}
|
|
var bar_rule = findCSSRule(".timeline .timeline-bar");
|
|
if(!bar_rule)
|
|
return;
|
|
bar_rule.style.width = w + "px";
|
|
pl.bar_width = w;
|
|
reformatThreadCSS();
|
|
break;
|
|
case "mile-width":
|
|
var w = parseInt(inp.value);
|
|
if(w < 1) {
|
|
w = 1;
|
|
inp.value = w;
|
|
}
|
|
var bar_rule = findCSSRule(".timeline .timeline-milestone", true);
|
|
if(!bar_rule)
|
|
return;
|
|
bar_rule.style.width = w + "px";
|
|
pl.milestone_width = w;
|
|
break;
|
|
case "label-width":
|
|
var w = parseInt(inp.value);
|
|
if(w < 0) {
|
|
w = 0;
|
|
inp.value = w;
|
|
}
|
|
var bar_rule = findCSSRule(".timeline .timeline-label");
|
|
if(!bar_rule)
|
|
return;
|
|
bar_rule.style.width = w + "px";
|
|
pl.label_width = w;
|
|
reformatThreadCSS();
|
|
break;
|
|
case "label-offset":
|
|
pl.label_offset_x = parseInt(inp.value);
|
|
reformatThreadCSS();
|
|
break;
|
|
case "thread-offset":
|
|
pl.thread_offset = parseInt(inp.value);
|
|
reformatThreadCSS();
|
|
break;
|
|
case "axis-offset":
|
|
// move the axis over
|
|
pl.axis_offset = parseInt(inp.value);
|
|
reformatThreadCSS();
|
|
break;
|
|
case "axis-label-offset":
|
|
// move axis label down
|
|
pl.axis_label_offset = parseInt(inp.value);
|
|
break;
|
|
case "bar-offset":
|
|
pl.bar_offset = parseInt(inp.value);
|
|
reformatThreadCSS();
|
|
break;
|
|
case "font-style":
|
|
if(active_font != "")
|
|
$("#"+inp.id).removeClass(active_font);
|
|
$("#"+inp.id).addClass(inp.value);
|
|
active_font = inp.value;
|
|
try{
|
|
findCSSRule(".timeline .timeline-label").style.fontFamily = findCSSRule("."+active_font).style.fontFamily;
|
|
}catch(e){}
|
|
break;
|
|
case "font-scaler":
|
|
var v = parseInt(inp.value)
|
|
if(v < 1) {
|
|
v = 1;
|
|
inp.value = 1;
|
|
}
|
|
findCSSRule(".timeline .timeline-label").style.fontSize = `${v/100}em`;
|
|
findCSSRule(".timeline .year-label").style.fontSize = `${v/100}em`;
|
|
pl.font_scale = v;
|
|
break;
|
|
}
|
|
if(!skip_build)
|
|
rebuildTimeline();
|
|
}
|
|
function initialAxis() { // generate a sensible starting major axis
|
|
var ms_diff = timelineObj.end_date.getTime() - timelineObj.start_date.getTime();
|
|
if(ms_diff == 0)
|
|
return; // evil
|
|
default_axis = false;
|
|
let days_diff = ms_diff / 86400000;
|
|
// less than 90 days, by day
|
|
// less than 5 years, by month
|
|
// aim at 10, max at 20
|
|
var divi = [2, days_diff];
|
|
if(days_diff >= 5*days_per_year) {
|
|
divi = [0, Math.floor(days_diff / days_per_year)];
|
|
}else if (days_diff >= 90) {
|
|
divi = [1, days_diff / 30]
|
|
}
|
|
var unit_diff = divi[1];
|
|
var holder = [unit_diff, 10];
|
|
for(var i=2; i<=20; i++) {
|
|
// optimize for least remainder, then closest to 10
|
|
var test = unit_diff % i;
|
|
if(test < holder[0]) {
|
|
holder = [test, i];
|
|
}else if(test == holder[0] && Math.abs(10-i) < Math.abs(10-holder[1])) {
|
|
holder = [test, i];
|
|
}
|
|
}
|
|
var optimal_divs = Math.floor(unit_diff / holder[1])
|
|
document.getElementById("axis-value").value = optimal_divs;
|
|
document.getElementById("axis-unit").value = divi[0];
|
|
timelineObj.majorAxis = [optimal_divs, divi[0]];
|
|
}
|
|
// threads
|
|
function updateThread(inp) { // process forms for thread options
|
|
var thread = focusedThread();
|
|
if(!thread && inp.id != "thread-index")
|
|
return;
|
|
switch(inp.id) {
|
|
case "thread-name":
|
|
thread.name = inp.value;
|
|
document.getElementById("pick-thread-"+thread.index).innerHTML = inp.value;
|
|
break;
|
|
case "thread-index":
|
|
var current_index = parseInt(document.getElementById("thread-selector").value);
|
|
thread = timelineObj.threads[current_index];
|
|
var new_index = parseInt(inp.value);
|
|
if(new_index < 0)
|
|
new_index = 0;
|
|
if(new_index >= timelineObj.threads.length)
|
|
new_index = timelineObj.threads.length-1;
|
|
if(new_index == current_index)
|
|
return;
|
|
arraymove(timelineObj.threads, current_index, new_index);
|
|
var ist = (new_index > current_index ? current_index : new_index);
|
|
var ien = (new_index < current_index ? current_index : new_index);
|
|
for(var i=ist; i<=ien; i++) {
|
|
var this_thread = timelineObj.threads[i];
|
|
this_thread.index = i;
|
|
var ele = document.getElementById('pick-thread-'+i);
|
|
ele.innerHTML = this_thread.name;
|
|
if(i == new_index)
|
|
ele.selected = true;
|
|
}
|
|
inp.value = new_index;
|
|
reformatThreadCSS();
|
|
rebuildTimeline();
|
|
break;
|
|
case "thread-color":
|
|
thread.color = inp.value;
|
|
renderThread(thread);
|
|
break;
|
|
case "thread-bar-width":
|
|
thread.bar_offset = parseInt(inp.value);
|
|
reformatThreadCSS();
|
|
break;
|
|
case "thread-label-width":
|
|
thread.label_width_offset = parseInt(inp.value);
|
|
reformatThreadCSS();
|
|
break;
|
|
case "thread-label-offset":
|
|
thread.label_offset = parseInt(inp.value);
|
|
reformatThreadCSS();
|
|
break;
|
|
case "thread-thread-offset":
|
|
thread.thread_offset = parseInt(inp.value);
|
|
reformatThreadCSS();
|
|
break;
|
|
}
|
|
}
|
|
function focusedThreadIndex() { // index of the currently viewed thread
|
|
return parseInt(document.getElementById("thread-index").value);
|
|
}
|
|
function focusedThread() { // object of the currently viewed thread
|
|
return getThread(focusedThreadIndex())
|
|
}
|
|
function createThread(thread) { // create new thread from button
|
|
if(!thread)
|
|
thread = newThread();
|
|
var sels = document.getElementsByClassName("thread-selector");
|
|
for(var e=0; e<sels.length; e++) {
|
|
var opt = document.createElement("option");
|
|
opt.value = thread.index;
|
|
opt.innerHTML = thread.name;
|
|
opt.id = "pick-thread-" + thread.index;
|
|
opt.selected = true;
|
|
sels.item(e).appendChild(opt);
|
|
}
|
|
openThreadOptions(thread);
|
|
return thread;
|
|
}
|
|
function focusThread(inp) { // display thread options from dropdown choice
|
|
if(typeof inp == "number") {
|
|
openThreadOptions(timelineObj.threads[inp], true);
|
|
}else{
|
|
openThreadOptions(timelineObj.threads[inp.value])
|
|
}
|
|
}
|
|
function openThreadOptions(thread, selec) { // update thread display to a particular thread
|
|
// change a thread's color, offset, index
|
|
// or add an event
|
|
document.getElementById("thread-name").value = thread.name;
|
|
document.getElementById("thread-color").value = thread.color;
|
|
document.getElementById("thread-index").value = thread.index;
|
|
document.getElementById("event-color").value = thread.color;
|
|
document.getElementById("thread-bar-width").value = thread.bar_offset;
|
|
document.getElementById("thread-label-width").value = thread.label_width_offset;
|
|
document.getElementById("thread-label-offset").value = thread.label_offset;
|
|
document.getElementById("thread-thread-offset").value = thread.thread_offset;
|
|
if(selec) {
|
|
document.getElementById("pick-thread-"+thread.id).selected = true;
|
|
}
|
|
}
|
|
function deleteThread(thread, force) { // delete the current thread
|
|
if(!thread)
|
|
thread = focusedThread();
|
|
if(force || confirm(`Are you sure you want to delete thread ${thread.index}: ${thread.name} and all of its events? This can't be undone.`)) {
|
|
// iterate delete event
|
|
// null out thread
|
|
// fix indexes
|
|
// rebuildTimeline
|
|
for(let even in thread.events) {
|
|
document.getElementById(thread.events[even].label_id).remove();
|
|
document.getElementById(thread.events[even].ele_id).remove();
|
|
delete thread.events[even].thread
|
|
}
|
|
timelineObj.threads.splice(timelineObj.threads.indexOf(thread), 1);
|
|
for(let t=0; t<timelineObj.threads-1; t++) {
|
|
timelineObj[threads][t].index = t;
|
|
}
|
|
delete thread.events;
|
|
reformatThreadCSS();
|
|
rebuildTimeline();
|
|
}
|
|
focusThread(0)
|
|
}
|
|
function mergeForm() { // merge from thread option form
|
|
var new_thread = timelineObj.threads[document.getElementById("merge-selector").value];
|
|
if(new_thread == focusedThread()) {
|
|
document.getElementById("merge-error").style.visibility = "";
|
|
return;
|
|
}
|
|
mergeThread(new_thread, document.getElementById("merge-color").value == "inherit");
|
|
focusThread(new_thread.index);
|
|
}
|
|
function mergeThread(host_thread, inherit_color) { // merge all the events from one thread into another
|
|
var thread = focusedThread();
|
|
for(var even in thread.events) {
|
|
migrateEvent(thread.events[even], host_thread, inherit_color);
|
|
}
|
|
deleteThread(thread, true);
|
|
}
|
|
// events
|
|
function createTimelineEvent() { // process submitted new event
|
|
var startY = parseFloat(document.getElementById("eventStartYear").value);
|
|
var endY = parseFloat(document.getElementById("eventEndYear").value);
|
|
|
|
var startM = document.getElementById("eventStartMonth");
|
|
var startD = document.getElementById("eventStartDay");
|
|
var endM = document.getElementById("eventEndMonth");
|
|
var endD = document.getElementById("eventEndDay");
|
|
|
|
var color = document.getElementById("event-color");
|
|
var label = document.getElementById("event-name");
|
|
correctify(startY, startM, startD);
|
|
correctify(endY, endM, endD);
|
|
var startDate = numsToDate(startY, parseInt(startM.value), parseInt(startD.value));
|
|
var endDate = numsToDate(endY, parseInt(endM.value), parseInt(endD.value));
|
|
var thread = focusedThread();
|
|
newEvent(startDate, endDate, label.value, thread, color.value);
|
|
// clear the info
|
|
document.getElementById("eventStartYear").value = "";
|
|
document.getElementById("eventEndYear").value = "";
|
|
startM.value = "";
|
|
startD.value = "";
|
|
endM.value = "";
|
|
endD.value = "";
|
|
color.value = thread.color || "#000000";
|
|
label.value = "";
|
|
}
|
|
function editEvent(event_ele, event_id, thread) { // begin event editing
|
|
var event_obj = getEvent(event_id, thread);
|
|
if(!event_obj)
|
|
return;
|
|
// if we're editing, clear unsaved changes
|
|
// preload the edit info and save a backup
|
|
// if we're not on this event's thread, switch to it
|
|
// if we're not on the event page, switch to it
|
|
// hide the create menu
|
|
// show the edit menu
|
|
// show Cancel button to return to create
|
|
resetEventEdit();
|
|
editing_backup = {};
|
|
editing_backup.start = new Date(event_obj.start.getTime());
|
|
editing_backup.end = new Date(event_obj.end.getTime());
|
|
editing_backup.color = "" + (event_obj.color || event_obj.thread.color);
|
|
editing_backup.label = "" + event_obj.label;
|
|
editing_backup.label_offset_x = 0 + event_obj.label_offset_x;
|
|
editing_backup.label_offset_y = 0 + event_obj.label_offset_y;
|
|
editing_backup.event = event_obj;
|
|
document.getElementById("edit-label").value = event_obj.label;
|
|
document.getElementById("edit-color").value = event_obj.color || event_obj.thread.color;
|
|
document.getElementById("edit-y").value = event_obj.label_offset_y;
|
|
document.getElementById("edit-x").value = event_obj.label_offset_x;
|
|
document.getElementById("edit-start-year").value = event_obj.start.getFullYear();
|
|
document.getElementById("edit-start-month").value = event_obj.start.getMonth()+1;
|
|
document.getElementById("edit-start-day").value = event_obj.start.getDate();
|
|
document.getElementById("edit-end-year").value = event_obj.end.getFullYear();
|
|
document.getElementById("edit-end-month").value = event_obj.end.getMonth()+1;
|
|
document.getElementById("edit-end-day").value = event_obj.end.getDate();
|
|
focusThread(thread.index);
|
|
updateThreadNameDisplay(thread);
|
|
switchTab({id:"tab3"})
|
|
$("#event-settings").hide();
|
|
$("#edit-settings").show();
|
|
}
|
|
function resetEventEdit(write) { // clear unsaved changes in an edit
|
|
var event_obj = editing_backup.event;
|
|
if(!event_obj)
|
|
return;
|
|
if(editing_backup.saved)
|
|
return;
|
|
event_obj.start = new Date(editing_backup.start.getTime());
|
|
event_obj.end = new Date(editing_backup.end.getTime());
|
|
event_obj.color = "" + editing_backup.color;
|
|
event_obj.label = "" + editing_backup.label;
|
|
event_obj.label_offset_x = 0 + editing_backup.label_offset_x;
|
|
event_obj.label_offset_y = 0 + editing_backup.label_offset_y;
|
|
if(write) {
|
|
document.getElementById("edit-label").value = event_obj.label;
|
|
document.getElementById("edit-color").value = event_obj.color || event_obj.thread.color;
|
|
document.getElementById("edit-y").value = event_obj.label_offset_y;
|
|
document.getElementById("edit-x").value = event_obj.label_offset_x;
|
|
document.getElementById("edit-start-year").value = event_obj.start.getFullYear();
|
|
document.getElementById("edit-start-month").value = event_obj.start.getMonth()+1;
|
|
document.getElementById("edit-start-day").value = event_obj.start.getDate();
|
|
document.getElementById("edit-end-year").value = event_obj.end.getFullYear();
|
|
document.getElementById("edit-end-month").value = event_obj.end.getMonth()+1;
|
|
document.getElementById("edit-end-day").value = event_obj.end.getDate();
|
|
}
|
|
renderEvent(event_obj);
|
|
}
|
|
function saveEventEdit() { // save changes in an edit
|
|
var event_obj = editing_backup.event;
|
|
if(!event_obj)
|
|
return;
|
|
editing_backup.start = new Date(event_obj.start.getTime());
|
|
editing_backup.end = new Date(event_obj.end.getTime());
|
|
editing_backup.color = "" + (event_obj.color || event_obj.thread.color);
|
|
editing_backup.label = "" + event_obj.label;
|
|
editing_backup.label_offset_x = 0 + event_obj.label_offset_x;
|
|
editing_backup.label_offset_y = 0 + event_obj.label_offset_y;
|
|
editing_backup.saved = true;
|
|
}
|
|
function deleteEventEdit() { // delete the event being edited
|
|
var event_obj = editing_backup.event;
|
|
if(!event_obj)
|
|
return;
|
|
if(confirm(`Delete event "${event_obj.label}"?`)) {
|
|
event_obj.thread.events.splice(event_obj.thread.events.indexOf(event_obj), 1);
|
|
document.getElementById(event_obj.label_id).remove();
|
|
document.getElementById(event_obj.ele_id).remove();
|
|
//todo return to main screen
|
|
}
|
|
}
|
|
function displayEdit(ele) { // process an edit preview
|
|
editing_backup.save = false;
|
|
switch(ele.id) {
|
|
case "btnPreview":
|
|
// change dates
|
|
var timey = document.getElementById("edit-start-year").value;
|
|
var timem = document.getElementById("edit-start-month");
|
|
var timed = document.getElementById("edit-start-day");
|
|
correctify(timey, timem, timed);
|
|
editing_backup.event.start = numsToDate(timey, timem.value, timed.value);
|
|
// change dates
|
|
timey = document.getElementById("edit-end-year").value;
|
|
timem = document.getElementById("edit-end-month");
|
|
timed = document.getElementById("edit-end-day");
|
|
correctify(timey, timem, timed);
|
|
editing_backup.event.end = numsToDate(timey, timem.value, timed.value);
|
|
var ele_id = editing_backup.event.ele_id;
|
|
if(editing_backup.event.start.getTime() == editing_backup.event.end.getTime()) {
|
|
$("#"+ele_id).addClass("timeline-milestone");
|
|
}else{
|
|
$("#"+ele_id).removeClass("timeline-milestone");
|
|
}
|
|
break;
|
|
case "edit-label":
|
|
// change label name
|
|
editing_backup.event.label = ele.value;
|
|
break;
|
|
case "edit-color":
|
|
// change color
|
|
if(ele.value == editing_backup.event.thread.color) {
|
|
delete editing_backup.event.color;
|
|
}else{
|
|
editing_backup.event.color = ele.value;
|
|
}
|
|
break;
|
|
case "edit-y":
|
|
// move label up/down
|
|
editing_backup.event.label_offset_y = parseInt(ele.value);
|
|
break;
|
|
case "edit-x":
|
|
// move label left/right
|
|
editing_backup.event.label_offset_x = parseInt(ele.value);
|
|
break;
|
|
}
|
|
renderEvent(editing_backup.event);
|
|
}
|
|
function cancelEditing() { // end event editing and return to the create menu
|
|
resetEventEdit();
|
|
editing_backup = {};
|
|
$("#edit-settings").hide();
|
|
$("#event-settings").show();
|
|
}
|
|
function migrateMetas(inp) { // update menus based on migrate options
|
|
switch(inp.id) {
|
|
case "migrate-selector":
|
|
var thread = timelineObj.threads[inp.value];
|
|
if(thread == focusedThread()) {
|
|
document.getElementById("migrate-error").style.visibility = "";
|
|
document.getElementById("btnMigrate").value = `Migrate Event`;
|
|
}else{
|
|
document.getElementById("btnMigrate").value = `Migrate Event to Thread ${thread.index}: ${thread.name}`;
|
|
document.getElementById("migrate-error").style.visibility = "hidden";
|
|
}
|
|
break;
|
|
case "merge-selector":
|
|
var thread = timelineObj.threads[inp.value];
|
|
if(thread == focusedThread()) {
|
|
document.getElementById("merge-error").style.visibility = "";
|
|
document.getElementById("btnMerge").value = `Merge Thread`;
|
|
}else{
|
|
document.getElementById("btnMerge").value = `Merge Thread into Thread ${thread.index}: ${thread.name}`;
|
|
document.getElementById("merge-error").style.visibility = "hidden";
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
function migrateEvent(event_obj, new_thread, inherit_color){// migrate event to another thread
|
|
var from_form = false;
|
|
if(!event_obj) {
|
|
// migrating from form
|
|
event_obj = editing_backup.event;
|
|
new_thread = timelineObj.threads[document.getElementById("migrate-selector").value];
|
|
inherit_color = document.getElementById("migrate-color").value == "inherit";
|
|
from_form = true;
|
|
}
|
|
var old_thread = event_obj.thread;
|
|
if(old_thread == new_thread) {
|
|
document.getElementById("migrate-error").style.visibility = "";
|
|
return;
|
|
}
|
|
var color;
|
|
if(inherit_color && event_obj.color) {
|
|
color = new_thread.color || "";
|
|
}else if(!inherit_color && !event_obj.color) {
|
|
color = old_thread.color || "";
|
|
}
|
|
|
|
var migrated = newEvent(event_obj.start, event_obj.end, event_obj.label, new_thread, color);
|
|
old_thread.events.splice(old_thread.events.indexOf(event_obj), 1);
|
|
document.getElementById(event_obj.label_id).remove();
|
|
document.getElementById(event_obj.ele_id).remove();
|
|
if(from_form) {
|
|
editing_backup = {}
|
|
editEvent(migrated.ele_id, migrated.id, new_thread);
|
|
}
|
|
}
|
|
// date handling
|
|
function numsToDate(trialY, trialM, trialD) { // convert three test numbers to a Date
|
|
var dy = deciYearToDates(trialY);
|
|
if(dy[0] > 0)
|
|
dy[0]--; // zero index the month
|
|
if(trialM) {
|
|
dy[0] = parseInt(trialM);
|
|
if(dy[0] != 0)
|
|
dy[0]--; // treat 0 as January in this case
|
|
}
|
|
if(trialD)
|
|
dy[1] = trialD;
|
|
if(!dy[0] || isNaN(dy[0]))
|
|
dy[0] = 0;
|
|
if(!dy[1] || isNaN(dy[1]))
|
|
dy[1] = 1;
|
|
var date = new Date(Math.trunc(trialY), dy[0], dy[1]);
|
|
date.setFullYear(Math.trunc(trialY));
|
|
return date;
|
|
}
|
|
function deciYearToDates(deciYear) { // convert deciyear (ie 1990.3) to [month, date] numbers
|
|
var deci = deciYear - Math.trunc(deciYear);
|
|
if(deci == 0)
|
|
return [0, 1];
|
|
var days = Math.floor(deci * days_per_year);
|
|
for(var m=1; m<calendar_arrays.length; m++) {
|
|
var c_a = calendar_arrays[m];
|
|
if(c_a[1] > days)
|
|
return [m, days];
|
|
days -= c_a[1];
|
|
}
|
|
return [m, days];
|
|
}
|
|
function arrayToDateString(source) { // convert array of [YYYY, M, D] to YYYY-MM-DD string if possible
|
|
var year = String(Math.trunc(source[0])).padStart(4, '0');
|
|
if(year.length > 4)
|
|
return "";
|
|
var month = source[1]
|
|
var day = source[2]
|
|
if(month == undefined || day == undefined || (month == 0 && day == 0)) {
|
|
var dy = deciYearToDates(source[0]);
|
|
month = dy[0];
|
|
day = dy[1];
|
|
}
|
|
month = String(month).padStart(2, '0');
|
|
day = String(day).padStart(2, '0');
|
|
if(month == "00") {
|
|
month = "01";
|
|
}
|
|
if(day == "00") {
|
|
day = "02";
|
|
}
|
|
return `${year}-${month}-${day}`;
|
|
}
|
|
function nextMajorTime(date) { // returns the Date of the next majorAxis time after this one
|
|
var dy = date.getFullYear();
|
|
var dm = date.getMonth();
|
|
var dd = date.getDate();
|
|
var ar = [dy, dm, dd];
|
|
if(timelineObj.reverse) {
|
|
ar[timelineObj.majorAxis[1]] -= timelineObj.majorAxis[0];
|
|
}else{
|
|
ar[timelineObj.majorAxis[1]] += timelineObj.majorAxis[0];
|
|
}
|
|
while(ar[1] >= calendar_arrays.length-1) {
|
|
ar[1] -= calendar_arrays.length-1;
|
|
ar[0] += 1;
|
|
}
|
|
d = new Date(ar[0], ar[1], ar[2]);
|
|
if(dy < 100)
|
|
d.setFullYear(ar[0]);
|
|
return d;
|
|
}
|
|
// rendering engine
|
|
function rebuildTimeline(flags) { // build the entire timeline
|
|
if(timelineObj.start.length == 0 || timelineObj.end.length == 0)
|
|
return;
|
|
//check minimum axis height
|
|
var min_height = timelineObj.plot.height;
|
|
var min_tick_size = 20;
|
|
var div = 0
|
|
if(timelineObj.majorAxis[1] == 0) {
|
|
// years are easy
|
|
var year_diff = Math.abs(timelineObj.start_date.getFullYear() - timelineObj.end_date.getFullYear());
|
|
div = parseFloat(year_diff / timelineObj.majorAxis[0]);
|
|
min_height = div * min_tick_size;
|
|
}else if(timelineObj.majorAxis[1] == 2) {
|
|
// days are a little trickier
|
|
var ms_diff = Math.abs(timelineObj.start_date.getTime() - timelineObj.end_date.getTime());
|
|
var days_diff = ms_diff / (86400*1000);
|
|
div = parseFloat(days_diff / timelineObj.majorAxis[0]);
|
|
min_height = div * min_tick_size;
|
|
}else{
|
|
// months are tricker still
|
|
let year_diff = Math.abs(timelineObj.start_date.getFullYear() - timelineObj.end_date.getFullYear());
|
|
var month_diff = year_diff * (calendar_arrays.length-1) + Math.abs(timelineObj.start_date.getMonth() - timelineObj.end_date.getMonth());
|
|
div = parseFloat(month_diff / timelineObj.majorAxis[0]);
|
|
min_height = div * min_tick_size;
|
|
}
|
|
if(min_height > timelineObj.plot.height) {
|
|
timelineObj.plot.height = min_height;
|
|
document.getElementById("height-definer").value = min_height;
|
|
document.getElementById("axis").style.height = min_height;
|
|
document.getElementById("timeline").style.height = min_height + 2*(timelineObj.plot.border_padding)
|
|
}
|
|
// set the start and end marks in the right place
|
|
var year_start = document.getElementById("year-start");
|
|
var year_end = document.getElementById("year-end");
|
|
|
|
year_start.style.top = timelineObj.plot.top;
|
|
var end_px = timelineObj.plot.top + parseInt(timelineObj.plot.height) - timelineObj.plot.marker_height;
|
|
year_end.style.top = end_px;
|
|
// calculate the year-marks
|
|
// pixel height of one year
|
|
var year_height = yr_height();
|
|
// add the starting year label
|
|
var start_label = getElement("year-label", 1);
|
|
start_label.innerHTML = timelineObj.label_pattern.replace(/{Y}/g, timelineObj.start_date.getFullYear()).replace(/{M}/g, calendar_arrays[timelineObj.start_date.getMonth()+1][0]).replace(/{D}/g, timelineObj.start_date.getDate());
|
|
start_label.style = `top:${timelineObj.plot.top+timelineObj.plot.axis_label_offset}px;`
|
|
// add up jumps to determine number of marks
|
|
var end_ms = timelineObj.end_date.getTime();
|
|
var dates = [new Date(timelineObj.start_date.getTime())];
|
|
dates.push(nextMajorTime(dates[0]));
|
|
var lbcount = 1;
|
|
while(true) {
|
|
lbcount++;
|
|
if(dates[0].getTime() == dates[1].getTime() || isNaN(dates[1].getTime()))
|
|
break;
|
|
if((dates[0].getTime() > end_ms && end_ms > dates[1].getTime()) || (dates[0].getTime() < end_ms && end_ms < dates[1].getTime()))
|
|
dates[1] = timelineObj.end_date;
|
|
dates.push(nextMajorTime(dates[1]));
|
|
if((dates[1].getTime() > end_ms && end_ms > dates[2].getTime()) || (dates[1].getTime() < end_ms && end_ms < dates[2].getTime()))
|
|
dates[1] = timelineObj.end_date;
|
|
var yr_px = timelineObj.plot.top+Math.round(year_height*(yr_diff(timelineObj.start_date, dates[1])))
|
|
if(dates[1] == timelineObj.end_date)
|
|
yr_px -= timelineObj.plot.marker_height;
|
|
if(lbcount > div)
|
|
yr_px = end_px;
|
|
var year_label = getElement("year-label", lbcount);
|
|
year_label.innerHTML = timelineObj.label_pattern.replace(/{Y}/g, dates[1].getFullYear()).replace(/{M}/g, calendar_arrays[dates[1].getMonth()+1][0]).replace(/{D}/g, dates[1].getDate());
|
|
year_label.style = `top:${yr_px+timelineObj.plot.axis_label_offset}px;`
|
|
if(dates[1].getTime() == end_ms)
|
|
break;
|
|
getElement("year-marker", lbcount).style.top = yr_px;
|
|
dates.splice(0, 1)
|
|
if(lbcount > div)
|
|
break;
|
|
}
|
|
for(var i=element_keeper["year-marker"].length-1; i>=lbcount-2; i--) {
|
|
document.getElementById(element_keeper["year-marker"][i]).remove();
|
|
element_keeper["year-marker"].splice(i, 1);
|
|
}
|
|
for(var i=element_keeper["year-label"].length-1; i>=lbcount; i--) {
|
|
document.getElementById(element_keeper["year-label"][i]).remove();
|
|
element_keeper["year-label"].splice(i, 1);
|
|
}
|
|
for(var t in timelineObj.threads)
|
|
renderThread(timelineObj.threads[t]);
|
|
}
|
|
function renderThread(thread) { // render all the events in a thread
|
|
for(let e in thread.events)
|
|
renderEvent(thread.events[e]);
|
|
}
|
|
function renderEvent(event_obj, event_ele) { // render an event
|
|
if(!event_ele)
|
|
event_ele = document.getElementById(event_obj.ele_id);
|
|
if(!event_ele)
|
|
return;
|
|
var year_height = yr_height();
|
|
var top_date = event_obj.start;
|
|
if(timelineObj.reverse)
|
|
top_date = event_obj.end;
|
|
var start_point = event_obj.start;
|
|
if(timelineObj.start_date.getTime() > start_point.getTime())
|
|
start_point = timelineObj.start_date
|
|
var end_point = event_obj.end;
|
|
if(timelineObj.end_date.getTime() < end_point.getTime())
|
|
end_point = timelineObj.end_date
|
|
var y_d = yr_diff(start_point, timelineObj.start_date);
|
|
var y_d2 = yr_diff(start_point, end_point);
|
|
var top_px = timelineObj.plot.top+Math.round(year_height*y_d);
|
|
var hgt_px;
|
|
var style = `top:${top_px}px;`
|
|
var oor = false;
|
|
if(timelineObj.start_date.getTime() > event_obj.end.getTime() || timelineObj.end_date.getTime() < event_obj.start.getTime()) {
|
|
// out of range
|
|
style += `visibility: hidden;`
|
|
oor = true;
|
|
}else if(event_obj.start.getTime() != event_obj.end.getTime()) {
|
|
hgt_px = Math.max(1,Math.round(year_height*y_d2))
|
|
event_ele.style.visibility = "";
|
|
}else{
|
|
hgt_px = timelineObj.plot.marker_height
|
|
event_ele.style.visibility = "";
|
|
}
|
|
if(hgt_px)
|
|
style += `height:${hgt_px};`
|
|
if(event_obj.color) {
|
|
style += `background-color:${event_obj.color};`
|
|
}else if(event_obj.thread.color) {
|
|
style += `background-color:${event_obj.thread.color};`
|
|
}
|
|
event_ele.style = style;
|
|
// make the label
|
|
var label_ele;
|
|
if(!event_obj.label_id) {
|
|
label_ele = createNewLabel(event_obj, event_ele)
|
|
}else{
|
|
label_ele = document.getElementById(event_obj.label_id);
|
|
if(!label_ele)
|
|
label_ele = createNewLabel(event_obj, event_ele)
|
|
}
|
|
label_ele.innerHTML = event_obj.label;
|
|
/*
|
|
var label_h = label_ele.offsetHeight;
|
|
// calculate bar_mid, bar_height halved + bar_top
|
|
var bar_mid = Math.max(timelineObj.plot.marker_height, year_height*y_d2)/2
|
|
bar_mid += year_height*(y_d)+timelineObj.plot.top;
|
|
label_ele.style = `top:${Math.round(bar_mid-(label_h/2)-event_obj.label_offset_y)}px;`;
|
|
*/
|
|
label_ele.style.top = top_px - Math.round(timelineObj.plot.font_scale/10) - event_obj.label_offset_y;
|
|
if(hgt_px)
|
|
label_ele.style.height = hgt_px;
|
|
var label_width = timelineObj.plot.label_width + event_obj.thread.label_width_offset
|
|
if(label_width < 1 || oor) {
|
|
label_ele.style.visibility = "hidden";
|
|
}else{
|
|
label_ele.style.visibility = "";
|
|
}
|
|
if(event_obj.label_offset_x)
|
|
label_ele.style.left = findNormalOffset("label", event_obj.thread).left + event_obj.label_offset_x;
|
|
return event_ele;
|
|
}
|
|
function createNewLabel(event_obj, event_ele) { // create a label for an event
|
|
var label_ele = document.createElement("div");
|
|
label_ele.id = event_ele.id + "-label";
|
|
label_ele.className = "timeline-label bar-" + event_obj.thread.id + "-label";
|
|
document.getElementById("timeline").appendChild(label_ele);
|
|
event_obj.label_id = event_ele.id + "-label";
|
|
return label_ele;
|
|
}
|
|
function reformatThreadCSS() { // rewrite CSS for offset changes
|
|
var pl = timelineObj.plot;
|
|
// update axis
|
|
var axis_rule = findCSSRule(".timeline .axis");
|
|
if(axis_rule)
|
|
axis_rule.style.left = pl.axis_offset;
|
|
var start_left = pl.axis_offset + pl.bar_offset;
|
|
for(var i=0; i<timelineObj.threads.length; i++) {
|
|
var thread = timelineObj.threads[i];
|
|
var bar_rule = findCSSRule(`.timeline .bar-${thread.id}`);
|
|
var label_rule = findCSSRule(`.timeline .bar-${thread.id}-label`);
|
|
// set bar left
|
|
if(bar_rule) {
|
|
bar_rule.style.left = start_left;
|
|
bar_rule.style.width = pl.bar_width + thread.bar_offset;
|
|
}
|
|
// apply bar width and label offset
|
|
start_left += pl.bar_width + thread.bar_offset;
|
|
start_left += pl.label_offset_x + thread.label_offset;
|
|
// set label left
|
|
if(label_rule) {
|
|
label_rule.style.left = start_left;
|
|
label_rule.style.width = pl.label_width + thread.label_width_offset;
|
|
}
|
|
// apply label width and thread offset
|
|
start_left += pl.label_width + thread.label_width_offset;
|
|
start_left += pl.thread_offset + thread.thread_offset;
|
|
}
|
|
// update bounding border
|
|
var border_rule = findCSSRule(".timeline");
|
|
if(border_rule) {
|
|
pl.width = Math.max(pl.min_width, start_left);
|
|
border_rule.style.width = pl.width;
|
|
}
|
|
// then the year markers now that width is known
|
|
updateYearMarkers(document.getElementById("axis-style").value);
|
|
}
|
|
function updateYearMarkers(val) {
|
|
var year_rule = findCSSRule(".timeline .year-marker");
|
|
if(!year_rule)
|
|
return;
|
|
var pl = timelineObj.plot;
|
|
switch(val) {
|
|
case "outer":
|
|
// small outer marks
|
|
year_rule.style.left = (pl.axis_offset-20)+"px";
|
|
year_rule.style.zIndex = "3";
|
|
break;
|
|
year_rule.style.height = "3px";
|
|
year_rule.style.width = "20px";
|
|
year_rule.style.backgroundColor = "#000";
|
|
case "inner":
|
|
// small inner marks
|
|
year_rule.style.left = pl.axis_offset+"px";
|
|
year_rule.style.zIndex = "3";
|
|
year_rule.style.height = "3px";
|
|
year_rule.style.width = "20px";
|
|
year_rule.style.backgroundColor = "#000";
|
|
break;
|
|
case "grid1":
|
|
// grid z-index: 0
|
|
year_rule.style.left = (pl.axis_offset-20)+"px";
|
|
year_rule.style.zIndex = "0";
|
|
year_rule.style.width = (pl.width-50)+"px";
|
|
year_rule.style.height = "1px";
|
|
year_rule.style.backgroundColor = "#000";
|
|
break;
|
|
case "grid2":
|
|
// grid z-index: 3
|
|
year_rule.style.left = (pl.axis_offset-20)+"px";
|
|
year_rule.style.zIndex = "3";
|
|
year_rule.style.width = (pl.width-50)+"px";
|
|
year_rule.style.height = "1px";
|
|
year_rule.style.backgroundColor = "#000";
|
|
break;
|
|
case "grid3":
|
|
// grid z-index: 3 & white
|
|
year_rule.style.left = (pl.axis_offset+1)+"px";
|
|
year_rule.style.zIndex = "3";
|
|
year_rule.style.backgroundColor = "#fff";
|
|
year_rule.style.width = (pl.width-50)+"px";
|
|
year_rule.style.height = "2px";
|
|
break;
|
|
}
|
|
}
|
|
// HTML/CSS output engine
|
|
var plorder = [
|
|
"height", "bar_width", "milestone_width", "label_width", "label_offset_x", "thread_offset",
|
|
"axis_offset", "axis_label_offset", "bar_offset", "font_scale"
|
|
]
|
|
function writeTMLFile() {
|
|
var to = timelineObj;
|
|
var pl = to.plot;
|
|
|
|
// Timeline data
|
|
let output = "\n===\n";
|
|
output += `${to.start.join("-")};${to.end.join("-")};${to.majorAxis.join("-")};`;
|
|
output += `${document.getElementById("axis-style").value};${to.label_pattern};${active_font}\n`;
|
|
for(let p in plorder) {
|
|
output += `${pl[plorder[p]]};`;
|
|
}
|
|
output += `\n`;
|
|
// Thread data
|
|
output += "===\n";
|
|
var events = "";
|
|
for(var t in to.threads) {
|
|
var thread = to.threads[t];
|
|
output += `${thread.name}\n`;
|
|
output += `${thread.color || "#000000"};`
|
|
output += `${thread.bar_offset};${thread.label_offset};`;
|
|
output += `${thread.label_width_offset};${thread.thread_offset}\n`;
|
|
|
|
for(var e in thread.events) {
|
|
var ev = thread.events[e];
|
|
events += `${ev.label}\n`
|
|
events += `${ev.thread.index};${ev.color || 0};${ev.start.getTime()};${ev.end.getTime()};`
|
|
events += `${ev.label_offset_x};${ev.label_offset_y}\n`
|
|
}
|
|
}
|
|
// Event data
|
|
output += `===\n${events}`;
|
|
return output;
|
|
}
|
|
function readTMLFile(input, err) {
|
|
try{
|
|
var currentThreads = timelineObj.threads.length;
|
|
// if we have one thread with no events, just overwrite
|
|
if(timelineObj.threads.length == 1 && timelineObj.threads[0].events.length == 0) {
|
|
currentThreads = 0;
|
|
thread_id = 0;
|
|
timelineObj.threads = [];
|
|
}
|
|
var pieces = input.split("\n===\n");
|
|
if(pieces.length == 0) {
|
|
err.innerHTML = "File has no timeline data."
|
|
return;
|
|
}
|
|
var global_data = pieces[1];
|
|
var thread_data = pieces[2];
|
|
var event_data = pieces[3];
|
|
if(global_data) {
|
|
var gdata = global_data.split("\n");
|
|
if(gdata[0]) {
|
|
// time and axis data
|
|
var ginfo = gdata[0].split(";");
|
|
var start_date = ginfo[0].split("-");
|
|
document.getElementById("startYear").value = (start_date[0] || 0)
|
|
document.getElementById("startMonth").value = (start_date[1] || 0)
|
|
document.getElementById("startDay").value = (start_date[2] || 0)
|
|
var end_date = ginfo[1].split("-");
|
|
document.getElementById("endYear").value = (end_date[0] || 0)
|
|
document.getElementById("endMonth").value = (end_date[1] || 0)
|
|
document.getElementById("endDay").value = (end_date[2] || 0)
|
|
var major_axis = ginfo[2].split("-");
|
|
document.getElementById("axis-style").value = (ginfo[3] || 0);
|
|
document.getElementById("label-definer").value = (ginfo[4] || "{Y}");
|
|
updateGlobals({id:"btnApplyBoundary"})
|
|
updateGlobals(document.getElementById("axis-style"))
|
|
updateGlobals(document.getElementById("label-definer"))
|
|
if(major_axis) {
|
|
document.getElementById("axis-value").value = (major_axis[0] || 1)
|
|
document.getElementById("axis-unit").value = (major_axis[1] || 0)
|
|
updateGlobals({id:"axis-button"})
|
|
}
|
|
}
|
|
if(gdata[1]) {
|
|
// plot data
|
|
var pinfo = gdata[1].split(";");
|
|
if(pinfo[10])
|
|
document.getElementById("font-style") = pinfo[10];
|
|
var eles = [
|
|
document.getElementById("height-definer"),
|
|
document.getElementById("bar-width"),
|
|
document.getElementById("mile-width"),
|
|
document.getElementById("label-width"),
|
|
document.getElementById("label-offset"),
|
|
document.getElementById("thread-offset"),
|
|
document.getElementById("axis-offset"),
|
|
document.getElementById("axis-label-offset"),
|
|
document.getElementById("bar-offset"),
|
|
document.getElementById("font-scaler"),
|
|
document.getElementById("font-style")
|
|
];
|
|
for(let e in eles) {
|
|
eles[e].value = (pinfo[e] || 0);
|
|
updateGlobals(eles[e]);
|
|
}
|
|
|
|
}
|
|
}
|
|
if(thread_data) {
|
|
var tdata = thread_data.split("\n");
|
|
for(var i=0; i<tdata.length; i+=2) {
|
|
var tinfo = tdata[i+1];
|
|
if(!tinfo)
|
|
continue;
|
|
tinfo = tinfo.split(";");
|
|
var tname = tdata[i];
|
|
var thread = createThread(newThread(tname, tinfo[0]));
|
|
thread.bar_offset = parseInt(tinfo[1] || 0);
|
|
thread.label_offset = parseInt(tinfo[2] || 0);
|
|
thread.label_width_offset = parseInt(tinfo[3] || 0);
|
|
thread.thread_offset = parseInt(tinfo[4] || 0);
|
|
}
|
|
}
|
|
if(event_data) {
|
|
var edata = event_data.split("\n");
|
|
for(var i=0; i<edata.length; i+=2) {
|
|
var einfo = edata[i+1]
|
|
if(!einfo)
|
|
continue;
|
|
einfo = einfo.split(";");
|
|
var ename = edata[i];
|
|
var tindex = parseInt(einfo[0])+currentThreads;
|
|
var es = new Date(parseInt(einfo[2]))
|
|
var ee = new Date(parseInt(einfo[3]))
|
|
var color = einfo[1];
|
|
if(!color || color === "0")
|
|
color = timelineObj.threads[tindex].color;
|
|
var even = newEvent(es, ee, ename, timelineObj.threads[tindex], color);
|
|
even.label_offset_x = parseInt(einfo[4] || 0);
|
|
even.label_offset_y = parseInt(einfo[5] || 0);
|
|
}
|
|
}
|
|
reformatThreadCSS();
|
|
rebuildTimeline();
|
|
}catch(e) {
|
|
err.innerHTML = "There was a problem downloading the file: \n" + e;
|
|
console.log(e)
|
|
}
|
|
}
|
|
function readyExports() {
|
|
var output = "data:text/plain;charset=utf-8," + encodeURIComponent(writeTMLFile());
|
|
document.getElementById("download-link").setAttribute("href", output);
|
|
}
|
|
function submitImport() {
|
|
var importer = document.getElementById("importer");
|
|
var err = document.getElementById("import-error");
|
|
if(!importer.value) {
|
|
err.innerHTML = "No file given."
|
|
return;
|
|
}else{
|
|
err.innerHTML = "";
|
|
}
|
|
importer.files[0].text()
|
|
.then(t => readTMLFile(t, err))
|
|
.catch(console.error)
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//comment
|