123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256 |
- /**
- * @file timeline.js
- *
- * @brief
- * The Timeline is an interactive visualization chart to visualize events in
- * time, having a start and end date.
- * You can freely move and zoom in the timeline by dragging
- * and scrolling in the Timeline. Items are optionally dragable. The time
- * scale on the axis is adjusted automatically, and supports scales ranging
- * from milliseconds to years.
- *
- * Timeline is part of the CHAP Links library.
- *
- * Timeline is tested on Firefox 3.6, Safari 5.0, Chrome 6.0, Opera 10.6, and
- * Internet Explorer 6+.
- *
- * @license
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy
- * of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- *
- * Copyright (c) 2011-2012 Almende B.V.
- *
- * @author Jos de Jong, <jos@almende.org>
- * @date 2012-06-15
- * @version 2.1.2
- */
- /*
- * TODO
- *
- * Add zooming with pinching on Android
- *
- * Bug: when an item contains a javascript onclick or a link, this does not work
- * when the item is not selected (when the item is being selected,
- * it is redrawn, which cancels any onclick or link action)
- * Bug: when an item contains an image without size, or a css max-width, it is not sized correctly
- * Bug: neglect items when they have no valid start/end, instead of throwing an error
- * Bug: Pinching on ipad does not work very well, sometimes the page will zoom when pinching vertically
- * Bug: cannot set max width for an item, like div.timeline-event-content {white-space: normal; max-width: 100px;}
- * Bug on IE in Quirks mode. When you have groups, and delete an item, the groups become invisible
- */
- /**
- * Declare a unique namespace for CHAP's Common Hybrid Visualisation Library,
- * "links"
- */
- if (typeof links === 'undefined') {
- links = {};
- // important: do not use var, as "var links = {};" will overwrite
- // the existing links variable value with undefined in IE8, IE7.
- }
- /**
- * Ensure the variable google exists
- */
- if (typeof google === 'undefined') {
- google = undefined;
- // important: do not use var, as "var google = undefined;" will overwrite
- // the existing google variable value with undefined in IE8, IE7.
- }
- /**
- * @constructor links.Timeline
- * The timeline is a visualization chart to visualize events in time.
- *
- * The timeline is developed in javascript as a Google Visualization Chart.
- *
- * @param {Element} container The DOM element in which the Timeline will
- * be created. Normally a div element.
- */
- links.Timeline = function(container) {
- // create variables and set default values
- this.dom = {};
- this.conversion = {};
- this.eventParams = {}; // stores parameters for mouse events
- this.groups = [];
- this.groupIndexes = {};
- this.items = [];
- this.visibleItems = 0;
- this.selection = undefined; // stores index and item which is currently selected
- this.listeners = {}; // event listener callbacks
- // Initialize sizes.
- // Needed for IE (which gives an error when you try to set an undefined
- // value in a style)
- this.size = {
- 'actualHeight': 0,
- 'axis': {
- 'characterMajorHeight': 0,
- 'characterMajorWidth': 0,
- 'characterMinorHeight': 0,
- 'characterMinorWidth': 0,
- 'height': 0,
- 'labelMajorTop': 0,
- 'labelMinorTop': 0,
- 'line': 0,
- 'lineMajorWidth': 0,
- 'lineMinorHeight': 0,
- 'lineMinorTop': 0,
- 'lineMinorWidth': 0,
- 'top': 0
- },
- 'contentHeight': 0,
- 'contentLeft': 0,
- 'contentWidth': 0,
- 'dataChanged': false,
- 'frameHeight': 0,
- 'frameWidth': 0,
- 'groupsLeft': 0,
- 'groupsWidth': 0,
- 'items': {
- 'top': 0
- }
- };
- this.dom.container = container;
- this.options = {
- 'width': "100%",
- 'height': "auto",
- 'minHeight': 0, // minimal height in pixels
- 'autoHeight': true,
- 'eventMargin': 10, // minimal margin between events
- 'eventMarginAxis': 20, // minimal margin beteen events and the axis
- 'dragAreaWidth': 10, // pixels
- 'min': undefined,
- 'max': undefined,
- 'intervalMin': 10, // milliseconds
- 'intervalMax': 1000 * 60 * 60 * 24 * 365 * 10000, // milliseconds
- 'moveable': true,
- 'zoomable': true,
- 'selectable': true,
- 'editable': false,
- 'snapEvents': true,
- 'groupChangeable': true,
- 'mousewheel': "zoom",
- 'dblclick':"edit",
- 'showCurrentTime': true, // show a red bar displaying the current time
- 'showCustomTime': false, // show a blue, draggable bar displaying a custom time
- 'showMajorLabels': true,
- 'showNavigation': false,
- 'showButtonAdd': true,
- 'groupsOnRight': false,
- 'axisOnTop': false,
- 'stackEvents': true,
- 'animate': true,
- 'animateZoom': true,
- 'style': 'box',
- 'language':'en'
- };
- this.clientTimeOffset = 0; // difference between client time and the time
- // set via Timeline.setCurrentTime()
- var dom = this.dom;
- // remove all elements from the container element.
- while (dom.container.hasChildNodes()) {
- dom.container.removeChild(dom.container.firstChild);
- }
- // create a step for drawing the axis
- this.step = new links.Timeline.StepDate();
- // initialize data
- this.data = [];
- this.firstDraw = true;
- // date interval must be initialized
- this.setVisibleChartRange(undefined, undefined, false);
- // create all DOM elements
- this.redrawFrame();
- // Internet Explorer does not support Array.indexof,
- // so we define it here in that case
- // http://soledadpenades.com/2007/05/17/arrayindexof-in-internet-explorer/
- if(!Array.prototype.indexOf) {
- Array.prototype.indexOf = function(obj){
- for(var i = 0; i < this.length; i++){
- if(this[i] == obj){
- return i;
- }
- }
- return -1;
- }
- }
- // fire the ready event
- this.trigger('ready');
- };
- /**
- * Main drawing logic. This is the function that needs to be called
- * in the html page, to draw the timeline.
- *
- * A data table with the events must be provided, and an options table.
- *
- * @param {google.visualization.DataTable} data
- * The data containing the events for the timeline.
- * Object DataTable is defined in
- * google.visualization.DataTable
- * @param {Object} options A name/value map containing settings for the
- * timeline. Optional.
- */
- links.Timeline.prototype.draw = function(data, options) {
- this.setOptions(options);
- // read the data
- this.setData(data);
- // set timer range. this will also redraw the timeline
- if (options && options.start && options.end) {
- this.setVisibleChartRange(options.start, options.end);
- }
- else if (this.firstDraw) {
- this.setVisibleChartRangeAuto();
- }
- this.firstDraw = false;
- };
- /**
- * Set options for the timeline.
- * Timeline must be redrawn afterwards
- * @param {Object} options A name/value map containing settings for the
- * timeline. Optional.
- */
- links.Timeline.prototype.setOptions = function(options) {
- if (options) {
- // retrieve parameter values
- for (var i in options) {
- if (options.hasOwnProperty(i)) {
- this.options[i] = options[i];
- }
- }
- }
- // validate options
- this.options.autoHeight = (this.options.height === "auto");
- };
- links.Timeline.prototype.getOptions = function(){
- return this.options;
- };
- /**
- * Set data for the timeline
- * @param {google.visualization.DataTable | array} data
- */
- links.Timeline.prototype.setData = function(data) {
- // unselect any previously selected item
- this.unselectItem();
- if (!data) {
- data = [];
- }
- this.items = [];
- this.data = data;
- var items = this.items;
- var options = this.options;
- // create groups from the data
- this.setGroups(data);
- if (google && google.visualization &&
- data instanceof google.visualization.DataTable) {
- // read DataTable
- var hasGroups = (data.getNumberOfColumns() > 3);
- for (var row = 0, rows = data.getNumberOfRows(); row < rows; row++) {
- items.push(this.createItem({
- 'start': data.getValue(row, 0),
- 'end': data.getValue(row, 1),
- 'content': data.getValue(row, 2),
- 'group': (hasGroups ? data.getValue(row, 3) : undefined)
- }));
- }
- }
- else if (links.Timeline.isArray(data)) {
- // read JSON array
- for (var row = 0, rows = data.length; row < rows; row++) {
- var itemData = data[row]
- var item = this.createItem(itemData);
- items.push(item);
- }
- }
- else {
- throw "Unknown data type. DataTable or Array expected.";
- }
- // set a flag to force the recalcSize method to recalculate the
- // heights and widths of the events
- this.size.dataChanged = true;
- this.redrawFrame(); // create the items for the new data
- this.recalcSize(); // position the items
- this.repositionItems();
- this.stackEvents(false);
- this.redrawFrame(); // redraw the items on the final positions
- this.size.dataChanged = false;
- };
- /**
- * Set the groups available in the given dataset
- * @param {google.visualization.DataTable | array} data
- */
- links.Timeline.prototype.setGroups = function (data) {
- this.deleteGroups();
- var groups = this.groups;
- var groupIndexes = this.groupIndexes;
- if (google && google.visualization &&
- data instanceof google.visualization.DataTable) {
- // get groups from DataTable
- var hasGroups = (data.getNumberOfColumns() > 3);
- if (hasGroups) {
- var groupNames = data.getDistinctValues(3);
- for (var i = 0, iMax = groupNames.length; i < iMax; i++) {
- this.addGroup(groupNames[i]);
- }
- }
- }
- else if (links.Timeline.isArray(data)){
- // get groups from JSON Array
- for (var i = 0, iMax = data.length; i < iMax; i++) {
- var row = data[i],
- group = row.group;
- if (group) {
- this.addGroup(group);
- }
- }
- }
- else {
- throw 'Unknown data type. DataTable or Array expected.';
- }
- };
- /**
- * Return the original data table.
- * @return {google.visualization.DataTable | Array} data
- */
- links.Timeline.prototype.getData = function () {
- return this.data;
- };
- /**
- * Update the original data with changed start, end or group.
- *
- * @param {Number} index
- * @param {Object} values An object containing some of the following parameters:
- * {Date} start,
- * {Date} end,
- * {String} content,
- * {String} group
- */
- links.Timeline.prototype.updateData = function (index, values) {
- var data = this.data;
- if (google && google.visualization &&
- data instanceof google.visualization.DataTable) {
- // update the original google DataTable
- var missingRows = (index + 1) - data.getNumberOfRows();
- if (missingRows > 0) {
- data.addRows(missingRows);
- }
- if (values.start) {
- data.setValue(index, 0, values.start);
- }
- if (values.end) {
- data.setValue(index, 1, values.end);
- }
- if (values.content) {
- data.setValue(index, 2, values.content);
- }
- if (values.group && data.getNumberOfColumns() > 3) {
- // TODO: append a column when needed?
- data.setValue(index, 3, values.group);
- }
- }
- else if (links.Timeline.isArray(data)) {
- // update the original JSON table
- var row = data[index];
- if (row == undefined) {
- row = {};
- data[index] = row;
- }
- if (values.start) {
- row.start = values.start;
- }
- if (values.end) {
- row.end = values.end;
- }
- if (values.content) {
- row.content = values.content;
- }
- if (values.group) {
- row.group = values.group;
- }
- }
- else {
- throw "Cannot update data, unknown type of data";
- }
- };
- /**
- * Find the item index from a given HTML element
- * If no item index is found, undefined is returned
- * @param {Element} element
- * @return {Number} index
- */
- links.Timeline.prototype.getItemIndex = function(element) {
- var e = element,
- dom = this.dom,
- items = this.items,
- index = undefined;
- // try to find the frame where the items are located in
- while (e.parentNode && e.parentNode !== dom.items.frame) {
- e = e.parentNode;
- }
- if (e.parentNode === dom.items.frame) {
- // yes! we have found the parent element of all items
- // retrieve its id from the array with items
- for (var i = 0, iMax = items.length; i < iMax; i++) {
- if (items[i].dom === e) {
- index = i;
- break;
- }
- }
- }
- return index;
- };
- /**
- * Set a new size for the timeline
- * @param {string} width Width in pixels or percentage (for example "800px"
- * or "50%")
- * @param {string} height Height in pixels or percentage (for example "400px"
- * or "30%")
- */
- links.Timeline.prototype.setSize = function(width, height) {
- if (width) {
- this.options.width = width;
- this.dom.frame.style.width = width;
- }
- if (height) {
- this.options.height = height;
- this.options.autoHeight = (this.options.height === "auto");
- if (height !== "auto" ) {
- this.dom.frame.style.height = height;
- }
- }
- this.recalcSize();
- this.repositionItems();
- this.stackEvents(false);
- this.redrawFrame();
- };
- /**
- * Set a new value for the visible range int the timeline.
- * Set start to null to include everything from the earliest date to end.
- * Set end to null to include everything from start to the last date.
- * Example usage:
- * myTimeline.setVisibleChartRange(new Date("2010-08-22"),
- * new Date("2010-09-13"));
- * @param {Date} start The start date for the timeline. optional
- * @param {Date} end The end date for the timeline. optional
- * @param {boolean} redraw Optional. If true (default) the Timeline is
- * directly redrawn
- */
- links.Timeline.prototype.setVisibleChartRange = function(start, end, redraw) {
- if (start == undefined) {
- // default of 3 days ago
- start = new Date();
- start.setDate(start.getDate() - 3);
- }
- if (end == undefined) {
- // default of 4 days ahead
- end = new Date();
- end.setDate(start.getDate() + 4);
- }
- // prevent start Date <= end Date
- if (end.valueOf() <= start.valueOf()) {
- end = new Date(start);
- end.setDate(end.getDate() + 7);
- }
- // limit to the allowed range (dont let this do by applyRange,
- // because that method will try to maintain the interval (end-start)
- var min = this.options.min ? this.options.min.valueOf() : undefined;
- if (min && start.valueOf() < min) {
- start = new Date(min);
- }
- var max = this.options.max ? this.options.max.valueOf() : undefined;
- if (max && end.valueOf() > max) {
- end = new Date(max);
- }
- this.applyRange(start, end);
- if (redraw == undefined || redraw == true) {
- this.recalcSize();
- this.repositionItems();
- this.stackEvents(false);
- this.redrawFrame();
- }
- else {
- this.recalcConversion();
- }
- };
- /**
- * Change the visible chart range such that all items become visible
- */
- links.Timeline.prototype.setVisibleChartRangeAuto = function() {
- var items = this.items,
- startMin = undefined, // long value of a data
- endMax = undefined; // long value of a data
- // find earliest start date from the data
- for (var i = 0, iMax = items.length; i < iMax; i++) {
- var item = items[i],
- start = item.start ? item.start.valueOf() : undefined,
- end = item.end ? item.end.valueOf() : start;
- if (startMin !== undefined && start !== undefined) {
- startMin = Math.min(startMin, start);
- }
- else {
- startMin = start;
- }
- if (endMax !== undefined && end !== undefined) {
- endMax = Math.max(endMax, end);
- }
- else {
- endMax = end;
- }
- }
- if (startMin !== undefined && endMax !== undefined) {
- // zoom out 5% such that you have a little white space on the left and right
- var center = (endMax + startMin) / 2,
- diff = (endMax - startMin);
- startMin = startMin - diff * 0.05;
- endMax = endMax + diff * 0.05;
- // adjust the start and end date
- this.setVisibleChartRange(new Date(startMin), new Date(endMax));
- }
- else {
- this.setVisibleChartRange(undefined, undefined);
- }
- };
- /**
- * Adjust the visible range such that the current time is located in the center
- * of the timeline
- */
- links.Timeline.prototype.setVisibleChartRangeNow = function() {
- var now = new Date();
- var diff = (this.end.getTime() - this.start.getTime());
- var startNew = new Date(now.getTime() - diff/2);
- var endNew = new Date(startNew.getTime() + diff);
- this.setVisibleChartRange(startNew, endNew);
- };
- /**
- * Retrieve the current visible range in the timeline.
- * @return {Object} An object with start and end properties
- */
- links.Timeline.prototype.getVisibleChartRange = function() {
- var range = {
- 'start': new Date(this.start),
- 'end': new Date(this.end)
- };
- return range;
- };
- /**
- * Redraw the timeline. This needs to be executed after the start and/or
- * end time are changed, or when data is added or removed dynamically.
- */
- links.Timeline.prototype.redrawFrame = function() {
- var dom = this.dom,
- options = this.options,
- size = this.size;
- if (!dom.frame) {
- // the surrounding main frame
- dom.frame = document.createElement("DIV");
- dom.frame.className = "timeline-frame";
- dom.frame.style.position = "relative";
- dom.frame.style.overflow = "hidden";
- dom.container.appendChild(dom.frame);
- }
- if (options.autoHeight) {
- dom.frame.style.height = size.frameHeight + "px";
- }
- else {
- dom.frame.style.height = options.height || "100%";
- }
- dom.frame.style.width = options.width || "100%";
- this.redrawContent();
- this.redrawGroups();
- this.redrawCurrentTime();
- this.redrawCustomTime();
- this.redrawNavigation();
- };
- /**
- * Redraw the content of the timeline: the axis and the items
- */
- links.Timeline.prototype.redrawContent = function() {
- var dom = this.dom,
- size = this.size;
- if (!dom.content) {
- // create content box where the axis and canvas will
- dom.content = document.createElement("DIV");
- //this.frame.className = "timeline-frame";
- dom.content.style.position = "relative";
- dom.content.style.overflow = "hidden";
- dom.frame.appendChild(dom.content);
- var timelines = document.createElement("DIV");
- timelines.style.position = "absolute";
- timelines.style.left = "0px";
- timelines.style.top = "0px";
- timelines.style.height = "100%";
- timelines.style.width = "0px";
- dom.content.appendChild(timelines);
- dom.contentTimelines = timelines;
- var params = this.eventParams,
- me = this;
- if (!params.onMouseDown) {
- params.onMouseDown = function (event) {me.onMouseDown(event);};
- links.Timeline.addEventListener(dom.content, "mousedown", params.onMouseDown);
- }
- if (!params.onTouchStart) {
- params.onTouchStart = function (event) {me.onTouchStart(event);};
- links.Timeline.addEventListener(dom.content, "touchstart", params.onTouchStart);
- }
- if (!params.onMouseWheel) {
- params.onMouseWheel = function (event) {me.onMouseWheel(event);};
- links.Timeline.addEventListener(dom.content, "mousewheel", params.onMouseWheel);
- }
- if (!params.onDblClick) {
- params.onDblClick = function (event) {me.onDblClick(event);};
- links.Timeline.addEventListener(dom.content, "dblclick", params.onDblClick);
- }
- }
- dom.content.style.left = size.contentLeft + "px";
- dom.content.style.top = "0px";
- dom.content.style.width = size.contentWidth + "px";
- dom.content.style.height = size.frameHeight + "px";
- this.redrawAxis();
- this.redrawItems();
- this.redrawDeleteButton();
- this.redrawDragAreas();
- };
- /**
- * Redraw the timeline axis with minor and major labels
- */
- links.Timeline.prototype.redrawAxis = function() {
- var dom = this.dom,
- options = this.options,
- size = this.size,
- step = this.step;
- var axis = dom.axis;
- if (!axis) {
- axis = {};
- dom.axis = axis;
- }
- if (size.axis.properties === undefined) {
- size.axis.properties = {};
- }
- if (axis.minorTexts === undefined) {
- axis.minorTexts = [];
- }
- if (axis.minorLines === undefined) {
- axis.minorLines = [];
- }
- if (axis.majorTexts === undefined) {
- axis.majorTexts = [];
- }
- if (axis.majorLines === undefined) {
- axis.majorLines = [];
- }
- if (!axis.frame) {
- axis.frame = document.createElement("DIV");
- axis.frame.style.position = "absolute";
- axis.frame.style.left = "0px";
- axis.frame.style.top = "0px";
- axis.frame.className = "axis";
- dom.content.appendChild(axis.frame);
- }
- // take axis offline
- dom.content.removeChild(axis.frame);
- axis.frame.style.width = (size.contentWidth) + "px";
- axis.frame.style.height = (size.axis.height) + "px";
- // the drawn axis is more wide than the actual visual part, such that
- // the axis can be dragged without having to redraw it each time again.
- var start = this.screenToTime(0);
- var end = this.screenToTime(size.contentWidth);
- var width = size.contentWidth;
- // calculate minimum step (in milliseconds) based on character size
- this.minimumStep = this.screenToTime(size.axis.characterMinorWidth * 6).valueOf() -
- this.screenToTime(0).valueOf();
- step.setRange(start, end, this.minimumStep);
- this.redrawAxisCharacters();
- this.redrawAxisStartOverwriting();
- step.start();
- var xFirstMajorLabel = undefined;
- while (!step.end()) {
- var cur = step.getCurrent(),
- x = this.timeToScreen(cur),
- isMajor = step.isMajor();
- this.redrawAxisMinorText(x, step.getLabelMinor(undefined, options.language));
- if (isMajor && options.showMajorLabels) {
- if (x > 0) {
- if (xFirstMajorLabel === undefined) {
- xFirstMajorLabel = x;
- }
- this.redrawAxisMajorText(x, step.getLabelMajor(undefined, options.language));
- }
- this.redrawAxisMajorLine(x);
- }
- else {
- this.redrawAxisMinorLine(x);
- }
- step.next();
- }
- // create a major label on the left when needed
- if (options.showMajorLabels) {
- var leftTime = this.screenToTime(0),
- leftText = this.step.getLabelMajor(leftTime, options.language),
- width = leftText.length * size.axis.characterMajorWidth + 10;// estimation
- if (xFirstMajorLabel === undefined || width < xFirstMajorLabel) {
- this.redrawAxisMajorText(0, leftText, leftTime);
- }
- }
- this.redrawAxisHorizontal();
- // cleanup left over labels
- this.redrawAxisEndOverwriting();
- // put axis online
- dom.content.insertBefore(axis.frame, dom.content.firstChild);
- };
- /**
- * Create characters used to determine the size of text on the axis
- */
- links.Timeline.prototype.redrawAxisCharacters = function () {
- // calculate the width and height of a single character
- // this is used to calculate the step size, and also the positioning of the
- // axis
- var dom = this.dom,
- axis = dom.axis;
- if (!axis.characterMinor) {
- var text = document.createTextNode("0");
- var characterMinor = document.createElement("DIV");
- characterMinor.className = "timeline-axis-text timeline-axis-text-minor";
- characterMinor.appendChild(text);
- characterMinor.style.position = "absolute";
- characterMinor.style.visibility = "hidden";
- characterMinor.style.paddingLeft = "0px";
- characterMinor.style.paddingRight = "0px";
- axis.frame.appendChild(characterMinor);
- axis.characterMinor = characterMinor;
- }
- if (!axis.characterMajor) {
- var text = document.createTextNode("0");
- var characterMajor = document.createElement("DIV");
- characterMajor.className = "timeline-axis-text timeline-axis-text-major";
- characterMajor.appendChild(text);
- characterMajor.style.position = "absolute";
- characterMajor.style.visibility = "hidden";
- characterMajor.style.paddingLeft = "0px";
- characterMajor.style.paddingRight = "0px";
- axis.frame.appendChild(characterMajor);
- axis.characterMajor = characterMajor;
- }
- };
- /**
- * Initialize redraw of the axis. All existing labels and lines will be
- * overwritten and reused.
- */
- links.Timeline.prototype.redrawAxisStartOverwriting = function () {
- var properties = this.size.axis.properties;
- properties.minorTextNum = 0;
- properties.minorLineNum = 0;
- properties.majorTextNum = 0;
- properties.majorLineNum = 0;
- };
- /**
- * End of overwriting HTML DOM elements of the axis.
- * remaining elements will be removed
- */
- links.Timeline.prototype.redrawAxisEndOverwriting = function () {
- var dom = this.dom,
- props = this.size.axis.properties,
- frame = this.dom.axis.frame;
- // remove leftovers
- var minorTexts = dom.axis.minorTexts,
- num = props.minorTextNum;
- while (minorTexts.length > num) {
- var minorText = minorTexts[num];
- frame.removeChild(minorText);
- minorTexts.splice(num, 1);
- }
- var minorLines = dom.axis.minorLines,
- num = props.minorLineNum;
- while (minorLines.length > num) {
- var minorLine = minorLines[num];
- frame.removeChild(minorLine);
- minorLines.splice(num, 1);
- }
- var majorTexts = dom.axis.majorTexts,
- num = props.majorTextNum;
- while (majorTexts.length > num) {
- var majorText = majorTexts[num];
- frame.removeChild(majorText);
- majorTexts.splice(num, 1);
- }
- var majorLines = dom.axis.majorLines,
- num = props.majorLineNum;
- while (majorLines.length > num) {
- var majorLine = majorLines[num];
- frame.removeChild(majorLine);
- majorLines.splice(num, 1);
- }
- };
- /**
- * Redraw the horizontal line and background of the axis
- */
- links.Timeline.prototype.redrawAxisHorizontal = function() {
- var axis = this.dom.axis,
- size = this.size;
- if (!axis.backgroundLine) {
- // create the axis line background (for a background color or so)
- var backgroundLine = document.createElement("DIV");
- backgroundLine.className = "timeline-axis";
- backgroundLine.style.position = "absolute";
- backgroundLine.style.left = "0px";
- backgroundLine.style.width = "100%";
- backgroundLine.style.border = "none";
- axis.frame.insertBefore(backgroundLine, axis.frame.firstChild);
- axis.backgroundLine = backgroundLine;
- }
- axis.backgroundLine.style.top = size.axis.top + "px";
- axis.backgroundLine.style.height = size.axis.height + "px";
- if (axis.line) {
- // put this line at the end of all childs
- var line = axis.frame.removeChild(axis.line);
- axis.frame.appendChild(line);
- }
- else {
- // make the axis line
- var line = document.createElement("DIV");
- line.className = "timeline-axis";
- line.style.position = "absolute";
- line.style.left = "0px";
- line.style.width = "100%";
- line.style.height = "0px";
- axis.frame.appendChild(line);
- axis.line = line;
- }
- axis.line.style.top = size.axis.line + "px";
- };
- /**
- * Create a minor label for the axis at position x
- * @param {Number} x
- * @param {String} text
- */
- links.Timeline.prototype.redrawAxisMinorText = function (x, text) {
- var size = this.size,
- dom = this.dom,
- props = size.axis.properties,
- frame = dom.axis.frame,
- minorTexts = dom.axis.minorTexts,
- index = props.minorTextNum,
- label;
- if (index < minorTexts.length) {
- label = minorTexts[index]
- }
- else {
- // create new label
- var content = document.createTextNode(""),
- label = document.createElement("DIV");
- label.appendChild(content);
- label.className = "timeline-axis-text timeline-axis-text-minor";
- label.style.position = "absolute";
- frame.appendChild(label);
- minorTexts.push(label);
- }
- label.childNodes[0].nodeValue = text;
- label.style.left = x + "px";
- label.style.top = size.axis.labelMinorTop + "px";
- //label.title = title; // TODO: this is a heavy operation
- props.minorTextNum++;
- };
- /**
- * Create a minor line for the axis at position x
- * @param {Number} x
- */
- links.Timeline.prototype.redrawAxisMinorLine = function (x) {
- var axis = this.size.axis,
- dom = this.dom,
- props = axis.properties,
- frame = dom.axis.frame,
- minorLines = dom.axis.minorLines,
- index = props.minorLineNum,
- line;
- if (index < minorLines.length) {
- line = minorLines[index];
- }
- else {
- // create vertical line
- line = document.createElement("DIV");
- line.className = "timeline-axis-grid timeline-axis-grid-minor";
- line.style.position = "absolute";
- line.style.width = "0px";
- frame.appendChild(line);
- minorLines.push(line);
- }
- line.style.top = axis.lineMinorTop + "px";
- line.style.height = axis.lineMinorHeight + "px";
- line.style.left = (x - axis.lineMinorWidth/2) + "px";
- props.minorLineNum++;
- };
- /**
- * Create a Major label for the axis at position x
- * @param {Number} x
- * @param {String} text
- */
- links.Timeline.prototype.redrawAxisMajorText = function (x, text) {
- var size = this.size,
- props = size.axis.properties,
- frame = this.dom.axis.frame,
- majorTexts = this.dom.axis.majorTexts,
- index = props.majorTextNum,
- label;
- if (index < majorTexts.length) {
- label = majorTexts[index];
- }
- else {
- // create label
- var content = document.createTextNode(text);
- label = document.createElement("DIV");
- label.className = "timeline-axis-text timeline-axis-text-major";
- label.appendChild(content);
- label.style.position = "absolute";
- label.style.top = "0px";
- frame.appendChild(label);
- majorTexts.push(label);
- }
- label.childNodes[0].nodeValue = text;
- label.style.top = size.axis.labelMajorTop + "px";
- label.style.left = x + "px";
- //label.title = title; // TODO: this is a heavy operation
- props.majorTextNum ++;
- };
- /**
- * Create a Major line for the axis at position x
- * @param {Number} x
- */
- links.Timeline.prototype.redrawAxisMajorLine = function (x) {
- var size = this.size,
- props = size.axis.properties,
- axis = this.size.axis,
- frame = this.dom.axis.frame,
- majorLines = this.dom.axis.majorLines,
- index = props.majorLineNum,
- line;
- if (index < majorLines.length) {
- var line = majorLines[index];
- }
- else {
- // create vertical line
- line = document.createElement("DIV");
- line.className = "timeline-axis-grid timeline-axis-grid-major";
- line.style.position = "absolute";
- line.style.top = "0px";
- line.style.width = "0px";
- frame.appendChild(line);
- majorLines.push(line);
- }
- line.style.left = (x - axis.lineMajorWidth/2) + "px";
- line.style.height = size.frameHeight + "px";
- props.majorLineNum ++;
- };
- /**
- * Redraw all items
- */
- links.Timeline.prototype.redrawItems = function() {
- var dom = this.dom,
- options = this.options,
- boxAlign = (options.box && options.box.align) ? options.box.align : undefined,
- size = this.size,
- contentWidth = size.contentWidth,
- items = this.items,
- visibleItems = [];
-
- // console.log("redrawItems");
-
- if (!dom.items) {
- dom.items = {};
- }
- // draw the frame containing the items
- var frame = dom.items.frame;
- if (!frame) {
- frame = document.createElement("DIV");
- frame.style.position = "relative";
- dom.content.appendChild(frame);
- dom.items.frame = frame;
- }
- frame.style.left = "0px";
- //frame.style.width = "0px";
- frame.style.top = size.items.top + "px";
- frame.style.height = (size.frameHeight - size.axis.height) + "px";
- // initialize arrarys for storing the items
- var ranges = dom.items.ranges;
- if (!ranges) {
- ranges = [];
- dom.items.ranges = ranges;
- }
- var boxes = dom.items.boxes;
- if (!boxes) {
- boxes = [];
- dom.items.boxes = boxes;
- }
- var dots = dom.items.dots;
- if (!dots) {
- dots = [];
- dom.items.dots = dots;
- }
- // Take frame offline
- dom.content.removeChild(frame);
- if (size.dataChanged) {
- // create the items
- var rangesCreated = ranges.length,
- boxesCreated = boxes.length,
- dotsCreated = dots.length,
- rangesUsed = 0,
- boxesUsed = 0,
- dotsUsed = 0,
- itemsLength = items.length;
- for (var i = 0, iMax = items.length; i < iMax; i++) {
- var item = items[i];
- switch (item.type) {
- case 'range':
- if (rangesUsed < rangesCreated) {
- // reuse existing range
- var domItem = ranges[rangesUsed];
- domItem.firstChild.innerHTML = item.content;
- domItem.style.display = '';
- item.dom = domItem;
- rangesUsed++;
- }
- else {
- // create a new range
- var domItem = this.createEventRange(item.content);
- ranges[rangesUsed] = domItem;
- frame.appendChild(domItem);
- item.dom = domItem;
- rangesUsed++;
- rangesCreated++;
- }
- break;
- case 'box':
- if (boxesUsed < boxesCreated) {
- // reuse existing box
- var domItem = boxes[boxesUsed];
- domItem.firstChild.innerHTML = item.content;
- domItem.style.display = '';
- item.dom = domItem;
- boxesUsed++;
- }
- else {
- // create a new box
- var domItem = this.createEventBox(item.content);
- boxes[boxesUsed] = domItem;
- frame.appendChild(domItem);
- frame.insertBefore(domItem.line, frame.firstChild);
- // Note: line must be added in front of the items,
- // such that it stays below all items
- frame.appendChild(domItem.dot);
- item.dom = domItem;
- boxesUsed++;
- boxesCreated++;
- }
- break;
- case 'dot':
- if (dotsUsed < dotsCreated) {
- // reuse existing box
- var domItem = dots[dotsUsed];
- domItem.firstChild.innerHTML = item.content;
- domItem.style.display = '';
- item.dom = domItem;
- dotsUsed++;
- }
- else {
- // create a new box
- var domItem = this.createEventDot(item.content);
- dots[dotsUsed] = domItem;
- frame.appendChild(domItem);
- item.dom = domItem;
- dotsUsed++;
- dotsCreated++;
- }
- break;
- default:
- // do nothing
- break;
- }
- }
- // remove redundant items when needed
- for (var i = rangesUsed; i < rangesCreated; i++) {
- frame.removeChild(ranges[i]);
- }
- ranges.splice(rangesUsed, rangesCreated - rangesUsed);
- for (var i = boxesUsed; i < boxesCreated; i++) {
- var box = boxes[i];
- frame.removeChild(box.line);
- frame.removeChild(box.dot);
- frame.removeChild(box);
- }
- boxes.splice(boxesUsed, boxesCreated - boxesUsed);
- for (var i = dotsUsed; i < dotsCreated; i++) {
- frame.removeChild(dots[i]);
- }
- dots.splice(dotsUsed, dotsCreated - dotsUsed);
- }
-
- for (var i = 0, iMax = this.items.length; i < iMax; i++) {
- item = this.items[i];
- domItem = item.dom;
- switch (item.type) {
- case 'range':
- domItem.style.top = item.top + "px";
- domItem.style.left = item.left + "px";
- //domItem.style.width = Math.max(right - left - 2 * item.borderWidth, 1) + "px"; // TODO: borderWidth
- domItem.style.width = Math.max(item.right - item.left, 1) + "px";
- break;
- case 'box':
- var axisOnTop = options.axisOnTop,
- axisHeight = size.axis.height,
- axisTop = size.axis.top;
- domItem.style.top = item.top + "px";
- if (boxAlign == 'right') {
- domItem.style.left = (item.left - item.width) + "px";
- }
- else if (boxAlign == 'left') {
- domItem.style.left = (item.left) + "px";
- }
- else { // default or 'center'
- domItem.style.left = (item.left - item.width/2) + "px";
- }
-
- var line = domItem.line;
- line.style.left = (item.left - item.lineWidth/2) + "px";
- if (axisOnTop) {
- line.style.top = "0px";
- line.style.height = Math.max(item.top, 0) + "px";
- }
- else {
- line.style.top = (item.top + item.height) + "px";
- line.style.height = Math.max(axisTop - item.top - item.height, 0) + "px";
- }
-
- var dot = domItem.dot;
- dot.style.left = (item.left - item.dotWidth/2) + "px";
- dot.style.top = (axisTop - item.dotHeight/2) + "px";
- break;
- case 'dot':
- domItem.style.top = item.top + "px";
- domItem.style.left = (item.left - item.dotWidth / 2) + "px";
-
- domItem.content.style.marginLeft = (1.5 * item.dotWidth) + "px";
- //domItem.content.style.marginRight = (0.5 * item.dotWidth) + "px"; // TODO
- domItem.dot.style.top = ((item.height - item.dotHeight) / 2) + "px";
- break;
- default:
- // do nothing
- break;
- }
- };
-
- // ! ! ! ! here there was the all items repositionig ! ! ! !
-
- // move selected item to the end, to ensure that it is always on top
- if (this.selection) {
- var item = this.selection.item;
- frame.removeChild(item);
- frame.appendChild(item);
- }
- // put frame online again
- dom.content.appendChild(frame);
- /* TODO
- // retrieve all image sources from the items, and set a callback once
- // all images are retrieved
- var urls = [];
- var timeline = this;
- links.Timeline.filterImageUrls(frame, urls);
- if (urls.length) {
- for (var i = 0; i < urls.length; i++) {
- var url = urls[i];
- var callback = function (url) {
- timeline.redraw();
- };
- var sendCallbackWhenAlreadyLoaded = false;
- links.imageloader.load(url, callback, sendCallbackWhenAlreadyLoaded);
- }
- }
- */
- };
- /**
- * Create an event in the timeline, with (optional) formatting: inside a box
- * with rounded corners, and a vertical line+dot to the axis.
- * @param {string} content The content for the event. This can be plain text
- * or HTML code.
- */
- links.Timeline.prototype.createEventBox = function(content) {
- // background box
- var divBox = document.createElement("DIV");
- divBox.style.position = "absolute";
- divBox.style.left = "0px";
- divBox.style.top = "0px";
- divBox.className = "timeline-event timeline-event-box";
- // contents box (inside the background box). used for making margins
- var divContent = document.createElement("DIV");
- divContent.className = "timeline-event-content";
- divContent.innerHTML = content;
- divBox.appendChild(divContent);
- // line to axis
- var divLine = document.createElement("DIV");
- divLine.style.position = "absolute";
- divLine.style.width = "0px";
- divLine.className = "timeline-event timeline-event-line";
- // important: the vertical line is added at the front of the list of elements,
- // so it will be drawn behind all boxes and ranges
- divBox.line = divLine;
- // dot on axis
- var divDot = document.createElement("DIV");
- divDot.style.position = "absolute";
- divDot.style.width = "0px";
- divDot.style.height = "0px";
- divDot.className = "timeline-event timeline-event-dot";
- divBox.dot = divDot;
- return divBox;
- };
- /**
- * Create an event in the timeline: a dot, followed by the content.
- * @param {string} content The content for the event. This can be plain text
- * or HTML code.
- */
- links.Timeline.prototype.createEventDot = function(content) {
- // background box
- var divBox = document.createElement("DIV");
- divBox.style.position = "absolute";
- // contents box, right from the dot
- var divContent = document.createElement("DIV");
- divContent.className = "timeline-event-content";
- divContent.innerHTML = content;
- divBox.appendChild(divContent);
- // dot at start
- var divDot = document.createElement("DIV");
- divDot.style.position = "absolute";
- divDot.className = "timeline-event timeline-event-dot";
- divDot.style.width = "0px";
- divDot.style.height = "0px";
- divBox.appendChild(divDot);
- divBox.content = divContent;
- divBox.dot = divDot;
- return divBox;
- };
- /**
- * Create an event range as a beam in the timeline.
- * @param {string} content The content for the event. This can be plain text
- * or HTML code.
- */
- links.Timeline.prototype.createEventRange = function(content) {
- // background box
- var divBox = document.createElement("DIV");
- divBox.style.position = "absolute";
- divBox.className = "timeline-event timeline-event-range";
- // contents box
- var divContent = document.createElement("DIV");
- divContent.className = "timeline-event-content";
- divContent.innerHTML = content;
- divBox.appendChild(divContent);
- return divBox;
- };
- /**
- * Redraw the group labels
- */
- links.Timeline.prototype.redrawGroups = function() {
- var dom = this.dom,
- options = this.options,
- size = this.size,
- groups = this.groups;
- if (dom.groups === undefined) {
- dom.groups = {};
- }
- var labels = dom.groups.labels;
- if (!labels) {
- labels = [];
- dom.groups.labels = labels;
- }
- var labelLines = dom.groups.labelLines;
- if (!labelLines) {
- labelLines = [];
- dom.groups.labelLines = labelLines;
- }
- var itemLines = dom.groups.itemLines;
- if (!itemLines) {
- itemLines = [];
- dom.groups.itemLines = itemLines;
- }
- // create the frame for holding the groups
- var frame = dom.groups.frame;
- if (!frame) {
- frame = document.createElement("DIV");
- frame.className = "timeline-groups-axis";
- frame.style.position = "absolute";
- frame.style.overflow = "hidden";
- frame.style.top = "0px";
- frame.style.height = "100%";
- dom.frame.appendChild(frame);
- dom.groups.frame = frame;
- }
- frame.style.left = size.groupsLeft + "px";
- frame.style.width = (options.groupsWidth !== undefined) ?
- options.groupsWidth :
- size.groupsWidth + "px";
- // hide groups axis when there are no groups
- if (groups.length == 0) {
- frame.style.display = 'none';
- }
- else {
- frame.style.display = '';
- }
- if (size.dataChanged) {
- // create the items
- var current = labels.length,
- needed = groups.length;
- // overwrite existing items
- for (var i = 0, iMax = Math.min(current, needed); i < iMax; i++) {
- var group = groups[i];
- var label = labels[i];
- label.innerHTML = group.content;
- label.style.display = '';
- }
- // append new items when needed
- for (var i = current; i < needed; i++) {
- var group = groups[i];
- // create text label
- var label = document.createElement("DIV");
- label.className = "timeline-groups-text";
- label.style.position = "absolute";
- if (options.groupsWidth === undefined) {
- label.style.whiteSpace = "nowrap";
- }
- label.innerHTML = group.content;
- frame.appendChild(label);
- labels[i] = label;
- // create the grid line between the group labels
- var labelLine = document.createElement("DIV");
- labelLine.className = "timeline-axis-grid timeline-axis-grid-minor";
- labelLine.style.position = "absolute";
- labelLine.style.left = "0px";
- labelLine.style.width = "100%";
- labelLine.style.height = "0px";
- labelLine.style.borderTopStyle = "solid";
- frame.appendChild(labelLine);
- labelLines[i] = labelLine;
- // create the grid line between the items
- var itemLine = document.createElement("DIV");
- itemLine.className = "timeline-axis-grid timeline-axis-grid-minor";
- itemLine.style.position = "absolute";
- itemLine.style.left = "0px";
- itemLine.style.width = "100%";
- itemLine.style.height = "0px";
- itemLine.style.borderTopStyle = "solid";
- dom.content.insertBefore(itemLine, dom.content.firstChild);
- itemLines[i] = itemLine;
- }
- // remove redundant items from the DOM when needed
- for (var i = needed; i < current; i++) {
- var label = labels[i],
- labelLine = labelLines[i],
- itemLine = itemLines[i];
- frame.removeChild(label);
- frame.removeChild(labelLine);
- dom.content.removeChild(itemLine);
- }
- labels.splice(needed, current - needed);
- labelLines.splice(needed, current - needed);
- itemLines.splice(needed, current - needed);
- frame.style.borderStyle = options.groupsOnRight ?
- "none none none solid" :
- "none solid none none";
- }
- // position the groups
- for (var i = 0, iMax = groups.length; i < iMax; i++) {
- var group = groups[i],
- label = labels[i],
- labelLine = labelLines[i],
- itemLine = itemLines[i];
- label.style.top = group.labelTop + "px";
- labelLine.style.top = group.lineTop + "px";
- itemLine.style.top = group.lineTop + "px";
- itemLine.style.width = size.contentWidth + "px";
- }
- if (!dom.groups.background) {
- // create the axis grid line background
- var background = document.createElement("DIV");
- background.className = "timeline-axis";
- background.style.position = "absolute";
- background.style.left = "0px";
- background.style.width = "100%";
- background.style.border = "none";
- frame.appendChild(background);
- dom.groups.background = background;
- }
- dom.groups.background.style.top = size.axis.top + 'px';
- dom.groups.background.style.height = size.axis.height + 'px';
- if (!dom.groups.line) {
- // create the axis grid line
- var line = document.createElement("DIV");
- line.className = "timeline-axis";
- line.style.position = "absolute";
- line.style.left = "0px";
- line.style.width = "100%";
- line.style.height = "0px";
- frame.appendChild(line);
- dom.groups.line = line;
- }
- dom.groups.line.style.top = size.axis.line + 'px';
- };
- /**
- * Redraw the current time bar
- */
- links.Timeline.prototype.redrawCurrentTime = function() {
- var options = this.options,
- dom = this.dom,
- size = this.size;
- if (!options.showCurrentTime) {
- if (dom.currentTime) {
- dom.contentTimelines.removeChild(dom.currentTime);
- delete dom.currentTime;
- }
- return;
- }
- if (!dom.currentTime) {
- // create the current time bar
- var currentTime = document.createElement("DIV");
- currentTime.className = "timeline-currenttime";
- currentTime.style.position = "absolute";
- currentTime.style.top = "0px";
- currentTime.style.height = "100%";
- dom.contentTimelines.appendChild(currentTime);
- dom.currentTime = currentTime;
- }
- var now = new Date();
- var nowOffset = new Date(now.getTime() + this.clientTimeOffset);
- var x = this.timeToScreen(nowOffset);
- var visible = (x > -size.contentWidth && x < 2 * size.contentWidth);
- dom.currentTime.style.display = visible ? '' : 'none';
- dom.currentTime.style.left = x + "px";
- dom.currentTime.title = "Current time: " + nowOffset;
- // start a timer to adjust for the new time
- if (this.currentTimeTimer != undefined) {
- clearTimeout(this.currentTimeTimer);
- delete this.currentTimeTimer;
- }
- var timeline = this;
- var onTimeout = function() {
- timeline.redrawCurrentTime();
- };
- // the time equal to the width of one pixel, divided by 2 for more smoothness
- var interval = 1 / this.conversion.factor / 2;
- if (interval < 30) interval = 30;
- this.currentTimeTimer = setTimeout(onTimeout, interval);
- };
- /**
- * Redraw the custom time bar
- */
- links.Timeline.prototype.redrawCustomTime = function() {
- var options = this.options,
- dom = this.dom,
- size = this.size;
- if (!options.showCustomTime) {
- if (dom.customTime) {
- dom.contentTimelines.removeChild(dom.customTime);
- delete dom.customTime;
- }
- return;
- }
- if (!dom.customTime) {
- var customTime = document.createElement("DIV");
- customTime.className = "timeline-customtime";
- customTime.style.position = "absolute";
- customTime.style.top = "0px";
- customTime.style.height = "100%";
- var drag = document.createElement("DIV");
- drag.style.position = "relative";
- drag.style.top = "0px";
- drag.style.left = "-10px";
- drag.style.height = "100%";
- drag.style.width = "20px";
- customTime.appendChild(drag);
- dom.contentTimelines.appendChild(customTime);
- dom.customTime = customTime;
- // initialize parameter
- this.customTime = new Date();
- }
- var x = this.timeToScreen(this.customTime),
- visible = (x > -size.contentWidth && x < 2 * size.contentWidth);
- dom.customTime.style.display = visible ? '' : 'none';
- dom.customTime.style.left = x + "px";
- dom.customTime.title = "Time: " + this.customTime;
- };
- /**
- * Redraw the delete button, on the top right of the currently selected item
- * if there is no item selected, the button is hidden.
- */
- links.Timeline.prototype.redrawDeleteButton = function () {
- var timeline = this,
- options = this.options,
- dom = this.dom,
- size = this.size,
- frame = dom.items.frame;
- if (!options.editable) {
- return;
- }
- var deleteButton = dom.items.deleteButton;
- if (!deleteButton) {
- // create a delete button
- deleteButton = document.createElement("DIV");
- deleteButton.className = "timeline-navigation-delete";
- deleteButton.style.position = "absolute";
- frame.appendChild(deleteButton);
- dom.items.deleteButton = deleteButton;
- }
- if (this.selection) {
- var index = this.selection.index,
- item = this.items[index],
- domItem = this.selection.item,
- right,
- top = item.top;
- switch (item.type) {
- case 'range':
- right = this.timeToScreen(item.end);
- break;
- case 'box':
- //right = this.timeToScreen(item.start) + item.width / 2 + item.borderWidth; // TODO: borderWidth
- right = this.timeToScreen(item.start) + item.width / 2;
- break;
- case 'dot':
- right = this.timeToScreen(item.start) + item.width;
- break;
- }
- // limit the position
- if (right < -size.contentWidth) {
- right = -size.contentWidth;
- }
- if (right > 2 * size.contentWidth) {
- right = 2 * size.contentWidth;
- }
- deleteButton.style.left = right + 'px';
- deleteButton.style.top = top + 'px';
- deleteButton.style.display = '';
- frame.removeChild(deleteButton);
- frame.appendChild(deleteButton);
- }
- else {
- deleteButton.style.display = 'none';
- }
- };
- /**
- * Redraw the drag areas. When an item (ranges only) is selected,
- * it gets a drag area on the left and right side, to change its width
- */
- links.Timeline.prototype.redrawDragAreas = function () {
- var timeline = this,
- options = this.options,
- dom = this.dom,
- size = this.size,
- frame = this.dom.items.frame;
- if (!options.editable) {
- return;
- }
- // create left drag area
- var dragLeft = dom.items.dragLeft;
- if (!dragLeft) {
- dragLeft = document.createElement("DIV");
- dragLeft.className="timeline-event-range-drag-left";
- dragLeft.style.width = options.dragAreaWidth + "px";
- dragLeft.style.position = "absolute";
- frame.appendChild(dragLeft);
- dom.items.dragLeft = dragLeft;
- }
- // create right drag area
- var dragRight = dom.items.dragRight;
- if (!dragRight) {
- dragRight = document.createElement("DIV");
- dragRight.className="timeline-event-range-drag-right";
- dragRight.style.width = options.dragAreaWidth + "px";
- dragRight.style.position = "absolute";
- frame.appendChild(dragRight);
- dom.items.dragRight = dragRight;
- }
- // reposition left and right drag area
- if (this.selection) {
- var index = this.selection.index,
- item = this.items[index];
- if (item.type == 'range') {
- var domItem = item.dom,
- left = this.timeToScreen(item.start),
- right = this.timeToScreen(item.end),
- top = item.top,
- height = item.height;
- dragLeft.style.left = left + 'px';
- dragLeft.style.top = top + 'px';
- dragLeft.style.height = height + 'px';
- dragLeft.style.display = '';
- frame.removeChild(dragLeft);
- frame.appendChild(dragLeft);
- dragRight.style.left = (right - options.dragAreaWidth) + 'px';
- dragRight.style.top = top + 'px';
- dragRight.style.height = height + 'px';
- dragRight.style.display = '';
- frame.removeChild(dragRight);
- frame.appendChild(dragRight);
- }
- }
- else {
- dragLeft.style.display = 'none';
- dragRight.style.display = 'none';
- }
- };
- /**
- * Create the navigation buttons for zooming and moving
- */
- links.Timeline.prototype.redrawNavigation = function () {
- var timeline = this,
- options = this.options,
- dom = this.dom,
- frame = dom.frame,
- navBar = dom.navBar;
- if (!navBar) {
- if (options.editable || options.showNavigation) {
- // create a navigation bar containing the navigation buttons
- navBar = document.createElement("DIV");
- navBar.style.position = "absolute";
- navBar.className = "timeline-navigation";
- if (options.groupsOnRight) {
- navBar.style.left = '10px';
- }
- else {
- navBar.style.right = '10px';
- }
- if (options.axisOnTop) {
- navBar.style.bottom = '10px';
- }
- else {
- navBar.style.top = '10px';
- }
- dom.navBar = navBar;
- frame.appendChild(navBar);
- }
- if (options.editable && options.showButtonAdd) {
- // create a new in button
- navBar.addButton = document.createElement("DIV");
- navBar.addButton.className = "timeline-navigation-new";
- navBar.addButton.title = "Create new event";
- var onAdd = function(event) {
- links.Timeline.preventDefault(event);
- links.Timeline.stopPropagation(event);
- // create a new event at the center of the frame
- var w = timeline.size.contentWidth;
- var x = w / 2;
- var xstart = timeline.screenToTime(x - w / 10); // subtract 10% of timeline width
- var xend = timeline.screenToTime(x + w / 10); // add 10% of timeline width
- if (options.snapEvents) {
- timeline.step.snap(xstart);
- timeline.step.snap(xend);
- }
- var content = "New";
- var group = timeline.groups.length ? timeline.groups[0].content : undefined;
- timeline.addItem({
- 'start': xstart,
- 'end': xend,
- 'content': content,
- 'group': group
- });
- var index = (timeline.items.length - 1);
- timeline.selectItem(index);
- timeline.applyAdd = true;
- // fire an add event.
- // Note that the change can be canceled from within an event listener if
- // this listener calls the method cancelAdd().
- timeline.trigger('add');
- if (!timeline.applyAdd) {
- // undo an add
- timeline.deleteItem(index);
- }
- timeline.redrawDeleteButton();
- timeline.redrawDragAreas();
- };
- links.Timeline.addEventListener(navBar.addButton, "mousedown", onAdd);
- navBar.appendChild(navBar.addButton);
- }
- if (options.editable && options.showButtonAdd && options.showNavigation) {
- // create a separator line
- navBar.addButton.style.borderRightWidth = "1px";
- navBar.addButton.style.borderRightStyle = "solid";
- }
- if (options.showNavigation) {
- // create a zoom out button
- navBar.zoomOutButton = document.createElement("DIV");
- navBar.zoomOutButton.className = "timeline-navigation-zoom-out";
- navBar.zoomOutButton.title = "Zoom out";
- var onZoomOut = function(event) {
- links.Timeline.preventDefault(event);
- links.Timeline.stopPropagation(event);
- timeline.zoom(-0.7);
- timeline.trigger("rangechange");
- timeline.trigger("rangechanged");
- };
- links.Timeline.addEventListener(navBar.zoomOutButton, "mousedown", onZoomOut);
- navBar.appendChild(navBar.zoomOutButton);
-
- // create a zoom in button
- navBar.zoomInButton = document.createElement("DIV");
- navBar.zoomInButton.className = "timeline-navigation-zoom-in";
- navBar.zoomInButton.title = "Zoom in";
- var onZoomIn = function(event) {
- links.Timeline.preventDefault(event);
- links.Timeline.stopPropagation(event);
- timeline.zoom(0.7);
- timeline.trigger("rangechange");
- timeline.trigger("rangechanged");
- };
- links.Timeline.addEventListener(navBar.zoomInButton, "mousedown", onZoomIn);
- navBar.appendChild(navBar.zoomInButton);
- // create a move left button
- navBar.moveLeftButton = document.createElement("DIV");
- navBar.moveLeftButton.className = "timeline-navigation-move-left";
- navBar.moveLeftButton.title = "Move left";
- var onMoveLeft = function(event) {
- links.Timeline.preventDefault(event);
- links.Timeline.stopPropagation(event);
- timeline.move(-0.2);
- timeline.trigger("rangechange");
- timeline.trigger("rangechanged");
- };
- links.Timeline.addEventListener(navBar.moveLeftButton, "mousedown", onMoveLeft);
- navBar.appendChild(navBar.moveLeftButton);
-
- // create a move right button
- navBar.moveRightButton = document.createElement("DIV");
- navBar.moveRightButton.className = "timeline-navigation-move-right";
- navBar.moveRightButton.title = "Move right";
- var onMoveRight = function(event) {
- links.Timeline.preventDefault(event);
- links.Timeline.stopPropagation(event);
- timeline.move(0.2);
- timeline.trigger("rangechange");
- timeline.trigger("rangechanged");
- };
- links.Timeline.addEventListener(navBar.moveRightButton, "mousedown", onMoveRight);
- navBar.appendChild(navBar.moveRightButton);
- }
- }
- };
- /**
- * Set current time. This function can be used to set the time in the client
- * timeline equal with the time on a server.
- * @param {Date} time
- */
- links.Timeline.prototype.setCurrentTime = function(time) {
- var now = new Date();
- this.clientTimeOffset = time.getTime() - now.getTime();
- this.redrawCurrentTime();
- };
- /**
- * Get current time. The time can have an offset from the real time, when
- * the current time has been changed via the method setCurrentTime.
- * @return {Date} time
- */
- links.Timeline.prototype.getCurrentTime = function() {
- var now = new Date();
- return new Date(now.getTime() + this.clientTimeOffset);
- };
- /**
- * Set custom time.
- * The custom time bar can be used to display events in past or future.
- * @param {Date} time
- */
- links.Timeline.prototype.setCustomTime = function(time) {
- this.customTime = new Date(time);
- this.redrawCustomTime();
- };
- /**
- * Retrieve the current custom time.
- * @return {Date} customTime
- */
- links.Timeline.prototype.getCustomTime = function() {
- return new Date(this.customTime);
- };
- /**
- * Set a custom scale. Autoscaling will be disabled.
- * For example setScale(SCALE.MINUTES, 5) will result
- * in minor steps of 5 minutes, and major steps of an hour.
- *
- * @param {links.Timeline.StepDate.SCALE} scale
- * A scale. Choose from SCALE.MILLISECOND,
- * SCALE.SECOND, SCALE.MINUTE, SCALE.HOUR,
- * SCALE.DAY, SCALE.MONTH, SCALE.YEAR.
- * @param {int} step A step size, by default 1. Choose for
- * example 1, 2, 5, or 10.
- */
- links.Timeline.prototype.setScale = function(scale, step) {
- this.step.setScale(scale, step);
- this.redrawFrame();
- };
- /**
- * Enable or disable autoscaling
- * @param {boolean} enable If true or not defined, autoscaling is enabled.
- * If false, autoscaling is disabled.
- */
- links.Timeline.prototype.setAutoScale = function(enable) {
- this.step.setAutoScale(enable);
- this.redrawFrame();
- };
- /**
- * Redraw the timeline
- * Reloads the (linked) data table and redraws the timeline when resized.
- * See also the method checkResize
- */
- links.Timeline.prototype.redraw = function() {
- this.setData(this.data);
- };
- /**
- * Check if the timeline is resized, and if so, redraw the timeline.
- * Useful when the webpage is resized.
- */
- links.Timeline.prototype.checkResize = function() {
- var resized = this.recalcSize();
- resized = this.repositionItems();
- if (resized) {
- this.redrawFrame();
- }
- };
- /**
- * Recursively retrieve all image urls from the images located inside a given
- * HTML element
- * @param {HTMLElement} elem
- * @param {String[]} urls Urls will be added here (no duplicates)
- */
- links.Timeline.filterImageUrls = function(elem, urls) {
- var child = elem.firstChild;
- while (child) {
- if (child.tagName == 'IMG') {
- var url = child.src;
- if (urls.indexOf(url) == -1) {
- urls.push(url);
- }
- }
- links.Timeline.filterImageUrls(child, urls);
- child = child.nextSibling;
- }
- };
- /**
- * Recalculate the sizes of all frames, groups, items, axis
- * After recalcSize() is executed, the Timeline should be redrawn normally
- *
- * @return {boolean} resized Returns true when the timeline has been resized
- */
- links.Timeline.prototype.recalcSize = function() {
- var resized = false;
- var timeline = this,
- size = this.size,
- options = this.options,
- axisOnTop = options.axisOnTop,
- dom = this.dom,
- axis = dom.axis,
- groups = this.groups,
- labels = dom.groups.labels,
- items = this.items;
- var groupsWidth = size.groupsWidth,
- characterMinorWidth = axis.characterMinor ? axis.characterMinor.clientWidth : 0,
- characterMinorHeight = axis.characterMinor ? axis.characterMinor.clientHeight : 0,
- characterMajorWidth = axis.characterMajor ? axis.characterMajor.clientWidth : 0,
- characterMajorHeight = axis.characterMajor ? axis.characterMajor.clientHeight : 0,
- axisHeight = characterMinorHeight + (options.showMajorLabels ? characterMajorHeight : 0),
- actualHeight = size.actualHeight || axisHeight;
- // TODO: move checking for loaded items when creating the dom
- if (size.dataChanged) {
- // retrieve all image sources from the items, and set a callback once
- // all images are retrieved
- var urls = [];
- for (var i = 0, iMax = items.length; i < iMax; i++) {
- var item = items[i],
- domItem = item.dom;
- if (domItem) {
- links.Timeline.filterImageUrls(domItem, urls);
- }
- }
- if (urls.length) {
- for (var i = 0; i < urls.length; i++) {
- var url = urls[i];
- var callback = function (url) {
- timeline.redraw();
- };
- var sendCallbackWhenAlreadyLoaded = false;
- links.imageloader.load(url, callback, sendCallbackWhenAlreadyLoaded);
- }
- }
- }
- // check sizes of the items and groups (width and height) when the data is changed
- if (size.dataChanged) { // TODO: always calculate the size of an item?
- //if (true) {
- groupsWidth = 0;
- // loop through all groups to get the maximum width and the heights
- for (var i = 0, iMax = labels.length; i < iMax; i++) {
- var group = groups[i];
- group.width = labels[i].clientWidth;
- group.height = labels[i].clientHeight;
- group.labelHeight = group.height;
- groupsWidth = Math.max(groupsWidth, group.width);
- }
- var resized = this.recalcSizeItems();
- // calculate the actual height of the timeline (needed for auto sizing
- // the timeline)
- actualHeight = axisHeight + 2 * options.eventMarginAxis;
- for (var i = 0, iMax = groups.length; i < iMax; i++) {
- actualHeight += groups[i].height + options.eventMargin;
- }
- }
- // calculate actual height of the timeline when there are no groups
- // but stacked items
- if (groups.length == 0 && options.autoHeight) {
- var min = 0,
- max = 0;
- if (this.animation && this.animation.finalItems) {
- // adjust the offset of all finalItems when the actualHeight has been changed
- var finalItems = this.animation.finalItems,
- finalItem = finalItems[0];
- if (finalItem && finalItem.top) {
- min = finalItem.top,
- max = finalItem.top + finalItem.height;
- }
- for (var i = 1, iMax = finalItems.length; i < iMax; i++) {
- finalItem = finalItems[i];
- min = Math.min(min, finalItem.top);
- max = Math.max(max, finalItem.top + finalItem.height);
- }
- }
- else {
- var item = items[0];
- if (item && item.top) {
- min = item.top,
- max = item.top + item.height;
- }
- for (var i = 1, iMax = items.length; i < iMax; i++) {
- var item = items[i];
- if (item.top) {
- min = Math.min(min, item.top);
- max = Math.max(max, (item.top + item.height));
- }
- }
- }
- actualHeight = (max - min) + 2 * options.eventMarginAxis + axisHeight;
- if (size.actualHeight != actualHeight && options.autoHeight && !options.axisOnTop) {
- // adjust the offset of all items when the actualHeight has been changed
- var diff = actualHeight - size.actualHeight;
- if (this.animation && this.animation.finalItems) {
- var finalItems = this.animation.finalItems;
- for (var i = 0, iMax = finalItems.length; i < iMax; i++) {
- finalItems[i].top += diff;
- finalItems[i].item.top += diff;
- }
- }
- else {
- for (var i = 0, iMax = items.length; i < iMax; i++) {
- items[i].top += diff;
- }
- }
- }
- }
- // now the heights of the elements are known, we can calculate the the
- // width and height of frame and axis and content
- // Note: IE7 has issues with giving frame.clientWidth, therefore I use offsetWidth instead
- var frameWidth = dom.frame ? dom.frame.offsetWidth : 0,
- frameHeight = Math.max(options.autoHeight ?
- actualHeight : (dom.frame ? dom.frame.clientHeight : 0),
- options.minHeight),
- axisTop = axisOnTop ? 0 : frameHeight - axisHeight,
- axisLine = axisOnTop ? axisHeight : axisTop,
- itemsTop = axisOnTop ? axisHeight : 0,
- contentHeight = Math.max(frameHeight - axisHeight, 0);
- if (options.groupsWidth !== undefined) {
- groupsWidth = dom.groups.frame ? dom.groups.frame.clientWidth : 0;
- }
- var groupsLeft = options.groupsOnRight ? frameWidth - groupsWidth : 0;
- if (size.dataChanged) {
- // calculate top positions of the group labels and lines
- var eventMargin = options.eventMargin,
- top = axisOnTop ?
- options.eventMarginAxis + eventMargin/2 :
- contentHeight - options.eventMarginAxis + eventMargin/2;
- for (var i = 0, iMax = groups.length; i < iMax; i++) {
- var group = groups[i];
- if (axisOnTop) {
- group.top = top;
- group.labelTop = top + axisHeight + (group.height - group.labelHeight) / 2;
- group.lineTop = top + axisHeight + group.height + eventMargin/2;
- top += group.height + eventMargin;
- }
- else {
- top -= group.height + eventMargin;
- group.top = top;
- group.labelTop = top + (group.height - group.labelHeight) / 2;
- group.lineTop = top - eventMargin/2;
- }
- }
- // calculate top position of the items
- for (var i = 0, iMax = items.length; i < iMax; i++) {
- var item = items[i],
- group = item.group;
- if (group) {
- item.top = group.top;
- }
- }
- resized = true;
- }
- resized = resized || (size.groupsWidth !== groupsWidth);
- resized = resized || (size.groupsLeft !== groupsLeft);
- resized = resized || (size.actualHeight !== actualHeight);
- size.groupsWidth = groupsWidth;
- size.groupsLeft = groupsLeft;
- size.actualHeight = actualHeight;
- resized = resized || (size.frameWidth !== frameWidth);
- resized = resized || (size.frameHeight !== frameHeight);
- size.frameWidth = frameWidth;
- size.frameHeight = frameHeight;
- resized = resized || (size.groupsWidth !== groupsWidth);
- size.groupsWidth = groupsWidth;
- size.contentLeft = options.groupsOnRight ? 0 : groupsWidth;
- size.contentWidth = Math.max(frameWidth - groupsWidth, 0);
- size.contentHeight = contentHeight;
- resized = resized || (size.axis.top !== axisTop);
- resized = resized || (size.axis.line !== axisLine);
- resized = resized || (size.axis.height !== axisHeight);
- resized = resized || (size.items.top !== itemsTop);
- size.axis.top = axisTop;
- size.axis.line = axisLine;
- size.axis.height = axisHeight;
- size.axis.labelMajorTop = options.axisOnTop ? 0 : axisLine + characterMinorHeight;
- size.axis.labelMinorTop = options.axisOnTop ?
- (options.showMajorLabels ? characterMajorHeight : 0) :
- axisLine;
- size.axis.lineMinorTop = options.axisOnTop ? size.axis.labelMinorTop : 0;
- size.axis.lineMinorHeight = options.showMajorLabels ?
- frameHeight - characterMajorHeight:
- frameHeight;
- size.axis.lineMinorWidth = dom.axis.minorLines.length ?
- dom.axis.minorLines[0].offsetWidth : 1;
- size.axis.lineMajorWidth = dom.axis.majorLines.length ?
- dom.axis.majorLines[0].offsetWidth : 1;
- size.items.top = itemsTop;
- resized = resized || (size.axis.characterMinorWidth !== characterMinorWidth);
- resized = resized || (size.axis.characterMinorHeight !== characterMinorHeight);
- resized = resized || (size.axis.characterMajorWidth !== characterMajorWidth);
- resized = resized || (size.axis.characterMajorHeight !== characterMajorHeight);
- size.axis.characterMinorWidth = characterMinorWidth;
- size.axis.characterMinorHeight = characterMinorHeight;
- size.axis.characterMajorWidth = characterMajorWidth;
- size.axis.characterMajorHeight = characterMajorHeight;
- // conversion factors can be changed when width of the Timeline is changed,
- // and when start or end are changed
- this.recalcConversion();
- return resized;
- };
- /**
- *
- *
- */
- links.Timeline.prototype.recalcSizeItems = function(resized) {
- // loop through the width and height of all items
- resized = resized || false;
-
- for (var i = 0, iMax = this.items.length; i < iMax; i++) {
- var item = this.items[i],
- domItem = item.dom,
- group = item.group;
- var width = domItem ? domItem.clientWidth : 0;
- var height = domItem ? domItem.clientHeight : 0;
- resized = resized || (item.width != width);
- resized = resized || (item.height != height);
- item.width = width;
- item.height = height;
- //item.borderWidth = (domItem.offsetWidth - domItem.clientWidth - 2) / 2; // TODO: borderWidth
- switch (item.type) {
- case 'range':
- break;
- case 'box':
- item.dotHeight = domItem.dot.offsetHeight;
- item.dotWidth = domItem.dot.offsetWidth;
- item.lineWidth = domItem.line.offsetWidth;
- break;
- case 'dot':
- item.dotHeight = domItem.dot.offsetHeight;
- item.dotWidth = domItem.dot.offsetWidth;
- item.contentHeight = domItem.content.offsetHeight;
- break;
- }
- if (group) {
- group.height = group.height ? Math.max(group.height, item.height) : item.height;
- }
- }
-
- return resized;
-
- }
- /**
- * Calculate the factor and offset to convert a position on screen to the
- * corresponding date and vice versa.
- * After the method calcConversionFactor is executed once, the methods screenToTime and
- * timeToScreen can be used.
- */
- links.Timeline.prototype.recalcConversion = function() {
- this.conversion.offset = parseFloat(this.start.valueOf());
- this.conversion.factor = parseFloat(this.size.contentWidth) /
- parseFloat(this.end.valueOf() - this.start.valueOf());
- };
- /**
- * Convert a position on screen (pixels) to a datetime
- * Before this method can be used, the method calcConversionFactor must be
- * executed once.
- * @param {int} x Position on the screen in pixels
- * @return {Date} time The datetime the corresponds with given position x
- */
- links.Timeline.prototype.screenToTime = function(x) {
- var conversion = this.conversion,
- time = new Date(parseFloat(x) / conversion.factor + conversion.offset);
- return time;
- };
- /**
- * Convert a datetime (Date object) into a position on the screen
- * Before this method can be used, the method calcConversionFactor must be
- * executed once.
- * @param {Date} time A date
- * @return {int} x The position on the screen in pixels which corresponds
- * with the given date.
- */
- links.Timeline.prototype.timeToScreen = function(time) {
- var conversion = this.conversion;
- var x = (time.valueOf() - conversion.offset) * conversion.factor;
- return x;
- };
- /**
- * Event handler for touchstart event on mobile devices
- */
- links.Timeline.prototype.onTouchStart = function(event) {
- var params = this.eventParams,
- dom = this.dom,
- me = this;
- if (params.touchDown) {
- // if already moving, return
- return;
- }
- params.touchDown = true;
- params.zoomed = false;
- this.onMouseDown(event);
- if (!params.onTouchMove) {
- params.onTouchMove = function (event) {me.onTouchMove(event);};
- links.Timeline.addEventListener(document, "touchmove", params.onTouchMove);
- }
- if (!params.onTouchEnd) {
- params.onTouchEnd = function (event) {me.onTouchEnd(event);};
- links.Timeline.addEventListener(document, "touchend", params.onTouchEnd);
- }
- };
- /**
- * Event handler for touchmove event on mobile devices
- */
- links.Timeline.prototype.onTouchMove = function(event) {
- var params = this.eventParams;
- if (event.scale && event.scale !== 1) {
- params.zoomed = true;
- }
- if (!params.zoomed) {
- // move
- this.onMouseMove(event);
- }
- else {
- if (this.options.zoomable) {
- // pinch
- // TODO: pinch only supported on iPhone/iPad. Create something manually for Android?
- params.zoomed = true;
- var scale = event.scale,
- oldWidth = (params.end.valueOf() - params.start.valueOf()),
- newWidth = oldWidth / scale,
- diff = newWidth - oldWidth,
- start = new Date(parseInt(params.start.valueOf() - diff/2)),
- end = new Date(parseInt(params.end.valueOf() + diff/2));
- // TODO: determine zoom-around-date from touch positions?
- this.setVisibleChartRange(start, end);
- timeline.trigger("rangechange");
- links.Timeline.preventDefault(event);
- }
- }
- };
- /**
- * Event handler for touchend event on mobile devices
- */
- links.Timeline.prototype.onTouchEnd = function(event) {
- var params = this.eventParams;
- params.touchDown = false;
- if (params.zoomed) {
- timeline.trigger("rangechanged");
- }
- if (params.onTouchMove) {
- links.Timeline.removeEventListener(document, "touchmove", params.onTouchMove);
- delete params.onTouchMove;
- }
- if (params.onTouchEnd) {
- links.Timeline.removeEventListener(document, "touchend", params.onTouchEnd);
- delete params.onTouchEnd;
- }
- this.onMouseUp(event);
- };
- /**
- * Start a moving operation inside the provided parent element
- * @param {event} event The event that occurred (required for
- * retrieving the mouse position)
- */
- links.Timeline.prototype.onMouseDown = function(event) {
- event = event || window.event;
- var params = this.eventParams,
- options = this.options,
- dom = this.dom;
- // only react on left mouse button down
- var leftButtonDown = event.which ? (event.which == 1) : (event.button == 1);
- if (!leftButtonDown && !params.touchDown) {
- return;
- }
- // check if frame is not resized (causing a mismatch with the end Date)
- this.recalcSize();
- // get mouse position
- if (!params.touchDown) {
- params.mouseX = event.clientX;
- params.mouseY = event.clientY;
- }
- else {
- params.mouseX = event.targetTouches[0].clientX;
- params.mouseY = event.targetTouches[0].clientY;
- }
- if (params.mouseX === undefined) {params.mouseX = 0;}
- if (params.mouseY === undefined) {params.mouseY = 0;}
- params.frameLeft = links.Timeline.getAbsoluteLeft(this.dom.content);
- params.frameTop = links.Timeline.getAbsoluteTop(this.dom.content);
- params.previousLeft = 0;
- params.previousOffset = 0;
- params.moved = false;
- params.start = new Date(this.start);
- params.end = new Date(this.end);
- params.target = links.Timeline.getTarget(event);
- params.itemDragLeft = (params.target === this.dom.items.dragLeft);
- params.itemDragRight = (params.target === this.dom.items.dragRight);
- if (params.itemDragLeft || params.itemDragRight) {
- params.itemIndex = this.selection ? this.selection.index : undefined;
- }
- else {
- params.itemIndex = this.getItemIndex(params.target);
- }
- params.customTime = (params.target === dom.customTime ||
- params.target.parentNode === dom.customTime) ?
- this.customTime :
- undefined;
- params.addItem = (options.editable && event.ctrlKey);
- if (params.addItem) {
- // create a new event at the current mouse position
- var x = params.mouseX - params.frameLeft;
- var y = params.mouseY - params.frameTop;
- var xstart = this.screenToTime(x);
- if (options.snapEvents) {
- this.step.snap(xstart);
- }
- var xend = new Date(xstart);
- var content = "New";
- var group = this.getGroupFromHeight(y);
- this.addItem({
- 'start': xstart,
- 'end': xend,
- 'content': content,
- 'group': group.content
- });
- params.itemIndex = (this.items.length - 1);
- this.selectItem(params.itemIndex);
- params.itemDragRight = true;
- }
- params.editItem = options.editable ? this.isSelected(params.itemIndex) : undefined;
- if (params.editItem) {
- var item = this.items[params.itemIndex];
- params.itemStart = item.start;
- params.itemEnd = item.end;
- params.itemType = item.type;
- if (params.itemType == 'range') {
- params.itemLeft = this.timeToScreen(item.start);
- params.itemRight = this.timeToScreen(item.end);
- }
- else {
- params.itemLeft = this.timeToScreen(item.start);
- }
- }
- else {
- this.dom.frame.style.cursor = 'move';
- }
- if (!params.touchDown) {
- // add event listeners to handle moving the contents
- // we store the function onmousemove and onmouseup in the timeline, so we can
- // remove the eventlisteners lateron in the function mouseUp()
- var me = this;
- if (!params.onMouseMove) {
- params.onMouseMove = function (event) {me.onMouseMove(event);};
- links.Timeline.addEventListener(document, "mousemove", params.onMouseMove);
- }
- if (!params.onMouseUp) {
- params.onMouseUp = function (event) {me.onMouseUp(event);};
- links.Timeline.addEventListener(document, "mouseup", params.onMouseUp);
- }
- links.Timeline.preventDefault(event);
- }
- };
- /**
- * Perform moving operating.
- * This function activated from within the funcion links.Timeline.onMouseDown().
- * @param {event} event Well, eehh, the event
- */
- links.Timeline.prototype.onMouseMove = function (event) {
- event = event || window.event;
- var params = this.eventParams,
- size = this.size,
- dom = this.dom,
- options = this.options;
- // calculate change in mouse position
- var mouseX, mouseY;
- if (!params.touchDown) {
- mouseX = event.clientX;
- mouseY = event.clientY;
- }
- else {
- mouseX = event.targetTouches[0].clientX;
- mouseY = event.targetTouches[0].clientY;
- }
- if (mouseX === undefined) {mouseX = 0;}
- if (mouseY === undefined) {mouseY = 0;}
- if (params.mouseX === undefined) {
- params.mouseX = mouseX;
- }
- if (params.mouseY === undefined) {
- params.mouseY = mouseY;
- }
- var diffX = parseFloat(mouseX) - params.mouseX;
- var diffY = parseFloat(mouseY) - params.mouseY;
- params.moved = true;
- if (params.customTime) {
- var x = this.timeToScreen(params.customTime);
- var xnew = x + diffX;
- this.customTime = this.screenToTime(xnew);
- this.redrawCustomTime();
- // fire a timechange event
- this.trigger('timechange');
- }
- else if (params.editItem) {
- var item = this.items[params.itemIndex],
- domItem = item.dom,
- left,
- right;
- if (params.itemDragLeft) {
- // move the start of the item
- left = params.itemLeft + diffX;
- right = params.itemRight;
- item.start = this.screenToTime(left);
- if (options.snapEvents) {
- this.step.snap(item.start);
- left = this.timeToScreen(item.start);
- }
- if (left > right) {
- left = right;
- item.start = this.screenToTime(left);
- }
- }
- else if (params.itemDragRight) {
- // move the end of the item
- left = params.itemLeft;
- right = params.itemRight + diffX;
- item.end = this.screenToTime(right);
- if (options.snapEvents) {
- this.step.snap(item.end);
- right = this.timeToScreen(item.end);
- }
- if (right < left) {
- right = left;
- item.end = this.screenToTime(right);
- }
- }
- else {
- // move the item
- left = params.itemLeft + diffX;
- item.start = this.screenToTime(left);
- if (options.snapEvents) {
- this.step.snap(item.start);
- left = this.timeToScreen(item.start);
- }
- if (item.end) {
- right = left + (params.itemRight - params.itemLeft);
- item.end = this.screenToTime(right);
- }
- }
- this.repositionItem(item, left, right);
- if (this.groups.length == 0) {
- // TODO: does not work well in FF, forces redraw with every mouse move it seems
- this.stackEvents(options.animate);
- if (!options.animate) {
- this.redrawFrame();
- }
- // Note: when animate==true, no redraw is needed here, its done by stackEvents animation
- }
- else {
- // move item from one group to another when needed
- if (options.groupsChangeable) {
- var y = mouseY - params.frameTop;
- var group = this.getGroupFromHeight(y);
- if (item.group !== group) {
- // move item to the other group
- //item.group = group;
- var index = this.items.indexOf(item);
- this.changeItem(index, {'group': group.content});
- item.top = group.top;
- this.repositionItem(item);
- }
- }
- }
- this.redrawDeleteButton();
- this.redrawDragAreas();
- }
- else if (options.moveable) {
- this.moveFrame(diffX);
- }
- links.Timeline.preventDefault(event);
- };
- links.Timeline.prototype.moveFrame = function(diffX){
- var params = this.eventParams,
- size = this.size,
- dom = this.dom,
- options = this.options;
- var interval = (params.end.valueOf() - params.start.valueOf());
- var diffMillisecs = Math.round(parseFloat(-diffX) / size.contentWidth * interval);
- var newStart = new Date(params.start.valueOf() + diffMillisecs);
- var newEnd = new Date(params.end.valueOf() + diffMillisecs);
- this.applyRange(newStart, newEnd);
- // console.log('interval = '+interval+'\ndiffMillisecs = '+diffMillisecs);//+'\nnewStart = '+newStart+'\nnewEnd = '+newEnd)
- // if the applied range is moved due to a fixed min or max,
- // change the diffMillisecs accordingly
- var appliedDiff = (this.start.valueOf() - newStart.valueOf());
- if (appliedDiff) {
- diffMillisecs += appliedDiff;
- }
- this.recalcConversion();
- // move the items by changing the left position of their frame.
- // this is much faster than repositioning all elements individually via the
- // redrawFrame() function (which is done once at mouseup)
- // note that we round diffX to prevent wrong positioning on millisecond scale
- var previousLeft = params.previousLeft || 0;
- var currentLeft = parseFloat(dom.items.frame.style.left) || 0;
- var previousOffset = params.previousOffset || 0;
- var frameOffset = previousOffset + (currentLeft - previousLeft);
- var frameLeft = Math.round(-diffMillisecs / interval * size.contentWidth + frameOffset);
- params.previousOffset = frameOffset;
- params.previousLeft = frameLeft;
- // console.log('previousLeft = '+previousLeft+' \ncurrentLeft = '+currentLeft+'\npreviousOffset = '+previousOffset+'\nframeOffset = '+frameOffset+'\nframeLeft = '+frameLeft);
- dom.items.frame.style.left = (frameLeft) + "px";
-
- this.redrawCurrentTime();
- this.redrawCustomTime();
- this.redrawAxis();
- // fire a rangechange event
- this.trigger('rangechange');
- // console.log('dom.items.frame.style.left = '+dom.items.frame.style.left);
- }
- /**
- * Stop moving operating.
- * This function activated from within the funcion links.Timeline.onMouseDown().
- * @param {event} event The event
- */
- links.Timeline.prototype.onMouseUp = function (event) {
- var params = this.eventParams,
- options = this.options;
- event = event || window.event;
- this.dom.frame.style.cursor = 'auto';
- // remove event listeners here, important for Safari
- if (params.onMouseMove) {
- links.Timeline.removeEventListener(document, "mousemove", params.onMouseMove);
- delete params.onMouseMove;
- }
- if (params.onMouseUp) {
- links.Timeline.removeEventListener(document, "mouseup", params.onMouseUp);
- delete params.onMouseUp;
- }
- //links.Timeline.preventDefault(event);
- if (params.customTime) {
- // fire a timechanged event
- this.trigger('timechanged');
- }
- else if (params.editItem) {
- var item = this.items[params.itemIndex];
- if (params.moved || params.addItem) {
- this.applyChange = true;
- this.applyAdd = true;
- this.updateData(params.itemIndex, {
- 'start': item.start,
- 'end': item.end
- });
- // fire an add or change event.
- // Note that the change can be canceled from within an event listener if
- // this listener calls the method cancelChange().
- this.trigger(params.addItem ? 'add' : 'change');
- if (params.addItem) {
- if (this.applyAdd) {
- this.updateData(params.itemIndex, {
- 'start': item.start,
- 'end': item.end,
- 'content': item.content,
- 'group': item.group ? item.group.content : undefined
- });
- }
- else {
- // undo an add
- this.deleteItem(params.itemIndex);
- }
- }
- else {
- if (this.applyChange) {
- this.updateData(params.itemIndex, {
- 'start': item.start,
- 'end': item.end
- });
- }
- else {
- // undo a change
- delete this.applyChange;
- delete this.applyAdd;
- var item = this.items[params.itemIndex],
- domItem = item.dom;
- item.start = params.itemStart;
- item.end = params.itemEnd;
- this.repositionItem(item, params.itemLeft, params.itemRight);
- }
- }
- this.recalcSize();
- this.repositionItems();
- this.stackEvents(options.animate);
- if (!options.animate) {
- this.redrawFrame();
- }
- this.redrawDeleteButton();
- this.redrawDragAreas();
- }
- }
- else {
- this.endMoveFrame();
- // mouse did not move -> user has selected an item
- }
- };
- links.Timeline.prototype.endMoveFrame = function(){
- //console.log('endMoveFrame');
- var params = this.eventParams,
- options = this.options;
-
- if (!params.moved && !params.zoomed) {
- // mouse did not move -> user has selected an item
- if (options.editable && (params.target === this.dom.items.deleteButton)) {
- // delete item
- if (this.selection) {
- this.confirmDeleteItem(this.selection.index);
- }
- this.redrawFrame();
- }
- else if (options.selectable) {
- // select/unselect item
- if (params.itemIndex !== undefined) {
- if (!this.isSelected(params.itemIndex)) {
- this.selectItem(params.itemIndex);
- this.redrawDeleteButton();
- this.redrawDragAreas();
- this.trigger('select');
- }
- }
- else {
- this.unselectItem();
- this.redrawDeleteButton();
- this.redrawDragAreas();
- }
- }
- }
- else {
- // timeline is moved
- this.repositionItems();
- this.stackEvents(options.animate);
- if (!options.animate) {
- this.redrawFrame();
- }
- if ((params.moved && options.moveable) || (params.zoomed && options.zoomable) ) {
- // fire a rangechanged event
- this.trigger('rangechanged');
- }
- }
-
-
-
- };
- /**
- * Double click event occurred for an item
- * @param {event} event
- */
- links.Timeline.prototype.onDblClick = function (event) {
- var params = this.eventParams,
- options = this.options,
- dom = this.dom,
- size = this.size;
- event = event || window.event;
- if (!options.editable && !options.zoomable) {
- return;
- }
-
- /*
- TODO WRONG here
- detect dblclick on editable element or on timeline background
- */
- switch(options.dblclick){
- case "edit":
- if(!options.editable)
- return;
-
- if (params.itemIndex !== undefined) {
- // fire the edit event
- this.trigger('edit');
- } else {
- // create a new item
- var x = event.clientX - links.Timeline.getAbsoluteLeft(dom.content);
- var y = event.clientY - links.Timeline.getAbsoluteTop(dom.content);
- // create a new event at the current mouse position
- var xstart = this.screenToTime(x);
- var xend = this.screenToTime(x + size.frameWidth / 10); // add 10% of timeline width
- if (options.snapEvents) {
- this.step.snap(xstart);
- this.step.snap(xend);
- }
- var content = "New";
- var group = this.getGroupFromHeight(y); // (group may be undefined)
- this.addItem({
- 'start': xstart,
- 'end': xend,
- 'content': content,
- 'group': group.content
- });
- params.itemIndex = (this.items.length - 1);
- this.selectItem(params.itemIndex);
- this.applyAdd = true;
- // fire an add event.
- // Note that the change can be canceled from within an event listener if
- // this listener calls the method cancelAdd().
- this.trigger('add');
- if (!this.applyAdd) {
- // undo an add
- this.deleteItem(params.itemIndex);
- }
- this.redrawDeleteButton();
- this.redrawDragAreas();
- }
-
- break;
- case "zoom":
- if(!options.zoomable)
- return;
- // this.zoom(0.7);
- var x = event.clientX - links.Timeline.getAbsoluteLeft(dom.content),
- range = size.frameWidth / 5,
- xstart = this.screenToTime(x - range),
- xend = this.screenToTime(x + range);
-
- this.setVisibleChartRange(xstart, xend);
- break;
- }
- links.Timeline.preventDefault(event);
- };
- /**
- * Event handler for mouse wheel event
- * @param {event} event The event
- */
- links.Timeline.prototype.onMouseWheel = function(event) {
-
- if (!this.options.zoomable && !this.options.moveable)
- return;
-
- if (!event) { /* For IE. */
- event = window.event;
- }
- // retrieve delta
- var delta = 0;
- if (event.wheelDelta) { /* IE/Opera. */
- delta = event.wheelDelta/120;
- } else if (event.detail) { /* Mozilla case. */
- // In Mozilla, sign of delta is different than in IE.
- // Also, delta is multiple of 3.
- delta = -event.detail/3;
- }
- // If delta is nonzero, handle it.
- // Basically, delta is now positive if wheel was scrolled up,
- // and negative, if wheel was scrolled down.
- if (delta) {
- switch(this.options.mousewheel){
- case 'zoom':
- this.onMouseWheelZoom(event, delta);
- break;
- case 'move':
- this.onMouseWheelMove(event, delta);
- break;
- default:
- break;
- }
- }
- // Prevent default actions caused by mouse wheel.
- // That might be ugly, but we handle scrolls somehow
- // anyway, so don't bother here...
- links.Timeline.preventDefault(event);
- }
- /**
- * Event handler for mouse wheel event, used to zoom the timeline
- * Code from http://adomas.org/javascript-mouse-wheel/
- * @param {event} event The event
- */
- links.Timeline.prototype.onMouseWheelZoom = function(event, delta) {
- if (!this.options.zoomable)
- return;
- // TODO: on FireFox, the window is not redrawn within repeated scroll-events
- // -> use a delayed redraw? Make a zoom queue?
- var timeline = this;
- var zoom = function () {
- // check if frame is not resized (causing a mismatch with the end date)
- timeline.recalcSize();
- // perform the zoom action. Delta is normally 1 or -1
- var zoomFactor = delta / 5.0;
- var frameLeft = links.Timeline.getAbsoluteLeft(timeline.dom.content);
- var zoomAroundDate =
- (event.clientX != undefined && frameLeft != undefined) ?
- timeline.screenToTime(event.clientX - frameLeft) :
- undefined;
- timeline.zoom(zoomFactor, zoomAroundDate);
- // fire a rangechange and a rangechanged event
- timeline.trigger("rangechange");
- timeline.trigger("rangechanged");
- /* TODO: smooth scrolling on FF
- timeline.zooming = false;
- if (timeline.zoomingQueue) {
- setTimeout(timeline.zoomingQueue, 100);
- timeline.zoomingQueue = undefined;
- }
- timeline.zoomCount = (timeline.zoomCount || 0) + 1;
- //console.log('zoomCount', timeline.zoomCount)
- */
- };
- zoom();
- /* TODO: smooth scrolling on FF
- if (!timeline.zooming || true) {
- timeline.zooming = true;
- setTimeout(zoom, 100);
- }
- else {
- timeline.zoomingQueue = zoom;
- }
- //*/
- };
- /**
- * Event handler for mouse wheel event, used to move the timeline
- * Code from http://adomas.org/javascript-mouse-wheel/
- * @param {event} event The event
- */
- links.Timeline.prototype.onMouseWheelMove = function(event, delta) {
- if (!this.options.moveable)
- return;
- var params = this.eventParams;
-
- if(params.scrolled === undefined || !params.scrolled){
- params.start = new Date(this.start);
- params.end = new Date(this.end);
- params.scrolled = true;
- params.wheelScrolledX = 0;
- params.previousLeft = 0;
- params.previousOffset = 0;
- params.dif
- }
- params.wheelScrolledX += Math.round(delta*11);
-
- this.moveFrame(params.wheelScrolledX);
-
-
- /* SET TIME OUT FOR END SCROLLING EVENT */
- if(this.eventParams.wheelEnderTimer !== undefined)
- clearTimeout(this.eventParams.wheelEnderTimer);
- /*
- var me = this;
- function endScorlling(){
- me.repositionItems();
- me.stackEvents(me.options.animate);
- if (!me.options.animate) {
- me.redrawFrame();
- }
- if (params.scrolled && me.options.moveable) {
- params.scrolled = false;
- // fire a rangechanged event
- me.trigger('rangechanged');
- }
- };
- */
-
- var me = this;
- this.eventParams.wheelEnderTimer = setTimeout(function(){
- params.scrolled = false;
- me.endMoveFrame();
- }, 100);
- };
- /**
- * Zoom the timeline the given zoomfactor in or out. Start and end date will
- * be adjusted, and the timeline will be redrawn. You can optionally give a
- * date around which to zoom.
- * For example, try zoomfactor = 0.1 or -0.1
- * @param {Number} zoomFactor Zooming amount. Positive value will zoom in,
- * negative value will zoom out
- * @param {Date} zoomAroundDate Date around which will be zoomed. Optional
- */
- links.Timeline.prototype.zoom = function(zoomFactor, zoomAroundDate) {
- // if zoomAroundDate is not provided, take it half between start Date and end Date
- if (zoomAroundDate == undefined) {
- zoomAroundDate = new Date((this.start.valueOf() + this.end.valueOf()) / 2);
- }
- // prevent zoom factor larger than 1 or smaller than -1 (larger than 1 will
- // result in a start>=end )
- if (zoomFactor >= 1) {
- zoomFactor = 0.9;
- }
- if (zoomFactor <= -1) {
- zoomFactor = -0.9;
- }
- // adjust a negative factor such that zooming in with 0.1 equals zooming
- // out with a factor -0.1
- if (zoomFactor < 0) {
- zoomFactor = zoomFactor / (1 + zoomFactor);
- }
- // zoom start Date and end Date relative to the zoomAroundDate
- var startDiff = parseFloat(this.start.valueOf() - zoomAroundDate.valueOf());
- var endDiff = parseFloat(this.end.valueOf() - zoomAroundDate.valueOf());
- // calculate new dates
- var newStart = new Date(this.start.valueOf() - startDiff * zoomFactor);
- var newEnd = new Date(this.end.valueOf() - endDiff * zoomFactor);
- this.applyRange(newStart, newEnd, zoomAroundDate);
- this.recalcSize();
- var animate = this.options.animate ? this.options.animateZoom : false;
- this.repositionItems();
- this.stackEvents(animate);
- if (!animate || this.groups.length > 0) {
- this.redrawFrame();
- }
- /* TODO
- else {
- this.redrawFrame();
- this.recalcSize();
- this.stackEvents(animate);
- this.redrawFrame();
- }*/
- };
- /**
- * Move the timeline the given movefactor to the left or right. Start and end
- * date will be adjusted, and the timeline will be redrawn.
- * For example, try moveFactor = 0.1 or -0.1
- * @param {Number} moveFactor Moving amount. Positive value will move right,
- * negative value will move left
- */
- links.Timeline.prototype.move = function(moveFactor) {
- // zoom start Date and end Date relative to the zoomAroundDate
- var diff = parseFloat(this.end.valueOf() - this.start.valueOf());
- // apply new dates
- var newStart = new Date(this.start.valueOf() + diff * moveFactor);
- var newEnd = new Date(this.end.valueOf() + diff * moveFactor);
- this.applyRange(newStart, newEnd);
- this.recalcConversion();
- this.repositionItems();
- this.stackEvents(this.options.animate);
- if (!this.options.animate) {
- this.redrawFrame();
- }
-
- };
- /**
- * Reposition given item
- * @param {Object} item
- * @param {Number} left
- * @param {Number} right
- */
- links.Timeline.prototype.repositionItem = function (item, left, right) {
- var domItem = item.dom;
- switch(item.type) {
- case 'range':
- domItem.style.left = left + "px";
- //domItem.style.width = Math.max(right - left - 2 * item.borderWidth, 1) + "px"; // TODO: borderwidth
- domItem.style.width = Math.max(right - left, 1) + "px";
- break;
- case 'box':
- domItem.style.left = (left - item.width / 2) + "px";
- domItem.line.style.left = (left - item.lineWidth / 2) + "px";
- domItem.dot.style.left = (left - item.dotWidth / 2) + "px";
- break;
- case 'dot':
- domItem.style.left = (left - item.dotWidth / 2) + "px";
- break;
- }
- if (this.groups.length > 0) {
- domItem.style.top = item.top + 'px';
- }
- };
- /**
- * Apply a visible range. The range is limited to feasible maximum and minimum
- * range.
- * @param {Date} start
- * @param {Date} end
- * @param {Date} zoomAroundDate Optional. Date around which will be zoomed.
- */
- links.Timeline.prototype.applyRange = function (start, end, zoomAroundDate) {
- // calculate new start and end value
- var startValue = start.valueOf();
- var endValue = end.valueOf();
- var interval = (endValue - startValue);
- // determine maximum and minimum interval
- var options = this.options;
- var year = 1000 * 60 * 60 * 24 * 365;
- var intervalMin = Number(options.intervalMin) || 10;
- if (intervalMin < 10) {
- intervalMin = 10;
- }
- var intervalMax = Number(options.intervalMax) || 10000 * year;
- if (intervalMax > 10000 * year) {
- intervalMax = 10000 * year;
- }
- if (intervalMax < intervalMin) {
- intervalMax = intervalMin;
- }
- // determine min and max date value
- var min = options.min ? options.min.valueOf() : undefined;
- var max = options.max ? options.max.valueOf() : undefined;
- if (min && max) {
- if (min >= max) {
- // empty range
- var day = 1000 * 60 * 60 * 24;
- max = min + day;
- }
- if (intervalMax > (max - min)) {
- intervalMax = (max - min);
- }
- if (intervalMin > (max - min)) {
- intervalMin = (max - min);
- }
- }
- // prevent empty interval
- if (startValue >= endValue) {
- endValue += 1000 * 60 * 60 * 24;
- }
- // prevent too small scale
- // TODO: IE has problems with milliseconds
- if (interval < intervalMin) {
- var diff = (intervalMin - interval);
- var f = zoomAroundDate ? (zoomAroundDate.valueOf() - startValue) / interval : 0.5;
- startValue -= Math.round(diff * f);
- endValue += Math.round(diff * (1 - f));
- }
- // prevent too large scale
- if (interval > intervalMax) {
- var diff = (interval - intervalMax);
- var f = zoomAroundDate ? (zoomAroundDate.valueOf() - startValue) / interval : 0.5;
- startValue += Math.round(diff * f);
- endValue -= Math.round(diff * (1 - f));
- }
- // prevent to small start date
- if (min) {
- var diff = (startValue - min);
- if (diff < 0) {
- startValue -= diff;
- endValue -= diff;
- }
- }
- // prevent to large end date
- if (max) {
- var diff = (max - endValue);
- if (diff < 0) {
- startValue += diff;
- endValue += diff;
- }
- }
- // apply new dates
- this.start = new Date(startValue);
- this.end = new Date(endValue);
- };
- /**
- * Delete an item after a confirmation.
- * The deletion can be cancelled by executing .cancelDelete() during the
- * triggered event 'delete'.
- * @param {int} index Index of the item to be deleted
- */
- links.Timeline.prototype.confirmDeleteItem = function(index) {
- this.applyDelete = true;
- // select the event to be deleted
- if (!this.isSelected(index)) {
- this.selectItem(index);
- }
- // fire a delete event trigger.
- // Note that the delete event can be canceled from within an event listener if
- // this listener calls the method cancelChange().
- this.trigger('delete');
- if (this.applyDelete) {
- this.deleteItem(index);
- }
- delete this.applyDelete;
- };
- /**
- * Delete an item
- * @param {int} index Index of the item to be deleted
- */
- links.Timeline.prototype.deleteItem = function(index) {
- if (index >= this.items.length) {
- throw "Cannot delete row, index out of range";
- }
- this.unselectItem();
- // actually delete the item
- this.items.splice(index, 1);
- // delete the row in the original data table
- if (this.data) {
- if (google && google.visualization &&
- this.data instanceof google.visualization.DataTable) {
- this.data.removeRow(index);
- }
- else if (links.Timeline.isArray(this.data)) {
- this.data.splice(index, 1);
- }
- else {
- throw "Cannot delete row from data, unknown data type";
- }
- }
- this.size.dataChanged = true;
- this.redrawFrame();
- this.recalcSize();
- this.repositionItems();
- this.stackEvents(this.options.animate);
- if (!this.options.animate) {
- this.redrawFrame();
- }
- this.size.dataChanged = false;
- };
- /**
- * Delete all items
- */
- links.Timeline.prototype.deleteAllItems = function() {
- this.unselectItem();
- // delete the loaded data
- this.items = [];
- // delete the groups
- this.deleteGroups();
- // empty original data table
- if (this.data) {
- if (google && google.visualization &&
- this.data instanceof google.visualization.DataTable) {
- this.data.removeRows(0, this.data.getNumberOfRows());
- }
- else if (links.Timeline.isArray(this.data)) {
- this.data.splice(0, this.data.length);
- }
- else {
- throw "Cannot delete row from data, unknown data type";
- }
- }
- this.size.dataChanged = true;
- this.redrawFrame();
- this.recalcSize();
- this.repositionItems();
- this.stackEvents(this.options.animate);
- if (!this.options.animate) {
- this.redrawFrame();
- }
- this.size.dataChanged = false;
- };
- /**
- * Find the group from a given height in the timeline
- * @param {Number} height Height in the timeline
- * @param {boolean}
- * @return {Object} group The group object, or undefined if out of range
- */
- links.Timeline.prototype.getGroupFromHeight = function(height) {
- var groups = this.groups,
- options = this.options,
- size = this.size,
- y = height - (options.axisOnTop ? size.axis.height : 0);
- if (groups) {
- var group;
- /* TODO: cleanup
- for (var i = 0, iMax = groups.length; i < iMax; i++) {
- group = groups[i];
- if (y > group.top && y < group.top + group.height) {
- return group;
- }
- }*/
- for (var i = groups.length - 1; i >= 0; i--) {
- group = groups[i];
- if (y > group.top) {
- return group;
- }
- }
- return group; // return the last group
- }
- return undefined;
- };
- /**
- * Retrieve the properties of an item.
- * @param {Number} index
- * @return {Object} properties Object containing item properties:<br>
- * {Date} start (required),
- * {Date} end (optional),
- * {String} content (required),
- * {String} group (optional)
- */
- links.Timeline.prototype.getItem = function (index) {
- if (index >= this.items.length) {
- throw "Cannot get item, index out of range";
- }
- var item = this.items[index];
- var properties = {};
- properties.start = new Date(item.start);
- if (item.end) {
- properties.end = new Date(item.end);
- }
- properties.content = item.content;
- if (item.group) {
- properties.group = item.group.content;
- }
- return properties;
- };
- /**
- * Add a new item.
- * @param {Object} itemData Object containing item properties:<br>
- * {Date} start (required),
- * {Date} end (optional),
- * {String} content (required),
- * {String} group (optional)
- */
- links.Timeline.prototype.addItem = function (itemData) {
- var items = [
- itemData
- ];
- this.addItems(items);
- };
- /**
- * Add new items.
- * @param {Array} items An array containing Objects.
- * The objects must have the following parameters:
- * {Date} start,
- * {Date} end,
- * {String} content with text or HTML code,
- * {String} group
- */
- links.Timeline.prototype.addItems = function (items) {
- var newItems = items,
- curItems = this.items;
- // append the items
- for (var i = 0, iMax = newItems.length; i < iMax; i++) {
- var itemData = items[i];
- this.addGroup(itemData.group);
- curItems.push(this.createItem(itemData));
- var index = curItems.length - 1;
- this.updateData(index, itemData);
- }
- // redraw timeline
- this.size.dataChanged = true;
- this.redrawFrame();
- this.recalcSize();
- this.repositionItems();
- this.stackEvents(false);
- this.redrawFrame();
- this.size.dataChanged = false;
- };
- /**
- * Create an item object, containing all needed parameters
- * @param {Object} itemData Object containing parameters start, end
- * content, group.
- * @return {Object} item
- */
- links.Timeline.prototype.createItem = function(itemData) {
- var item = {
- 'start': itemData.start,
- 'end': itemData.end,
- 'content': itemData.content,
- 'type': itemData.end ? 'range' : this.options.style,
- 'group': this.findGroup(itemData.group),
- 'top': 0,
- 'left': 0,
- 'width': 0,
- 'height': 0,
- 'lineWidth' : 0,
- 'dotWidth': 0,
- 'dotHeight': 0
- };
- return item;
- };
- /**
- * Edit an item
- * @param {Number} index
- * @param {Object} itemData Object containing item properties:<br>
- * {Date} start (required),
- * {Date} end (optional),
- * {String} content (required),
- * {String} group (optional)
- */
- links.Timeline.prototype.changeItem = function (index, itemData) {
- if (index >= this.items.length) {
- throw "Cannot change item, index out of range";
- }
- var style = this.options.style;
- var item = this.items[index];
- // edit the item
- if (itemData.start) {
- item.start = itemData.start;
- }
- if (itemData.end) {
- item.end = itemData.end;
- }
- if (itemData.content) {
- item.content = itemData.content;
- }
- if (itemData.group) {
- item.group = this.addGroup(itemData.group);
- }
- // update the original data table
- this.updateData(index, itemData);
- // redraw timeline
- this.size.dataChanged = true;
- this.redrawFrame();
- this.recalcSize();
- this.repositionItems();
- this.stackEvents(false);
- this.redrawFrame();
- this.size.dataChanged = false;
- };
- /**
- * Find a group by its name.
- * @param {String} group
- * @return {Object} a group object or undefined when group is not found
- */
- links.Timeline.prototype.findGroup = function (group) {
- var index = this.groupIndexes[group];
- return (index != undefined) ? this.groups[index] : undefined;
- };
- /**
- * Delete all groups
- */
- links.Timeline.prototype.deleteGroups = function () {
- this.groups = [];
- this.groupIndexes = {};
- };
- /**
- * Add a group. When the group already exists, no new group is created
- * but the existing group is returned.
- * @param {String} groupName the name of the group
- * @return {Object} groupObject
- */
- links.Timeline.prototype.addGroup = function (groupName) {
- var groups = this.groups,
- groupIndexes = this.groupIndexes,
- groupObj = undefined;
- var groupIndex = groupIndexes[groupName];
- if (groupIndex === undefined && groupName !== undefined) {
- groupObj = {
- 'content': groupName,
- 'labelTop': 0,
- 'lineTop': 0
- // note: this object will lateron get addition information,
- // such as height and width of the group
- };
- groups.push(groupObj);
- // sort the groups
- groups = groups.sort(function (a, b) {
- if (a.content > b.content) {
- return 1;
- }
- if (a.content < b.content) {
- return -1;
- }
- return 0;
- });
- // rebuilt the groupIndexes
- for (var i = 0, iMax = groups.length; i < iMax; i++) {
- groupIndexes[groups[i].content] = i;
- }
- }
- else {
- groupObj = groups[groupIndex];
- }
- return groupObj;
- };
- /**
- * Cancel a change item
- * This method can be called insed an event listener which catches the "change"
- * event. The changed event position will be undone.
- */
- links.Timeline.prototype.cancelChange = function () {
- this.applyChange = false;
- };
- /**
- * Cancel deletion of an item
- * This method can be called insed an event listener which catches the "delete"
- * event. Deletion of the event will be undone.
- */
- links.Timeline.prototype.cancelDelete = function () {
- this.applyDelete = false;
- };
- /**
- * Cancel creation of a new item
- * This method can be called insed an event listener which catches the "new"
- * event. Creation of the new the event will be undone.
- */
- links.Timeline.prototype.cancelAdd = function () {
- this.applyAdd = false;
- };
- /**
- * Select an event. The visible chart range will be moved such that the selected
- * event is placed in the middle.
- * For example selection = [{row: 5}];
- * @param {Array} selection An array with a column row, containing the row
- * number (the id) of the event to be selected.
- * @return {boolean} true if selection is succesfully set, else false.
- */
- links.Timeline.prototype.setSelection = function(selection) {
- if (selection != undefined && selection.length > 0) {
- if (selection[0].row != undefined) {
- var index = selection[0].row;
- if (this.items[index]) {
- var item = this.items[index];
- this.selectItem(index);
- // move the visible chart range to the selected event.
- var start = item.start;
- var end = item.end;
- var middle;
- if (end != undefined) {
- middle = new Date((end.valueOf() + start.valueOf()) / 2);
- } else {
- middle = new Date(start);
- }
- var diff = (this.end.valueOf() - this.start.valueOf()),
- newStart = new Date(middle.valueOf() - diff/2),
- newEnd = new Date(middle.valueOf() + diff/2);
- this.setVisibleChartRange(newStart, newEnd);
- return true;
- }
- }
- }
- else {
- // unselect current selection
- this.unselectItem();
- }
- return false;
- };
- /**
- * Retrieve the currently selected event
- * @return {Array} sel An array with a column row, containing the row number
- * of the selected event. If there is no selection, an
- * empty array is returned.
- */
- links.Timeline.prototype.getSelection = function() {
- var sel = [];
- if (this.selection) {
- sel.push({"row": this.selection.index});
- }
- return sel;
- };
- /**
- * Select an item by its index
- * @param {Number} index
- */
- links.Timeline.prototype.selectItem = function(index) {
- this.unselectItem();
- this.selection = undefined;
- if (this.items[index] !== undefined) {
- var item = this.items[index],
- domItem = item.dom;
- this.selection = {
- 'index': index,
- 'item': domItem
- };
- if (this.options.editable) {
- domItem.style.cursor = 'move';
- }
- switch (item.type) {
- case 'range':
- domItem.className = "timeline-event timeline-event-selected timeline-event-range";
- break;
- case 'box':
- domItem.className = "timeline-event timeline-event-selected timeline-event-box";
- domItem.line.className = "timeline-event timeline-event-selected timeline-event-line";
- domItem.dot.className = "timeline-event timeline-event-selected timeline-event-dot";
- break;
- case 'dot':
- domItem.className = "timeline-event timeline-event-selected";
- domItem.dot.className = "timeline-event timeline-event-selected timeline-event-dot";
- break;
- }
- /* TODO: cleanup this cannot work as this breaks any javscript action inside the item
- // move the item to the end, such that it will be displayed on top of the other items
- var parent = domItem.parentNode;
- if (parent) {
- parent.removeChild(domItem);
- parent.appendChild(domItem);
- }
- */
- }
- };
- /**
- * Check if an item is currently selected
- * @param {Number} index
- * @return {boolean} true if row is selected, else false
- */
- links.Timeline.prototype.isSelected = function (index) {
- return (this.selection && this.selection.index === index);
- };
- /**
- * Unselect the currently selected event (if any)
- */
- links.Timeline.prototype.unselectItem = function() {
- if (this.selection) {
- var item = this.items[this.selection.index];
- if (item && item.dom) {
- var domItem = item.dom;
- domItem.style.cursor = '';
- switch (item.type) {
- case 'range':
- domItem.className = "timeline-event timeline-event-range";
- break;
- case 'box':
- domItem.className = "timeline-event timeline-event-box";
- domItem.line.className = "timeline-event timeline-event-line";
- domItem.dot.className = "timeline-event timeline-event-dot";
- break;
- case 'dot':
- domItem.className = "";
- domItem.dot.className = "timeline-event timeline-event-dot";
- break;
- }
- }
- }
- this.selection = undefined;
- };
- /**
- *
- *
- *
- */
- links.Timeline.prototype.repositionItems = function(){
- var size = this.size,
- contentWidth = size.contentWidth,
- item, domItem,
- visibleItems = [];
- // reposition all items
- for (var i = 0, iMax = this.items.length; i < iMax; i++) {
- item = this.items[i];
- domItem = item.dom;
- switch (item.type) {
- case 'range':
- item.left = this.timeToScreen(item.start);
- item.right = this.timeToScreen(item.end);
- // limit the width of the item, as browsers cannot draw very wide divs
- if (item.left < -contentWidth) {
- item.left = -contentWidth;
- }
- if (item.right > 2 * contentWidth) {
- item.right = 2 * contentWidth;
- }
- var visible = item.right > -contentWidth && item.left < 2 * contentWidth;
- if (visible || size.dataChanged) {
- // when data is changed, all items must be kept visible, as their heights must be measured
- if (item.hidden) {
- item.hidden = false;
- domItem.style.display = '';
- }
- }
- else {
- // hide when outside of the current window
- if (!item.hidden) {
- domItem.style.display = 'none';
- item.hidden = true;
- }
- }
- break;
- case 'box':
- item.left = this.timeToScreen(item.start);
- var visible = ((item.left + item.width/2 > -contentWidth) &&
- (item.left - item.width/2 < 2 * contentWidth));
- if (visible || size.dataChanged) {
- //when data is changed, all items must be kept visible, as their heights must be measured
- if (item.hidden) {
- item.hidden = false;
- domItem.style.display = '';
- domItem.line.style.display = '';
- domItem.dot.style.display = '';
- }
- }
- else {
- // hide when outside of the current window
- if (!item.hidden) {
- domItem.style.display = 'none';
- domItem.line.style.display = 'none';
- domItem.dot.style.display = 'none';
- item.hidden = true;
- }
- }
- break;
- case 'dot':
- item.left = this.timeToScreen(item.start);
- var visible = (item.left + item.width > -contentWidth) && (item.left < 2 * contentWidth);
- if (visible || size.dataChanged) {
- // when data is changed, all items must be kept visible, as their heights must be measured
- if (item.hidden) {
- item.hidden = false;
- domItem.style.display = '';
- }
- }
- else {
- // hide when outside of the current window
- if (!item.hidden) {
- domItem.style.display = 'none';
- item.hidden = true;
- }
- }
- break;
- default:
- // do nothing
- break;
- }
- if(!item.hidden)
- visibleItems.push(item);
-
-
- this.items[i] = item;
-
- }
-
-
- if(this.visibleItems.length != visibleItems.length){
- this.visibleItemsChanged = true;
- }else{
- this.visibleItemsChanged = false;
- }
- this.visibleItems = visibleItems;
-
- this.itemsSizeChanged = false;
- }
- /**
- * Stack the items such that they don't overlap. The items will have a minimal
- * distance equal to options.eventMargin.
- * @param {boolean} animate if animate is true, the items are moved to
- * their new position animated
- */
- links.Timeline.prototype.stackEvents = function(animate) {
-
- this.trigger('on-before-stack-events');
-
- if(this.itemsSizeChanged)
- this.recalcSizeItems();
-
- if (this.groups.length > 0) {
- // under this conditions we refuse to stack the events
- return;
- }
- if (animate == undefined) {
- animate = false;
- }
- var sortedItems = this.stackOrder(this.items);
- var finalItems = this.stackCalculateFinal(sortedItems, animate);
- if (animate) {
- // move animated to the final positions
- var animation = this.animation;
- if (!animation) {
- animation = {};
- this.animation = animation;
- }
- animation.finalItems = finalItems;
- var timeline = this;
- var step = function () {
- var arrived = timeline.stackMoveOneStep(sortedItems, animation.finalItems);
- timeline.recalcSize();
- timeline.repositionItems();
- timeline.redrawFrame();
- if (!arrived) {
- animation.timer = setTimeout(step, 30);
- }
- else {
- delete animation.finalItems;
- delete animation.timer;
- }
- };
- if (!animation.timer) {
- animation.timer = setTimeout(step, 30);
- }
- }
- else {
- this.stackMoveToFinal(sortedItems, finalItems);
- this.recalcSize();
- }
- };
- /**
- * Order the items in the array this.items. The order is determined via:
- * - Ranges go before boxes and dots.
- * - The item with the left most location goes first
- * @param {Array} items Array with items
- * @return {Array} sortedItems Array with sorted items
- */
- links.Timeline.prototype.stackOrder = function(items) {
- // TODO: store the sorted items, to have less work later on
- var sortedItems = items.concat([]);
- var f = function (a, b) {
- if (a.type == 'range' && b.type != 'range') {
- return -1;
- }
- if (a.type != 'range' && b.type == 'range') {
- return 1;
- }
- return (a.left - b.left);
- };
- sortedItems.sort(f);
- return sortedItems;
- };
- /**
- * Adjust vertical positions of the events such that they don't overlap each
- * other.
- */
- links.Timeline.prototype.stackCalculateFinal = function(items) {
- var size = this.size,
- axisTop = size.axis.top,
- options = this.options,
- axisOnTop = options.axisOnTop,
- eventMargin = options.eventMargin,
- eventMarginAxis = options.eventMarginAxis,
- finalItems = [];
- // initialize final positions
- for (var i = 0, iMax = items.length; i < iMax; i++) {
- var item = items[i],
- top,
- left,
- right,
- bottom,
- height = item.height,
- width = item.width;
- if (axisOnTop) {
- top = axisTop + eventMarginAxis + eventMargin / 2;
- }
- else {
- top = axisTop - height - eventMarginAxis - eventMargin / 2;
- }
- bottom = top + height;
- switch (item.type) {
- case 'range':
- case 'dot':
- left = this.timeToScreen(item.start);
- right = item.end ? this.timeToScreen(item.end) : left + width;
- break;
- case 'box':
- left = this.timeToScreen(item.start) - width / 2;
- right = left + width;
- break;
- }
- finalItems[i] = {
- 'left': left,
- 'top': top,
- 'right': right,
- 'bottom': bottom,
- 'height': height,
- 'item': item
- };
- }
- if (this.options.stackEvents) {
- // calculate new, non-overlapping positions
- //var items = sortedItems;
- for (var i = 0, iMax = finalItems.length; i < iMax; i++) {
- //for (var i = finalItems.length - 1; i >= 0; i--) {
- var finalItem = finalItems[i];
- var collidingItem = null;
- do {
- // TODO: optimize checking for overlap. when there is a gap without items,
- // you only need to check for items from the next item on, not from zero
- collidingItem = this.stackEventsCheckOverlap(finalItems, i, 0, i-1);
- if (collidingItem != null) {
- // There is a collision. Reposition the event above the colliding element
- if (axisOnTop) {
- finalItem.top = collidingItem.top + collidingItem.height + eventMargin;
- }
- else {
- finalItem.top = collidingItem.top - finalItem.height - eventMargin;
- }
- finalItem.bottom = finalItem.top + finalItem.height;
- }
- } while (collidingItem);
- }
- }
- return finalItems;
- };
- /**
- * Move the events one step in the direction of their final positions
- * @param {Array} currentItems Array with the real items and their current
- * positions
- * @param {Array} finalItems Array with objects containing the final
- * positions of the items
- * @return {boolean} arrived True if all items have reached their final
- * location, else false
- */
- links.Timeline.prototype.stackMoveOneStep = function(currentItems, finalItems) {
- var arrived = true;
- // apply new positions animated
- for (i = 0, iMax = currentItems.length; i < iMax; i++) {
- var finalItem = finalItems[i],
- item = finalItem.item;
- var topNow = parseInt(item.top);
- var topFinal = parseInt(finalItem.top);
- var diff = (topFinal - topNow);
- if (diff) {
- var step = (topFinal == topNow) ? 0 : ((topFinal > topNow) ? 1 : -1);
- if (Math.abs(diff) > 4) step = diff / 4;
- var topNew = parseInt(topNow + step);
- if (topNew != topFinal) {
- arrived = false;
- }
- item.top = topNew;
- item.bottom = item.top + item.height;
- }
- else {
- item.top = finalItem.top;
- item.bottom = finalItem.bottom;
- }
- item.left = finalItem.left;
- item.right = finalItem.right;
- }
- return arrived;
- };
- /**
- * Move the events from their current position to the final position
- * @param {Array} currentItems Array with the real items and their current
- * positions
- * @param {Array} finalItems Array with objects containing the final
- * positions of the items
- */
- links.Timeline.prototype.stackMoveToFinal = function(currentItems, finalItems) {
- // Put the events directly at there final position
- for (i = 0, iMax = currentItems.length; i < iMax; i++) {
- var current = currentItems[i],
- finalItem = finalItems[i];
- current.left = finalItem.left;
- current.top = finalItem.top;
- current.right = finalItem.right;
- current.bottom = finalItem.bottom;
- }
- };
- /**
- * Check if the destiny position of given item overlaps with any
- * of the other items from index itemStart to itemEnd.
- * @param {Array} items Array with items
- * @param {int} itemIndex Number of the item to be checked for overlap
- * @param {int} itemStart First item to be checked.
- * @param {int} itemEnd Last item to be checked.
- * @return {Object} colliding item, or undefined when no collisions
- */
- links.Timeline.prototype.stackEventsCheckOverlap = function(items, itemIndex,
- itemStart, itemEnd) {
- var eventMargin = this.options.eventMargin,
- collision = this.collision;
- // we loop from end to start, as we suppose that the chance of a
- // collision is larger for items at the end, so check these first.
- var item1 = items[itemIndex];
- for (var i = itemEnd; i >= itemStart; i--) {
- var item2 = items[i];
- if (collision(item1, item2, eventMargin)) {
- if (i != itemIndex) {
- return item2;
- }
- }
- }
- return undefined;
- };
- /**
- * Test if the two provided items collide
- * The items must have parameters left, right, top, and bottom.
- * @param {Element} item1 The first item
- * @param {Element} item2 The second item
- * @param {Number} margin A minimum required margin. Optional.
- * If margin is provided, the two items will be
- * marked colliding when they overlap or
- * when the margin between the two is smaller than
- * the requested margin.
- * @return {boolean} true if item1 and item2 collide, else false
- */
- links.Timeline.prototype.collision = function(item1, item2, margin) {
- // set margin if not specified
- if (margin == undefined) {
- margin = 0;
- }
- // calculate if there is overlap (collision)
- return (item1.left - margin < item2.right &&
- item1.right + margin > item2.left &&
- item1.top - margin < item2.bottom &&
- item1.bottom + margin > item2.top);
- };
- /**
- * fire an event
- * @param {String} event The name of an event, for example "rangechange" or "edit"
- */
- links.Timeline.prototype.trigger = function (event) {
- // built up properties
- var properties = null;
- switch (event) {
- case 'rangechange':
- case 'rangechanged':
- properties = {
- 'start': new Date(this.start),
- 'end': new Date(this.end)
- };
- break;
- case 'timechange':
- case 'timechanged':
- properties = {
- 'time': new Date(this.customTime)
- };
- break;
- }
- // trigger the links event bus
- links.events.trigger(this, event, properties);
- // trigger the google event bus
- if (google && google.visualization) {
- google.visualization.events.trigger(this, event, properties);
- }
- };
- /** ------------------------------------------------------------------------ **/
- /**
- * Event listener (singleton)
- */
- links.events = links.events || {
- 'listeners': [],
- /**
- * Find a single listener by its object
- * @param {Object} object
- * @return {Number} index -1 when not found
- */
- 'indexOf': function (object) {
- var listeners = this.listeners;
- for (var i = 0, iMax = this.listeners.length; i < iMax; i++) {
- var listener = listeners[i];
- if (listener && listener.object == object) {
- return i;
- }
- }
- return -1;
- },
- /**
- * Add an event listener
- * @param {Object} object
- * @param {String} event The name of an event, for example 'select'
- * @param {function} callback The callback method, called when the
- * event takes place
- */
- 'addListener': function (object, event, callback) {
- var index = this.indexOf(object);
- var listener = this.listeners[index];
- if (!listener) {
- listener = {
- 'object': object,
- 'events': {}
- };
- this.listeners.push(listener);
- }
- var callbacks = listener.events[event];
- if (!callbacks) {
- callbacks = [];
- listener.events[event] = callbacks;
- }
- // add the callback if it does not yet exist
- if (callbacks.indexOf(callback) == -1) {
- callbacks.push(callback);
- }
- },
- /**
- * Remove an event listener
- * @param {Object} object
- * @param {String} event The name of an event, for example 'select'
- * @param {function} callback The registered callback method
- */
- 'removeListener': function (object, event, callback) {
- var index = this.indexOf(object);
- var listener = this.listeners[index];
- if (listener) {
- var callbacks = listener.events[event];
- if (callbacks) {
- var index = callbacks.indexOf(callback);
- if (index != -1) {
- callbacks.splice(index, 1);
- }
- // remove the array when empty
- if (callbacks.length == 0) {
- delete listener.events[event];
- }
- }
- // count the number of registered events. remove listener when empty
- var count = 0;
- var events = listener.events;
- for (var e in events) {
- if (events.hasOwnProperty(e)) {
- count++;
- }
- }
- if (count == 0) {
- delete this.listeners[index];
- }
- }
- },
- /**
- * Remove all registered event listeners
- */
- 'removeAllListeners': function () {
- this.listeners = [];
- },
- /**
- * Trigger an event. All registered event handlers will be called
- * @param {Object} object
- * @param {String} event
- * @param {Object} properties (optional)
- */
- 'trigger': function (object, event, properties) {
- var index = this.indexOf(object);
- var listener = this.listeners[index];
- if (listener) {
- var callbacks = listener.events[event];
- if (callbacks) {
- for (var i = 0, iMax = callbacks.length; i < iMax; i++) {
- callbacks[i](properties);
- }
- }
- }
- }
- };
- /** ------------------------------------------------------------------------ **/
- /**
- * @constructor links.Timeline.StepDate
- * The class StepDate is an iterator for dates. You provide a start date and an
- * end date. The class itself determines the best scale (step size) based on the
- * provided start Date, end Date, and minimumStep.
- *
- * If minimumStep is provided, the step size is chosen as close as possible
- * to the minimumStep but larger than minimumStep. If minimumStep is not
- * provided, the scale is set to 1 DAY.
- * The minimumStep should correspond with the onscreen size of about 6 characters
- *
- * Alternatively, you can set a scale by hand.
- * After creation, you can initialize the class by executing start(). Then you
- * can iterate from the start date to the end date via next(). You can check if
- * the end date is reached with the function end(). After each step, you can
- * retrieve the current date via get().
- * The class step has scales ranging from milliseconds, seconds, minutes, hours,
- * days, to years.
- *
- * Version: 1.0
- *
- * @param {Date} start The start date, for example new Date(2010, 9, 21)
- * or new Date(2010, 9,21,23,45,00)
- * @param {Date} end The end date
- * @param {int} minimumStep Optional. Minimum step size in milliseconds
- */
- links.Timeline.StepDate = function(start, end, minimumStep) {
- // variables
- this.current = new Date();
- this._start = new Date();
- this._end = new Date();
- this.autoScale = true;
- this.scale = links.Timeline.StepDate.SCALE.DAY;
- this.step = 1;
- // initialize the range
- this.setRange(start, end, minimumStep);
- };
- /// enum scale
- links.Timeline.StepDate.SCALE = { MILLISECOND : 1,
- SECOND : 2,
- MINUTE : 3,
- HOUR : 4,
- DAY : 5,
- MONTH : 6,
- YEAR : 7};
- /**
- * Set a new range
- * If minimumStep is provided, the step size is chosen as close as possible
- * to the minimumStep but larger than minimumStep. If minimumStep is not
- * provided, the scale is set to 1 DAY.
- * The minimumStep should correspond with the onscreen size of about 6 characters
- * @param {Date} start The start date and time.
- * @param {Date} end The end date and time.
- * @param {int} minimumStep Optional. Minimum step size in milliseconds
- */
- links.Timeline.StepDate.prototype.setRange = function(start, end, minimumStep) {
- if (isNaN(start) || isNaN(end)) {
- //throw "No legal start or end date in method setRange";
- return;
- }
- this._start = (start != undefined) ? new Date(start) : new Date();
- this._end = (end != undefined) ? new Date(end) : new Date();
- if (this.autoScale) {
- this.setMinimumStep(minimumStep);
- }
- };
- /**
- * Set the step iterator to the start date.
- */
- links.Timeline.StepDate.prototype.start = function() {
- this.current = new Date(this._start);
- this.roundToMinor();
- };
- /**
- * Round the current date to the first minor date value
- * This must be executed once when the current date is set to start Date
- */
- links.Timeline.StepDate.prototype.roundToMinor = function() {
- // round to floor
- // IMPORTANT: we have no breaks in this switch! (this is no bug)
- switch (this.scale) {
- case links.Timeline.StepDate.SCALE.YEAR:
- this.current.setFullYear(this.step * Math.floor(this.current.getFullYear() / this.step));
- this.current.setMonth(0);
- case links.Timeline.StepDate.SCALE.MONTH: this.current.setDate(1);
- case links.Timeline.StepDate.SCALE.DAY: this.current.setHours(0);
- case links.Timeline.StepDate.SCALE.HOUR: this.current.setMinutes(0);
- case links.Timeline.StepDate.SCALE.MINUTE: this.current.setSeconds(0);
- case links.Timeline.StepDate.SCALE.SECOND: this.current.setMilliseconds(0);
- //case links.Timeline.StepDate.SCALE.MILLISECOND: // nothing to do for milliseconds
- }
- if (this.step != 1) {
- // round down to the first minor value that is a multiple of the current step size
- switch (this.scale) {
- case links.Timeline.StepDate.SCALE.MILLISECOND: this.current.setMilliseconds(this.current.getMilliseconds() - this.current.getMilliseconds() % this.step); break;
- case links.Timeline.StepDate.SCALE.SECOND: this.current.setSeconds(this.current.getSeconds() - this.current.getSeconds() % this.step); break;
- case links.Timeline.StepDate.SCALE.MINUTE: this.current.setMinutes(this.current.getMinutes() - this.current.getMinutes() % this.step); break;
- case links.Timeline.StepDate.SCALE.HOUR: this.current.setHours(this.current.getHours() - this.current.getHours() % this.step); break;
- case links.Timeline.StepDate.SCALE.DAY: this.current.setDate((this.current.getDate()-1) - (this.current.getDate()-1) % this.step + 1); break;
- case links.Timeline.StepDate.SCALE.MONTH: this.current.setMonth(this.current.getMonth() - this.current.getMonth() % this.step); break;
- case links.Timeline.StepDate.SCALE.YEAR: this.current.setFullYear(this.current.getFullYear() - this.current.getFullYear() % this.step); break;
- default: break;
- }
- }
- };
- /**
- * Check if the end date is reached
- * @return {boolean} true if the current date has passed the end date
- */
- links.Timeline.StepDate.prototype.end = function () {
- return (this.current.getTime() > this._end.getTime());
- };
- /**
- * Do the next step
- */
- links.Timeline.StepDate.prototype.next = function() {
- var prev = this.current.getTime();
- // Two cases, needed to prevent issues with switching daylight savings
- // (end of March and end of October)
- if (this.current.getMonth() < 6) {
- switch (this.scale)
- {
- case links.Timeline.StepDate.SCALE.MILLISECOND:
- this.current = new Date(this.current.getTime() + this.step); break;
- case links.Timeline.StepDate.SCALE.SECOND: this.current = new Date(this.current.getTime() + this.step * 1000); break;
- case links.Timeline.StepDate.SCALE.MINUTE: this.current = new Date(this.current.getTime() + this.step * 1000 * 60); break;
- case links.Timeline.StepDate.SCALE.HOUR:
- this.current = new Date(this.current.getTime() + this.step * 1000 * 60 * 60);
- // in case of skipping an hour for daylight savings, adjust the hour again (else you get: 0h 5h 9h ... instead of 0h 4h 8h ...)
- var h = this.current.getHours();
- this.current.setHours(h - (h % this.step));
- break;
- case links.Timeline.StepDate.SCALE.DAY: this.current.setDate(this.current.getDate() + this.step); break;
- case links.Timeline.StepDate.SCALE.MONTH: this.current.setMonth(this.current.getMonth() + this.step); break;
- case links.Timeline.StepDate.SCALE.YEAR: this.current.setFullYear(this.current.getFullYear() + this.step); break;
- default: break;
- }
- }
- else {
- switch (this.scale)
- {
- case links.Timeline.StepDate.SCALE.MILLISECOND:
- this.current = new Date(this.current.getTime() + this.step); break;
- case links.Timeline.StepDate.SCALE.SECOND: this.current.setSeconds(this.current.getSeconds() + this.step); break;
- case links.Timeline.StepDate.SCALE.MINUTE: this.current.setMinutes(this.current.getMinutes() + this.step); break;
- case links.Timeline.StepDate.SCALE.HOUR: this.current.setHours(this.current.getHours() + this.step); break;
- case links.Timeline.StepDate.SCALE.DAY: this.current.setDate(this.current.getDate() + this.step); break;
- case links.Timeline.StepDate.SCALE.MONTH: this.current.setMonth(this.current.getMonth() + this.step); break;
- case links.Timeline.StepDate.SCALE.YEAR: this.current.setFullYear(this.current.getFullYear() + this.step); break;
- default: break;
- }
- }
- if (this.step != 1) {
- // round down to the correct major value
- switch (this.scale) {
- case links.Timeline.StepDate.SCALE.MILLISECOND: if(this.current.getMilliseconds() < this.step) this.current.setMilliseconds(0); break;
- case links.Timeline.StepDate.SCALE.SECOND: if(this.current.getSeconds() < this.step) this.current.setSeconds(0); break;
- case links.Timeline.StepDate.SCALE.MINUTE: if(this.current.getMinutes() < this.step) this.current.setMinutes(0); break;
- case links.Timeline.StepDate.SCALE.HOUR: if(this.current.getHours() < this.step) this.current.setHours(0); break;
- case links.Timeline.StepDate.SCALE.DAY: if(this.current.getDate() < this.step+1) this.current.setDate(1); break;
- case links.Timeline.StepDate.SCALE.MONTH: if(this.current.getMonth() < this.step) this.current.setMonth(0); break;
- case links.Timeline.StepDate.SCALE.YEAR: break; // nothing to do for year
- default: break;
- }
- }
- // safety mechanism: if current time is still unchanged, move to the end
- if (this.current.getTime() == prev) {
- this.current = new Date(this._end);
- }
- };
- /**
- * Get the current datetime
- * @return {Date} current The current date
- */
- links.Timeline.StepDate.prototype.getCurrent = function() {
- return this.current;
- };
- /**
- * Set a custom scale. Autoscaling will be disabled.
- * For example setScale(SCALE.MINUTES, 5) will result
- * in minor steps of 5 minutes, and major steps of an hour.
- *
- * @param {links.Timeline.StepDate.SCALE} newScale
- * A scale. Choose from SCALE.MILLISECOND,
- * SCALE.SECOND, SCALE.MINUTE, SCALE.HOUR,
- * SCALE.DAY, SCALE.MONTH, SCALE.YEAR.
- * @param {int} newStep A step size, by default 1. Choose for
- * example 1, 2, 5, or 10.
- */
- links.Timeline.StepDate.prototype.setScale = function(newScale, newStep) {
- this.scale = newScale;
- if (newStep > 0)
- this.step = newStep;
- this.autoScale = false;
- };
- /**
- * Enable or disable autoscaling
- * @param {boolean} enable If true, autoascaling is set true
- */
- links.Timeline.StepDate.prototype.setAutoScale = function (enable) {
- this.autoScale = enable;
- };
- /**
- * Automatically determine the scale that bests fits the provided minimum step
- * @param {int} minimumStep The minimum step size in milliseconds
- */
- links.Timeline.StepDate.prototype.setMinimumStep = function(minimumStep) {
- if (minimumStep == undefined)
- return;
- var stepYear = (1000 * 60 * 60 * 24 * 30 * 12);
- var stepMonth = (1000 * 60 * 60 * 24 * 30);
- var stepDay = (1000 * 60 * 60 * 24);
- var stepHour = (1000 * 60 * 60);
- var stepMinute = (1000 * 60);
- var stepSecond = (1000);
- var stepMillisecond= (1);
- // find the smallest step that is larger than the provided minimumStep
- if (stepYear*1000 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.YEAR; this.step = 1000;}
- if (stepYear*500 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.YEAR; this.step = 500;}
- if (stepYear*100 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.YEAR; this.step = 100;}
- if (stepYear*50 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.YEAR; this.step = 50;}
- if (stepYear*10 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.YEAR; this.step = 10;}
- if (stepYear*5 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.YEAR; this.step = 5;}
- if (stepYear > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.YEAR; this.step = 1;}
- if (stepMonth*3 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.MONTH; this.step = 3;}
- if (stepMonth > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.MONTH; this.step = 1;}
- if (stepDay*5 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.DAY; this.step = 5;}
- if (stepDay*2 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.DAY; this.step = 2;}
- if (stepDay > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.DAY; this.step = 1;}
- if (stepHour*4 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.HOUR; this.step = 4;}
- if (stepHour > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.HOUR; this.step = 1;}
- if (stepMinute*15 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.MINUTE; this.step = 15;}
- if (stepMinute*10 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.MINUTE; this.step = 10;}
- if (stepMinute*5 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.MINUTE; this.step = 5;}
- if (stepMinute > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.MINUTE; this.step = 1;}
- if (stepSecond*15 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.SECOND; this.step = 15;}
- if (stepSecond*10 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.SECOND; this.step = 10;}
- if (stepSecond*5 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.SECOND; this.step = 5;}
- if (stepSecond > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.SECOND; this.step = 1;}
- if (stepMillisecond*200 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.MILLISECOND; this.step = 200;}
- if (stepMillisecond*100 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.MILLISECOND; this.step = 100;}
- if (stepMillisecond*50 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.MILLISECOND; this.step = 50;}
- if (stepMillisecond*10 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.MILLISECOND; this.step = 10;}
- if (stepMillisecond*5 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.MILLISECOND; this.step = 5;}
- if (stepMillisecond > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.MILLISECOND; this.step = 1;}
- };
- /**
- * Snap a date to a rounded value. The snap intervals are dependent on the
- * current scale and step.
- * @param {Date} date the date to be snapped
- */
- links.Timeline.StepDate.prototype.snap = function(date) {
- if (this.scale == links.Timeline.StepDate.SCALE.YEAR) {
- var year = date.getFullYear() + Math.round(date.getMonth() / 12);
- date.setFullYear(Math.round(year / this.step) * this.step);
- date.setMonth(0);
- date.setDate(0);
- date.setHours(0);
- date.setMinutes(0);
- date.setSeconds(0);
- date.setMilliseconds(0);
- }
- else if (this.scale == links.Timeline.StepDate.SCALE.MONTH) {
- if (date.getDate() > 15) {
- date.setDate(1);
- date.setMonth(date.getMonth() + 1);
- // important: first set Date to 1, after that change the month.
- }
- else {
- date.setDate(1);
- }
- date.setHours(0);
- date.setMinutes(0);
- date.setSeconds(0);
- date.setMilliseconds(0);
- }
- else if (this.scale == links.Timeline.StepDate.SCALE.DAY) {
- switch (this.step) {
- case 5:
- case 2:
- date.setHours(Math.round(date.getHours() / 24) * 24); break;
- default:
- date.setHours(Math.round(date.getHours() / 12) * 12); break;
- }
- date.setMinutes(0);
- date.setSeconds(0);
- date.setMilliseconds(0);
- }
- else if (this.scale == links.Timeline.StepDate.SCALE.HOUR) {
- switch (this.step) {
- case 4:
- date.setMinutes(Math.round(date.getMinutes() / 60) * 60); break;
- default:
- date.setMinutes(Math.round(date.getMinutes() / 30) * 30); break;
- }
- date.setSeconds(0);
- date.setMilliseconds(0);
- } else if (this.scale == links.Timeline.StepDate.SCALE.MINUTE) {
- switch (this.step) {
- case 15:
- case 10:
- date.setMinutes(Math.round(date.getMinutes() / 5) * 5);
- date.setSeconds(0);
- break;
- case 5:
- date.setSeconds(Math.round(date.getSeconds() / 60) * 60); break;
- default:
- date.setSeconds(Math.round(date.getSeconds() / 30) * 30); break;
- }
- date.setMilliseconds(0);
- }
- else if (this.scale == links.Timeline.StepDate.SCALE.SECOND) {
- switch (this.step) {
- case 15:
- case 10:
- date.setSeconds(Math.round(date.getSeconds() / 5) * 5);
- date.setMilliseconds(0);
- break;
- case 5:
- date.setMilliseconds(Math.round(date.getMilliseconds() / 1000) * 1000); break;
- default:
- date.setMilliseconds(Math.round(date.getMilliseconds() / 500) * 500); break;
- }
- }
- else if (this.scale == links.Timeline.StepDate.SCALE.MILLISECOND) {
- var step = this.step > 5 ? this.step / 2 : 1;
- date.setMilliseconds(Math.round(date.getMilliseconds() / step) * step);
- }
- };
- /**
- * Check if the current step is a major step (for example when the step
- * is DAY, a major step is each first day of the MONTH)
- * @return true if current date is major, else false.
- */
- links.Timeline.StepDate.prototype.isMajor = function() {
- switch (this.scale)
- {
- case links.Timeline.StepDate.SCALE.MILLISECOND:
- return (this.current.getMilliseconds() == 0);
- case links.Timeline.StepDate.SCALE.SECOND:
- return (this.current.getSeconds() == 0);
- case links.Timeline.StepDate.SCALE.MINUTE:
- return (this.current.getHours() == 0) && (this.current.getMinutes() == 0);
- // Note: this is no bug. Major label is equal for both minute and hour scale
- case links.Timeline.StepDate.SCALE.HOUR:
- return (this.current.getHours() == 0);
- case links.Timeline.StepDate.SCALE.DAY:
- return (this.current.getDate() == 1);
- case links.Timeline.StepDate.SCALE.MONTH:
- return (this.current.getMonth() == 0);
- case links.Timeline.StepDate.SCALE.YEAR:
- return false;
- default:
- return false;
- }
- };
- /**
- * Returns formatted text for the minor axislabel, depending on the current
- * date and the scale. For example when scale is MINUTE, the current time is
- * formatted as "hh:mm".
- * @param {Date} [date] custom date. if not provided, current date is taken
- * @return {string} minor axislabel
- */
- links.Timeline.StepDate.prototype.getLabelMinor = function(date, language) {
- // console.log('getLabelMinor', language);
- switch(language){
- case 'fr':
- var MONTHS_SHORT = new Array("Jan", "Fev", "Mar", "Avr", "Mai", "Juin", "Juil", "Aout", "Sep", "Oct", "Nov", "Dec");
- break;
- case 'en':
- default:
- var MONTHS_SHORT = new Array("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
- break;
- }
-
- if (date == undefined) {
- date = this.current;
- }
- switch (this.scale)
- {
- case links.Timeline.StepDate.SCALE.MILLISECOND: return String(date.getMilliseconds());
- case links.Timeline.StepDate.SCALE.SECOND: return String(date.getSeconds());
- case links.Timeline.StepDate.SCALE.MINUTE: return this.addZeros(date.getHours(), 2) + ":" +
- this.addZeros(date.getMinutes(), 2);
- case links.Timeline.StepDate.SCALE.HOUR: return this.addZeros(date.getHours(), 2) + ":" +
- this.addZeros(date.getMinutes(), 2);
- case links.Timeline.StepDate.SCALE.DAY: return String(date.getDate());
- case links.Timeline.StepDate.SCALE.MONTH: return MONTHS_SHORT[date.getMonth()]; // month is zero based
- case links.Timeline.StepDate.SCALE.YEAR: return String(date.getFullYear());
- default: return "";
- }
- };
- /**
- * Returns formatted text for the major axislabel, depending on the current
- * date and the scale. For example when scale is MINUTE, the major scale is
- * hours, and the hour will be formatted as "hh".
- * @param {Date} [date] custom date. if not provided, current date is taken
- * @return {string} major axislabel
- */
- links.Timeline.StepDate.prototype.getLabelMajor = function(date, language) {
- // console.log('getLabelMajor',language);
- switch(language){
- case 'fr':
- var MONTHS = new Array("Janvier", "Février", "Mars","Avril", "Mai", "Juin","Juillet", "Aout", "Septembre","Octobre", "Novembre", "Decembre");
- var DAYS = new Array("Dimanche", "Lundi", "Mardi","mercredi", "Jeudi", "Vendredi", "Samedi");
- break;
- case 'en':
- default:
- var MONTHS = new Array("January", "February", "March","April", "May", "June","July", "August", "September","October", "November", "December");
- var DAYS = new Array("Sunday", "Monday", "Tuesday","Wednesday", "Thursday", "Friday", "Saturday");
- break;
- }
-
- if (date == undefined) {
- date = this.current;
- }
- switch (this.scale) {
- case links.Timeline.StepDate.SCALE.MILLISECOND:
- return this.addZeros(date.getHours(), 2) + ":" +
- this.addZeros(date.getMinutes(), 2) + ":" +
- this.addZeros(date.getSeconds(), 2);
- case links.Timeline.StepDate.SCALE.SECOND:
- return date.getDate() + " " +
- MONTHS[date.getMonth()] + " " +
- this.addZeros(date.getHours(), 2) + ":" +
- this.addZeros(date.getMinutes(), 2);
- case links.Timeline.StepDate.SCALE.MINUTE:
- return DAYS[date.getDay()] + " " +
- date.getDate() + " " +
- MONTHS[date.getMonth()] + " " +
- date.getFullYear();
- case links.Timeline.StepDate.SCALE.HOUR:
- return DAYS[date.getDay()] + " " +
- date.getDate() + " " +
- MONTHS[date.getMonth()] + " " +
- date.getFullYear();
- case links.Timeline.StepDate.SCALE.DAY:
- return MONTHS[date.getMonth()] + " " +
- date.getFullYear();
- case links.Timeline.StepDate.SCALE.MONTH:
- return String(date.getFullYear());
- default:
- return "";
- }
- };
- /**
- * Add leading zeros to the given value to match the desired length.
- * For example addZeros(123, 5) returns "00123"
- * @param {int} value A value
- * @param {int} len Desired final length
- * @return {string} value with leading zeros
- */
- links.Timeline.StepDate.prototype.addZeros = function(value, len) {
- var str = "" + value;
- while (str.length < len) {
- str = "0" + str;
- }
- return str;
- };
- /** ------------------------------------------------------------------------ **/
- /**
- * Image Loader service.
- * can be used to get a callback when a certain image is loaded
- *
- */
- links.imageloader = (function () {
- var urls = {}; // the loaded urls
- var callbacks = {}; // the urls currently being loaded. Each key contains
- // an array with callbacks
- /**
- * Check if an image url is loaded
- * @param {String} url
- * @return {boolean} loaded True when loaded, false when not loaded
- * or when being loaded
- */
- function isLoaded (url) {
- if (urls[url] == true) {
- return true;
- }
- var image = new Image();
- image.src = url;
- if (image.complete) {
- return true;
- }
- return false;
- }
- /**
- * Check if an image url is being loaded
- * @param {String} url
- * @return {boolean} loading True when being loaded, false when not loading
- * or when already loaded
- */
- function isLoading (url) {
- return (callbacks[url] != undefined);
- }
- /**
- * Load given image url
- * @param {String} url
- * @param {function} callback
- * @param {boolean} sendCallbackWhenAlreadyLoaded optional
- */
- function load (url, callback, sendCallbackWhenAlreadyLoaded) {
- if (sendCallbackWhenAlreadyLoaded == undefined) {
- sendCallbackWhenAlreadyLoaded = true;
- }
- if (isLoaded(url)) {
- if (sendCallbackWhenAlreadyLoaded) {
- callback(url);
- }
- return;
- }
- if (isLoading(url) && !sendCallbackWhenAlreadyLoaded) {
- return;
- }
- var c = callbacks[url];
- if (!c) {
- var image = new Image();
- image.src = url;
- c = [];
- callbacks[url] = c;
- image.onload = function (event) {
- urls[url] = true;
- delete callbacks[url];
- for (var i = 0; i < c.length; i++) {
- c[i](url);
- }
- }
- }
- if (c.indexOf(callback) == -1) {
- c.push(callback);
- }
- }
- return {
- 'isLoaded': isLoaded,
- 'isLoading': isLoading,
- 'load': load
- };
- })();
- /** ------------------------------------------------------------------------ **/
- /**
- * Add and event listener. Works for all browsers
- * @param {Element} element An html element
- * @param {string} action The action, for example "click",
- * without the prefix "on"
- * @param {function} listener The callback function to be executed
- * @param {boolean} useCapture
- */
- links.Timeline.addEventListener = function (element, action, listener, useCapture) {
- if (element.addEventListener) {
- if (useCapture === undefined)
- useCapture = false;
- if (action === "mousewheel" && navigator.userAgent.indexOf("Firefox") >= 0) {
- action = "DOMMouseScroll"; // For Firefox
- }
- element.addEventListener(action, listener, useCapture);
- } else {
- element.attachEvent("on" + action, listener); // IE browsers
- }
- };
- /**
- * Remove an event listener from an element
- * @param {Element} element An html dom element
- * @param {string} action The name of the event, for example "mousedown"
- * @param {function} listener The listener function
- * @param {boolean} useCapture
- */
- links.Timeline.removeEventListener = function(element, action, listener, useCapture) {
- if (element.removeEventListener) {
- // non-IE browsers
- if (useCapture === undefined)
- useCapture = false;
- if (action === "mousewheel" && navigator.userAgent.indexOf("Firefox") >= 0) {
- action = "DOMMouseScroll"; // For Firefox
- }
- element.removeEventListener(action, listener, useCapture);
- } else {
- // IE browsers
- element.detachEvent("on" + action, listener);
- }
- };
- /**
- * Get HTML element which is the target of the event
- * @param {MouseEvent} event
- * @return {Element} target element
- */
- links.Timeline.getTarget = function (event) {
- // code from http://www.quirksmode.org/js/events_properties.html
- if (!event) {
- event = window.event;
- }
- var target;
- if (event.target) {
- target = event.target;
- }
- else if (event.srcElement) {
- target = event.srcElement;
- }
- if (target.nodeType !== undefined && target.nodeType == 3) {
- // defeat Safari bug
- target = target.parentNode;
- }
- return target;
- };
- /**
- * Stop event propagation
- */
- links.Timeline.stopPropagation = function (event) {
- if (!event)
- event = window.event;
- if (event.stopPropagation) {
- event.stopPropagation(); // non-IE browsers
- }
- else {
- event.cancelBubble = true; // IE browsers
- }
- };
- /**
- * Cancels the event if it is cancelable, without stopping further propagation of the event.
- */
- links.Timeline.preventDefault = function (event) {
- if (!event)
- event = window.event;
- if (event.preventDefault) {
- event.preventDefault(); // non-IE browsers
- }
- else {
- event.returnValue = false; // IE browsers
- }
- };
- /**
- * Retrieve the absolute left value of a DOM element
- * @param {Element} elem A dom element, for example a div
- * @return {number} left The absolute left position of this element
- * in the browser page.
- */
- links.Timeline.getAbsoluteLeft = function(elem) {
- var left = 0;
- while( elem != null ) {
- left += elem.offsetLeft;
- left -= elem.scrollLeft;
- elem = elem.offsetParent;
- }
- if (!document.body.scrollLeft && window.pageXOffset) {
- // FF
- left -= window.pageXOffset;
- }
- return left;
- };
- /**
- * Retrieve the absolute top value of a DOM element
- * @param {Element} elem A dom element, for example a div
- * @return {number} top The absolute top position of this element
- * in the browser page.
- */
- links.Timeline.getAbsoluteTop = function(elem) {
- var top = 0;
- while( elem != null ) {
- top += elem.offsetTop;
- top -= elem.scrollTop;
- elem = elem.offsetParent;
- }
- if (!document.body.scrollTop && window.pageYOffset) {
- // FF
- top -= window.pageYOffset;
- }
- return top;
- };
- /**
- * Check if given object is a Javascript Array
- * @param {*} obj
- * @return {Boolean} isArray true if the given object is an array
- */
- // See http://stackoverflow.com/questions/2943805/javascript-instanceof-typeof-in-gwt-jsni
- links.Timeline.isArray = function (obj) {
- if (obj instanceof Array) {
- return true;
- }
- return (Object.prototype.toString.call(obj) === '[object Array]');
- };
|