diff --git a/build/css/ox.ui.css b/build/css/ox.ui.css index dbac9d51..eca5341a 100644 --- a/build/css/ox.ui.css +++ b/build/css/ox.ui.css @@ -102,6 +102,68 @@ Bars margin: 4px 0 0 4px; } +/* +================================================================================ +Calendar +================================================================================ +*/ + +.OxCalendar { + position: absolute; +} + +.OxCalendar > .OxCalendarContainer { + position: absolute; + left: 0; + right: 0; + overflow: hidden; +} + +.OxCalendar .OxLine { + position: absolute; +} + +.OxCalendar .OxDate { + position: absolute; + height: 16px; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + cursor: pointer; +} +.OxCalendar .OxLine:nth-child(even) .OxDate { + background-color: rgb(255, 0, 0); +} +.OxCalendar .OxLine:nth-child(odd) .OxDate { + background-color: rgb(255, 64, 64); +} +.OxCalendar .OxTimeline { + position: absolute; + height: 16px; + //overflow: hidden; +} + +.OxCalendar .OxTimeline .OxDate { + position: absolute; + border-radius: 0; + cursor: ew-resize; +} +.OxCalendar .OxOverlay { + position: absolute; + left: 0; + right: 0; + height: 16px; +} +.OxCalendar .OxOverlay div { + position: absolute; + height: 16px; + cursor: ew-resize; +} +.OxCalendar .OxOverlay div:nth-child(odd) { + background-color: rgba(0, 0, 0, 0.333); +} + + /* ================================================================================ Dialog diff --git a/build/css/ox.ui.modern.css b/build/css/ox.ui.modern.css index 59fe1a61..8667e419 100644 --- a/build/css/ox.ui.modern.css +++ b/build/css/ox.ui.modern.css @@ -31,6 +31,22 @@ Bars background-color: rgb(48, 48, 48); } +/* +================================================================================ +Calendar +================================================================================ +*/ + +.OxThemeModern .OxCalendar .OxTimeline > div.even { + background: -moz-linear-gradient(top, rgb(64, 64, 64), rgb(32, 32, 32)); + background: -webkit-gradient(linear, left top, left bottom, from(rgb(64, 64, 64)), to(rgb(32, 32, 32))); +} + +.OxThemeModern .OxCalendar .OxTimeline > div.odd { + background: -moz-linear-gradient(top, rgb(56, 56, 56), rgb(24, 24, 24)); + background: -webkit-gradient(linear, left top, left bottom, from(rgb(56, 56, 56)), to(rgb(24, 24, 24))); +} + /* ================================================================================ Dialog diff --git a/build/js/ox.js b/build/js/ox.js index 5323ea37..2e9dfbf0 100644 --- a/build/js/ox.js +++ b/build/js/ox.js @@ -43,6 +43,9 @@ Ox.MONTHS = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ]; +Ox.SHORT_MONTHS = Ox.MONTHS.map(function(val) { + return val.substr(0, 3); +}); Ox.PREFIXES = ['K', 'M', 'G', 'T', 'P']; Ox.SYMBOLS = { DOLLAR: '\u0024', @@ -77,6 +80,9 @@ Ox.VERSION = '0.1.2'; Ox.WEEKDAYS = [ 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' ]; +Ox.SHORT_WEEKDAYS = Ox.WEEKDAYS.map(function(val) { + return val.substr(0, 3); +}); /* ================================================================================ @@ -491,9 +497,14 @@ Ox.len = function(obj) { return Ox.isObject(obj) ? Ox.values(obj).length : obj.length; }; -Ox.loop = function(num, fn) { - var i; - for (i = 0; i < num; i++) { +Ox.loop = function() { + var length = arguments.length, + fn = arguments[length - 1], + step = length == 4 ? arguments[2] : 1, + stop = arguments[length > 2 ? 1 : 0], + start = length > 2 ? arguments[0] : 0, + i; + for (i = start; i < stop; i += step) { fn(i); } }; @@ -957,6 +968,10 @@ Ox.getDaysInMonth = function(year, month) { //return Ox.DAYS[month - 1] + (month == 2 && Ox.isLeapYear(year)); } +Ox.getDaysInYear = function(year) { + return 365 + Ox.isLeapYear(year); +}; + Ox.getFirstDayOfTheYear = function(date) { /* Decimal weekday of January 1 (0-6, Sunday as first day) @@ -1610,10 +1625,10 @@ Ox.formatDate = function() { ["v", function() {return "%e-%b-%Y";}], ["\\+", function() {return "%a %b %e %H:%M:%S %Z %Y";}], ["A", function(d) {return Ox.WEEKDAYS[(d.getDay() + 6) % 7];}], - ["a", function(d) {return Ox.WEEKDAYS[(d.getDay() + 6) % 7].toString().substr(0, 3);}], + ["a", function(d) {return Ox.SHORT_WEEKDAYS[(d.getDay() + 6) % 7];}], ["B", function(d) {return Ox.MONTHS[d.getMonth()];}], - ["b", function(d) {return Ox.MONTHS[d.getMonth()].toString().substr(0, 3);}], - ["C", function(d) {return d.getFullYear().toString().substr(0, 2);}], + ["b", function(d) {return Ox.SHORT_MONTHS[d.getMonth()];}], + ["C", function(d) {return (d.getFullYear() / 100).toString();}], ["d", function(d) {return Ox.pad(d.getDate(), 2);}], ["e", function(d) {return Ox.pad(d.getDate(), 2, " ");}], ["G", function(d) {return Ox.getISOYear(d);}], @@ -1737,6 +1752,38 @@ Ox.formatNumber = function(num, dec) { return (num < 0 ? '-' : '') + spl.join('.'); }; +Ox.formatOrdinal = function(num) { + /* + >>> Ox.formatOrdinal(1) + "1st" + >>> Ox.formatOrdinal(2) + "2nd" + >>> Ox.formatOrdinal(3) + "3rd" + >>> Ox.formatOrdinal(4) + "4th" + >>> Ox.formatOrdinal(11) + "11th" + >>> Ox.formatOrdinal(12) + "12th" + >>> Ox.formatOrdinal(13) + "13th" + */ + var str = num.toString(), + end = str[str.length - 1], + ten = str.length > 1 && str[str.length - 2] == '1'; + if (end == '1' && !ten) { + str += 'st'; + } else if (end == '2' && !ten) { + str += 'nd'; + } else if (end == '3' && !ten) { + str += 'rd'; + } else { + str += 'th'; + } + return str; +}; + Ox.formatPercent = function(num, total, dec) { /* >>> Ox.formatPercent(1, 1000, 2) @@ -2245,6 +2292,17 @@ Ox.log = function(num, base) { return Math.log(num) / Math.log(base || Math.E); }; +Ox.mod = function(num, by) { + /* + >>> Ox.mod(11, 10) + 1 + >>> Ox.mod(-11, 10) + 9 + */ + var mod = num % by; + return mod >= 0 ? mod : mod + by; +}; + Ox.rad = function(deg) { /* >>> Ox.rad(360) diff --git a/build/js/ox.ui.js b/build/js/ox.ui.js index 39cefdb3..2aeb2d05 100644 --- a/build/js/ox.ui.js +++ b/build/js/ox.ui.js @@ -9858,7 +9858,7 @@ requires function initMap() { var mapBounds; - updateFormElements() + updateFormElements(); self.elevationService = new google.maps.ElevationService(); self.geocoder = new google.maps.Geocoder(); @@ -10760,16 +10760,17 @@ requires }; - /** - options - height image height (px) - places array of either names (''), points ([0, 0]), - or objects ({name, point, highlight}) - type map type ('hybrid', 'roadmap', 'satellite', 'terrain') - width image width (px) - */ Ox.MapImage = function(options, self) { + /** + options + height image height (px) + places array of either names (''), points ([0, 0]), + or objects ({name, point, highlight}) + type map type ('hybrid', 'roadmap', 'satellite', 'terrain') + width image width (px) + */ + var self = self || {}, that = new Ox.Element('img', self) .defaults({ @@ -10827,6 +10828,426 @@ requires }; + Ox.Calendar = function(options, self) { + + self = self || {}; + var that = new Ox.Element({}, self) + .defaults({ + date: new Date(), + dates: [], + height: 512, + range: [100, 5101], + width: 512, + zoom: 5 + }) + .options(options || {}) + .addClass('OxCalendar') + .css({ + width: self.options.width + 'px', + height: self.options.height + 'px' + }); + + self.overlayWidths = [Math.round(self.options.width / 16)]; + self.overlayWidths = [ + Math.floor((self.options.width - self.overlayWidths[0]) / 2), + self.overlayWidths[0], + Math.ceil((self.options.width - self.overlayWidths[0]) / 2), + ]; + self.units = [ + { + id: 'century', + seconds: 36524 * 86400, + date: function(i) { + return new Date((i + 19) + '00'); + }, + name: function(i) { + return Ox.formatOrdinal(i + 20) + ' century'; + }, + value: function(date) { + return Math.floor(date.getFullYear() / 100) - 19; + } + }, + { + id: 'decade', + seconds: 3652 * 86400, + date: function(i) { + return (i + 197) + '0' + }, + name: function(i) { + return (i + 197) + '0s' + }, + value: function(date) { + return Math.floor(date.getFullYear() / 10) - 197; + } + }, + { + id: 'year', + seconds: 365 * 86400, + date: function(i) { + return (i + 1970) + ''; + }, + name: function(i) { + return (i + 1970) + ''; + }, + value: function(date) { + return date.getFullYear() - 1970; + } + }, + { + id: 'month', + seconds: 28 * 86000, + date: function(i) { + return (Math.floor(i / 12) + 1970) + '-' + (Ox.mod(i, 12) + 1); + }, + name: function(i) { + return Ox.SHORT_MONTHS[Ox.mod(i, 12)] + ' ' + Math.floor(i / 12 + 1970) + }, + value: function(date) { + return (date.getFullYear() - 1970) * 12 + date.getMonth(); + } + }, + { + id: 'day', + seconds: 86400, + date: function(i) { + return i * 86400000; + }, + name: function(i) { + return Ox.formatDate(new Date(i * 86400000), '%a, %b %e, %Y'); + }, + value: function(date) { + return Math.floor(date / 86400000); + } + }, + { + id: 'hour', + seconds: 3600, + date: function(i) { + return i * 3600000; + }, + name: function(i) { + return Ox.formatDate(new Date(i * 3600000), '%b %e, %H:00'); + }, + value: function(date) { + return Math.floor(date / 3600000); + } + }, + { + id: 'minute', + seconds: 60, + date: function(i) { + return i * 60000; + }, + name: function(i) { + return Ox.formatDate(new Date(i * 60000), '%b %e, %H:%M'); + }, + value: function(date) { + return Math.floor(date / 60000); + } + }, + { + id: 'second', + seconds: 1, + date: function(i) { + return i * 1000; + }, + name: function(i) { + return Ox.formatDate(new Date(i * 1000), '%H:%M:%S'); + }, + value: function(date) { + return Math.floor(date / 1000); + } + } + ]; + + self.$container = new Ox.Element() + .addClass('OxCalendarContainer') + .css({ + top: '24px', + bottom: '40px' + }) + .bindEvent({ + dragstart: dragstart, + drag: drag, + dragend: dragend + }) + .appendTo(that); + + self.$content = new Ox.Element() + .addClass('OxCalendarContent') + .appendTo(self.$container) + + self.$scalebar = new Ox.Element() + .addClass('OxTimeline') + .css({ + posision: 'absolute', + }) + .appendTo(self.$content); + + self.$scrollbar = new Ox.Element() + .addClass('OxTimeline') + .css({ + posision: 'absolute', + bottom: '40px' + }) + .appendTo(that); + self.$overlay = new Ox.Element() + .addClass('OxOverlay') + .css({ + bottom: '40px' + }) + .append( + $('
').css({ + width: self.overlayWidths[0] + 'px' + }) + ) + .append( + $('
').css({ + left: self.overlayWidths[0] + 'px', + width: self.overlayWidths[1] + 'px' + }) + ) + .append( + $('
').css({ + left: (self.overlayWidths[0] + self.overlayWidths[1]) + 'px', + width: self.overlayWidths[2] + 'px' + }) + ) + .bindEvent({ + dragstart: dragstartScrollbar, + drag: dragScrollbar, + dragend: dragendScrollbar + }) + .appendTo(that); + + self.$zoombar = new Ox.Bar({ + size: 16 + }) + .css({ + position: 'absolute', + bottom: 24 + 'px' + }) + .appendTo(that); + self.$zoomInput = new Ox.Range({ + arrows: true, + max: 28, + min: 0, + size: self.options.width, + thumbSize: 32, + thumbValue: true, + value: self.options.zoom + }) + .bindEvent({ + change: changeZoom + }) + .appendTo(self.$zoombar); + + sortDates(); + renderTimelines(); + renderDates(); + + function changeDate() { + + } + + function changeZoom(event, data) { + self.options.zoom = data.value; + $('.OxDate').remove(); + renderTimelines(); + renderDates(); + } + + function dragstart(event, e) { + if ($(e.target).is(':not(.OxLine > .OxDate)')) { + self.drag = {x: e.clientX}; + } + } + + function drag(event, e) { + if (self.drag) { + self.$content.css({ + marginLeft: (e.clientX - self.drag.x) + 'px' + }); + self.$scrollbar.css({ + marginLeft: Math.round((e.clientX - self.drag.x) / 16) + 'px' + }); + } + } + + function dragend(event, e) { + if (self.drag) { + self.options.date = new Date( + +self.options.date + (self.drag.x - e.clientX) * getSecondsPerPixel() * 1000 + ); + dragafter(); + } + } + + function dragstartScrollbar(event, e) { + self.drag = {x: e.clientX}; + } + + function dragScrollbar(event, e) { + self.$content.css({ + marginLeft: ((e.clientX - self.drag.x) * 16) + 'px' + }); + self.$scrollbar.css({ + marginLeft: (e.clientX - self.drag.x) + 'px' + }); + } + + function dragendScrollbar(event, e) { + self.options.date = new Date( + +self.options.date + (self.drag.x - e.clientX) * getSecondsPerPixel() * 1000 * 16 + ); + dragafter(); + } + + function dragafter() { + self.drag = null; + self.$content.css({ + marginLeft: 0 + }); + self.$scrollbar.css({ + marginLeft: 0 + }); + $('.OxDate').remove(); + renderTimelines(); + renderDates(); + } + + function getDateElement(date, zoom) { + var left = getPosition(date.start, zoom), + width = Math.max(getPosition(date.stop, zoom) - left, 1); + return new Ox.Element() + .addClass('OxDate') + .attr({ + title: date.name + }) + .css({ + left: left + 'px', + width: width + 'px' + }) + .html(' ' + date.name); + } + + function getPixelsPerSecond(zoom) { + return Math.pow(2, (zoom || self.options.zoom) - 24); + } + + function getSecondsPerPixel(zoom) { + return 1 / getPixelsPerSecond(zoom); + } + + function getPosition(date, zoom) { + zoom = zoom || self.options.zoom + return Math.round( + self.options.width / 2 + + (date - self.options.date) / 1000 * getPixelsPerSecond(zoom) + ); + } + + function getTimelineElements(zoom) { + var $elements = [], + pixelsPerSecond = getPixelsPerSecond(zoom), + n, unit, value, width; + self.units = self.units.reverse(); + Ox.forEach(self.units, function(v) { + width = v.seconds * pixelsPerSecond; + if (width >= 64) { + unit = Ox.getObjectById(self.units, v.id); + return false; + } + }); + self.units = self.units.reverse(); + n = Math.ceil(self.options.width * 1.5/* * 16*/ / width); + value = unit.value(self.options.date); + Ox.loop(-n, n + 1, function(i) { + $elements.push( + getDateElement({ + name: unit.name(value + i)/* + unit.date(value + i) + '-' + unit.date(value + i + 1)*/, + start: new Date(unit.date(value + i)), + stop: new Date(unit.date(value + i + 1)) + }, zoom) + .addClass(Ox.mod(value + i, 2) == 0 ? 'even' : 'odd') + ); + }); + return $elements; + } + + function overlaps(date0, date1) { + return ( + date0.start >= date1.start && date0.start < date1.stop + ) || ( + date1.start >= date0.start && date1.start < date0.stop + ); + } + + function renderDates() { + self.lineDates = []; + self.$lines = []; + self.options.dates.forEach(function(date, i) { + var line = self.$lines.length; + Ox.forEach(self.lineDates, function(dates, line_) { + var fits = true; + Ox.forEach(dates, function(date_) { + if (overlaps(date, date_)) { + Ox.print('over', date.name, date_.name) + fits = false; + return false; + } + }); + if (fits) { + Ox.print(date.name, 'fits', line_) + line = line_; + return false; + } + }); + Ox.print(date.name, line) + if (line == self.$lines.length) { + self.lineDates[line] = []; + self.$lines[line] = new Ox.Element() + .addClass('OxLine') + .css({ + top: ((line + 1) * 16) + 'px' + }); + } + self.lineDates[line].push(date); + self.$lines[line].append(getDateElement(date)); + }); + $('.OxLine').remove(); + self.$lines.forEach(function($line) { + $line.appendTo(self.$content); + }); + } + + function renderTimelines() { + getTimelineElements(self.options.zoom).forEach(function($element) { + $element.appendTo(self.$scalebar.$element); + }); + getTimelineElements(Math.max(self.options.zoom - 4, 0)).forEach(function($element) { + $element.appendTo(self.$scrollbar.$element); + }); + } + + function sortDates() { + self.options.dates.sort(function(a, b) { + return (b.stop - b.start) - (a.stop - a.start); + }); + } + + self.onChange = function(key, val) { + if (key == 'date') { + + } else if (key == 'zoom') { + + } + }; + + return that; + + }; + /* ============================================================================ Menus @@ -10836,6 +11257,7 @@ requires /** */ Ox.MainMenu = function(options, self) { + var self = self || {}, that = new Ox.Bar({}, self) .defaults({ diff --git a/demos/calendar/index.html b/demos/calendar/index.html new file mode 100644 index 00000000..55080e00 --- /dev/null +++ b/demos/calendar/index.html @@ -0,0 +1,13 @@ + + + + ox.js calendar demo + + + + + + + + + \ No newline at end of file diff --git a/demos/calendar/js/calendar.js b/demos/calendar/js/calendar.js new file mode 100644 index 00000000..f2127627 --- /dev/null +++ b/demos/calendar/js/calendar.js @@ -0,0 +1,34 @@ +$(function() { + + Ox.theme('modern'); + + new Ox.Calendar({ + date: new Date(0), + dates: [ + {name: 'December 1969', start: new Date('1969-12-01'), stop: new Date('1970-01-01')}, + {name: '20th century', start: new Date('1900-01-01'), stop: new Date('2000-01-01')}, + {name: 'World War One', start: new Date('1914-01-01'), stop: new Date('1919-01-01')}, + {name: 'The Third Reich', start: new Date('1933-01-30'), stop: new Date('1945-05-09')}, + {name: 'World War Two', start: new Date('1939-01-01'), stop: new Date('1946-01-01')}, + {name: '1960s', start: new Date('1960-01-01'), stop: new Date('1970-01-01')}, + {name: 'Assassination of John F. Kennedy', start: new Date('1963-11-22'), stop: new Date('1963-11-23')}, + {name: 'Assassination of Benno Ohnesorg', start: new Date('1967-06-02'), stop: new Date('1967-06-03')}, + {name: '1968', start: new Date('1968-01-01'), stop: new Date('1969-01-01')}, + {name: 'Assassination of Martin Luther King', start: new Date('1968-04-04'), stop: new Date('1968-04-05')}, + {name: 'Assassination of Rudi Dutschke', start: new Date('1968-04-11'), stop: new Date('1968-04-12')}, + {name: 'May 1968', start: new Date('1968-05-01'), stop: new Date('1968-06-01')}, + {name: '1968 Cannes Film Festival', start: new Date('1968-05-10'), stop: new Date('1968-05-20')}, + {name: 'Assassination of Robert F. Kennedy', start: new Date('1968-06-05'), stop: new Date('1968-06-06')}, + {name: 'Apollo 11', start: new Date('1969-07-16'), stop: new Date('1969-07-25')}, + {name: 'Moon Landing', start: new Date('1969-07-20'), stop: new Date('1969-07-21')}, + {name: 'The Epoch', start: new Date('1970-01-01 00:00:00'), stop: new Date('1970-01-01 00:00:01')}, + {name: 'Apollo 17', start: new Date('1972-12-07'), stop: new Date('1972-12-20')}, + {name: 'Tschernobyl', start: new Date('1986-04-26'), stop: new Date('1986-04-27')}, + {name: '9-11', start: new Date('2001-09-11'), stop: new Date('2001-09-12')} + ], + height: window.innerHeight, + width: window.innerWidth, + zoom: 5 + }).appendTo(Ox.UI.$body); + +}); \ No newline at end of file